From a6c5042767aef200de5f5af30e7e00927417fed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=ED=98=95=EC=9D=B5?= Date: Tue, 22 Jul 2025 16:56:47 +0900 Subject: [PATCH 1/7] feat: Initial project setup --- .github/PULL_REQUEST_TEMPLATE.md | 21 ----------- COMMIT_EDITMSG | 1 + README.md | 30 ++++++++++++++++ package.json | 17 +++++++++ src/Article.js | 14 ++++++++ src/ArticleService.js | 62 ++++++++++++++++++++++++++++++++ src/ElectronicProduct.js | 9 +++++ src/Product.js | 15 ++++++++ src/ProductService.js | 61 +++++++++++++++++++++++++++++++ src/main.js | 31 ++++++++++++++++ 10 files changed, 240 insertions(+), 21 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 COMMIT_EDITMSG create mode 100644 README.md create mode 100644 package.json create mode 100644 src/Article.js create mode 100644 src/ArticleService.js create mode 100644 src/ElectronicProduct.js create mode 100644 src/Product.js create mode 100644 src/ProductService.js create mode 100644 src/main.js diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index ec85f6f1a..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,21 +0,0 @@ - -## 요구사항 - -### 기본 -- [x] 기본 항목 1 -- [ ] 기본 항목 2 - -### 심화 -- [ ] 심화 항목 1 -- [ ] 심화 항목 2 - -## 주요 변경사항 -- -- - -## 스크린샷 -![image](이미지url) - -## 멘토에게 -- 셀프 코드 리뷰를 통해 질문 이어가겠습니다. -- diff --git a/COMMIT_EDITMSG b/COMMIT_EDITMSG new file mode 100644 index 000000000..cb61fcce8 --- /dev/null +++ b/COMMIT_EDITMSG @@ -0,0 +1 @@ +feat: Initial project setup \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..8513555af --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Panda Market Client + +이 프로젝트는 Panda Market API와 통신하여 상품 및 게시글을 관리하는 웹 클라이언트입니다. + +## 주요 기능 + +- 상품(Product) 및 전자제품(ElectronicProduct) 데이터 모델링 +- 게시글(Article) 데이터 모델링 +- API 서비스를 통한 데이터 연동 (조회, 생성, 수정, 삭제) + +## 실행 방법 + +```bash +# 필요한 경우 Node.js 환경에서 실행합니다. +node src/main.js +``` + +## 프로젝트 구조 + +``` +/ +├── src/ +│ ├── Article.js # 게시글 클래스 +│ ├── ArticleService.js # 게시글 API 연동 서비스 +│ ├── ElectronicProduct.js # 전자제품 클래스 (상품 상속) +│ ├── main.js # 메인 실행 파일 +│ ├── Product.js # 상품 클래스 +│ └── ProductService.js # 상품 API 연동 서비스 +└── package.json +``` diff --git a/package.json b/package.json new file mode 100644 index 000000000..712ba22c2 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "splint_m_2", + "version": "1.0.0", + "main": "main.js", + "type": "module", + "scripts": { + + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + + "description": "" + + +} diff --git a/src/Article.js b/src/Article.js new file mode 100644 index 000000000..64b1126c0 --- /dev/null +++ b/src/Article.js @@ -0,0 +1,14 @@ +// 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 new file mode 100644 index 000000000..3c081963b --- /dev/null +++ b/src/ArticleService.js @@ -0,0 +1,62 @@ +// ArticleService.js +const ARTICLE_BASE = "https://panda-market-api-crud.vercel.app/articles"; + +export async function getArticleList(page = 1, pageSize = 10, keyword = "") { + try { + const res = await fetch(`${ARTICLE_BASE}?page=${page}&pageSize=${pageSize}&keyword=${keyword}`); + if (!res.ok) throw new Error("서버 응답 실패"); + return await res.json(); + } catch (err) { + console.error("getArticleList 실패:", err.message); + } +} + +export async function getArticle(id) { + try { + const res = await fetch(`${ARTICLE_BASE}/${id}`); + if (!res.ok) throw new Error("게시글 없음"); + return await res.json(); + } catch (err) { + console.error("getArticle 실패:", err.message); + } +} + +export async function createArticle(articleObj) { + try { + const res = await fetch(ARTICLE_BASE, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(articleObj), + }); + if (!res.ok) throw new Error("생성 실패"); + return await res.json(); + } catch (err) { + console.error("createArticle 실패:", err.message); + } +} + +export async function patchArticle(id, patchData) { + try { + const res = await fetch(`${ARTICLE_BASE}/${id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(patchData), + }); + if (!res.ok) throw new Error("수정 실패"); + return await res.json(); + } catch (err) { + console.error("patchArticle 실패:", err.message); + } +} + +export async function deleteArticle(id) { + try { + const res = await fetch(`${ARTICLE_BASE}/${id}`, { + method: "DELETE", + }); + if (!res.ok) throw new Error("삭제 실패"); + return await res.json(); + } catch (err) { + console.error("deleteArticle 실패:", err.message); + } +} \ No newline at end of file diff --git a/src/ElectronicProduct.js b/src/ElectronicProduct.js new file mode 100644 index 000000000..28af3cf8d --- /dev/null +++ b/src/ElectronicProduct.js @@ -0,0 +1,9 @@ +// 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 new file mode 100644 index 000000000..773971900 --- /dev/null +++ b/src/Product.js @@ -0,0 +1,15 @@ +// 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 new file mode 100644 index 000000000..7b3649698 --- /dev/null +++ b/src/ProductService.js @@ -0,0 +1,61 @@ +// 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/main.js b/src/main.js new file mode 100644 index 000000000..27ea72fe2 --- /dev/null +++ b/src/main.js @@ -0,0 +1,31 @@ +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(); // ← 반드시 호출해줘야 실행됨 From f715e2303af6ac0be5841035fce0d4b1c68ef6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=ED=98=95=EC=9D=B5?= Date: Tue, 22 Jul 2025 17:06:06 +0900 Subject: [PATCH 2/7] fix: Revert ArticleService to use .then/catch --- src/ArticleService.js | 88 ++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/src/ArticleService.js b/src/ArticleService.js index 3c081963b..c1188c180 100644 --- a/src/ArticleService.js +++ b/src/ArticleService.js @@ -1,62 +1,48 @@ // ArticleService.js const ARTICLE_BASE = "https://panda-market-api-crud.vercel.app/articles"; -export async function getArticleList(page = 1, pageSize = 10, keyword = "") { - try { - const res = await fetch(`${ARTICLE_BASE}?page=${page}&pageSize=${pageSize}&keyword=${keyword}`); - if (!res.ok) throw new Error("서버 응답 실패"); - return await res.json(); - } catch (err) { - console.error("getArticleList 실패:", err.message); - } +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 async function getArticle(id) { - try { - const res = await fetch(`${ARTICLE_BASE}/${id}`); - if (!res.ok) throw new Error("게시글 없음"); - return await res.json(); - } catch (err) { - console.error("getArticle 실패:", 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 async function createArticle(articleObj) { - try { - const res = await fetch(ARTICLE_BASE, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(articleObj), - }); - if (!res.ok) throw new Error("생성 실패"); - return await res.json(); - } catch (err) { - console.error("createArticle 실패:", 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 async function patchArticle(id, patchData) { - try { - const res = await fetch(`${ARTICLE_BASE}/${id}`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(patchData), - }); - if (!res.ok) throw new Error("수정 실패"); - return await res.json(); - } catch (err) { - console.error("patchArticle 실패:", 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 async function deleteArticle(id) { - try { - const res = await fetch(`${ARTICLE_BASE}/${id}`, { - method: "DELETE", - }); - if (!res.ok) throw new Error("삭제 실패"); - return await res.json(); - } catch (err) { - console.error("deleteArticle 실패:", err.message); - } -} \ No newline at end of file +export function deleteArticle(id) { + return fetch(`${ARTICLE_BASE}/${id}`, { + method: "DELETE", + }) + .then(res => res.json()) + .catch(err => console.error("deleteArticle 실패:", err.message)); +} From a020965a85b2a991eea2009648e77a68e1c706bf Mon Sep 17 00:00:00 2001 From: Park Date: Mon, 11 Aug 2025 00:33:20 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 9 + .github/PULL_REQUEST_TEMPLATE.md | 21 - .gitignore | 11 + .vs/ProjectSettings.json | 3 + .vs/Splint_M_3/v17/.suo | Bin 0 -> 14336 bytes .vs/VSWorkspaceState.json | 6 + .vs/slnx.sqlite | Bin 0 -> 90112 bytes README.md | 29 + package-lock.json | 1526 +++++++++++++++++ package.json | 29 + .../20250801023915_init/migration.sql | 41 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 45 + prisma/seed.js | 49 + render-build.sh | 7 + 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/middlewares/error-handler.middleware.js | 20 + src/middlewares/upload.middleware.js | 24 + src/middlewares/validation.middleware.js | 28 + src/server.js | 7 + 27 files changed, 2259 insertions(+), 21 deletions(-) create mode 100644 .env.example delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .vs/ProjectSettings.json create mode 100644 .vs/Splint_M_3/v17/.suo create mode 100644 .vs/VSWorkspaceState.json create mode 100644 .vs/slnx.sqlite create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 prisma/migrations/20250801023915_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.js create mode 100644 render-build.sh create mode 100644 src/api/articles/articles.controller.js create mode 100644 src/api/articles/articles.router.js create mode 100644 src/api/comments/comments.controller.js create mode 100644 src/api/comments/comments.router.js create mode 100644 src/api/index.js create mode 100644 src/api/products/products.controller.js create mode 100644 src/api/products/products.router.js create mode 100644 src/app.js create mode 100644 src/middlewares/error-handler.middleware.js create mode 100644 src/middlewares/upload.middleware.js create mode 100644 src/middlewares/validation.middleware.js create mode 100644 src/server.js diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..03418e064 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Server Configuration +PORT=3000 + +# Database URL (PostgreSQL) +# Example: DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public" +DATABASE_URL="" + +# CORS Origin +CORS_ORIGIN="*" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index ec85f6f1a..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,21 +0,0 @@ - -## 요구사항 - -### 기본 -- [x] 기본 항목 1 -- [ ] 기본 항목 2 - -### 심화 -- [ ] 심화 항목 1 -- [ ] 심화 항목 2 - -## 주요 변경사항 -- -- - -## 스크린샷 -![image](이미지url) - -## 멘토에게 -- 셀프 코드 리뷰를 통해 질문 이어가겠습니다. -- diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ac63ad616 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules +.env +dist + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Uploads +uploads/ diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 000000000..f8b488856 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/Splint_M_3/v17/.suo b/.vs/Splint_M_3/v17/.suo new file mode 100644 index 0000000000000000000000000000000000000000..8b7ae36829f9c01f1b4533a48d4c8779663aefa0 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 000000000..6b6114114 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..c0d03394001c83751e6458d141bf3ec366538841 GIT binary patch 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=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "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" + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0" + }, + "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==", + "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" + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "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/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "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==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "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/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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" + }, + "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==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "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", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "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/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prisma": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.22.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "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/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "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/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_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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "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/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "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/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "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/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..b4017acb1 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "guru-market-board", + "version": "1.0.0", + "description": "Used Market and Community Board API Server", + "main": "src/server.js", + "scripts": { + "start": "node src/server.js", + "dev": "nodemon src/server.js", + "prisma:migrate": "prisma migrate dev --name init", + "prisma:deploy": "prisma migrate deploy", + "prisma:seed": "node prisma/seed.js", + "build": "npm install && npm run prisma:deploy && npm run prisma:seed" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@prisma/client": "^5.15.0", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "express-validator": "^7.1.0", + "multer": "^1.4.5-lts.1" + }, + "devDependencies": { + "nodemon": "^3.1.3", + "prisma": "^5.15.0" + } +} diff --git a/prisma/migrations/20250801023915_init/migration.sql b/prisma/migrations/20250801023915_init/migration.sql new file mode 100644 index 000000000..388a6c359 --- /dev/null +++ b/prisma/migrations/20250801023915_init/migration.sql @@ -0,0 +1,41 @@ +-- CreateTable +CREATE TABLE "Product" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "price" INTEGER NOT NULL, + "tags" TEXT[], + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Product_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Article" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "content" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Article_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Comment" ( + "id" SERIAL NOT NULL, + "content" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "productId" INTEGER, + "articleId" INTEGER, + + CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000..fbffa92c2 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 000000000..555cd555b --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,45 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Product { + id Int @id @default(autoincrement()) + name String + description String + price Int + tags String[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + comments Comment[] @relation("ProductComments") +} + +model Article { + id Int @id @default(autoincrement()) + title String + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + comments Comment[] @relation("ArticleComments") +} + +model Comment { + id Int @id @default(autoincrement()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + productId Int? + product Product? @relation("ProductComments", fields: [productId], references: [id], onDelete: Cascade) + + articleId Int? + article Article? @relation("ArticleComments", fields: [articleId], references: [id], onDelete: Cascade) +} diff --git a/prisma/seed.js b/prisma/seed.js new file mode 100644 index 000000000..8f37d0346 --- /dev/null +++ b/prisma/seed.js @@ -0,0 +1,49 @@ +const { PrismaClient } = require('@prisma/client'); +const prisma = new PrismaClient(); + +async function main() { + // Create Products + await prisma.product.createMany({ + data: [ + { + name: '게이밍 마우스', + description: '고성능 게이밍 마우스입니다. RGB 조명이 특징입니다.', + price: 50000, + tags: ['게이밍', '마우스', 'RGB'], + }, + { + name: '기계식 키보드', + description: '청축 기계식 키보드입니다. 타건감이 뛰어납니다.', + price: 120000, + tags: ['기계식키보드', '청축', '게이밍'], + }, + ], + skipDuplicates: true, // Skip if already exists + }); + + // Create Articles + await prisma.article.createMany({ + data: [ + { + title: '첫 번째 게시글', + content: '안녕하세요! 자유게시판의 첫 번째 글입니다.', + }, + { + title: 'Prisma 사용법 질문', + content: 'Prisma로 댓글 기능을 구현하는 방법이 궁금합니다.', + }, + ], + skipDuplicates: true, + }); + + console.log('Seeding finished.'); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/render-build.sh b/render-build.sh new file mode 100644 index 000000000..12333bf53 --- /dev/null +++ b/render-build.sh @@ -0,0 +1,7 @@ +#!/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/api/articles/articles.controller.js b/src/api/articles/articles.controller.js new file mode 100644 index 000000000..44eb04fbb --- /dev/null +++ b/src/api/articles/articles.controller.js @@ -0,0 +1,95 @@ +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 new file mode 100644 index 000000000..c7e40c697 --- /dev/null +++ b/src/api/articles/articles.router.js @@ -0,0 +1,18 @@ +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 new file mode 100644 index 000000000..80b71b33c --- /dev/null +++ b/src/api/comments/comments.controller.js @@ -0,0 +1,106 @@ +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 new file mode 100644 index 000000000..c19fee163 --- /dev/null +++ b/src/api/comments/comments.router.js @@ -0,0 +1,25 @@ +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 new file mode 100644 index 000000000..0979d9e78 --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,12 @@ +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 new file mode 100644 index 000000000..5e17566cf --- /dev/null +++ b/src/api/products/products.controller.js @@ -0,0 +1,114 @@ +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 new file mode 100644 index 000000000..822111189 --- /dev/null +++ b/src/api/products/products.router.js @@ -0,0 +1,22 @@ +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 new file mode 100644 index 000000000..3d854396c --- /dev/null +++ b/src/app.js @@ -0,0 +1,30 @@ +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/middlewares/error-handler.middleware.js b/src/middlewares/error-handler.middleware.js new file mode 100644 index 000000000..e269d5b2a --- /dev/null +++ b/src/middlewares/error-handler.middleware.js @@ -0,0 +1,20 @@ +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 new file mode 100644 index 000000000..a11d5cf9d --- /dev/null +++ b/src/middlewares/upload.middleware.js @@ -0,0 +1,24 @@ +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 new file mode 100644 index 000000000..01c4e16c3 --- /dev/null +++ b/src/middlewares/validation.middleware.js @@ -0,0 +1,28 @@ +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/server.js b/src/server.js new file mode 100644 index 000000000..9ced3af19 --- /dev/null +++ b/src/server.js @@ -0,0 +1,7 @@ +const app = require('./app'); + +const PORT = process.env.PORT || 3000; + +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); +}); From 5ec0f1e36b8f032d3e2b7fefdb16938593c337f8 Mon Sep 17 00:00:00 2001 From: Park Date: Sun, 21 Sep 2025 21:58:04 +0900 Subject: [PATCH 4/7] =?UTF-8?q?TypeScript=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=EB=B0=8F=20Layered=20Architectur?= =?UTF-8?q?e=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodemon.json | 6 ++ package.json | 25 +++-- src/app.ts | 47 +++++++++ src/controllers/post.controller.ts | 147 ++++++++++++++++++++++++++++ src/controllers/user.controller.ts | 128 ++++++++++++++++++++++++ src/dto/post.dto.ts | 29 ++++++ src/dto/user.dto.ts | 29 ++++++ src/repositories/post.repository.ts | 62 ++++++++++++ src/repositories/user.repository.ts | 62 ++++++++++++ src/routes/index.ts | 11 +++ src/routes/post.routes.ts | 15 +++ src/routes/user.routes.ts | 14 +++ src/services/post.service.ts | 120 +++++++++++++++++++++++ src/services/user.service.ts | 124 +++++++++++++++++++++++ src/types/index.ts | 39 ++++++++ tsconfig.json | 35 +++++++ 16 files changed, 884 insertions(+), 9 deletions(-) create mode 100644 nodemon.json create mode 100644 src/app.ts create mode 100644 src/controllers/post.controller.ts create mode 100644 src/controllers/user.controller.ts create mode 100644 src/dto/post.dto.ts create mode 100644 src/dto/user.dto.ts create mode 100644 src/repositories/post.repository.ts create mode 100644 src/repositories/user.repository.ts create mode 100644 src/routes/index.ts create mode 100644 src/routes/post.routes.ts create mode 100644 src/routes/user.routes.ts create mode 100644 src/services/post.service.ts create mode 100644 src/services/user.service.ts create mode 100644 src/types/index.ts create mode 100644 tsconfig.json diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 000000000..bf3553669 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": ["src"], + "ext": "ts,json", + "ignore": ["src/**/*.spec.ts"], + "exec": "ts-node -r tsconfig-paths/register ./src/app.ts" +} \ No newline at end of file diff --git a/package.json b/package.json index 0160ed44f..5b9e91c8a 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,16 @@ "name": "guru-market-board", "version": "1.0.0", "description": "Used Market and Community Board API Server", - "main": "src/server.js", + "main": "dist/app.js", "scripts": { - "start": "node src/server.js", - "dev": "nodemon src/server.js", + "build": "tsc", + "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", - "build": "npm install && npm run prisma:deploy && npm run prisma:seed" - + "prisma:seed": "node prisma/seed.js" }, "keywords": [], "author": "", @@ -19,12 +20,18 @@ "@prisma/client": "^5.15.0", "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.19.2", + "express": "^5.1.0", "express-validator": "^7.1.0", "multer": "^1.4.5-lts.1" }, "devDependencies": { - "nodemon": "^3.1.3", - "prisma": "^5.15.0" + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/node": "^24.3.1", + "nodemon": "^3.1.10", + "prisma": "^5.15.0", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.9.2" } } diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 000000000..5a2180153 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,47 @@ +import express, { Application, Request, Response, NextFunction } from 'express'; +import cors from 'cors'; +import routes from './routes'; + +const app: Application = express(); +const PORT = process.env.PORT || 3000; + +// 미들웨어 설정 +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// API 라우트 +app.use('/api', routes); + +// 헬스 체크 엔드포인트 +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}에서 실행 중입니다.`); +}); + +export default app; \ No newline at end of file diff --git a/src/controllers/post.controller.ts b/src/controllers/post.controller.ts new file mode 100644 index 000000000..6b7f83dbb --- /dev/null +++ b/src/controllers/post.controller.ts @@ -0,0 +1,147 @@ +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/user.controller.ts b/src/controllers/user.controller.ts new file mode 100644 index 000000000..a197e2378 --- /dev/null +++ b/src/controllers/user.controller.ts @@ -0,0 +1,128 @@ +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/dto/post.dto.ts b/src/dto/post.dto.ts new file mode 100644 index 000000000..1a0320eea --- /dev/null +++ b/src/dto/post.dto.ts @@ -0,0 +1,29 @@ +// 포스트 생성 요청 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 new file mode 100644 index 000000000..eb4dbcf08 --- /dev/null +++ b/src/dto/user.dto.ts @@ -0,0 +1,29 @@ +// 사용자 생성 요청 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/repositories/post.repository.ts b/src/repositories/post.repository.ts new file mode 100644 index 000000000..70bc9e857 --- /dev/null +++ b/src/repositories/post.repository.ts @@ -0,0 +1,62 @@ +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/user.repository.ts b/src/repositories/user.repository.ts new file mode 100644 index 000000000..35f23f9a2 --- /dev/null +++ b/src/repositories/user.repository.ts @@ -0,0 +1,62 @@ +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/index.ts b/src/routes/index.ts new file mode 100644 index 000000000..8876ed646 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,11 @@ +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 new file mode 100644 index 000000000..8dcd1bcfc --- /dev/null +++ b/src/routes/post.routes.ts @@ -0,0 +1,15 @@ +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/user.routes.ts b/src/routes/user.routes.ts new file mode 100644 index 000000000..ae9c7b04b --- /dev/null +++ b/src/routes/user.routes.ts @@ -0,0 +1,14 @@ +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/services/post.service.ts b/src/services/post.service.ts new file mode 100644 index 000000000..fd2a94a7a --- /dev/null +++ b/src/services/post.service.ts @@ -0,0 +1,120 @@ +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/user.service.ts b/src/services/user.service.ts new file mode 100644 index 000000000..6cf29ec9d --- /dev/null +++ b/src/services/user.service.ts @@ -0,0 +1,124 @@ +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 new file mode 100644 index 000000000..cf4363e9e --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,39 @@ +// 사용자 엔티티 타입 +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; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..cda5c67b8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "lib": ["es2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "removeComments": false, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "baseUrl": "./src", + "paths": { + "@/*": ["*"], + "@/controllers/*": ["controllers/*"], + "@/services/*": ["services/*"], + "@/repositories/*": ["repositories/*"], + "@/types/*": ["types/*"], + "@/dto/*": ["dto/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file From b18aec85fc2f486e2fd7766927bfcadaea9eaa2d Mon Sep 17 00:00:00 2001 From: Park Date: Sun, 14 Sep 2025 20:49:44 +0900 Subject: [PATCH 5/7] =?UTF-8?q?TypeScript=20=EA=B8=B0=EB=B0=98=20JWT=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 15 +- README.md | 80 +- package-lock.json | 5194 ++++++++++++++++++++++---- package.json | 28 +- prisma/dev.db | Bin 0 -> 53248 bytes prisma/schema.prisma | 135 +- src/app.ts | 36 +- src/controllers/authController.ts | 280 ++ src/controllers/commentController.ts | 380 ++ src/controllers/likeController.ts | 370 ++ src/controllers/postController.ts | 371 ++ src/controllers/productController.ts | 376 ++ src/controllers/userController.ts | 348 ++ src/middleware/auth.ts | 105 + src/routes/auth.ts | 25 + src/routes/comments.ts | 17 + src/routes/posts.ts | 54 + src/routes/products.ts | 54 + src/routes/users.ts | 30 + src/types/index.ts | 4 +- src/utils/auth.ts | 106 + src/utils/prisma.ts | 11 + 22 files changed, 7252 insertions(+), 767 deletions(-) create mode 100644 prisma/dev.db create mode 100644 src/controllers/authController.ts create mode 100644 src/controllers/commentController.ts create mode 100644 src/controllers/likeController.ts create mode 100644 src/controllers/postController.ts create mode 100644 src/controllers/productController.ts create mode 100644 src/controllers/userController.ts create mode 100644 src/middleware/auth.ts create mode 100644 src/routes/auth.ts create mode 100644 src/routes/comments.ts create mode 100644 src/routes/posts.ts create mode 100644 src/routes/products.ts create mode 100644 src/routes/users.ts create mode 100644 src/utils/auth.ts create mode 100644 src/utils/prisma.ts diff --git a/.env.example b/.env.example index 03418e064..fc3a713d4 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,6 @@ -# Server Configuration -PORT=3000 - -# Database URL (PostgreSQL) -# Example: DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public" -DATABASE_URL="" - -# CORS Origin -CORS_ORIGIN="*" +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/README.md b/README.md index a4357b4ca..c2ffd41ce 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,67 @@ -# 중고 마켓 & 게시판 API 서버 +# 토큰 기반 유저 인증/인가 API 시스템 -중고 마켓과 커뮤니티 게시판 애플리케이션을 위한 API 서버입니다. +Node.js, Express, TypeScript, Prisma를 사용한 JWT 기반 인증/인가 시스템입니다. -## 주요 기능 +## 🚀 구현된 기능 -- **중고 마켓:** - - 상품 CRUD (생성, 조회, 수정, 삭제) - - 페이지네이션, 검색, 정렬 기능이 포함된 상품 목록 조회 - - 상품 이미지 업로드 -- **커뮤니티 게시판:** - - 게시글 CRUD - - 페이지네이션, 검색, 정렬 기능이 포함된 게시글 목록 조회 -- **댓글:** - - 상품과 게시글에 댓글 추가 기능 - - 댓글 CRUD - - 커서 기반 페이지네이션을 사용한 댓글 목록 조회 +### 기본 요구사항 +- ✅ JWT Access Token 기반 인증 +- ✅ 회원가입/로그인 API +- ✅ 비밀번호 해싱 (bcrypt) +- ✅ 상품/게시글/댓글 CRUD + 인가 +- ✅ 유저 정보 관리 +### 심화 요구사항 +- ✅ Refresh Token 구현 +- ✅ 상품/게시글 좋아요 기능 +- ✅ Prisma 관계형 데이터베이스 +- ✅ isLiked 필드로 좋아요 상태 표시 -## 배포 +## 🛠 기술 스택 -이 프로젝트는 [Render](https://splint-mission-3.onrender.com/)에 배포할 수 있도록 설정되어 있습니다. +- **언어**: TypeScript +- **프레임워크**: Node.js + Express +- **데이터베이스**: SQLite + Prisma ORM +- **인증**: JWT + bcrypt -`render-build.sh` 스크립트가 빌드 및 배포 과정을 처리합니다: -1. 의존성 설치 (`npm install`). -2. 데이터베이스 마이그레이션 적용 (`npm run prisma:deploy`). -3. 데이터베이스 시딩 (`npm run prisma:seed`). +서버: `http://localhost:3000` -Render 대시보드에서 시작 명령어는 `npm start`로 설정해야 합니다. \ No newline at end of file +## 📖 주요 API + +### 인증 +- `POST /api/auth/signup` - 회원가입 +- `POST /api/auth/login` - 로그인 +- `POST /api/auth/refresh` - 토큰 갱신 +- `POST /api/auth/logout` - 로그아웃 + +### 유저 +- `GET /api/users/me` - 내 정보 조회 +- `PUT /api/users/me` - 내 정보 수정 +- `PUT /api/users/me/password` - 비밀번호 변경 + +### 상품/게시글 +- `GET /api/products` - 목록 조회 +- `POST /api/products` - 등록 (인증 필요) +- `PUT /api/products/:id` - 수정 (작성자만) +- `DELETE /api/products/:id` - 삭제 (작성자만) +- `POST /api/products/:id/like` - 좋아요/취소 + +게시글 API(`/api/posts`)도 동일한 패턴으로 구현됨 + +## 🔐 인증/인가 + +- **Access Token**: 1시간, API 요청 인증용 +- **Refresh Token**: 7일, 토큰 갱신용 +- **권한**: 비인증(조회만) → 인증(CRUD) → 작성자(수정/삭제) + +## 📁 프로젝트 구조 + +``` +src/ +├── types/ # TypeScript 타입 정의 +├── controllers/ # API 컨트롤러 +├── middleware/ # 인증 미들웨어 +├── routes/ # 라우터 +├── utils/ # 유틸리티 +└── app.ts # 메인 애플리케이션 +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1530b380b..c1f7cc3a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,807 +1,3882 @@ { - "name": "guru-market-board", + "name": "authentication-api-ts", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "guru-market-board", + "name": "authentication-api-ts", "version": "1.0.0", "license": "ISC", "dependencies": { - "@prisma/client": "^5.15.0", + "@prisma/client": "^5.6.0", + "bcryptjs": "^2.4.3", "cors": "^2.8.5", - "dotenv": "^16.4.5", - "express": "^4.19.2", - "express-validator": "^7.1.0", - "multer": "^1.4.5-lts.1" + "dotenv": "^16.3.1", + "express": "^4.18.2", + "express-validator": "^7.0.1", + "jsonwebtoken": "^9.0.2" }, "devDependencies": { - "nodemon": "^3.1.3", - "prisma": "^5.15.0" + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.16", + "@types/express": "^4.17.20", + "@types/jest": "^29.5.7", + "@types/jsonwebtoken": "^9.0.5", + "@types/node": "^20.8.0", + "jest": "^29.7.0", + "prisma": "^5.6.0", + "ts-node": "^10.9.1", + "ts-node-dev": "^2.0.0", + "typescript": "^5.2.2" } }, - "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, - "license": "Apache-2.0", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": ">=16.13" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, - "peerDependencies": { - "prisma": "*" + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/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": { - "prisma": { + "supports-color": { "optional": true } } }, - "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==", - "devOptional": true, - "license": "Apache-2.0" + "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/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", "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" + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "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==", - "devOptional": true, - "license": "Apache-2.0" - }, - "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==", - "devOptional": true, - "license": "Apache-2.0", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/get-platform": "5.22.0" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "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==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.0" + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", - "license": "MIT" - }, - "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==", - "license": "MIT" + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "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==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "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" - }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", "dependencies": { - "streamsearch": "^1.1.0" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=10.16.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "@babel/helper-plugin-utils": "^7.12.13" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0-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==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=6.9.0" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-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==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "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", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "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", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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", + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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", + "@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", + "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-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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", + "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", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "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" + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0" + }, + "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==", + "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" + } + }, + "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==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@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", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "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==", + "license": "MIT" + }, + "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==", + "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", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.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==", + "dev": true, + "license": "BSD-3-Clause", + "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", + "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_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==", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "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==", + "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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", + "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "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" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "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" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "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/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "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==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "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==", + "dev": true, + "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==", + "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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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" + }, + "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==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.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, + "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", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { "node": ">= 0.6" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "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, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "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", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "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==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "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", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "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", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "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", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "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" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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", + "@types/node": "*", + "chalk": "^4.0.0", + "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", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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==", + "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" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "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", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "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/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "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/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "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==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "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==", + "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" + }, "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "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==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "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==", - "license": "BSD-2-Clause", + "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": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "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==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@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", + "walker": "^1.0.8" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "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==", + "dev": true, "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "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==", + "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" + }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "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==", + "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", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "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==", + "dev": true, "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" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, "license": "MIT", "dependencies": { - "lodash": "^4.17.21", - "validator": "~13.12.0" + "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" }, "engines": { - "node": ">= 8.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, "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" + "@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", + "@types/node": "*", + "chalk": "^4.0.0", + "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", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "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==", + "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", + "@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", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "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": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=10" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "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==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "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_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "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==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "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==", + "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "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==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "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==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "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==", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "bin": { + "jsesc": "bin/jsesc" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">=6" } }, - "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==", + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" }, "engines": { - "node": ">= 0.8" + "node": ">=12", + "npm": ">=6" } }, - "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==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "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": ">=0.10.0" + "node": ">=10" } }, - "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/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "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": ">= 0.10" + "node": ">=6" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "p-locate": "^4.1.0" }, "engines": { "node": ">=8" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "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/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "semver": "^7.5.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "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": "MIT", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.12.0" + "node": ">=10" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -830,6 +3905,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -839,6 +3921,20 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -872,6 +3968,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -889,21 +3995,23 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, "bin": { "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/ms": { @@ -912,85 +4020,33 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", - "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 6.0.0" - } + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "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==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", - "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", - "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/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "license": "MIT", "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/nodemon/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==", + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "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==", "dev": true, "license": "MIT" }, @@ -1004,6 +4060,19 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1037,6 +4106,106 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "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" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1046,12 +4215,56 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": 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==", + "dev": true, + "license": "MIT" + }, "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==", "license": "MIT" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -1065,6 +4278,57 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "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, + "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" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prisma": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", @@ -1085,11 +4349,19 @@ "fsevents": "2.3.3" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" + "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, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -1104,11 +4376,21 @@ "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==", + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], "license": "MIT" }, "node_modules/qs": { @@ -1150,25 +4432,11 @@ "node": ">= 0.8" } }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/readdirp": { @@ -1184,6 +4452,84 @@ "node": ">=8.10.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "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", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "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, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1211,16 +4557,13 @@ "license": "MIT" }, "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==", + "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" - }, - "engines": { - "node": ">=10" } }, "node_modules/send": { @@ -1283,6 +4626,29 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -1355,14 +4721,66 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "escape-string-regexp": "^2.0.0" }, "engines": { "node": ">=10" @@ -1377,42 +4795,129 @@ "node": ">= 0.8" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=10.0.0" + "node": ">=10" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "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/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": { - "safe-buffer": "~5.1.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "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==", + "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": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" } }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1435,14 +4940,149 @@ "node": ">=0.6" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "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==", "dev": true, - "license": "ISC", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "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==", + "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": { - "nodetouch": "bin/nodetouch.js" + "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" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-is": { @@ -1458,16 +5098,24 @@ "node": ">= 0.6" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" + "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, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "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==", "dev": true, "license": "MIT" }, @@ -1480,11 +5128,36 @@ "node": ">= 0.8" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" + "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==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } }, "node_modules/utils-merge": { "version": "1.0.1", @@ -1495,6 +5168,28 @@ "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", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/validator": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", @@ -1513,14 +5208,149 @@ "node": ">= 0.8" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "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_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 5b9e91c8a..cdc412633 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "guru-market-board", + "name": "authentication-api-ts", "version": "1.0.0", "description": "Used Market and Community Board API Server", "main": "dist/app.js", @@ -17,7 +17,10 @@ "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "^5.15.0", + "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", "express": "^5.1.0", @@ -25,13 +28,16 @@ "multer": "^1.4.5-lts.1" }, "devDependencies": { - "@types/cors": "^2.8.19", - "@types/express": "^5.0.3", - "@types/node": "^24.3.1", - "nodemon": "^3.1.10", - "prisma": "^5.15.0", - "ts-node": "^10.9.2", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.9.2" + "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" } -} +} \ No newline at end of file diff --git a/prisma/dev.db b/prisma/dev.db new file mode 100644 index 0000000000000000000000000000000000000000..d7d2b392c0d36d5c96db489fe9e8d8aa4ab70944 GIT binary patch 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) { - res.json({ - status: 'OK', - - message: '서버가 정상적으로 작동 중입니다.', - timestamp: new Date().toISOString() +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 + 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 + res.status(404).json({ + error: "요청하신 리소스를 찾을 수 없습니다.", + path: req.path, }); }); @@ -44,4 +44,4 @@ app.listen(PORT, () => { console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`); }); -export default app; \ No newline at end of file +export default app; diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts new file mode 100644 index 000000000..9d9d1e3de --- /dev/null +++ b/src/controllers/authController.ts @@ -0,0 +1,280 @@ +import { 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 { + AuthenticatedRequest, + 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: AuthenticatedRequest & { body: SignupRequest }, + res: Response +): Promise => { + try { + // 유효성 검사 결과 확인 + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ + error: '유효성 검사 실패', + details: errors.array() + }); + return; + } + + const { email, nickname, password } = req.body; + + // 이메일 중복 검사 + 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: AuthenticatedRequest & { body: LoginRequest }, + 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 } = req.body; + + // 사용자 조회 + 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: AuthenticatedRequest & { body: RefreshTokenRequest }, + res: Response +): Promise => { + try { + const { refreshToken } = req.body; + + 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: AuthenticatedRequest & { body: RefreshTokenRequest }, + res: Response +): Promise => { + try { + const { refreshToken } = req.body; + + if (refreshToken) { + // Refresh Token을 DB에서 삭제 + await deleteRefreshToken(refreshToken); + } + + 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/commentController.ts b/src/controllers/commentController.ts new file mode 100644 index 000000000..08a67b265 --- /dev/null +++ b/src/controllers/commentController.ts @@ -0,0 +1,380 @@ +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 new file mode 100644 index 000000000..8884f36bf --- /dev/null +++ b/src/controllers/likeController.ts @@ -0,0 +1,370 @@ +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/postController.ts b/src/controllers/postController.ts new file mode 100644 index 000000000..fe2a39471 --- /dev/null +++ b/src/controllers/postController.ts @@ -0,0 +1,371 @@ +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 new file mode 100644 index 000000000..3aa239b3c --- /dev/null +++ b/src/controllers/productController.ts @@ -0,0 +1,376 @@ +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/userController.ts b/src/controllers/userController.ts new file mode 100644 index 000000000..ece99dddd --- /dev/null +++ b/src/controllers/userController.ts @@ -0,0 +1,348 @@ +import { Response } from 'express'; +import { body, validationResult, ValidationChain } from 'express-validator'; +import prisma from '../utils/prisma'; +import { hashPassword, comparePassword } from '../utils/auth'; +import { + AuthenticatedRequest, + UpdateProfileRequest, + ChangePasswordRequest, + PaginationQuery, + PaginatedResponse, + Product, + ApiResponse +} 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: AuthenticatedRequest, + res: Response +): Promise => { + try { + const user = await prisma.user.findUnique({ + where: { id: req.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: AuthenticatedRequest & { body: UpdateProfileRequest }, + 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: req.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: req.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: AuthenticatedRequest & { body: ChangePasswordRequest }, + 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: req.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: req.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: AuthenticatedRequest & { query: PaginationQuery }, + res: Response> +): Promise => { + try { + const { page = '1', limit = '10' } = req.query; + const skip = (parseInt(page) - 1) * parseInt(limit); + + const products = await prisma.product.findMany({ + where: { authorId: req.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) + }); + + // 총 개수 조회 + const totalCount = await prisma.product.count({ + where: { authorId: req.user.id } + }); + + const result = { + data: products.map(product => ({ + ...product, + likesCount: product._count.likes, + commentsCount: product._count.comments, + _count: 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 getMyLikedProducts = async ( + req: AuthenticatedRequest & { query: PaginationQuery }, + res: Response> +): Promise => { + try { + const { page = '1', limit = '10' } = req.query; + const skip = (parseInt(page) - 1) * parseInt(limit); + + const likedProducts = await prisma.productLike.findMany({ + where: { userId: req.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) + }); + + // 총 개수 조회 + const totalCount = await prisma.productLike.count({ + where: { userId: req.user.id } + }); + + const result = { + data: likedProducts.map(like => ({ + ...like.product, + likesCount: like.product._count.likes, + commentsCount: like.product._count.comments, + isLiked: true, + _count: undefined as any + })), + pagination: { + currentPage: parseInt(page), + totalPages: Math.ceil(totalCount / parseInt(limit)), + totalCount, + hasNext: skip + likedProducts.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/middleware/auth.ts b/src/middleware/auth.ts new file mode 100644 index 000000000..9161b65db --- /dev/null +++ b/src/middleware/auth.ts @@ -0,0 +1,105 @@ +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: AuthenticatedRequest, + 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; + } + + // req 객체에 사용자 정보 추가 + req.user = user as UserResponse; + next(); + } catch (error) { + console.error('Auth middleware error:', error); + res.status(500).json({ + error: '서버 내부 오류가 발생했습니다.' + }); + } +}; + +// 선택적 인증 미들웨어 (로그인한 사용자 정보가 있으면 추가, 없어도 통과) +export const optionalAuth = async ( + req: OptionalAuthRequest, + res: Response, + next: NextFunction +): Promise => { + try { + const authHeader = req.headers['authorization'] as string; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + req.user = null; + next(); + return; + } + + const decoded = verifyAccessToken(token); + if (!decoded) { + req.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 + } + }); + + req.user = user ? (user as UserResponse) : null; + next(); + } catch (error) { + console.error('Optional auth middleware error:', error); + req.user = null; + next(); + } +}; \ No newline at end of file diff --git a/src/routes/auth.ts b/src/routes/auth.ts new file mode 100644 index 000000000..b252b5c3e --- /dev/null +++ b/src/routes/auth.ts @@ -0,0 +1,25 @@ +import express from 'express'; +import { + signup, + signupValidation, + login, + loginValidation, + refreshTokens, + logout +} from '../controllers/authController'; + +const router = express.Router(); + +// 회원가입 +router.post('/signup', signupValidation, signup); + +// 로그인 +router.post('/login', loginValidation, login); + +// 토큰 갱신 +router.post('/refresh', refreshTokens); + +// 로그아웃 +router.post('/logout', logout); + +export default router; \ No newline at end of file diff --git a/src/routes/comments.ts b/src/routes/comments.ts new file mode 100644 index 000000000..e04cef033 --- /dev/null +++ b/src/routes/comments.ts @@ -0,0 +1,17 @@ +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/posts.ts b/src/routes/posts.ts new file mode 100644 index 000000000..cd16cbb8b --- /dev/null +++ b/src/routes/posts.ts @@ -0,0 +1,54 @@ +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/products.ts b/src/routes/products.ts new file mode 100644 index 000000000..17b48f56d --- /dev/null +++ b/src/routes/products.ts @@ -0,0 +1,54 @@ +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/users.ts b/src/routes/users.ts new file mode 100644 index 000000000..5b68d1cae --- /dev/null +++ b/src/routes/users.ts @@ -0,0 +1,30 @@ +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; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index cf4363e9e..5b3df51cc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -30,10 +30,10 @@ export interface PaginationQuery { // 정렬 타입 export interface SortQuery { sortBy?: string; - sortOrder?: 'asc' | 'desc'; + sortOrder?: "asc" | "desc"; } // 공통 쿼리 파라미터 타입 export interface CommonQuery extends PaginationQuery, SortQuery { search?: string; -} \ No newline at end of file +} diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 000000000..8a68a96e1 --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,106 @@ +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: process.env.JWT_EXPIRES_IN || '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: process.env.JWT_REFRESH_EXPIRES_IN || '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() + } + } + }); +}; \ No newline at end of file diff --git a/src/utils/prisma.ts b/src/utils/prisma.ts new file mode 100644 index 000000000..f3514dbfc --- /dev/null +++ b/src/utils/prisma.ts @@ -0,0 +1,11 @@ +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 From 2523846ad06ae608a0f5cd62f0aa1932a53688f2 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Thu, 9 Oct 2025 22:06:26 +0900 Subject: [PATCH 6/7] =?UTF-8?q?auth,=20user=20=EC=BB=B4=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/controllers/authController.ts | 141 ++++++++++---------- src/controllers/userController.ts | 208 +++++++++++++++--------------- src/middleware/auth.ts | 67 +++++----- src/routes/auth.ts | 29 +++-- src/routes/users.ts | 27 ++-- src/types/index.ts | 120 +++++++++++++++++ src/utils/auth.ts | 72 ++++++----- 8 files changed, 398 insertions(+), 267 deletions(-) diff --git a/package.json b/package.json index cdc412633..fef8af8ef 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "dist/app.js", "scripts": { "build": "tsc", + "typecheck": "tsc --noEmit", "start": "node dist/app.js", "dev": "nodemon", "dev:ts": "ts-node -r tsconfig-paths/register src/app.ts", diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index 9d9d1e3de..63c3067d4 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -1,57 +1,54 @@ -import { Response } from 'express'; -import { body, validationResult, ValidationChain } from 'express-validator'; -import prisma from '../utils/prisma'; -import { - hashPassword, - comparePassword, - generateAccessToken, +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 { - AuthenticatedRequest, - SignupRequest, - LoginRequest, + cleanupExpiredTokens, +} from "../utils/auth"; +import { + SignupRequest, + LoginRequest, RefreshTokenRequest, TokenResponse, - ApiResponse -} from '../types'; + ApiResponse, +} from "../types"; // 회원가입 유효성 검사 규칙 export const signupValidation: ValidationChain[] = [ - body('email') + body("email") .isEmail() - .withMessage('올바른 이메일 형식이 아닙니다.') + .withMessage("올바른 이메일 형식이 아닙니다.") .normalizeEmail(), - body('nickname') + body("nickname") .isLength({ min: 2, max: 20 }) - .withMessage('닉네임은 2~20자 사이여야 합니다.') + .withMessage("닉네임은 2~20자 사이여야 합니다.") .matches(/^[가-힣a-zA-Z0-9_]+$/) - .withMessage('닉네임은 한글, 영문, 숫자, 언더스코어만 사용 가능합니다.'), - body('password') + .withMessage("닉네임은 한글, 영문, 숫자, 언더스코어만 사용 가능합니다."), + body("password") .isLength({ min: 6 }) - .withMessage('비밀번호는 최소 6자 이상이어야 합니다.') + .withMessage("비밀번호는 최소 6자 이상이어야 합니다.") .matches(/^(?=.*[a-zA-Z])(?=.*\d)/) - .withMessage('비밀번호는 영문과 숫자를 포함해야 합니다.') + .withMessage("비밀번호는 영문과 숫자를 포함해야 합니다."), ]; // 로그인 유효성 검사 규칙 export const loginValidation: ValidationChain[] = [ - body('email') + body("email") .isEmail() - .withMessage('올바른 이메일 형식이 아닙니다.') + .withMessage("올바른 이메일 형식이 아닙니다.") .normalizeEmail(), - body('password') - .notEmpty() - .withMessage('비밀번호를 입력해주세요.') + body("password").notEmpty().withMessage("비밀번호를 입력해주세요."), ]; // 회원가입 export const signup = async ( - req: AuthenticatedRequest & { body: SignupRequest }, + req: Request, res: Response ): Promise => { try { @@ -59,34 +56,34 @@ export const signup = async ( const errors = validationResult(req); if (!errors.isEmpty()) { res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() + error: "유효성 검사 실패", + details: errors.array(), }); return; } - const { email, nickname, password } = req.body; + const { email, nickname, password } = res.locals.user; // 이메일 중복 검사 const existingUserByEmail = await prisma.user.findUnique({ - where: { email } + where: { email }, }); if (existingUserByEmail) { res.status(409).json({ - error: '이미 사용 중인 이메일입니다.' + error: "이미 사용 중인 이메일입니다.", }); return; } // 닉네임 중복 검사 const existingUserByNickname = await prisma.user.findFirst({ - where: { nickname } + where: { nickname }, }); if (existingUserByNickname) { res.status(409).json({ - error: '이미 사용 중인 닉네임입니다.' + error: "이미 사용 중인 닉네임입니다.", }); return; } @@ -99,7 +96,7 @@ export const signup = async ( data: { email, nickname, - password: hashedPassword + password: hashedPassword, }, select: { id: true, @@ -107,26 +104,25 @@ export const signup = async ( nickname: true, image: true, createdAt: true, - updatedAt: true - } + updatedAt: true, + }, }); res.status(201).json({ - message: '회원가입이 완료되었습니다.', - data: { user } + message: "회원가입이 완료되었습니다.", + data: { user }, }); - } catch (error) { - console.error('회원가입 오류:', error); + console.error("회원가입 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", }); } }; // 로그인 export const login = async ( - req: AuthenticatedRequest & { body: LoginRequest }, + req: Request, res: Response ): Promise => { try { @@ -134,22 +130,22 @@ export const login = async ( const errors = validationResult(req); if (!errors.isEmpty()) { res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() + error: "유효성 검사 실패", + details: errors.array(), } as any); return; } - const { email, password } = req.body; + const { email, password } = res.locals.user; // 사용자 조회 const user = await prisma.user.findUnique({ - where: { email } + where: { email }, }); if (!user) { res.status(401).json({ - error: '이메일 또는 비밀번호가 올바르지 않습니다.' + error: "이메일 또는 비밀번호가 올바르지 않습니다.", } as any); return; } @@ -158,7 +154,7 @@ export const login = async ( const isPasswordValid = await comparePassword(password, user.password); if (!isPasswordValid) { res.status(401).json({ - error: '이메일 또는 비밀번호가 올바르지 않습니다.' + error: "이메일 또는 비밀번호가 올바르지 않습니다.", } as any); return; } @@ -177,31 +173,30 @@ export const login = async ( const { password: _, ...userWithoutPassword } = user; res.status(200).json({ - message: '로그인이 완료되었습니다.', + message: "로그인이 완료되었습니다.", user: userWithoutPassword, accessToken, - refreshToken + refreshToken, }); - } catch (error) { - console.error('로그인 오류:', error); + console.error("로그인 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", } as any); } }; // 토큰 갱신 export const refreshTokens = async ( - req: AuthenticatedRequest & { body: RefreshTokenRequest }, + req: Request, res: Response ): Promise => { try { - const { refreshToken } = req.body; + const { refreshToken } = res.locals.user; if (!refreshToken) { res.status(401).json({ - error: 'Refresh token이 필요합니다.' + error: "Refresh token이 필요합니다.", } as any); return; } @@ -210,7 +205,7 @@ export const refreshTokens = async ( const decoded = verifyRefreshToken(refreshToken); if (!decoded) { res.status(401).json({ - error: '유효하지 않은 refresh token입니다.' + error: "유효하지 않은 refresh token입니다.", } as any); return; } @@ -218,12 +213,12 @@ export const refreshTokens = async ( // DB에서 Refresh Token 확인 const storedToken = await prisma.refreshToken.findUnique({ where: { token: refreshToken }, - include: { user: true } + include: { user: true }, }); if (!storedToken || storedToken.expiresAt < new Date()) { res.status(401).json({ - error: '만료된 refresh token입니다.' + error: "만료된 refresh token입니다.", } as any); return; } @@ -240,27 +235,26 @@ export const refreshTokens = async ( const { password: _, ...userWithoutPassword } = storedToken.user; res.status(200).json({ - message: '토큰이 갱신되었습니다.', + message: "토큰이 갱신되었습니다.", user: userWithoutPassword, accessToken: newAccessToken, - refreshToken: newRefreshToken + refreshToken: newRefreshToken, }); - } catch (error) { - console.error('토큰 갱신 오류:', error); + console.error("토큰 갱신 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", } as any); } }; // 로그아웃 export const logout = async ( - req: AuthenticatedRequest & { body: RefreshTokenRequest }, + req: Request, res: Response ): Promise => { try { - const { refreshToken } = req.body; + const { refreshToken } = res.locals.user; if (refreshToken) { // Refresh Token을 DB에서 삭제 @@ -268,13 +262,12 @@ export const logout = async ( } res.status(200).json({ - message: '로그아웃이 완료되었습니다.' + message: "로그아웃이 완료되었습니다.", }); - } catch (error) { - console.error('로그아웃 오류:', error); + console.error("로그아웃 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", }); } -}; \ No newline at end of file +}; diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index ece99dddd..74ae957ec 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -1,81 +1,78 @@ -import { Response } from 'express'; -import { body, validationResult, ValidationChain } from 'express-validator'; -import prisma from '../utils/prisma'; -import { hashPassword, comparePassword } from '../utils/auth'; -import { - AuthenticatedRequest, +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, - ChangePasswordRequest, - PaginationQuery, PaginatedResponse, Product, - ApiResponse -} from '../types'; + ApiResponse, + UserResponse, +} from "../types"; // 비밀번호 변경 유효성 검사 export const changePasswordValidation: ValidationChain[] = [ - body('currentPassword') + body("currentPassword") .notEmpty() - .withMessage('현재 비밀번호를 입력해주세요.'), - body('newPassword') + .withMessage("현재 비밀번호를 입력해주세요."), + body("newPassword") .isLength({ min: 6 }) - .withMessage('새 비밀번호는 최소 6자 이상이어야 합니다.') + .withMessage("새 비밀번호는 최소 6자 이상이어야 합니다.") .matches(/^(?=.*[a-zA-Z])(?=.*\d)/) - .withMessage('새 비밀번호는 영문과 숫자를 포함해야 합니다.') + .withMessage("새 비밀번호는 영문과 숫자를 포함해야 합니다."), ]; // 프로필 업데이트 유효성 검사 export const updateProfileValidation: ValidationChain[] = [ - body('nickname') + body("nickname") .optional() .isLength({ min: 2, max: 20 }) - .withMessage('닉네임은 2~20자 사이여야 합니다.') + .withMessage("닉네임은 2~20자 사이여야 합니다.") .matches(/^[가-힣a-zA-Z0-9_]+$/) - .withMessage('닉네임은 한글, 영문, 숫자, 언더스코어만 사용 가능합니다.'), - body('image') + .withMessage("닉네임은 한글, 영문, 숫자, 언더스코어만 사용 가능합니다."), + body("image") .optional() .isURL() - .withMessage('올바른 이미지 URL 형식이 아닙니다.') + .withMessage("올바른 이미지 URL 형식이 아닙니다."), ]; // 내 정보 조회 export const getMyProfile = async ( - req: AuthenticatedRequest, + req: Request, res: Response ): Promise => { try { const user = await prisma.user.findUnique({ - where: { id: req.user.id }, + where: { id: res.locals.user.id }, select: { id: true, email: true, nickname: true, image: true, createdAt: true, - updatedAt: true - } + updatedAt: true, + }, }); if (!user) { res.status(404).json({ - error: '사용자를 찾을 수 없습니다.' + error: "사용자를 찾을 수 없습니다.", }); return; } res.status(200).json({ data: { user } }); - } catch (error) { - console.error('내 정보 조회 오류:', error); + console.error("내 정보 조회 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", }); } }; // 내 정보 수정 export const updateMyProfile = async ( - req: AuthenticatedRequest & { body: UpdateProfileRequest }, + req: Request, res: Response ): Promise => { try { @@ -83,8 +80,8 @@ export const updateMyProfile = async ( const errors = validationResult(req); if (!errors.isEmpty()) { res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() + error: "유효성 검사 실패", + details: errors.array(), }); return; } @@ -97,13 +94,13 @@ export const updateMyProfile = async ( const existingUser = await prisma.user.findFirst({ where: { nickname, - NOT: { id: req.user.id } - } + NOT: { id: res.locals.user.id }, + }, }); if (existingUser) { res.status(409).json({ - error: '이미 사용 중인 닉네임입니다.' + error: "이미 사용 중인 닉네임입니다.", }); return; } @@ -119,14 +116,14 @@ export const updateMyProfile = async ( // 업데이트할 데이터가 없는 경우 if (Object.keys(updateData).length === 0) { res.status(400).json({ - error: '업데이트할 정보가 없습니다.' + error: "업데이트할 정보가 없습니다.", }); return; } // 사용자 정보 업데이트 const updatedUser = await prisma.user.update({ - where: { id: req.user.id }, + where: { id: res.locals.user.id }, data: updateData, select: { id: true, @@ -134,26 +131,25 @@ export const updateMyProfile = async ( nickname: true, image: true, createdAt: true, - updatedAt: true - } + updatedAt: true, + }, }); res.status(200).json({ - message: '프로필이 성공적으로 업데이트되었습니다.', - data: { user: updatedUser } + message: "프로필이 성공적으로 업데이트되었습니다.", + data: { user: updatedUser }, }); - } catch (error) { - console.error('프로필 업데이트 오류:', error); + console.error("프로필 업데이트 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", }); } }; // 비밀번호 변경 export const changePassword = async ( - req: AuthenticatedRequest & { body: ChangePasswordRequest }, + req: Request, res: Response ): Promise => { try { @@ -161,8 +157,8 @@ export const changePassword = async ( const errors = validationResult(req); if (!errors.isEmpty()) { res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() + error: "유효성 검사 실패", + details: errors.array(), }); return; } @@ -171,21 +167,24 @@ export const changePassword = async ( // 현재 사용자 정보 조회 const user = await prisma.user.findUnique({ - where: { id: req.user.id } + where: { id: res.locals.user.id }, }); if (!user) { res.status(404).json({ - error: '사용자를 찾을 수 없습니다.' + error: "사용자를 찾을 수 없습니다.", }); return; } // 현재 비밀번호 확인 - const isCurrentPasswordValid = await comparePassword(currentPassword, user.password); + const isCurrentPasswordValid = await comparePassword( + currentPassword, + user.password + ); if (!isCurrentPasswordValid) { res.status(401).json({ - error: '현재 비밀번호가 올바르지 않습니다.' + error: "현재 비밀번호가 올바르지 않습니다.", }); return; } @@ -194,7 +193,7 @@ export const changePassword = async ( const isSamePassword = await comparePassword(newPassword, user.password); if (isSamePassword) { res.status(400).json({ - error: '새 비밀번호는 현재 비밀번호와 달라야 합니다.' + error: "새 비밀번호는 현재 비밀번호와 달라야 합니다.", }); return; } @@ -204,94 +203,94 @@ export const changePassword = async ( // 비밀번호 업데이트 await prisma.user.update({ - where: { id: req.user.id }, - data: { password: hashedNewPassword } + where: { id: res.locals.user.id }, + data: { password: hashedNewPassword }, }); res.status(200).json({ - message: '비밀번호가 성공적으로 변경되었습니다.' + message: "비밀번호가 성공적으로 변경되었습니다.", }); - } catch (error) { - console.error('비밀번호 변경 오류:', error); + console.error("비밀번호 변경 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", }); } }; // 내가 등록한 상품 목록 조회 export const getMyProducts = async ( - req: AuthenticatedRequest & { query: PaginationQuery }, + req: Request, res: Response> ): Promise => { try { - const { page = '1', limit = '10' } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); + 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: req.user.id }, + where: { authorId: res.locals.user.id }, include: { author: { select: { id: true, nickname: true, - image: true - } + image: true, + }, }, _count: { select: { likes: true, - comments: true - } - } + comments: true, + }, + }, }, - orderBy: { createdAt: 'desc' }, + orderBy: { createdAt: "desc" }, skip: parseInt(skip.toString()), - take: parseInt(limit) + take: parseInt(limit as string), }); // 총 개수 조회 const totalCount = await prisma.product.count({ - where: { authorId: req.user.id } + where: { authorId: res.locals.user.id }, }); - const result = { - data: products.map(product => ({ + const result: PaginatedResponse = { + data: products.map((product) => ({ ...product, likesCount: product._count.likes, commentsCount: product._count.comments, - _count: undefined as any + image: product.image || undefined, + author: product.author as UserResponse, + _count: undefined as any, })), pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), + currentPage: parseInt(page as string), + totalPages: Math.ceil(totalCount / parseInt(limit as string)), totalCount, - hasNext: skip + products.length < totalCount - } + hasNext: skip + products.length < totalCount, + }, }; res.status(200).json(result); - } catch (error) { - console.error('내 상품 목록 조회 오류:', error); + console.error("내 상품 목록 조회 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", } as any); } }; // 내가 좋아요한 상품 목록 조회 export const getMyLikedProducts = async ( - req: AuthenticatedRequest & { query: PaginationQuery }, + req: Request, res: Response> ): Promise => { try { - const { page = '1', limit = '10' } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); + 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: req.user.id }, + where: { userId: res.locals.user.id }, include: { product: { include: { @@ -299,50 +298,51 @@ export const getMyLikedProducts = async ( select: { id: true, nickname: true, - image: true - } + image: true, + }, }, _count: { select: { likes: true, - comments: true - } - } - } - } + comments: true, + }, + }, + }, + }, }, - orderBy: { createdAt: 'desc' }, + orderBy: { createdAt: "desc" }, skip: parseInt(skip.toString()), - take: parseInt(limit) + take: parseInt(limit as string), }); // 총 개수 조회 const totalCount = await prisma.productLike.count({ - where: { userId: req.user.id } + where: { userId: res.locals.user.id }, }); - const result = { - data: likedProducts.map(like => ({ + 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 + _count: undefined as any, })), pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), + currentPage: parseInt(page as string), + totalPages: Math.ceil(totalCount / parseInt(limit as string)), totalCount, - hasNext: skip + likedProducts.length < totalCount - } + hasNext: skip + likedProducts.length < totalCount, + }, }; res.status(200).json(result); - } catch (error) { - console.error('내가 좋아요한 상품 목록 조회 오류:', error); + console.error("내가 좋아요한 상품 목록 조회 오류:", error); res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + error: "서버 내부 오류가 발생했습니다.", } as any); } -}; \ No newline at end of file +}; diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index 9161b65db..3553d1ee4 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -1,21 +1,25 @@ -import { Request, Response, NextFunction } from 'express'; -import { verifyAccessToken } from '../utils/auth'; -import prisma from '../utils/prisma'; -import { AuthenticatedRequest, OptionalAuthRequest, UserResponse } from '../types'; +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: AuthenticatedRequest, + req: Request, res: Response, next: NextFunction ): Promise => { try { - const authHeader = req.headers['authorization'] as string; - const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN + const authHeader = req.headers["authorization"] as string; + const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN if (!token) { - res.status(401).json({ - error: 'Access token이 필요합니다.' + res.status(401).json({ + error: "Access token이 필요합니다.", }); return; } @@ -23,8 +27,8 @@ export const authenticateToken = async ( // 토큰 검증 const decoded = verifyAccessToken(token); if (!decoded) { - res.status(401).json({ - error: '유효하지 않은 토큰입니다.' + res.status(401).json({ + error: "유효하지 않은 토큰입니다.", }); return; } @@ -38,47 +42,48 @@ export const authenticateToken = async ( nickname: true, image: true, createdAt: true, - updatedAt: true - } + updatedAt: true, + }, }); if (!user) { - res.status(401).json({ - error: '존재하지 않는 사용자입니다.' + res.status(401).json({ + error: "존재하지 않는 사용자입니다.", }); return; } - // req 객체에 사용자 정보 추가 - req.user = user as UserResponse; + // res.locals 객체에 사용자 정보 추가 + res.locals.user = user; next(); } catch (error) { - console.error('Auth middleware error:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' + console.error("Auth middleware error:", error); + res.status(500).json({ + error: "서버 내부 오류가 발생했습니다.", }); } }; // 선택적 인증 미들웨어 (로그인한 사용자 정보가 있으면 추가, 없어도 통과) export const optionalAuth = async ( - req: OptionalAuthRequest, + req: Request, res: Response, next: NextFunction ): Promise => { try { - const authHeader = req.headers['authorization'] as string; - const token = authHeader && authHeader.split(' ')[1]; + const authHeader = + req.headers.authorization && req.headers.authorization.split(" ")[1]; + const token = authHeader && authHeader.split(" ")[1]; if (!token) { - req.user = null; + res.locals.user = null; next(); return; } const decoded = verifyAccessToken(token); if (!decoded) { - req.user = null; + res.locals.user = null; next(); return; } @@ -91,15 +96,15 @@ export const optionalAuth = async ( nickname: true, image: true, createdAt: true, - updatedAt: true - } + updatedAt: true, + }, }); - req.user = user ? (user as UserResponse) : null; + res.locals.user = user ? (user as UserResponse) : null; next(); } catch (error) { - console.error('Optional auth middleware error:', error); - req.user = null; + console.error("Optional auth middleware error:", error); + res.locals.user = null; next(); } -}; \ No newline at end of file +}; diff --git a/src/routes/auth.ts b/src/routes/auth.ts index b252b5c3e..14591b534 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,25 +1,30 @@ -import express from 'express'; -import { - signup, - signupValidation, - login, +import express from "express"; +import { + signup, + signupValidation, + login, loginValidation, refreshTokens, - logout -} from '../controllers/authController'; + 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("/signup", signupValidation, signup); // 로그인 -router.post('/login', loginValidation, login); +router.post("/login", loginValidation, login); // 토큰 갱신 -router.post('/refresh', refreshTokens); +router.post("/refresh", refreshTokens); // 로그아웃 -router.post('/logout', logout); +router.post("/logout", logout); -export default router; \ No newline at end of file +export default router; diff --git a/src/routes/users.ts b/src/routes/users.ts index 5b68d1cae..53929f8f4 100644 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -1,30 +1,35 @@ -import express from 'express'; -import { +import express from "express"; +import { getMyProfile, updateMyProfile, updateProfileValidation, changePassword, changePasswordValidation, getMyProducts, - getMyLikedProducts -} from '../controllers/userController'; -import { authenticateToken } from '../middleware/auth'; + getMyLikedProducts, +} from "../controllers/userController"; +import { authenticateToken } from "../middleware/auth"; const router = express.Router(); // 내 정보 조회 -router.get('/me', authenticateToken, getMyProfile); +router.get("/me", authenticateToken, getMyProfile); // 내 정보 수정 -router.put('/me', authenticateToken, updateProfileValidation, updateMyProfile); +router.put("/me", authenticateToken, updateProfileValidation, updateMyProfile); // 비밀번호 변경 -router.put('/me/password', authenticateToken, changePasswordValidation, changePassword); +router.put( + "/me/password", + authenticateToken, + changePasswordValidation, + changePassword +); // 내가 등록한 상품 목록 -router.get('/me/products', authenticateToken, getMyProducts); +router.get("/me/products", authenticateToken, getMyProducts); // 내가 좋아요한 상품 목록 -router.get('/me/liked-products', authenticateToken, getMyLikedProducts); +router.get("/me/liked-products", authenticateToken, getMyLikedProducts); -export default router; \ No newline at end of file +export default router; diff --git a/src/types/index.ts b/src/types/index.ts index 5b3df51cc..62ee7eba1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,123 @@ +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; diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 8a68a96e1..88cffd709 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,7 +1,7 @@ -import bcrypt from 'bcryptjs'; -import jwt from 'jsonwebtoken'; -import { PrismaClient } from '@prisma/client'; -import { JwtTokenPayload } from '../types'; +import bcrypt from "bcryptjs"; +import jwt from "jsonwebtoken"; +import { PrismaClient } from "@prisma/client"; +import { JwtTokenPayload } from "../types"; const prisma = new PrismaClient(); @@ -13,7 +13,7 @@ export const hashPassword = async (password: string): Promise => { // 비밀번호 검증 export const comparePassword = async ( - password: string, + password: string, hashedPassword: string ): Promise => { return await bcrypt.compare(password, hashedPassword); @@ -21,36 +21,35 @@ export const comparePassword = async ( // Access Token 생성 export const generateAccessToken = (userId: number): string => { - const payload: Omit = { - userId, - type: 'access' + const payload: Omit = { + userId, + type: "access", }; - - return jwt.sign( - payload, - process.env.JWT_SECRET as string, - { expiresIn: process.env.JWT_EXPIRES_IN || '1h' } - ); + + 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' + const payload: Omit = { + userId, + type: "refresh", }; - - return jwt.sign( - payload, - process.env.JWT_REFRESH_SECRET as string, - { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d' } - ); + + 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; + return jwt.verify( + token, + process.env.JWT_SECRET as string + ) as JwtTokenPayload; } catch (error) { return null; } @@ -59,7 +58,10 @@ export const verifyAccessToken = (token: string): JwtTokenPayload | null => { // Refresh Token 검증 export const verifyRefreshToken = (token: string): JwtTokenPayload | null => { try { - return jwt.verify(token, process.env.JWT_REFRESH_SECRET as string) as JwtTokenPayload; + return jwt.verify( + token, + process.env.JWT_REFRESH_SECRET as string + ) as JwtTokenPayload; } catch (error) { return null; } @@ -67,18 +69,18 @@ export const verifyRefreshToken = (token: string): JwtTokenPayload | null => { // Refresh Token을 DB에 저장 export const saveRefreshToken = async ( - userId: number, + userId: number, token: string ): Promise => { const expiresAt = new Date(); expiresAt.setDate(expiresAt.getDate() + 7); // 7일 후 만료 - + await prisma.refreshToken.create({ data: { token, userId, - expiresAt - } + expiresAt, + }, }); }; @@ -86,11 +88,11 @@ export const saveRefreshToken = async ( export const deleteRefreshToken = async (token: string): Promise => { try { await prisma.refreshToken.delete({ - where: { token } + where: { token }, }); } catch (error) { // 토큰이 이미 없는 경우 무시 - console.log('Token not found or already deleted'); + console.log("Token not found or already deleted"); } }; @@ -99,8 +101,8 @@ export const cleanupExpiredTokens = async (): Promise => { await prisma.refreshToken.deleteMany({ where: { expiresAt: { - lt: new Date() - } - } + lt: new Date(), + }, + }, }); -}; \ No newline at end of file +}; From 664e09ac9fc05d7ec174da6de8727c15144f99dd Mon Sep 17 00:00:00 2001 From: Park Date: Thu, 20 Nov 2025 18:36:05 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[=EB=B0=95=ED=98=95=EC=9D=B5]=20sprint8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 9 +- .gitignore | 17 +- README.md | 220 +- package-lock.json | 5405 +++++------------ package.json | 62 +- prisma/dev.db | Bin 53248 -> 0 bytes .../20250801023915_init/migration.sql | 41 - prisma/migrations/migration_lock.toml | 3 - prisma/schema.prisma | 176 +- prisma/seed.js | 49 - 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 | 47 - src/controllers/articlesController.ts | 34 + src/controllers/authController.ts | 296 +- src/controllers/commentController.ts | 380 -- src/controllers/commentsController.ts | 27 + src/controllers/errorController.ts | 12 + src/controllers/likeController.ts | 370 -- src/controllers/notificationsController.ts | 10 + src/controllers/post.controller.ts | 147 - src/controllers/postController.ts | 371 -- src/controllers/productController.ts | 376 -- src/controllers/productsController.ts | 36 + src/controllers/user.controller.ts | 128 - src/controllers/userController.ts | 348 -- src/controllers/usersController.ts | 75 + src/dto/post.dto.ts | 29 - src/dto/user.dto.ts | 29 - src/dto/userResponseDTO.ts | 12 + src/lib/constants.ts | 5 + src/lib/errors/BadRequestError.ts | 8 + src/lib/errors/ForbiddenError.ts | 8 + src/lib/errors/NotFoundError.ts | 8 + src/lib/errors/UnauthorizedError.ts | 8 + src/lib/prismaClient.ts | 3 + src/lib/withAsync.ts | 7 + src/main.js | 31 - src/main.ts | 41 + src/middleware/auth.ts | 110 - src/middlewares/authenticate.ts | 44 + src/middlewares/error-handler.middleware.js | 20 - src/middlewares/upload.middleware.js | 24 - src/middlewares/validation.middleware.js | 28 - src/repositories/articlesRepository.ts | 30 + src/repositories/commentsRepository.ts | 51 + src/repositories/favoritesRepository.ts | 28 + src/repositories/notificationsRepository.ts | 60 + src/repositories/post.repository.ts | 62 - src/repositories/productsRepository.ts | 97 + src/repositories/user.repository.ts | 62 - src/repositories/usersRepository.ts | 31 + src/routers/articlesRouter.ts | 18 + src/routers/authRouter.ts | 10 + src/routers/commentsRouter.ts | 16 + src/routers/imagesRouter.ts | 30 + src/routers/notificationsRouter.ts | 10 + src/routers/productsRouter.ts | 18 + src/routers/usersRouter.ts | 22 + src/routes/auth.ts | 30 - src/routes/comments.ts | 17 - src/routes/index.ts | 11 - src/routes/post.routes.ts | 15 - src/routes/posts.ts | 54 - src/routes/products.ts | 54 - src/routes/user.routes.ts | 14 - src/routes/users.ts | 35 - src/server.js | 7 - src/services/articlesService.ts | 54 + src/services/authService.ts | 73 + src/services/commentsService.ts | 122 + src/services/notificationsService.ts | 69 + src/services/post.service.ts | 120 - src/services/productsService.ts | 80 + src/services/socketService.ts | 39 + src/services/user.service.ts | 124 - src/services/usersService.ts | 30 + src/structs/articlesStructs.ts | 13 + src/structs/authStructs.ts | 12 + src/structs/commentsStructs.ts | 11 + src/structs/commonStructs.ts | 7 + src/structs/productsStructs.ts | 17 + src/structs/usersStructs.ts | 27 + src/types/Article.ts | 11 + src/types/Comment.ts | 9 + src/types/Notification.ts | 14 + src/types/Product.ts | 13 + src/types/User.ts | 9 + src/types/index.ts | 159 - src/types/pagination.ts | 21 + src/utils/auth.ts | 108 - src/utils/prisma.ts | 11 - tsconfig.json | 29 +- 104 files changed, 3127 insertions(+), 8460 deletions(-) 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 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/app.ts create mode 100644 src/controllers/articlesController.ts delete mode 100644 src/controllers/commentController.ts create mode 100644 src/controllers/commentsController.ts create mode 100644 src/controllers/errorController.ts delete mode 100644 src/controllers/likeController.ts create mode 100644 src/controllers/notificationsController.ts delete mode 100644 src/controllers/post.controller.ts delete mode 100644 src/controllers/postController.ts delete mode 100644 src/controllers/productController.ts create mode 100644 src/controllers/productsController.ts delete mode 100644 src/controllers/user.controller.ts delete mode 100644 src/controllers/userController.ts create mode 100644 src/controllers/usersController.ts delete mode 100644 src/dto/post.dto.ts delete mode 100644 src/dto/user.dto.ts create mode 100644 src/dto/userResponseDTO.ts create mode 100644 src/lib/constants.ts create mode 100644 src/lib/errors/BadRequestError.ts create mode 100644 src/lib/errors/ForbiddenError.ts create mode 100644 src/lib/errors/NotFoundError.ts create mode 100644 src/lib/errors/UnauthorizedError.ts create mode 100644 src/lib/prismaClient.ts create mode 100644 src/lib/withAsync.ts delete mode 100644 src/main.js create mode 100644 src/main.ts delete mode 100644 src/middleware/auth.ts create mode 100644 src/middlewares/authenticate.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/articlesRepository.ts create mode 100644 src/repositories/commentsRepository.ts create mode 100644 src/repositories/favoritesRepository.ts create mode 100644 src/repositories/notificationsRepository.ts delete mode 100644 src/repositories/post.repository.ts create mode 100644 src/repositories/productsRepository.ts delete mode 100644 src/repositories/user.repository.ts create mode 100644 src/repositories/usersRepository.ts create mode 100644 src/routers/articlesRouter.ts create mode 100644 src/routers/authRouter.ts create mode 100644 src/routers/commentsRouter.ts create mode 100644 src/routers/imagesRouter.ts create mode 100644 src/routers/notificationsRouter.ts create mode 100644 src/routers/productsRouter.ts create mode 100644 src/routers/usersRouter.ts delete mode 100644 src/routes/auth.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 delete mode 100644 src/routes/products.ts delete mode 100644 src/routes/user.routes.ts delete mode 100644 src/routes/users.ts delete mode 100644 src/server.js create mode 100644 src/services/articlesService.ts create mode 100644 src/services/authService.ts create mode 100644 src/services/commentsService.ts create mode 100644 src/services/notificationsService.ts delete mode 100644 src/services/post.service.ts create mode 100644 src/services/productsService.ts create mode 100644 src/services/socketService.ts delete mode 100644 src/services/user.service.ts create mode 100644 src/services/usersService.ts create mode 100644 src/structs/articlesStructs.ts create mode 100644 src/structs/authStructs.ts create mode 100644 src/structs/commentsStructs.ts create mode 100644 src/structs/commonStructs.ts create mode 100644 src/structs/productsStructs.ts create mode 100644 src/structs/usersStructs.ts create mode 100644 src/types/Article.ts create mode 100644 src/types/Comment.ts create mode 100644 src/types/Notification.ts create mode 100644 src/types/Product.ts create mode 100644 src/types/User.ts delete mode 100644 src/types/index.ts create mode 100644 src/types/pagination.ts delete mode 100644 src/utils/auth.ts delete mode 100644 src/utils/prisma.ts diff --git a/.env.example b/.env.example index fc3a713d4..40768ed37 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,3 @@ -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 +DATABASE_URL="postgresql://user:password@localhost:5432/pandamarket?schema=public" +JWT_SECRET="your-secret-key-here" +PORT=3000 diff --git a/.gitignore b/.gitignore index ac63ad616..3732bc9a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,8 @@ -node_modules +node_modules/ +build/ .env -dist - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Uploads -uploads/ +public/uploads/ +*.log +.DS_Store +.claude/ +USAGE_GUIDE.md diff --git a/README.md b/README.md index c2ffd41ce..a28752cd8 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,189 @@ -# 토큰 기반 유저 인증/인가 API 시스템 +# Pandamarket Notification System -Node.js, Express, TypeScript, Prisma를 사용한 JWT 기반 인증/인가 시스템입니다. +판다마켓 실시간 알림 시스템 (Mission 8) -## 🚀 구현된 기능 +## 주요 기능 -### 기본 요구사항 -- ✅ JWT Access Token 기반 인증 -- ✅ 회원가입/로그인 API -- ✅ 비밀번호 해싱 (bcrypt) -- ✅ 상품/게시글/댓글 CRUD + 인가 -- ✅ 유저 정보 관리 +### 알림 시스템 +- **실시간 알림**: Socket.IO를 사용한 실시간 알림 전송 +- **알림 종류**: + - `PRICE_CHANGED`: 좋아요한 상품의 가격 변동 + - `NEW_COMMENT`: 작성한 게시글에 댓글 등록 -### 심화 요구사항 -- ✅ Refresh Token 구현 -- ✅ 상품/게시글 좋아요 기능 -- ✅ Prisma 관계형 데이터베이스 -- ✅ isLiked 필드로 좋아요 상태 표시 +### API 엔드포인트 -## 🛠 기술 스택 +#### 인증 +- `POST /auth/signup` - 회원가입 +- `POST /auth/signin` - 로그인 -- **언어**: TypeScript -- **프레임워크**: Node.js + Express -- **데이터베이스**: SQLite + Prisma ORM -- **인증**: JWT + bcrypt +#### 사용자 +- `GET /users/me` - 내 정보 조회 +- `PATCH /users/me` - 내 정보 수정 +- `PATCH /users/me/password` - 비밀번호 변경 +- `GET /users/me/notifications` - 내 알림 목록 조회 (cursor pagination) + - Query: `cursor`, `limit` + - Response: `list`, `totalCount`, `unreadCount`, `nextCursor` -서버: `http://localhost:3000` +#### 알림 +- `PATCH /notifications/:id/read` - 알림 읽음 처리 -## 📖 주요 API +#### 상품 +- `POST /products` - 상품 생성 +- `GET /products/:id` - 상품 조회 +- `PATCH /products/:id` - 상품 수정 (가격 변경 시 좋아요한 사용자에게 알림) +- `DELETE /products/:id` - 상품 삭제 -### 인증 -- `POST /api/auth/signup` - 회원가입 -- `POST /api/auth/login` - 로그인 -- `POST /api/auth/refresh` - 토큰 갱신 -- `POST /api/auth/logout` - 로그아웃 +#### 게시글 +- `POST /articles` - 게시글 생성 +- `GET /articles/:id` - 게시글 조회 +- `PATCH /articles/:id` - 게시글 수정 +- `DELETE /articles/:id` - 게시글 삭제 -### 유저 -- `GET /api/users/me` - 내 정보 조회 -- `PUT /api/users/me` - 내 정보 수정 -- `PUT /api/users/me/password` - 비밀번호 변경 +#### 댓글 +- `POST /comments` - 댓글 생성 (게시글 작성자에게 알림) +- `PATCH /comments/:id` - 댓글 수정 +- `DELETE /comments/:id` - 댓글 삭제 -### 상품/게시글 -- `GET /api/products` - 목록 조회 -- `POST /api/products` - 등록 (인증 필요) -- `PUT /api/products/:id` - 수정 (작성자만) -- `DELETE /api/products/:id` - 삭제 (작성자만) -- `POST /api/products/:id/like` - 좋아요/취소 +#### 이미지 +- `POST /images/upload` - 이미지 업로드 -게시글 API(`/api/posts`)도 동일한 패턴으로 구현됨 +## 설치 및 실행 -## 🔐 인증/인가 +### 1. 패키지 설치 +```bash +npm install +``` + +### 2. 환경 변수 설정 +`.env` 파일을 생성하고 다음 내용을 설정하세요: +``` +DATABASE_URL="postgresql://user:password@localhost:5432/pandamarket?schema=public" +JWT_SECRET="your-secret-key" +PORT=3000 +``` + +### 3. 데이터베이스 마이그레이션 +```bash +npx prisma migrate dev +``` -- **Access Token**: 1시간, API 요청 인증용 -- **Refresh Token**: 7일, 토큰 갱신용 -- **권한**: 비인증(조회만) → 인증(CRUD) → 작성자(수정/삭제) +### 4. Prisma Client 생성 +```bash +npx prisma generate +``` -## 📁 프로젝트 구조 +### 5. 개발 서버 실행 +```bash +npm run dev +``` + +### 6. 프로덕션 빌드 및 실행 +```bash +npm run build +npm start +``` +## Socket.IO 클라이언트 연결 + +### 연결 방법 +```javascript +import { io } from 'socket.io-client'; + +const socket = io('http://localhost:3000', { + auth: { + accessToken: 'your-jwt-token' + } +}); + +// 알림 수신 +socket.on('notification', (notification) => { + console.log('New notification:', notification); + // notification.type: 'PRICE_CHANGED' | 'NEW_COMMENT' + // notification.payload: { productId, price } | { articleId } +}); + +// 연결 오류 +socket.on('connect_error', (error) => { + console.error('Connection error:', error); +}); +``` + +## 알림 페이로드 구조 + +### PRICE_CHANGED +```json +{ + "id": 1, + "userId": 123, + "type": "PRICE_CHANGED", + "payload": { + "productId": 456, + "price": 50000 + }, + "read": false, + "createdAt": "2025-01-20T10:00:00.000Z", + "updatedAt": "2025-01-20T10:00:00.000Z" +} +``` + +### NEW_COMMENT +```json +{ + "id": 2, + "userId": 123, + "type": "NEW_COMMENT", + "payload": { + "articleId": 789 + }, + "read": false, + "createdAt": "2025-01-20T10:00:00.000Z", + "updatedAt": "2025-01-20T10:00:00.000Z" +} +``` + +## 기술 스택 +- **Backend**: TypeScript, Node.js, Express +- **Database**: PostgreSQL, Prisma ORM +- **Real-time**: Socket.IO +- **Authentication**: JWT +- **Validation**: Superstruct + +## 프로젝트 구조 ``` src/ -├── types/ # TypeScript 타입 정의 -├── controllers/ # API 컨트롤러 -├── middleware/ # 인증 미들웨어 -├── routes/ # 라우터 -├── utils/ # 유틸리티 -└── app.ts # 메인 애플리케이션 -``` \ No newline at end of file +├── controllers/ # 요청 처리 로직 +├── dto/ # Data Transfer Objects +├── lib/ # 유틸리티 및 에러 클래스 +├── middlewares/ # 미들웨어 (인증 등) +├── repositories/ # 데이터베이스 접근 계층 +├── routers/ # 라우팅 +├── services/ # 비즈니스 로직 +├── structs/ # 유효성 검사 스키마 +├── types/ # TypeScript 타입 정의 +└── main.ts # 애플리케이션 진입점 +``` + +## 주요 구현 사항 + +### 1. 실시간 알림 시스템 +- Socket.IO를 사용한 WebSocket 연결 +- JWT 토큰 기반 WebSocket 인증 +- 사용자별 room 분리로 개인 알림 전송 + +### 2. 알림 트리거 +- **상품 가격 변동**: 상품 업데이트 시 가격이 변경되면 해당 상품을 좋아요한 모든 사용자에게 알림 +- **댓글 작성**: 게시글에 댓글이 달리면 게시글 작성자에게 알림 (본인이 작성한 댓글 제외) + +### 3. 알림 조회 +- Cursor 기반 페이지네이션 +- 안읽은 알림 개수 반환 +- 읽음 처리 기능 + +## 데이터베이스 스키마 +- **User**: 사용자 정보 +- **Product**: 상품 정보 +- **Article**: 게시글 정보 +- **Comment**: 댓글 정보 +- **Favorite**: 상품 좋아요 +- **Like**: 게시글 좋아요 +- **Notification**: 알림 (type, payload, read) diff --git a/package-lock.json b/package-lock.json index c1f7cc3a1..f4d3aa1ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,541 +1,436 @@ { - "name": "authentication-api-ts", + "name": "pandamarket-notification", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "authentication-api-ts", + "name": "pandamarket-notification", "version": "1.0.0", - "license": "ISC", "dependencies": { - "@prisma/client": "^5.6.0", - "bcryptjs": "^2.4.3", + "@prisma/client": "^5.16.2", + "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "express-validator": "^7.0.1", - "jsonwebtoken": "^9.0.2" + "dotenv": "^16.4.5", + "express": "^4.19.2", + "jsonwebtoken": "^9.0.2", + "multer": "^1.4.5-lts.1", + "socket.io": "^4.8.1", + "superstruct": "^2.0.2", + "uuid": "^11.0.5" }, "devDependencies": { - "@types/bcryptjs": "^2.4.6", - "@types/cors": "^2.8.16", - "@types/express": "^4.17.20", - "@types/jest": "^29.5.7", - "@types/jsonwebtoken": "^9.0.5", - "@types/node": "^20.8.0", - "jest": "^29.7.0", - "prisma": "^5.6.0", - "ts-node": "^10.9.1", - "ts-node-dev": "^2.0.0", - "typescript": "^5.2.2" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1.4.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jsonwebtoken": "^9.0.9", + "@types/multer": "^1.4.12", + "nodemon": "^3.1.9", + "prettier": "^3.3.2", + "prisma": "^5.16.2", + "ts-node": "^10.9.2", + "typescript": "^5.8.2" + } + }, + "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", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/core/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", + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", "dependencies": { - "ms": "^2.1.3" + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "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, + "license": "Apache-2.0", "engines": { - "node": ">=6.0" + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" }, "peerDependenciesMeta": { - "supports-color": { + "prisma": { "optional": true } } }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.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==", + "devOptional": true, + "license": "Apache-2.0" }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" + "@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" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "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==", + "devOptional": true, + "license": "Apache-2.0" }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", + "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==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", + "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==", + "devOptional": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@prisma/debug": "5.22.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@tsconfig/node10": { + "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", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "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==", + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } + "license": "MIT" }, - "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==", + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" + "@types/node": "*" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/node": "*" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "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": { - "@babel/helper-plugin-utils": "^7.12.13" - }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/express": "*" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, + "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==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/node": "*" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "node_modules/@types/express": { + "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": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@types/express-serve-static-core": { + "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": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/ms": "*", + "@types/node": "*" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "license": "MIT" }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/express": "*" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "undici-types": "~7.16.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/node": "*" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@types/serve-static": { + "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": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "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": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "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==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 0.6" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.4.0" } }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "acorn": "^8.11.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=0.4.0" } }, - "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "dev": true, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" + "debug": "4" }, "engines": { - "node": ">=6.9.0" + "node": ">= 6.0.0" } }, - "node_modules/@babel/traverse/node_modules/debug": { + "node_modules/agent-base/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" @@ -549,2446 +444,525 @@ } } }, - "node_modules/@babel/traverse/node_modules/ms": { + "node_modules/agent-base/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==", - "dev": true, + "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==", "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "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", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=12" + "node": ">= 8" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" }, - "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", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", "license": "ISC", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 6" } }, - "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==", + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "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", - "@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" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": "^4.5.0 || >= 5.9" } }, - "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==", - "dev": true, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 10.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==", + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", - "dev": true, + "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==", "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "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" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "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==", - "dev": true, + "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==", "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "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" + "fill-range": "^7.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "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", - "@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", - "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-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" + "streamsearch": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=10.16.0" } }, - "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==", - "dev": true, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.8" } }, - "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==", - "dev": true, + "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", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" } }, - "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==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "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" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "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", - "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", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "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, - "license": "Apache-2.0", - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } - } - }, - "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==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", - "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" - } - }, - "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==", - "devOptional": true, - "license": "Apache-2.0" - }, - "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==", - "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" - } - }, - "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==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "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==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@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", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", - "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "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==", - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "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==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "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==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "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==", - "license": "MIT" - }, - "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==", - "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", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.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==", - "dev": true, - "license": "BSD-3-Clause", - "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", - "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_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==", - "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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "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==", - "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/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "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" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "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/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", - "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "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" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "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==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "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==", - "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==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "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==", - "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" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "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/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "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==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "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==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "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/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "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==", - "dev": true, - "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==", - "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" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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" - }, - "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==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.21", - "validator": "~13.12.0" - }, - "engines": { - "node": ">= 8.0.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, - "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", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "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" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "node": ">= 8.10.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "url": "https://paulmillr.com/funding/" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "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, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "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": ">=10" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "color-support": "bin.js" } }, - "node_modules/graceful-fs": { - "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, - "license": "ISC" + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "node_modules/has-symbols": { + "node_modules/console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "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==", "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "safe-buffer": "5.2.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "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==", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", "engines": { - "node": ">=10.17.0" + "node": ">= 0.6" } }, - "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==", + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, + "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": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "cookie": "0.7.2", + "cookie-signature": "1.0.6" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" + "node": ">= 0.8.0" } }, - "node_modules/inflight": { + "node_modules/cookie-signature": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, - "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, + "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==", "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.10" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, + "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": ">=6" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=0.12.0" + "node": ">=0.3.1" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://dotenvx.com" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" + "safe-buffer": "^5.0.1" } }, - "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" - }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" }, "engines": { - "node": ">=10" + "node": ">=10.2.0" } }, - "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==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=10.0.0" } }, - "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, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3002,677 +976,517 @@ } } }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "node_modules/engine.io/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", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "node": ">= 0.10.0" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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==", - "dev": true, + "node_modules/express/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==", "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "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==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "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", - "@types/node": "*", - "chalk": "^4.0.0", - "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", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "to-regex-range": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "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==", - "dev": true, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "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" - }, - "bin": { - "jest": "bin/jest.js" + "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" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 0.8" } }, - "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, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "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", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } + "node": ">= 0.6" } }, - "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==", - "dev": true, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.6" } }, - "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==", - "dev": true, - "license": "MIT", + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", "dependencies": { - "detect-newline": "^3.0.0" + "minipass": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 8" } }, - "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==", - "dev": true, - "license": "MIT", + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "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" + "yallist": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "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==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^8.16.0 || ^10.6.0 || >=11.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, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "dev": true, - "license": "MIT", + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@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", - "walker": "^1.0.8" + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": ">=10" } }, - "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==", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "dev": true, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" } }, - "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==", - "dev": true, - "license": "MIT", + "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", + "license": "ISC", "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", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" + "is-glob": "^4.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 6" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=4" } }, - "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==", - "dev": true, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "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" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "dev": true, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "function-bind": "^1.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" } }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, + "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==", "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", - "@types/node": "*", - "chalk": "^4.0.0", - "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", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.8" } }, - "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==", - "dev": true, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "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", - "@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", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 6" } }, - "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, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "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" + "ms": "^2.1.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=6.0" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "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==", - "dev": true, + "node_modules/https-proxy-agent/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/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "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" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "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==", + "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": "MIT", + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.10" } }, - "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==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "binary-extensions": "^2.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "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==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=8" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "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==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "is-extglob": "^2.1.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, "engines": { - "node": ">=6" + "node": ">=0.12.0" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -3701,18 +1515,6 @@ "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,52 +1536,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", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -3822,43 +1578,28 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "semver": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "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, + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/make-error": { @@ -3868,16 +1609,6 @@ "dev": true, "license": "ISC" }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3905,13 +1636,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3921,20 +1645,6 @@ "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -3968,21 +1678,10 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "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" @@ -3995,23 +1694,55 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, "bin": { "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/ms": { @@ -4020,12 +1751,24 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } }, "node_modules/negotiator": { "version": "0.6.3", @@ -4036,20 +1779,101 @@ "node": ">= 0.6" } }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "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==", + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "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/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/nodemon/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/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4060,17 +1884,17 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" } }, "node_modules/object-assign": { @@ -4110,102 +1934,11 @@ "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" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4215,56 +1948,21 @@ "node": ">= 0.8" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": 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==", - "dev": true, - "license": "MIT" - }, "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==", "license": "MIT" }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4278,55 +1976,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "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==", + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/prisma": { @@ -4349,19 +2012,11 @@ "fsevents": "2.3.3" } }, - "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, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -4376,21 +2031,11 @@ "node": ">= 0.10" } }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "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, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], "license": "MIT" }, "node_modules/qs": { @@ -4432,11 +2077,25 @@ "node": ">= 0.8" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, "node_modules/readdirp": { @@ -4452,82 +2111,20 @@ "node": ">=8.10.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "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", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "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, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/safe-buffer": { @@ -4557,13 +2154,15 @@ "license": "MIT" }, "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" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -4620,35 +2219,18 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -4725,67 +2307,131 @@ "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/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", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, "engines": { - "node": ">=8" + "node": ">=10.2.0" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, + "node_modules/socket.io-adapter/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/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, + "node_modules/socket.io-parser/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/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", "dependencies": { - "escape-string-regexp": "^2.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=10" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/socket.io/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/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4795,25 +2441,33 @@ "node": ">= 0.8" } }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" + "safe-buffer": "~5.1.0" } }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "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", @@ -4828,7 +2482,6 @@ "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" @@ -4837,86 +2490,56 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.0.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==", + "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": "^4.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "license": "ISC", "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -4940,16 +2563,22 @@ "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": "MIT", + "license": "ISC", "bin": { - "tree-kill": "cli.js" + "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -4994,97 +2623,6 @@ } } }, - "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==", - "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" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5098,10 +2636,16 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5112,13 +2656,19 @@ "node": ">=14.17" } }, - "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==", + "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": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -5128,36 +2678,11 @@ "node": ">= 0.8" } }, - "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==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -5168,6 +2693,19 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "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", @@ -5175,30 +2713,6 @@ "dev": true, "license": "MIT" }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "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==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5208,127 +2722,73 @@ "node": ">= 0.8" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "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", + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", "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" + "string-width": "^1.0.2 || 2 || 3 || 4" } }, "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==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "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_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -5338,19 +2798,6 @@ "engines": { "node": ">=6" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index fef8af8ef..c31fa3160 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,36 @@ { - "name": "authentication-api-ts", + "name": "pandamarket-notification", "version": "1.0.0", - "description": "Used Market and Community Board API Server", - "main": "dist/app.js", + "description": "Pandamarket notification system with Socket.IO", "scripts": { + "start": "node ./build/main.js", "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" + "dev": "nodemon ./src/main.ts --watch ./src" + }, + "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1.4.8", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/jsonwebtoken": "^9.0.9", + "@types/multer": "^1.4.12", + "nodemon": "^3.1.9", + "prettier": "^3.3.2", + "prisma": "^5.16.2", + "ts-node": "^10.9.2", + "typescript": "^5.8.2" }, - "keywords": [], - "author": "", - "license": "ISC", "dependencies": { - "express": "^4.18.2", - "bcryptjs": "^2.4.3", - "jsonwebtoken": "^9.0.2", - "@prisma/client": "^5.6.0", + "@prisma/client": "^5.16.2", + "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^5.1.0", - "express-validator": "^7.1.0", - "multer": "^1.4.5-lts.1" - }, - "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" + "express": "^4.19.2", + "jsonwebtoken": "^9.0.2", + "multer": "^1.4.5-lts.1", + "socket.io": "^4.8.1", + "superstruct": "^2.0.2", + "uuid": "^11.0.5" } -} \ No newline at end of file +} 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/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 deleted file mode 100644 index d342cf8c2..000000000 --- a/src/app.ts +++ /dev/null @@ -1,47 +0,0 @@ -import express, { Application, Request, Response, NextFunction } from "express"; -import cors from "cors"; -import routes from "./routes"; - -const app: Application = express(); -const PORT = process.env.PORT || 3000; - -// 미들웨어 설정 -app.use(cors()); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); - -// API 라우트 -app.use("/api", routes); - -// 헬스 체크 엔드포인트 -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}에서 실행 중입니다.`); -}); - -export default app; diff --git a/src/controllers/articlesController.ts b/src/controllers/articlesController.ts new file mode 100644 index 000000000..42351972a --- /dev/null +++ b/src/controllers/articlesController.ts @@ -0,0 +1,34 @@ +import { Request, Response } from 'express'; +import { create } from 'superstruct'; +import { IdParamsStruct } from '../structs/commonStructs'; +import { CreateArticleBodyStruct, UpdateArticleBodyStruct } from '../structs/articlesStructs'; +import * as articlesService from '../services/articlesService'; + +export async function createArticle(req: Request, res: Response) { + const data = create(req.body, CreateArticleBodyStruct); + const article = await articlesService.createArticle({ + ...data, + image: data.image ?? null, + userId: req.user!.id, + }); + res.status(201).json(article); +} + +export async function getArticle(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + const article = await articlesService.getArticle(id); + res.json(article); +} + +export async function updateArticle(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + const data = create(req.body, UpdateArticleBodyStruct); + const article = await articlesService.updateArticle(id, req.user!.id, data); + res.json(article); +} + +export async function deleteArticle(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + await articlesService.deleteArticle(id, req.user!.id); + res.status(204).send(); +} diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts index 63c3067d4..67c6f6904 100644 --- a/src/controllers/authController.ts +++ b/src/controllers/authController.ts @@ -1,273 +1,23 @@ -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: "서버 내부 오류가 발생했습니다.", - }); - } -}; +import { Request, Response } from 'express'; +import { create } from 'superstruct'; +import { SignUpBodyStruct, SignInBodyStruct } from '../structs/authStructs'; +import * as authService from '../services/authService'; +import userResponseDTO from '../dto/userResponseDTO'; + +export async function signUp(req: Request, res: Response) { + const { email, nickname, password } = create(req.body, SignUpBodyStruct); + const { user, accessToken } = await authService.signUp(email, nickname, password); + res.status(201).json({ + user: userResponseDTO(user), + accessToken, + }); +} + +export async function signIn(req: Request, res: Response) { + const { email, password } = create(req.body, SignInBodyStruct); + const { user, accessToken } = await authService.signIn(email, password); + res.status(200).json({ + user: userResponseDTO(user), + accessToken, + }); +} 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/commentsController.ts b/src/controllers/commentsController.ts new file mode 100644 index 000000000..5b42d29d5 --- /dev/null +++ b/src/controllers/commentsController.ts @@ -0,0 +1,27 @@ +import { Request, Response } from 'express'; +import { create } from 'superstruct'; +import { IdParamsStruct } from '../structs/commonStructs'; +import { CreateCommentBodyStruct, UpdateCommentBodyStruct } from '../structs/commentsStructs'; +import * as commentsService from '../services/commentsService'; + +export async function createComment(req: Request, res: Response) { + const data = create(req.body, CreateCommentBodyStruct); + const comment = await commentsService.createComment({ + ...data, + userId: req.user!.id, + }); + res.status(201).json(comment); +} + +export async function updateComment(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + const { content } = create(req.body, UpdateCommentBodyStruct); + const comment = await commentsService.updateComment(id, req.user!.id, content); + res.json(comment); +} + +export async function deleteComment(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + await commentsService.deleteComment(id, req.user!.id); + res.status(204).send(); +} diff --git a/src/controllers/errorController.ts b/src/controllers/errorController.ts new file mode 100644 index 000000000..141e99607 --- /dev/null +++ b/src/controllers/errorController.ts @@ -0,0 +1,12 @@ +import { Request, Response, NextFunction } from 'express'; + +export function defaultNotFoundHandler(req: Request, res: Response) { + res.status(404).json({ message: 'Not found' }); +} + +export function globalErrorHandler(err: any, req: Request, res: Response, next: NextFunction) { + console.error(err); + const status = err.status || 500; + const message = err.message || 'Internal server error'; + res.status(status).json({ message }); +} 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/notificationsController.ts b/src/controllers/notificationsController.ts new file mode 100644 index 000000000..e98559d70 --- /dev/null +++ b/src/controllers/notificationsController.ts @@ -0,0 +1,10 @@ +import { Request, Response } from 'express'; +import { create } from 'superstruct'; +import { IdParamsStruct } from '../structs/commonStructs'; +import * as notificationsService from '../services/notificationsService'; + +export async function readNotification(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + await notificationsService.readNotificationById(id, req.user?.id); + res.status(200).send(); +} 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/productsController.ts b/src/controllers/productsController.ts new file mode 100644 index 000000000..3c11bb48e --- /dev/null +++ b/src/controllers/productsController.ts @@ -0,0 +1,36 @@ +import { Request, Response } from 'express'; +import { create } from 'superstruct'; +import { IdParamsStruct } from '../structs/commonStructs'; +import { CreateProductBodyStruct, UpdateProductBodyStruct } from '../structs/productsStructs'; +import * as productsService from '../services/productsService'; + +export async function createProduct(req: Request, res: Response) { + const data = create(req.body, CreateProductBodyStruct); + const product = await productsService.createProduct({ + ...data, + userId: req.user!.id, + }); + res.status(201).json(product); +} + +export async function getProduct(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + const product = await productsService.getProduct(id); + res.json(product); +} + +export async function updateProduct(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + const data = create(req.body, UpdateProductBodyStruct); + const product = await productsService.updateProduct(id, { + ...data, + userId: req.user!.id, + }); + res.json(product); +} + +export async function deleteProduct(req: Request, res: Response) { + const { id } = create(req.params, IdParamsStruct); + await productsService.deleteProduct(id, req.user!.id); + res.status(204).send(); +} 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/controllers/usersController.ts b/src/controllers/usersController.ts new file mode 100644 index 000000000..255f965b6 --- /dev/null +++ b/src/controllers/usersController.ts @@ -0,0 +1,75 @@ +import { Request, Response } from 'express'; +import { create } from 'superstruct'; +import { + UpdateMeBodyStruct, + UpdatePasswordBodyStruct, + GetMyProductListParamsStruct, + GetMyFavoriteListParamsStruct, + GetMyNotificationsParamsStruct, +} from '../structs/usersStructs'; +import * as usersService from '../services/usersService'; +import * as authService from '../services/authService'; +import * as notificationsService from '../services/notificationsService'; +import userResponseDTO from '../dto/userResponseDTO'; + +export async function getMe(req: Request, res: Response) { + const user = await usersService.getUser(req.user!.id); + res.send(userResponseDTO(user)); +} + +export async function updateMe(req: Request, res: Response) { + const data = create(req.body, UpdateMeBodyStruct); + const updatedUser = await usersService.updateUser(req.user!.id, data); + res.status(200).send(userResponseDTO(updatedUser)); +} + +export async function updateMyPassword(req: Request, res: Response) { + const { password, newPassword } = create(req.body, UpdatePasswordBodyStruct); + await authService.updateMyPassword(req.user!.id, password, newPassword); + res.status(200).send(); +} + +export async function getMyProductList(req: Request, res: Response) { + const { page, pageSize, orderBy, keyword } = create(req.query, GetMyProductListParamsStruct); + const { list, totalCount } = await usersService.getMyProductList(req.user!.id, { + page, + pageSize, + orderBy, + keyword, + }); + + res.send({ + list, + totalCount, + }); +} + +export async function getMyFavoriteList(req: Request, res: Response) { + const { page, pageSize, orderBy, keyword } = create(req.query, GetMyFavoriteListParamsStruct); + const { list, totalCount } = await usersService.getMyFavoriteList(req.user!.id, { + page, + pageSize, + orderBy, + keyword, + }); + + res.send({ + list, + totalCount, + }); +} + +export async function getMyNotifications(req: Request, res: Response) { + const { cursor, limit } = create(req.query, GetMyNotificationsParamsStruct); + const { list, totalCount, unreadCount, nextCursor } = await notificationsService.getMyNotifications(req.user!.id, { + cursor, + limit, + }); + + res.send({ + list, + nextCursor, + unreadCount, + totalCount, + }); +} 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/dto/userResponseDTO.ts b/src/dto/userResponseDTO.ts new file mode 100644 index 000000000..fe2626788 --- /dev/null +++ b/src/dto/userResponseDTO.ts @@ -0,0 +1,12 @@ +import User from '../types/User'; + +export default function userResponseDTO(user: User) { + return { + id: user.id, + email: user.email, + nickname: user.nickname, + image: user.image, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }; +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 000000000..3e6e7cda9 --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,5 @@ +export const PORT = process.env.PORT || 3000; +export const PUBLIC_PATH = 'public'; +export const STATIC_PATH = '/static'; +export const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; +export const UPLOAD_DIR = 'public/uploads'; diff --git a/src/lib/errors/BadRequestError.ts b/src/lib/errors/BadRequestError.ts new file mode 100644 index 000000000..9629ac6b3 --- /dev/null +++ b/src/lib/errors/BadRequestError.ts @@ -0,0 +1,8 @@ +export default class BadRequestError extends Error { + status = 400; + + constructor(message: string) { + super(message); + this.name = 'BadRequestError'; + } +} diff --git a/src/lib/errors/ForbiddenError.ts b/src/lib/errors/ForbiddenError.ts new file mode 100644 index 000000000..e4148d4f8 --- /dev/null +++ b/src/lib/errors/ForbiddenError.ts @@ -0,0 +1,8 @@ +export default class ForbiddenError extends Error { + status = 403; + + constructor(message: string) { + super(message); + this.name = 'ForbiddenError'; + } +} diff --git a/src/lib/errors/NotFoundError.ts b/src/lib/errors/NotFoundError.ts new file mode 100644 index 000000000..bfbfdbf7e --- /dev/null +++ b/src/lib/errors/NotFoundError.ts @@ -0,0 +1,8 @@ +export default class NotFoundError extends Error { + status = 404; + + constructor(resource: string, id: number | string) { + super(`${resource} with id ${id} not found`); + this.name = 'NotFoundError'; + } +} diff --git a/src/lib/errors/UnauthorizedError.ts b/src/lib/errors/UnauthorizedError.ts new file mode 100644 index 000000000..e74b543d7 --- /dev/null +++ b/src/lib/errors/UnauthorizedError.ts @@ -0,0 +1,8 @@ +export default class UnauthorizedError extends Error { + status = 401; + + constructor(message: string) { + super(message); + this.name = 'UnauthorizedError'; + } +} diff --git a/src/lib/prismaClient.ts b/src/lib/prismaClient.ts new file mode 100644 index 000000000..5e41d6fe7 --- /dev/null +++ b/src/lib/prismaClient.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from '@prisma/client'; + +export const prismaClient = new PrismaClient(); diff --git a/src/lib/withAsync.ts b/src/lib/withAsync.ts new file mode 100644 index 000000000..7891f262f --- /dev/null +++ b/src/lib/withAsync.ts @@ -0,0 +1,7 @@ +import { Request, Response, NextFunction } from 'express'; + +export function withAsync(fn: (req: Request, res: Response, next: NextFunction) => Promise) { + return (req: Request, res: Response, next: NextFunction) => { + fn(req, res, next).catch(next); + }; +} 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/main.ts b/src/main.ts new file mode 100644 index 000000000..67816219d --- /dev/null +++ b/src/main.ts @@ -0,0 +1,41 @@ +import 'dotenv/config'; +import express from 'express'; +import cors from 'cors'; +import path from 'path'; +import { createServer } from 'http'; +import cookieParser from 'cookie-parser'; +import { PORT, PUBLIC_PATH, STATIC_PATH } from './lib/constants'; +import articlesRouter from './routers/articlesRouter'; +import productsRouter from './routers/productsRouter'; +import commentsRouter from './routers/commentsRouter'; +import imagesRouter from './routers/imagesRouter'; +import authRouter from './routers/authRouter'; +import usersRouter from './routers/usersRouter'; +import { defaultNotFoundHandler, globalErrorHandler } from './controllers/errorController'; +import socketService from './services/socketService'; +import notificationsRouter from './routers/notificationsRouter'; + +const app = express(); + +app.use(cors()); +app.use(express.json()); +app.use(cookieParser()); +app.use(STATIC_PATH, express.static(path.resolve(process.cwd(), PUBLIC_PATH))); + +app.use('/articles', articlesRouter); +app.use('/products', productsRouter); +app.use('/comments', commentsRouter); +app.use('/images', imagesRouter); +app.use('/auth', authRouter); +app.use('/users', usersRouter); +app.use('/notifications', notificationsRouter); + +app.use(defaultNotFoundHandler); +app.use(globalErrorHandler); + +const server = createServer(app); +socketService.initialize(server); + +server.listen(PORT, () => { + console.log(`Server started on port ${PORT}`); +}); 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/authenticate.ts b/src/middlewares/authenticate.ts new file mode 100644 index 000000000..7066be87c --- /dev/null +++ b/src/middlewares/authenticate.ts @@ -0,0 +1,44 @@ +import { Request, Response, NextFunction } from 'express'; +import * as authService from '../services/authService'; +import UnauthorizedError from '../lib/errors/UnauthorizedError'; + +declare global { + namespace Express { + interface Request { + user?: { + id: number; + email: string; + nickname: string; + }; + } + } +} + +export default function authenticate(required: boolean = true) { + return async (req: Request, res: Response, next: NextFunction) => { + try { + const accessToken = req.headers.authorization?.replace('Bearer ', ''); + + if (!accessToken) { + if (required) { + throw new UnauthorizedError('Authentication required'); + } + return next(); + } + + const user = await authService.authenticate(accessToken); + req.user = { + id: user.id, + email: user.email, + nickname: user.nickname, + }; + next(); + } catch (error) { + if (required) { + next(error); + } else { + 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/articlesRepository.ts b/src/repositories/articlesRepository.ts new file mode 100644 index 000000000..7b60a0f0a --- /dev/null +++ b/src/repositories/articlesRepository.ts @@ -0,0 +1,30 @@ +import { prismaClient } from '../lib/prismaClient'; +import Article from '../types/Article'; + +export async function getArticle(id: number) { + const article = await prismaClient.article.findUnique({ + where: { id }, + }); + return article; +} + +export async function createArticle(data: Omit) { + const article = await prismaClient.article.create({ + data, + }); + return article; +} + +export async function updateArticle(id: number, data: Partial
) { + const article = await prismaClient.article.update({ + where: { id }, + data, + }); + return article; +} + +export async function deleteArticle(id: number) { + await prismaClient.article.delete({ + where: { id }, + }); +} diff --git a/src/repositories/commentsRepository.ts b/src/repositories/commentsRepository.ts new file mode 100644 index 000000000..d81442e22 --- /dev/null +++ b/src/repositories/commentsRepository.ts @@ -0,0 +1,51 @@ +import { prismaClient } from '../lib/prismaClient'; +import Comment from '../types/Comment'; +import { CursorPaginationParams } from '../types/pagination'; + +export async function getComment(id: number) { + const comment = await prismaClient.comment.findUnique({ + where: { id }, + }); + return comment; +} + +export async function createComment(data: Omit) { + const comment = await prismaClient.comment.create({ + data, + }); + return comment; +} + +export async function getCommentList( + where: { articleId?: number; productId?: number }, + params: CursorPaginationParams, +) { + const { cursor, limit } = params; + + const commentsWithCursor = await prismaClient.comment.findMany({ + cursor: cursor ? { id: cursor } : undefined, + take: limit + 1, + where, + orderBy: [{ createdAt: 'desc' }, { id: 'asc' }], + }); + + const list = commentsWithCursor.slice(0, limit); + const cursorComment = commentsWithCursor[commentsWithCursor.length - 1]; + const nextCursor = cursorComment ? cursorComment.id : null; + + return { list, nextCursor }; +} + +export async function updateComment(id: number, data: Partial) { + const comment = await prismaClient.comment.update({ + where: { id }, + data, + }); + return comment; +} + +export async function deleteComment(id: number) { + await prismaClient.comment.delete({ + where: { id }, + }); +} diff --git a/src/repositories/favoritesRepository.ts b/src/repositories/favoritesRepository.ts new file mode 100644 index 000000000..9307a89d7 --- /dev/null +++ b/src/repositories/favoritesRepository.ts @@ -0,0 +1,28 @@ +import { prismaClient } from '../lib/prismaClient'; + +export async function getFavoritesByProductId(productId: number) { + const favorites = await prismaClient.favorite.findMany({ + where: { productId }, + }); + return favorites; +} + +export async function getFavoriteByUserAndProduct(userId: number, productId: number) { + const favorite = await prismaClient.favorite.findFirst({ + where: { userId, productId }, + }); + return favorite; +} + +export async function createFavorite(userId: number, productId: number) { + const favorite = await prismaClient.favorite.create({ + data: { userId, productId }, + }); + return favorite; +} + +export async function deleteFavorite(userId: number, productId: number) { + await prismaClient.favorite.deleteMany({ + where: { userId, productId }, + }); +} diff --git a/src/repositories/notificationsRepository.ts b/src/repositories/notificationsRepository.ts new file mode 100644 index 000000000..a0e51c41e --- /dev/null +++ b/src/repositories/notificationsRepository.ts @@ -0,0 +1,60 @@ +import { prismaClient } from '../lib/prismaClient'; +import { Notification } from '../types/Notification'; +import { CursorPaginationParams } from '../types/pagination'; + +export async function getNotificationsByUserId(userId: number, params: CursorPaginationParams) { + const { cursor, limit } = params; + const where = { + userId, + }; + const notificationsWithCursor = await prismaClient.notification.findMany({ + cursor: cursor ? { id: cursor } : undefined, + take: limit + 1, + where, + orderBy: [{ createdAt: 'desc' }, { id: 'asc' }], + }); + const totalCount = await prismaClient.notification.count({ where }); + const unreadCount = await prismaClient.notification.count({ where: { ...where, read: false } }); + const notifications = notificationsWithCursor.slice(0, limit); + const cursorNotification = notificationsWithCursor[notificationsWithCursor.length - 1]; + const nextCursor = cursorNotification ? cursorNotification.id : null; + return { notifications, totalCount, unreadCount, nextCursor }; +} + +export async function createNotification( + data: Omit, +) { + const notification = await prismaClient.notification.create({ + data, + }); + return notification; +} + +export async function createNotifications( + data: Omit[], +) { + await prismaClient.notification.createMany({ + data, + }); +} + +export async function getNotificationById(id: number) { + const notification = await prismaClient.notification.findUnique({ + where: { id }, + }); + return notification; +} + +export async function updateNotificationById(id: number, data: Partial) { + await prismaClient.notification.update({ + where: { id }, + data, + }); +} + +export async function updateNotificationsByUserId(userId: number, data: Partial) { + await prismaClient.notification.updateMany({ + where: { userId }, + data, + }); +} 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/productsRepository.ts b/src/repositories/productsRepository.ts new file mode 100644 index 000000000..4c268428a --- /dev/null +++ b/src/repositories/productsRepository.ts @@ -0,0 +1,97 @@ +import { prismaClient } from '../lib/prismaClient'; +import Product from '../types/Product'; +import { PagePaginationParams } from '../types/pagination'; + +export async function getProduct(id: number) { + const product = await prismaClient.product.findUnique({ + where: { id }, + }); + return product; +} + +export async function getProductWithFavorites(id: number, userId?: number): Promise { + const product = await prismaClient.product.findUnique({ + where: { id }, + include: { + _count: { + select: { favorites: true }, + }, + favorites: userId ? { where: { userId } } : false, + }, + }); + + if (!product) return null; + + return { + ...product, + favoriteCount: product._count.favorites, + isFavorited: userId ? product.favorites.length > 0 : false, + }; +} + +export async function createProduct(data: Omit) { + const product = await prismaClient.product.create({ + data, + }); + return product; +} + +export async function updateProduct(id: number, data: Partial) { + const product = await prismaClient.product.update({ + where: { id }, + data, + }); + return product; +} + +export async function updateProductWithFavorites(id: number, data: Partial): Promise { + const product = await prismaClient.product.update({ + where: { id }, + data, + include: { + _count: { + select: { favorites: true }, + }, + }, + }); + + return { + ...product, + favoriteCount: product._count.favorites, + isFavorited: false, + }; +} + +export async function deleteProduct(id: number) { + await prismaClient.product.delete({ + where: { id }, + }); +} + +export async function getProductListWithFavorites(params: PagePaginationParams, options: { userId?: number }) { + const { page, pageSize } = params; + const skip = (page - 1) * pageSize; + + const products = await prismaClient.product.findMany({ + skip, + take: pageSize, + orderBy: { createdAt: 'desc' }, + include: { + _count: { + select: { favorites: true }, + }, + favorites: options.userId ? { where: { userId: options.userId } } : false, + }, + }); + + const totalCount = await prismaClient.product.count(); + + return { + list: products.map((p) => ({ + ...p, + favoriteCount: p._count.favorites, + isFavorited: options.userId ? p.favorites.length > 0 : false, + })), + totalCount, + }; +} 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/repositories/usersRepository.ts b/src/repositories/usersRepository.ts new file mode 100644 index 000000000..d5dcb748d --- /dev/null +++ b/src/repositories/usersRepository.ts @@ -0,0 +1,31 @@ +import { prismaClient } from '../lib/prismaClient'; +import User from '../types/User'; + +export async function getUser(id: number): Promise { + const user = await prismaClient.user.findUnique({ + where: { id }, + }); + return user; +} + +export async function getUserByEmail(email: string): Promise { + const user = await prismaClient.user.findUnique({ + where: { email }, + }); + return user; +} + +export async function createUser(data: Omit): Promise { + const user = await prismaClient.user.create({ + data, + }); + return user; +} + +export async function updateUser(id: number, data: Partial): Promise { + const user = await prismaClient.user.update({ + where: { id }, + data, + }); + return user; +} diff --git a/src/routers/articlesRouter.ts b/src/routers/articlesRouter.ts new file mode 100644 index 000000000..6a1cf6169 --- /dev/null +++ b/src/routers/articlesRouter.ts @@ -0,0 +1,18 @@ +import express from 'express'; +import { withAsync } from '../lib/withAsync'; +import authenticate from '../middlewares/authenticate'; +import { + createArticle, + getArticle, + updateArticle, + deleteArticle, +} from '../controllers/articlesController'; + +const articlesRouter = express.Router(); + +articlesRouter.post('/', authenticate(), withAsync(createArticle)); +articlesRouter.get('/:id', withAsync(getArticle)); +articlesRouter.patch('/:id', authenticate(), withAsync(updateArticle)); +articlesRouter.delete('/:id', authenticate(), withAsync(deleteArticle)); + +export default articlesRouter; diff --git a/src/routers/authRouter.ts b/src/routers/authRouter.ts new file mode 100644 index 000000000..21b2d1c6d --- /dev/null +++ b/src/routers/authRouter.ts @@ -0,0 +1,10 @@ +import express from 'express'; +import { withAsync } from '../lib/withAsync'; +import { signUp, signIn } from '../controllers/authController'; + +const authRouter = express.Router(); + +authRouter.post('/signup', withAsync(signUp)); +authRouter.post('/signin', withAsync(signIn)); + +export default authRouter; diff --git a/src/routers/commentsRouter.ts b/src/routers/commentsRouter.ts new file mode 100644 index 000000000..85935922d --- /dev/null +++ b/src/routers/commentsRouter.ts @@ -0,0 +1,16 @@ +import express from 'express'; +import { withAsync } from '../lib/withAsync'; +import authenticate from '../middlewares/authenticate'; +import { + createComment, + updateComment, + deleteComment, +} from '../controllers/commentsController'; + +const commentsRouter = express.Router(); + +commentsRouter.post('/', authenticate(), withAsync(createComment)); +commentsRouter.patch('/:id', authenticate(), withAsync(updateComment)); +commentsRouter.delete('/:id', authenticate(), withAsync(deleteComment)); + +export default commentsRouter; diff --git a/src/routers/imagesRouter.ts b/src/routers/imagesRouter.ts new file mode 100644 index 000000000..a467cec3d --- /dev/null +++ b/src/routers/imagesRouter.ts @@ -0,0 +1,30 @@ +import express from 'express'; +import multer from 'multer'; +import path from 'path'; +import { v4 as uuidv4 } from 'uuid'; +import { UPLOAD_DIR } from '../lib/constants'; +import authenticate from '../middlewares/authenticate'; + +const imagesRouter = express.Router(); + +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, UPLOAD_DIR); + }, + filename: (req, file, cb) => { + const ext = path.extname(file.originalname); + cb(null, `${uuidv4()}${ext}`); + }, +}); + +const upload = multer({ storage }); + +imagesRouter.post('/upload', authenticate(), upload.single('image'), (req, res) => { + if (!req.file) { + return res.status(400).json({ message: 'No file uploaded' }); + } + const imageUrl = `/static/uploads/${req.file.filename}`; + res.json({ url: imageUrl }); +}); + +export default imagesRouter; diff --git a/src/routers/notificationsRouter.ts b/src/routers/notificationsRouter.ts new file mode 100644 index 000000000..7a183ba71 --- /dev/null +++ b/src/routers/notificationsRouter.ts @@ -0,0 +1,10 @@ +import express from 'express'; +import { withAsync } from '../lib/withAsync'; +import authenticate from '../middlewares/authenticate'; +import { readNotification } from '../controllers/notificationsController'; + +const notificationsRouter = express.Router(); + +notificationsRouter.patch('/:id/read', authenticate(), withAsync(readNotification)); + +export default notificationsRouter; diff --git a/src/routers/productsRouter.ts b/src/routers/productsRouter.ts new file mode 100644 index 000000000..4b3c1b730 --- /dev/null +++ b/src/routers/productsRouter.ts @@ -0,0 +1,18 @@ +import express from 'express'; +import { withAsync } from '../lib/withAsync'; +import authenticate from '../middlewares/authenticate'; +import { + createProduct, + getProduct, + updateProduct, + deleteProduct, +} from '../controllers/productsController'; + +const productsRouter = express.Router(); + +productsRouter.post('/', authenticate(), withAsync(createProduct)); +productsRouter.get('/:id', withAsync(getProduct)); +productsRouter.patch('/:id', authenticate(), withAsync(updateProduct)); +productsRouter.delete('/:id', authenticate(), withAsync(deleteProduct)); + +export default productsRouter; diff --git a/src/routers/usersRouter.ts b/src/routers/usersRouter.ts new file mode 100644 index 000000000..48e79271c --- /dev/null +++ b/src/routers/usersRouter.ts @@ -0,0 +1,22 @@ +import express from 'express'; +import { withAsync } from '../lib/withAsync'; +import { + getMe, + updateMe, + updateMyPassword, + getMyProductList, + getMyFavoriteList, + getMyNotifications, +} from '../controllers/usersController'; +import authenticate from '../middlewares/authenticate'; + +const usersRouter = express.Router(); + +usersRouter.get('/me', authenticate(), withAsync(getMe)); +usersRouter.patch('/me', authenticate(), withAsync(updateMe)); +usersRouter.patch('/me/password', authenticate(), withAsync(updateMyPassword)); +usersRouter.get('/me/products', authenticate(), withAsync(getMyProductList)); +usersRouter.get('/me/favorites', authenticate(), withAsync(getMyFavoriteList)); +usersRouter.get('/me/notifications', authenticate(), withAsync(getMyNotifications)); + +export default usersRouter; 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/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/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/server.js b/src/server.js deleted file mode 100644 index 9ced3af19..000000000 --- a/src/server.js +++ /dev/null @@ -1,7 +0,0 @@ -const app = require('./app'); - -const PORT = process.env.PORT || 3000; - -app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); -}); diff --git a/src/services/articlesService.ts b/src/services/articlesService.ts new file mode 100644 index 000000000..5d0c4445c --- /dev/null +++ b/src/services/articlesService.ts @@ -0,0 +1,54 @@ +import * as articlesRepository from '../repositories/articlesRepository'; +import ForbiddenError from '../lib/errors/ForbiddenError'; +import NotFoundError from '../lib/errors/NotFoundError'; +import Article from '../types/Article'; + +type CreateArticleData = Omit; + +export async function createArticle(data: CreateArticleData): Promise
{ + const article = await articlesRepository.createArticle(data); + return { + ...article, + likeCount: 0, + isLiked: false, + }; +} + +export async function getArticle(id: number): Promise
{ + const article = await articlesRepository.getArticle(id); + if (!article) { + throw new NotFoundError('Article', id); + } + return { + ...article, + likeCount: 0, + isLiked: false, + }; +} + +export async function updateArticle(id: number, userId: number, data: Partial): Promise
{ + const existingArticle = await articlesRepository.getArticle(id); + if (!existingArticle) { + throw new NotFoundError('Article', id); + } + if (existingArticle.userId !== userId) { + throw new ForbiddenError('Should be the owner of the article'); + } + const article = await articlesRepository.updateArticle(id, data); + return { + ...article, + likeCount: 0, + isLiked: false, + }; +} + +export async function deleteArticle(id: number, userId: number): Promise { + const existingArticle = await articlesRepository.getArticle(id); + if (!existingArticle) { + throw new NotFoundError('Article', id); + } + if (existingArticle.userId !== userId) { + throw new ForbiddenError('Should be the owner of the article'); + } + await articlesRepository.deleteArticle(id); +} diff --git a/src/services/authService.ts b/src/services/authService.ts new file mode 100644 index 000000000..896904ba1 --- /dev/null +++ b/src/services/authService.ts @@ -0,0 +1,73 @@ +import bcrypt from 'bcrypt'; +import jwt from 'jsonwebtoken'; +import { JWT_SECRET } from '../lib/constants'; +import * as usersRepository from '../repositories/usersRepository'; +import BadRequestError from '../lib/errors/BadRequestError'; +import UnauthorizedError from '../lib/errors/UnauthorizedError'; +import User from '../types/User'; + +interface JwtPayload { + id: number; + email: string; +} + +export async function signUp(email: string, nickname: string, password: string) { + const existingUser = await usersRepository.getUserByEmail(email); + if (existingUser) { + throw new BadRequestError('Email already exists'); + } + + const hashedPassword = await bcrypt.hash(password, 10); + const user = await usersRepository.createUser({ + email, + nickname, + password: hashedPassword, + image: null, + }); + + const accessToken = jwt.sign({ id: user.id, email: user.email }, JWT_SECRET); + return { user, accessToken }; +} + +export async function signIn(email: string, password: string) { + const user = await usersRepository.getUserByEmail(email); + if (!user) { + throw new UnauthorizedError('Invalid email or password'); + } + + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + throw new UnauthorizedError('Invalid email or password'); + } + + const accessToken = jwt.sign({ id: user.id, email: user.email }, JWT_SECRET); + return { user, accessToken }; +} + +export async function authenticate(accessToken: string): Promise { + try { + const decoded = jwt.verify(accessToken, JWT_SECRET) as JwtPayload; + const user = await usersRepository.getUser(decoded.id); + if (!user) { + throw new UnauthorizedError('User not found'); + } + return user; + } catch (error) { + throw new UnauthorizedError('Invalid token'); + } +} + +export async function updateMyPassword(userId: number, password: string, newPassword: string) { + const user = await usersRepository.getUser(userId); + if (!user) { + throw new UnauthorizedError('User not found'); + } + + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + throw new UnauthorizedError('Invalid password'); + } + + const hashedPassword = await bcrypt.hash(newPassword, 10); + await usersRepository.updateUser(userId, { password: hashedPassword }); +} diff --git a/src/services/commentsService.ts b/src/services/commentsService.ts new file mode 100644 index 000000000..40db919e4 --- /dev/null +++ b/src/services/commentsService.ts @@ -0,0 +1,122 @@ +import * as articlesRepository from '../repositories/articlesRepository'; +import * as commentsRepository from '../repositories/commentsRepository'; +import * as productsRepository from '../repositories/productsRepository'; +import * as notificationsService from './notificationsService'; +import { CursorPaginationParams, CursorPaginationResult } from '../types/pagination'; +import BadRequestError from '../lib/errors/BadRequestError'; +import ForbiddenError from '../lib/errors/ForbiddenError'; +import NotFoundError from '../lib/errors/NotFoundError'; +import Comment from '../types/Comment'; +import Article from '../types/Article'; +import { NotificationType } from '../types/Notification'; + +type CreateCommentData = Omit< + Comment, + 'id' | 'productId' | 'articleId' | 'createdAt' | 'updatedAt' +> & { + productId?: number; + articleId?: number; +}; + +export async function createComment(data: CreateCommentData): Promise { + if (!data.articleId && !data.productId) { + throw new BadRequestError('Either articleId or productId must be provided'); + } + + if (data.articleId) { + const article = await articlesRepository.getArticle(data.articleId); + if (!article) { + throw new NotFoundError('article', data.articleId); + } + } + + if (data.productId) { + const product = await productsRepository.getProduct(data.productId); + if (!product) { + throw new NotFoundError('product', data.productId); + } + } + + const comment = await commentsRepository.createComment({ + ...data, + articleId: data.articleId ?? null, + productId: data.productId ?? null, + }); + + // New comment notification + if (data.articleId) { + const article = (await articlesRepository.getArticle(data.articleId)) as Article; + const commentWriterId = data.userId; + const articleWriterId = article.userId; + if (articleWriterId !== commentWriterId) { + await notificationsService.createNotification({ + userId: articleWriterId, + type: NotificationType.NEW_COMMENT, + payload: { + articleId: data.articleId, + }, + }); + } + } + return comment; +} + +export async function getComment(id: number): Promise { + const comment = await commentsRepository.getComment(id); + if (!comment) { + throw new NotFoundError('comment', id); + } + return comment; +} + +export async function getCommentListByArticleId( + articleId: number, + params: CursorPaginationParams, +): Promise> { + const article = await articlesRepository.getArticle(articleId); + if (!article) { + throw new NotFoundError('article', articleId); + } + + const result = commentsRepository.getCommentList({ articleId }, params); + return result; +} + +export async function getCommentListByProductId( + productId: number, + params: CursorPaginationParams, +): Promise> { + const product = await productsRepository.getProduct(productId); + if (!product) { + throw new NotFoundError('product', productId); + } + + const result = commentsRepository.getCommentList({ productId }, params); + return result; +} + +export async function updateComment(id: number, userId: number, content: string): Promise { + const comment = await commentsRepository.getComment(id); + if (!comment) { + throw new NotFoundError('comment', id); + } + + if (comment.userId !== userId) { + throw new ForbiddenError('Should be the owner of the comment'); + } + + return commentsRepository.updateComment(id, { content }); +} + +export async function deleteComment(id: number, userId: number): Promise { + const comment = await commentsRepository.getComment(id); + if (!comment) { + throw new NotFoundError('comment', id); + } + + if (comment.userId !== userId) { + throw new ForbiddenError('Should be the owner of the comment'); + } + + await commentsRepository.deleteComment(id); +} diff --git a/src/services/notificationsService.ts b/src/services/notificationsService.ts new file mode 100644 index 000000000..0fa38e3b8 --- /dev/null +++ b/src/services/notificationsService.ts @@ -0,0 +1,69 @@ +import UnauthorizedError from '../lib/errors/UnauthorizedError'; +import NotFoundError from '../lib/errors/NotFoundError'; +import ForbiddenError from '../lib/errors/ForbiddenError'; +import { CursorPaginationParams } from '../types/pagination'; +import { Notification } from '../types/Notification'; +import * as notificationsRepository from '../repositories/notificationsRepository'; +import * as usersRepository from '../repositories/usersRepository'; +import socketService from './socketService'; + +export async function createNotification( + data: Omit, +) { + const existingUser = await usersRepository.getUser(data.userId); + if (!existingUser) { + throw new NotFoundError('User', data.userId); + } + + const notification = await notificationsRepository.createNotification({ + ...data, + read: false, + }); + + socketService.sendNotification(notification as Notification); + + return notification; +} + +export async function createNotifications( + notifications: Omit[], +) { + await notificationsRepository.createNotifications( + notifications.map((notification) => ({ + ...notification, + read: false, + })), + ); + + notifications.forEach((notification) => { + socketService.sendNotification(notification as Notification); + }); +} + +export async function readNotificationById(id: number, userId?: number) { + if (!userId) { + throw new UnauthorizedError('Unauthorized'); + } + + const notification = await notificationsRepository.getNotificationById(id); + if (!notification) { + throw new NotFoundError('Notification', id); + } + + if (notification.userId !== userId) { + throw new ForbiddenError("Cannot read other user's notification"); + } + + await notificationsRepository.updateNotificationById(id, { read: true }); +} + +export async function getMyNotifications(userId: number, params: CursorPaginationParams) { + if (!userId) { + throw new UnauthorizedError('Unauthorized'); + } + + const { cursor, limit } = params; + const { notifications, totalCount, unreadCount, nextCursor } = + await notificationsRepository.getNotificationsByUserId(userId, { cursor, limit }); + return { list: notifications, totalCount, unreadCount, nextCursor }; +} 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/productsService.ts b/src/services/productsService.ts new file mode 100644 index 000000000..d71a6edd3 --- /dev/null +++ b/src/services/productsService.ts @@ -0,0 +1,80 @@ +import ForbiddenError from '../lib/errors/ForbiddenError'; +import NotFoundError from '../lib/errors/NotFoundError'; +import * as productsRepository from '../repositories/productsRepository'; +import * as favoritesRepository from '../repositories/favoritesRepository'; +import * as notificationsService from './notificationsService'; +import { PagePaginationParams, PagePaginationResult } from '../types/pagination'; +import Product from '../types/Product'; +import { NotificationType } from '../types/Notification'; + +type CreateProductData = Omit< + Product, + 'id' | 'createdAt' | 'updatedAt' | 'favoriteCount' | 'isFavorited' +>; +type UpdateProductData = Partial & { userId: number }; + +export async function createProduct(data: CreateProductData): Promise { + const createdProduct = await productsRepository.createProduct(data); + return { + ...createdProduct, + favoriteCount: 0, + isFavorited: false, + }; +} + +export async function getProduct(id: number): Promise { + const product = await productsRepository.getProductWithFavorites(id); + if (!product) { + throw new NotFoundError('product', id); + } + return product; +} + +export async function getProductList( + params: PagePaginationParams, + { userId }: { userId?: number } = {}, +): Promise> { + const products = await productsRepository.getProductListWithFavorites(params, { userId }); + return products; +} + +export async function updateProduct(id: number, data: UpdateProductData): Promise { + const existingProduct = await productsRepository.getProduct(id); + if (!existingProduct) { + throw new NotFoundError('product', id); + } + if (existingProduct.userId !== data.userId) { + throw new ForbiddenError('Should be the owner of the product'); + } + const updatedProduct = await productsRepository.updateProductWithFavorites(id, data); + + // Price change notification + const previousPrice = existingProduct.price; + const updatedPrice = updatedProduct.price; + if (previousPrice !== updatedPrice) { + const favorites = await favoritesRepository.getFavoritesByProductId(id); + const likedUserIds = favorites.map((favorite) => favorite.userId); + const notifications = likedUserIds.map((userId) => ({ + userId, + type: NotificationType.PRICE_CHANGED, + payload: { + productId: id, + price: updatedPrice, + }, + })); + await notificationsService.createNotifications(notifications); + } + + return updatedProduct; +} + +export async function deleteProduct(id: number, userId: number): Promise { + const existingProduct = await productsRepository.getProduct(id); + if (!existingProduct) { + throw new NotFoundError('product', id); + } + if (existingProduct.userId !== userId) { + throw new ForbiddenError('Should be the owner of the product'); + } + await productsRepository.deleteProduct(id); +} diff --git a/src/services/socketService.ts b/src/services/socketService.ts new file mode 100644 index 000000000..a21b19e33 --- /dev/null +++ b/src/services/socketService.ts @@ -0,0 +1,39 @@ +import { ExtendedError, Server, Socket } from 'socket.io'; +import http from 'http'; +import * as authService from './authService'; +import { Notification } from '../types/Notification'; +import User from '../types/User'; + +class SocketService { + private io: Server; + + constructor() { + this.io = new Server(); + this.io.use(this.authenticate); + } + + private async authenticate(socket: Socket, next: (err?: ExtendedError) => void) { + let user: User; + try { + const accessToken = socket.handshake.auth.accessToken; + user = await authService.authenticate(accessToken); + } catch (error) { + console.log('error', error); + next(error as ExtendedError); + return; + } + socket.join(user.id.toString()); + next(); + } + + initialize(httpServer: http.Server) { + this.io.attach(httpServer); + } + + sendNotification(notification: Notification) { + const userId = notification.userId; + this.io.to(userId.toString()).emit('notification', notification); + } +} + +export default new SocketService(); 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/services/usersService.ts b/src/services/usersService.ts new file mode 100644 index 000000000..3afda8fbf --- /dev/null +++ b/src/services/usersService.ts @@ -0,0 +1,30 @@ +import * as usersRepository from '../repositories/usersRepository'; +import * as productsRepository from '../repositories/productsRepository'; +import * as favoritesRepository from '../repositories/favoritesRepository'; +import NotFoundError from '../lib/errors/NotFoundError'; +import User from '../types/User'; +import { PagePaginationParams } from '../types/pagination'; + +export async function getUser(id: number): Promise { + const user = await usersRepository.getUser(id); + if (!user) { + throw new NotFoundError('User', id); + } + return user; +} + +export async function updateUser( + id: number, + data: Partial>, +): Promise { + const user = await usersRepository.updateUser(id, data); + return user; +} + +export async function getMyProductList(userId: number, params: PagePaginationParams) { + return { list: [], totalCount: 0 }; +} + +export async function getMyFavoriteList(userId: number, params: PagePaginationParams) { + return { list: [], totalCount: 0 }; +} diff --git a/src/structs/articlesStructs.ts b/src/structs/articlesStructs.ts new file mode 100644 index 000000000..24b2259c4 --- /dev/null +++ b/src/structs/articlesStructs.ts @@ -0,0 +1,13 @@ +import { object, string, optional } from 'superstruct'; + +export const CreateArticleBodyStruct = object({ + title: string(), + content: string(), + image: optional(string()), +}); + +export const UpdateArticleBodyStruct = object({ + title: optional(string()), + content: optional(string()), + image: optional(string()), +}); diff --git a/src/structs/authStructs.ts b/src/structs/authStructs.ts new file mode 100644 index 000000000..31b260584 --- /dev/null +++ b/src/structs/authStructs.ts @@ -0,0 +1,12 @@ +import { object, string } from 'superstruct'; + +export const SignUpBodyStruct = object({ + email: string(), + nickname: string(), + password: string(), +}); + +export const SignInBodyStruct = object({ + email: string(), + password: string(), +}); diff --git a/src/structs/commentsStructs.ts b/src/structs/commentsStructs.ts new file mode 100644 index 000000000..a54e5b511 --- /dev/null +++ b/src/structs/commentsStructs.ts @@ -0,0 +1,11 @@ +import { object, string, number, optional } from 'superstruct'; + +export const CreateCommentBodyStruct = object({ + content: string(), + articleId: optional(number()), + productId: optional(number()), +}); + +export const UpdateCommentBodyStruct = object({ + content: string(), +}); diff --git a/src/structs/commonStructs.ts b/src/structs/commonStructs.ts new file mode 100644 index 000000000..ec924189f --- /dev/null +++ b/src/structs/commonStructs.ts @@ -0,0 +1,7 @@ +import { coerce, integer, object, string } from 'superstruct'; + +const integerString = coerce(integer(), string(), (value) => parseInt(value)); + +export const IdParamsStruct = object({ + id: integerString, +}); diff --git a/src/structs/productsStructs.ts b/src/structs/productsStructs.ts new file mode 100644 index 000000000..777748318 --- /dev/null +++ b/src/structs/productsStructs.ts @@ -0,0 +1,17 @@ +import { object, string, number, array, optional } from 'superstruct'; + +export const CreateProductBodyStruct = object({ + name: string(), + description: string(), + price: number(), + tags: array(string()), + images: array(string()), +}); + +export const UpdateProductBodyStruct = object({ + name: optional(string()), + description: optional(string()), + price: optional(number()), + tags: optional(array(string())), + images: optional(array(string())), +}); diff --git a/src/structs/usersStructs.ts b/src/structs/usersStructs.ts new file mode 100644 index 000000000..49a714c09 --- /dev/null +++ b/src/structs/usersStructs.ts @@ -0,0 +1,27 @@ +import { coerce, integer, object, string, optional, defaulted } from 'superstruct'; + +const integerString = coerce(integer(), string(), (value) => parseInt(value)); + +export const UpdateMeBodyStruct = object({ + nickname: optional(string()), + image: optional(string()), +}); + +export const UpdatePasswordBodyStruct = object({ + password: string(), + newPassword: string(), +}); + +export const GetMyProductListParamsStruct = object({ + page: defaulted(integerString, 1), + pageSize: defaulted(integerString, 10), + orderBy: defaulted(string(), 'recent'), + keyword: optional(string()), +}); + +export const GetMyFavoriteListParamsStruct = GetMyProductListParamsStruct; + +export const GetMyNotificationsParamsStruct = object({ + cursor: optional(integerString), + limit: defaulted(integerString, 10), +}); diff --git a/src/types/Article.ts b/src/types/Article.ts new file mode 100644 index 000000000..3bb565fe7 --- /dev/null +++ b/src/types/Article.ts @@ -0,0 +1,11 @@ +export default interface Article { + id: number; + title: string; + content: string; + image: string | null; + userId: number; + likeCount: number; + isLiked: boolean; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/types/Comment.ts b/src/types/Comment.ts new file mode 100644 index 000000000..1c642b1aa --- /dev/null +++ b/src/types/Comment.ts @@ -0,0 +1,9 @@ +export default interface Comment { + id: number; + content: string; + userId: number; + productId: number | null; + articleId: number | null; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/types/Notification.ts b/src/types/Notification.ts new file mode 100644 index 000000000..7e6d7ba78 --- /dev/null +++ b/src/types/Notification.ts @@ -0,0 +1,14 @@ +export enum NotificationType { + PRICE_CHANGED = 'PRICE_CHANGED', + NEW_COMMENT = 'NEW_COMMENT', +} + +export interface Notification { + id: number; + userId: number; + type: NotificationType; + payload: any; + read: boolean; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/types/Product.ts b/src/types/Product.ts new file mode 100644 index 000000000..eec63fad6 --- /dev/null +++ b/src/types/Product.ts @@ -0,0 +1,13 @@ +export default interface Product { + id: number; + name: string; + description: string; + price: number; + tags: string[]; + images: string[]; + userId: number; + favoriteCount: number; + isFavorited: boolean; + createdAt: Date; + updatedAt: Date; +} diff --git a/src/types/User.ts b/src/types/User.ts new file mode 100644 index 000000000..9cbbe6aa7 --- /dev/null +++ b/src/types/User.ts @@ -0,0 +1,9 @@ +export default interface User { + id: number; + email: string; + nickname: string; + image: string | null; + password: string; + createdAt: Date; + updatedAt: Date; +} 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/types/pagination.ts b/src/types/pagination.ts new file mode 100644 index 000000000..a4c6e238f --- /dev/null +++ b/src/types/pagination.ts @@ -0,0 +1,21 @@ +export interface PagePaginationParams { + page: number; + pageSize: number; + orderBy?: string; + keyword?: string; +} + +export interface PagePaginationResult { + list: T[]; + totalCount: number; +} + +export interface CursorPaginationParams { + cursor?: number; + limit: number; +} + +export interface CursorPaginationResult { + list: T[]; + nextCursor: number | null; +} 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/prisma.ts b/src/utils/prisma.ts deleted file mode 100644 index f3514dbfc..000000000 --- a/src/utils/prisma.ts +++ /dev/null @@ -1,11 +0,0 @@ -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 diff --git a/tsconfig.json b/tsconfig.json index cda5c67b8..d837e4ddd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,35 +1,18 @@ { "compilerOptions": { - "target": "es2020", + "target": "ES2020", "module": "commonjs", - "lib": ["es2020"], - "outDir": "./dist", + "lib": ["ES2020"], + "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "removeComments": false, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, "moduleResolution": "node", - "baseUrl": "./src", - "paths": { - "@/*": ["*"], - "@/controllers/*": ["controllers/*"], - "@/services/*": ["services/*"], - "@/repositories/*": ["repositories/*"], - "@/types/*": ["types/*"], - "@/dto/*": ["dto/*"] - } + "allowSyntheticDefaultImports": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} \ No newline at end of file + "exclude": ["node_modules"] +}