diff --git a/README.md b/README.md
deleted file mode 100644
index 0946b53..0000000
--- a/README.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# 10th-node-study
-
-## ๐ฒ๋ธ๋์น ๊ท์น
-- ๋ธ๋์น ๋ช
: week00_๋๋ค์(์์ด)
----
-## ๐ํด๋ ๊ตฌ์กฐ
-- ๋ณธ์ธ ๋๋ค์์ผ๋ก ์์ฑ๋ ํด๋ ๋ด๋ถ์ ํ๋ก์ ํธ๋ฅผ ์์ฑํฉ๋๋ค.
-- ํด๋น ํ๋ก์ ํธ์์ ์ค์ต์ ์งํํด์ฃผ์๋ฉด ๋ฉ๋๋ค.
-```
-โโโ vex
-โ โโโ src
-โโโ unyeon
-โโโ chunwol
-โโโ README.md
-```
-
-## โ
Pull Request ๊ท์น
-- PR ์ ๋ชฉ: [week00] ๋๋ค์ n์ฃผ์ฐจ ๋ฏธ์
-- ๋ด์ฉ : ์ํฌ๋ถ ์งํ ๋ฐ ์ค์ต ์งํ ๊ฐ์ ๋๋ ์ ํ๋ง๋
-
-PR ์์ฑ ์ดํ์ ์ฐ์ธก๋ถ๋ถ์์ Reviewer ๋ฐ Assignee ์ง์ ํด์ฃผ์ธ์!
-
-
-### Reviewer & Assignee
-- Reviewer : ๊ฐ ํํธ์ฅ๋ง ์ง์
-- Assignee : ๋ณธ์ธ(์์ฑ์) ์ง์
-
-๐ **Merge ๊ท์น :
-PR ๋ณํฉ์ ์คํฐ๋ ์งํ ํ, ํด๋น ํํธ์ฅ์ด ์ต์ข
Merge (๊ทธ ์ธ ์ธ์์ Merge ํ์ง ์์)**
-
----
-
-## โ
์ปค๋ฐ ๋ฉ์์ง ๊ท์น
-| ํ์
| ์ค๋ช
|
-|-----------|------|
-| feat | ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ |
-| fix | ๋ฒ๊ทธ ์์ |
-| docs | ๋ฌธ์ ์์ (README, ์ฃผ์ ๋ฑ) |
-| style | ์ฝ๋ ์คํ์ผ ๋ณ๊ฒฝ (ํฌ๋งท, ์ธ๋ฏธ์ฝ๋ก ๋ฑ) |
-| refactor | ๋ฆฌํฉํ ๋ง (๊ธฐ๋ฅ ๋ณํ ์์) |
-| test | ํ
์คํธ ์ฝ๋ ์ถ๊ฐ / ์์ |
-| chore | ๋น๋ ์ค์ , ํจํค์ง ๊ด๋ฆฌ ๋ฑ ๊ธฐํ ์์
|
-| build | ๋น๋ ๊ด๋ จ ํ์ผ ์์ |
-| revert | ์ด์ ์ปค๋ฐ ๋๋๋ฆฌ๊ธฐ |
-
-`(ex: feat: ๊ธฐ๋ฅ ์ถ๊ฐ)`
-
----
diff --git "a/\353\217\204\354\226\217/week2/mission_queries.sql" "b/\353\217\204\354\226\217/week2/mission_queries.sql"
new file mode 100644
index 0000000..5d1588a
--- /dev/null
+++ "b/\353\217\204\354\226\217/week2/mission_queries.sql"
@@ -0,0 +1,117 @@
+-- =============================================
+-- UMC 2์ฃผ์ฐจ ๋ฏธ์
์ฟผ๋ฆฌ (์ ๊ณตํด์ฃผ์ ๋ก์ง + 1์ฃผ์ฐจ ERD ์คํค๋ง ๋ฐ์)
+-- =============================================
+
+-- =============================================
+-- 1. ๋ด๊ฐ ์งํ์ค/์งํ ์๋ฃํ ๋ฏธ์
๋ชฉ๋ก ์กฐํ (ํ์ด์ง ํฌํจ)
+-- =============================================
+SELECT
+ m.c_mis_id AS mission_id,
+ s.c_sto_name AS store_name,
+ m.c_mis_title AS mission_title,
+ m.c_mis_reward AS point,
+ mm.c_mm_status AS mission_status,
+ mm.c_mm_created AS started_at,
+ mm.c_mm_updated AS completed_at
+FROM t_member_mission mm
+JOIN t_mission m ON mm.c_mm_mission_id = m.c_mis_id
+JOIN t_store s ON m.c_mis_store_id = s.c_sto_id
+WHERE mm.c_mm_member_id = 1 -- ๋ก๊ทธ์ธํ ์ ์ id
+ AND mm.c_mm_status = 'CHALLENGING' -- ์งํ์ค: 'CHALLENGING' / ์งํ์๋ฃ: 'COMPLETE'
+ORDER BY mm.c_mm_created DESC
+LIMIT 10 OFFSET 0; -- 1ํ์ด์ง: OFFSET 0, 2ํ์ด์ง: OFFSET 10
+
+-- ์งํ์๋ฃ ํญ์ผ๋ก ๋ฐ๊ฟ ๋๋ status ์กฐ๊ฑด๋ง ๋ณ๊ฒฝ
+-- AND mm.c_mm_status = 'COMPLETE'
+
+
+-- =============================================
+-- 2. ๋ฆฌ๋ทฐ ์์ฑ ์ฟผ๋ฆฌ (์ฌ์ง ์ ์ธ)
+-- =============================================
+INSERT INTO t_review (
+ c_rev_member_id,
+ c_rev_store_id,
+ c_rev_content,
+ c_rev_score,
+ c_rev_created,
+ c_rev_updated
+)
+SELECT
+ mm.c_mm_member_id,
+ m.c_mis_store_id,
+ '๋๋ฌด ๋ง์์ด์! ํฌ์ธํธ๋ ๋ฐ๊ณ ์ข์์ต๋๋ค.', -- ์ค์ ๋ก๋ ์ ์ ๊ฐ ์
๋ ฅํ ๋ด์ฉ
+ 4.5, -- ์ค์ ๋ก๋ ์ ์ ๊ฐ ์ ํํ ๋ณ์
+ NOW(),
+ NOW()
+FROM t_member_mission mm
+JOIN t_mission m ON mm.c_mm_mission_id = m.c_mis_id
+WHERE mm.c_mm_member_id = 1 -- ๋ก๊ทธ์ธํ ์ ์ id (๋ณธ์ธ ๋ฏธ์
๋ง ์์ฑ ๊ฐ๋ฅ)
+ AND mm.c_mm_mission_id = 1 -- ๋ฆฌ๋ทฐ ์์ฑํ ๋ฏธ์
id
+ AND mm.c_mm_status = 'COMPLETE'; -- ์๋ฃ๋ ๋ฏธ์
๋ง ๋ฆฌ๋ทฐ ์์ฑ ๊ฐ๋ฅ
+
+-- ๋ฆฌ๋ทฐ ์์ฑ ํ ํด๋น ๊ฐ๊ฒ์ ํ๊ท ๋ณ์ ์
๋ฐ์ดํธ
+-- (์ฐธ๊ณ : ํ์ฌ ERD์ t_store์๋ ๋ณ์ ์ปฌ๋ผ์ด ์์ผ๋, 'c_sto_score' ์ปฌ๋ผ์ด ์๋ค๊ณ ๊ฐ์ )
+/*
+UPDATE t_store s
+SET c_sto_score = (
+ SELECT AVG(r.c_rev_score)
+ FROM t_review r
+ WHERE r.c_rev_store_id = s.c_sto_id
+)
+WHERE s.c_sto_id = (
+ SELECT m.c_mis_store_id
+ FROM t_mission m
+ WHERE m.c_mis_id = 1
+);
+*/
+
+
+-- =============================================
+-- 3. ํ ํ๋ฉด - ํ์ฌ ์ ํ๋ ์ง์ญ์์ ๋์ ๊ฐ๋ฅํ ๋ฏธ์
๋ชฉ๋ก (ํ์ด์ง ํฌํจ)
+-- =============================================
+
+-- 3-1. ํ์ฌ ์ง์ญ ์๋ฃ ๋ฏธ์
์ (์๋จ 7/10 ํ์์ฉ)
+SELECT COUNT(*) AS complete_count
+FROM t_member_mission mm
+JOIN t_mission m ON mm.c_mm_mission_id = m.c_mis_id
+JOIN t_store s ON m.c_mis_store_id = s.c_sto_id
+WHERE mm.c_mm_member_id = 1 -- ๋ก๊ทธ์ธํ ์ ์ id
+ AND s.c_sto_region_id = 1 -- ํ์ฌ ์ ํ๋ ์ง์ญ id
+ AND mm.c_mm_status = 'COMPLETE';
+
+-- 3-2. ํ์ฌ ์ง์ญ์์ ๋์ ๊ฐ๋ฅํ ๋ฏธ์
๋ชฉ๋ก (์์ง ๋์ ์ ํ ๋ฏธ์
)
+SELECT
+ m.c_mis_id AS mission_id,
+ s.c_sto_name AS store_name,
+ fc.c_fc_name AS category,
+ m.c_mis_title AS mission_title,
+ m.c_mis_reward AS point,
+ m.c_mis_deadline AS deadline
+FROM t_mission m
+JOIN t_store s ON m.c_mis_store_id = s.c_sto_id
+JOIN t_food_category fc ON s.c_sto_fc_id = fc.c_fc_id
+WHERE s.c_sto_region_id = 1 -- ํ์ฌ ์ ํ๋ ์ง์ญ id
+ AND s.c_sto_status = 'OPEN' -- ์์ ์คํ ์ํ ๊ฐ์
+ AND m.c_mis_id NOT IN ( -- ๋ด๊ฐ ์ด๋ฏธ ๋์ ์ค์ด๊ฑฐ๋ ์๋ฃํ ๋ฏธ์
์ ์ธ
+ SELECT c_mm_mission_id
+ FROM t_member_mission
+ WHERE c_mm_member_id = 1 -- ๋ก๊ทธ์ธํ ์ ์ id
+ )
+ORDER BY m.c_mis_created DESC
+LIMIT 10 OFFSET 0; -- 1ํ์ด์ง: OFFSET 0, 2ํ์ด์ง: OFFSET 10
+
+
+-- =============================================
+-- 4. ๋ง์ดํ์ด์ง ํ๋ฉด ์ฟผ๋ฆฌ
+-- =============================================
+SELECT
+ u.c_mem_nickname AS nickname,
+ u.c_mem_social_id AS email, -- ์ด๋ฉ์ผ ์ ์ฉ ์ปฌ๋ผ ๋ถ์ฌ๋ก ์์
๊ณ์ ID ๋งคํ
+ u.c_mem_phone_num AS phone,
+ u.c_mem_profile_image_url AS profile_img,
+ u.c_mem_point AS point,
+ COUNT(r.c_rev_id) AS review_count -- ์์ฑํ ๋ฆฌ๋ทฐ ์
+FROM t_member u
+LEFT JOIN t_review r ON r.c_rev_member_id = u.c_mem_id
+WHERE u.c_mem_id = 1 -- ๋ก๊ทธ์ธํ ์ ์ id
+GROUP BY u.c_mem_id;
diff --git "a/\353\217\204\354\226\217/week3/WEEK3_API.md" "b/\353\217\204\354\226\217/week3/WEEK3_API.md"
new file mode 100644
index 0000000..2ebf111
--- /dev/null
+++ "b/\353\217\204\354\226\217/week3/WEEK3_API.md"
@@ -0,0 +1,107 @@
+
+---
+
+# ๋ฏธ์
์๋น์ค API ๋ช
์ธ์
+
+## 1. ํ์ ๊ด๋ จ API
+
+### **[POST] ํ์๊ฐ์
**
+* **Endpoint**: `/users/signup`
+* **Request Header**: `Content-Type: application/json`
+* **Request Body**:
+ ```json
+ {
+ "email": "string",
+ "password": "string",
+ "name": "string",
+ "gender": "integer",
+ "birth": "string",
+ "address": "string"
+ }
+ ```
+* **์ค๋ช
**: ์๋ก์ด ์ฌ์ฉ์ ๊ณ์ ์ ์์ฑํฉ๋๋ค.
+
+### **[POST] ์ ํธ ์กฐ์ฌ ๋ด์ญ ์ ์ฅ**
+* **Endpoint**: `/users/preferences`
+* **Request Header**: `Authorization: Bearer {token}`, `Content-Type: application/json`
+* **Request Body**:
+ ```json
+ {
+ "category_ids": [1, 2, 3]
+ }
+ ```
+* **์ค๋ช
**: ํ์๊ฐ์
ํ ์ฌ์ฉ์์ ๊ด์ฌ ์นดํ
๊ณ ๋ฆฌ ์ ๋ณด๋ฅผ ์ ์ฅํฉ๋๋ค.
+
+---
+
+## 2. ํ ๋ฐ ๋ฏธ์
๊ด๋ฆฌ API
+
+### **[GET] ํ ํ๋ฉด: ๋ด๊ฐ ๋ฐ์ ๋ฏธ์
์กฐํ**
+* **Endpoint**: `/members/me/missions/active`
+* **Request Header**: `Authorization: Bearer {token}`
+* **Query String**: `page=0&size=10`
+* **์ค๋ช
**: ํ ํ๋ฉด์์ ํ์ฌ ์ฌ์ฉ์๊ฐ ํ ๋น๋ฐ๊ฑฐ๋ ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+
+### **[GET] ๋ฏธ์
๋ชฉ๋ก ์กฐํ (์ํ ์ค / ์๋ฃ)**
+* **Endpoint**: `/members/me/missions`
+* **Request Header**: `Authorization: Bearer {token}`
+* **Query String**:
+ * `status`: `CHALLENGING` (์ํ ์ค) ๋๋ `COMPLETE` (์๋ฃ)
+ * `page`: ํ์ด์ง ๋ฒํธ
+* **์ค๋ช
**: ์ฌ์ฉ์์ ๋ฏธ์
์ํ ๊ธฐ๋ก์ ์ํ๋ณ๋ก ํํฐ๋งํ์ฌ ์กฐํํฉ๋๋ค.
+
+### **[POST] ๋ฏธ์
๋์ ํ๊ธฐ (์ฑ๊ณต ๋๋ฅด๊ธฐ)**
+* **Endpoint**: `/members/me/missions/{missionId}`
+* **Path Variable**: `missionId` (๋์ ํ ๋ฏธ์
์ ID)
+* **Request Header**: `Authorization: Bearer {token}`
+* **์ค๋ช
**: ํน์ ๋ฏธ์
์ ์ํํ๊ธฐ ์์ํ๊ฑฐ๋ ์๋ฃ๋ฅผ ์์ฒญํฉ๋๋ค.
+
+---
+
+## 3. ์ง๋ ๋ฐ ๊ฐ๊ฒ ๊ด๋ จ API
+
+### **[GET] ์ง์ญ๋ณ ๊ฐ๊ฒ ๋ฆฌ์คํธ ์กฐํ**
+* **Endpoint**: `/regions/{regionId}/stores`
+* **Path Variable**: `regionId` (์ง์ญ ID)
+* **Query String**: `last_store_id=10&size=10`
+* **์ค๋ช
**: ํน์ ์ง์ญ์ ๋ฑ๋ก๋ ๊ฐ๊ฒ๋ค์ ๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+
+### **[GET] ๊ฐ๊ฒ ์ ๋ณด ๋ฐ ๋ฏธ์
์กฐํ**
+* **Endpoint**: `/stores/{storeId}`
+* **Path Variable**: `storeId` (๊ฐ๊ฒ ID)
+* **์ค๋ช
**: ํน์ ๊ฐ๊ฒ์ ์์ธ ์ ๋ณด์ ํด๋น ๊ฐ๊ฒ์์ ์งํ ๊ฐ๋ฅํ ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+
+---
+
+## 4. ๋ง์ดํ์ด์ง ๋ฐ ๋ฆฌ๋ทฐ API
+
+### **[POST] ๋ฆฌ๋ทฐ ์์ฑํ๊ธฐ**
+* **Endpoint**: `/members/me/missions/{memberMissionId}/reviews`
+* **Path Variable**: `memberMissionId` (์๋ฃ๋ ๋ฏธ์
์ํ ๊ธฐ๋ก ID)
+* **Request Header**: `Authorization: Bearer {token}`
+* **Request Body**:
+ ```json
+ {
+ "content": "string",
+ "score": "float",
+ "image_url": "string"
+ }
+ ```
+* **์ค๋ช
**: ์๋ฃ๋ ๋ฏธ์
์ ๋ํด ๊ฐ๊ฒ ๋ฆฌ๋ทฐ์ ๋ณ์ ์ ์์ฑํฉ๋๋ค.
+
+### **[GET] ๋ด ํฌ์ธํธ ์กฐํ**
+* **Endpoint**: `/members/me/points`
+* **Request Header**: `Authorization: Bearer {token}`
+* **์ค๋ช
**: ์ฌ์ฉ์๊ฐ ํ์ฌ ๋ณด์ ํ ์ด ํฌ์ธํธ์ ์ ๋ฆฝ ๋ด์ญ์ ํ์ธํฉ๋๋ค.
+
+---
+
+## ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง
+
+### **์ง์ญ ๋ณด๋์ค ํฌ์ธํธ ์๋ ์ง๊ธ**
+* **์ ์ฉ ๋์ API**: `POST /members/me/missions/{missionId}` (๋ฏธ์
์๋ฃ ์ฒ๋ฆฌ ์)
+* **๋ก์ง ์์ธ**:
+ 1. ์ฌ์ฉ์๊ฐ ๋ฏธ์
์ ์๋ฃํ ๋๋ง๋ค ํด๋น ๊ฐ๊ฒ์ `region_id`๋ฅผ ํ์ธํฉ๋๋ค.
+ 2. ์๋ฒ ๋ด๋ถ์์ ํด๋น ์ฌ์ฉ์๊ฐ ๋์ผ ์ง์ญ์์ ์๋ฃํ ๋ฏธ์
์ ์ด๊ฐ์๋ฅผ ์นด์ดํธํฉ๋๋ค.
+ 3. **๋ชจ๋ ์ง์ญ๋ง๋ค ๋์ ์๋ฃ ๋ฏธ์
์ด 10๊ฐ๊ฐ ๋ ๋๋ง๋ค 1000 point๋ฅผ ์ฆ์ ์ง๊ธ**ํฉ๋๋ค.
+ 4. ๋ณด๋์ค ์ง๊ธ ์ ์ฌ์ฉ์์๊ฒ ์๋ฆผ์ ๋ฐ์กํฉ๋๋ค.
\ No newline at end of file
diff --git "a/\353\217\204\354\226\217/week4/.gitignore" "b/\353\217\204\354\226\217/week4/.gitignore"
new file mode 100644
index 0000000..deed335
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/.gitignore"
@@ -0,0 +1,3 @@
+node_modules/
+dist/
+.env
diff --git "a/\353\217\204\354\226\217/week4/package-lock.json" "b/\353\217\204\354\226\217/week4/package-lock.json"
new file mode 100644
index 0000000..96c9186
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/package-lock.json"
@@ -0,0 +1,1983 @@
+{
+ "name": "umc-week4",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "umc-week4",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "bcryptjs": "^2.4.3",
+ "express": "^5.2.1",
+ "jsonwebtoken": "^9.0.2"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/express": "^5.0.1",
+ "@types/jsonwebtoken": "^9.0.9",
+ "@types/node": "^22.14.0",
+ "nodemon": "^3.1.14",
+ "tsx": "^4.19.3",
+ "typescript": "^5.8.3"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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/express": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "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/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/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": "22.19.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
+ "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "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": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "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/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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-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/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/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "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": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "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/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/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/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/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "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": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/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": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/get-tsconfig": {
+ "version": "4.13.7",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
+ "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "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.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/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/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "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": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "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/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": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "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/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-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/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "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": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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.15.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "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": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "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/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "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/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/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ }
+ }
+}
diff --git "a/\353\217\204\354\226\217/week4/package.json" "b/\353\217\204\354\226\217/week4/package.json"
new file mode 100644
index 0000000..5010ac4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/package.json"
@@ -0,0 +1,28 @@
+{
+ "name": "umc-week4",
+ "version": "1.0.0",
+ "description": "UMC 4์ฃผ์ฐจ - Node.js TypeScript ๊ธฐ๋ฐ ์๋ฒ ๊ฐ๋ฐ",
+ "main": "src/index.ts",
+ "scripts": {
+ "start": "tsx src/index.ts",
+ "dev": "nodemon --exec tsx src/index.ts",
+ "build": "tsc",
+ "start:prod": "node dist/index.js"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "bcryptjs": "^2.4.3",
+ "express": "^5.2.1",
+ "jsonwebtoken": "^9.0.2"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/express": "^5.0.1",
+ "@types/jsonwebtoken": "^9.0.9",
+ "@types/node": "^22.14.0",
+ "nodemon": "^3.1.14",
+ "tsx": "^4.19.3",
+ "typescript": "^5.8.3"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week4/schema.sql" "b/\353\217\204\354\226\217/week4/schema.sql"
new file mode 100644
index 0000000..743615e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/schema.sql"
@@ -0,0 +1,223 @@
+-- ============================================================
+-- UMC ๋ฏธ์
์๋น์ค - MySQL DDL
+-- ๊ธฐ๋ฐ: week1 ERD + week3 API ๋ช
์ธ
+-- ============================================================
+
+CREATE DATABASE IF NOT EXISTS umc_mission DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE umc_mission;
+
+-- ============================================================
+-- 1. region (์ง์ญ)
+-- ============================================================
+CREATE TABLE region (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์ง์ญ๋ช
',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+-- ============================================================
+-- 2. food_category (์์ ์นดํ
๊ณ ๋ฆฌ)
+-- ============================================================
+CREATE TABLE food_category (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์นดํ
๊ณ ๋ฆฌ๋ช
(ํ์, ์ค์, ์ผ์ ๋ฑ)',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+-- ============================================================
+-- 3. terms (์ฝ๊ด)
+-- ============================================================
+CREATE TABLE terms (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ title VARCHAR(100) NOT NULL COMMENT '์ฝ๊ด ์ ๋ชฉ',
+ content TEXT NOT NULL COMMENT '์ฝ๊ด ๋ด์ฉ',
+ optional BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'TRUE: ์ ํ ๋์, FALSE: ํ์ ๋์',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+-- ============================================================
+-- 4. member (ํ์)
+-- - social_type / social_id: ์์
๋ก๊ทธ์ธ (kakao, naver, google ๋ฑ)
+-- - email / password: ์ผ๋ฐ ์ด๋ฉ์ผ ๋ก๊ทธ์ธ (week3 API ๋ช
์ธ ๋ฐ์)
+-- - status: ACTIVE | INACTIVE | BANNED
+-- - gender: MALE | FEMALE | OTHER
+-- ============================================================
+CREATE TABLE member (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ social_type VARCHAR(20) NULL COMMENT '์์
๋ก๊ทธ์ธ ํ์
(kakao, naver, google)',
+ social_id VARCHAR(100) NULL COMMENT '์์
๊ณ ์ ID',
+ email VARCHAR(100) NULL COMMENT '์ด๋ฉ์ผ (์ผ๋ฐ ๋ก๊ทธ์ธ)',
+ password VARCHAR(255) NULL COMMENT '๋น๋ฐ๋ฒํธ ํด์ (์ผ๋ฐ ๋ก๊ทธ์ธ)',
+ name VARCHAR(50) NOT NULL COMMENT '์ค๋ช
',
+ nickname VARCHAR(50) NOT NULL COMMENT '๋๋ค์',
+ profile_image_url VARCHAR(500) NULL COMMENT 'ํ๋กํ ์ด๋ฏธ์ง URL',
+ phone_num VARCHAR(20) NULL COMMENT '์ ํ๋ฒํธ',
+ phone_verified BOOLEAN NOT NULL DEFAULT FALSE COMMENT '์ ํ๋ฒํธ ์ธ์ฆ ์ฌ๋ถ',
+ birth DATE NULL COMMENT '์๋
์์ผ',
+ gender VARCHAR(10) NULL COMMENT 'MALE | FEMALE | OTHER',
+ address VARCHAR(200) NULL COMMENT '๊ธฐ๋ณธ ์ฃผ์',
+ spec_address VARCHAR(200) NULL COMMENT '์์ธ ์ฃผ์',
+ point INT NOT NULL DEFAULT 0 COMMENT '๋ณด์ ํฌ์ธํธ',
+ status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE | INACTIVE | BANNED',
+ inactive_date DATETIME NULL COMMENT '๋นํ์ฑํ ์ผ์',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_email (email),
+ UNIQUE KEY uq_member_social (social_type, social_id)
+);
+
+-- ============================================================
+-- 5. member_agree (ํ์ ์ฝ๊ด ๋์)
+-- ============================================================
+CREATE TABLE member_agree (
+ member_id BIGINT NOT NULL,
+ terms_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, terms_id),
+ CONSTRAINT fk_member_agree_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_agree_terms FOREIGN KEY (terms_id) REFERENCES terms (id)
+);
+
+-- ============================================================
+-- 6. member_prefer (ํ์ ์์ ์นดํ
๊ณ ๋ฆฌ ์ ํธ)
+-- ============================================================
+CREATE TABLE member_prefer (
+ member_id BIGINT NOT NULL,
+ food_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, food_id),
+ CONSTRAINT fk_member_prefer_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_prefer_food FOREIGN KEY (food_id) REFERENCES food_category (id)
+);
+
+-- ============================================================
+-- 7. store (๊ฐ๊ฒ)
+-- - status: OPEN | CLOSED | PENDING
+-- ============================================================
+CREATE TABLE store (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ region_id BIGINT NOT NULL,
+ food_category_id BIGINT NOT NULL,
+ name VARCHAR(100) NOT NULL COMMENT '๊ฐ๊ฒ๋ช
',
+ description TEXT NULL COMMENT '๊ฐ๊ฒ ์ค๋ช
',
+ lat DECIMAL(10,7) NULL COMMENT '์๋',
+ lng DECIMAL(10,7) NULL COMMENT '๊ฒฝ๋',
+ address VARCHAR(200) NOT NULL COMMENT '์ฃผ์',
+ status VARCHAR(20) NOT NULL DEFAULT 'OPEN' COMMENT 'OPEN | CLOSED | PENDING',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_region FOREIGN KEY (region_id) REFERENCES region (id),
+ CONSTRAINT fk_store_category FOREIGN KEY (food_category_id) REFERENCES food_category (id)
+);
+
+-- ============================================================
+-- 8. store_image (๊ฐ๊ฒ ์ด๋ฏธ์ง)
+-- ============================================================
+CREATE TABLE store_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL COMMENT '์ด๋ฏธ์ง URL',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_image_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+-- ============================================================
+-- 9. store_hours (๊ฐ๊ฒ ์์
์๊ฐ)
+-- - day_of_week: MON | TUE | WED | THU | FRI | SAT | SUN
+-- ============================================================
+CREATE TABLE store_hours (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ day_of_week VARCHAR(3) NOT NULL COMMENT 'MON | TUE | WED | THU | FRI | SAT | SUN',
+ open_time TIME NOT NULL COMMENT '์์
์์ ์๊ฐ',
+ close_time TIME NOT NULL COMMENT '์์
์ข
๋ฃ ์๊ฐ',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_store_hours_day (store_id, day_of_week),
+ CONSTRAINT fk_store_hours_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+-- ============================================================
+-- 10. mission (๋ฏธ์
)
+-- ============================================================
+CREATE TABLE mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ title VARCHAR(200) NOT NULL COMMENT '๋ฏธ์
์ ๋ชฉ',
+ reward INT NOT NULL DEFAULT 0 COMMENT '์๋ฃ ์ ์ง๊ธ ํฌ์ธํธ',
+ spec VARCHAR(500) NULL COMMENT '๋ฏธ์
์์ธ ์กฐ๊ฑด',
+ dead_line DATE NULL COMMENT '๋ฏธ์
๋ง๊ฐ์ผ',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_mission_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+-- ============================================================
+-- 11. member_mission (ํ์ ๋ฏธ์
์ํ ๊ธฐ๋ก)
+-- - status: CHALLENGING | COMPLETE
+-- - surrogate PK(id) ์ฌ์ฉ: review์์ FK ์ฐธ์กฐ ๊ฐ๋ฅํ๋๋ก
+-- (week3 API: /members/me/missions/{memberMissionId}/reviews)
+-- ============================================================
+CREATE TABLE member_mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ mission_id BIGINT NOT NULL,
+ status VARCHAR(20) NOT NULL DEFAULT 'CHALLENGING' COMMENT 'CHALLENGING | COMPLETE',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_mission (member_id, mission_id),
+ CONSTRAINT fk_member_mission_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_mission_mission FOREIGN KEY (mission_id) REFERENCES mission (id)
+);
+
+-- ============================================================
+-- 12. review (๋ฆฌ๋ทฐ)
+-- - member_mission_id: ์๋ฃ๋ ๋ฏธ์
์ํ ๊ธฐ๋ก๊ณผ ์ฐ๊ฒฐ
+-- (week3 API: POST /members/me/missions/{memberMissionId}/reviews)
+-- - score: 1.0 ~ 5.0 (์์์ ์ฒซ์งธ ์๋ฆฌ)
+-- ============================================================
+CREATE TABLE review (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ store_id BIGINT NOT NULL,
+ member_mission_id BIGINT NULL COMMENT '์ฐ๊ฒฐ๋ ๋ฏธ์
์ํ ๊ธฐ๋ก (์ ํ)',
+ content TEXT NOT NULL COMMENT '๋ฆฌ๋ทฐ ๋ด์ฉ',
+ score DECIMAL(2,1) NOT NULL COMMENT '๋ณ์ (1.0 ~ 5.0)',
+ owner_reply VARCHAR(500) NULL COMMENT '์ฌ์ฅ๋ ๋ต๊ธ',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_review_store FOREIGN KEY (store_id) REFERENCES store (id),
+ CONSTRAINT fk_review_member_mission FOREIGN KEY (member_mission_id) REFERENCES member_mission (id),
+ CONSTRAINT chk_review_score CHECK (score BETWEEN 1.0 AND 5.0)
+);
+
+-- ============================================================
+-- 13. review_image (๋ฆฌ๋ทฐ ์ด๋ฏธ์ง)
+-- - week3 API์ image_url ํ๋ ์ ์ฅ (๋ค์ค ์ด๋ฏธ์ง ์ง์)
+-- ============================================================
+CREATE TABLE review_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ review_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL COMMENT '์ด๋ฏธ์ง URL',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_image_review FOREIGN KEY (review_id) REFERENCES review (id)
+);
diff --git "a/\353\217\204\354\226\217/week4/src/controllers/member.controller.ts" "b/\353\217\204\354\226\217/week4/src/controllers/member.controller.ts"
new file mode 100644
index 0000000..fce6fd2
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/controllers/member.controller.ts"
@@ -0,0 +1,41 @@
+import { Request, Response, NextFunction } from 'express'
+import { memberService } from '../services/member.service.js'
+
+export const memberController = {
+ signUp: async (req: Request, res: Response, next: NextFunction): Promise => {
+ try {
+ const result = await memberService.signUp(req.body)
+ res.status(201).json({ success: true, code: 'S201', message: 'ํ์๊ฐ์
์ด ์๋ฃ๋์์ต๋๋ค.', data: result })
+ } catch (e) {
+ next(e)
+ }
+ },
+
+ login: async (req: Request, res: Response, next: NextFunction): Promise => {
+ try {
+ const result = await memberService.login(req.body)
+ res.status(200).json({ success: true, code: 'S200', message: '๋ก๊ทธ์ธ์ ์ฑ๊ณตํ์ต๋๋ค.', data: result })
+ } catch (e) {
+ next(e)
+ }
+ },
+
+ getMyPage: (req: Request, res: Response, next: NextFunction): void => {
+ try {
+ const result = memberService.getMyPage(req.memberId!)
+ res.status(200).json({ success: true, code: 'S200', message: '๋ง์ดํ์ด์ง ์กฐํ์ ์ฑ๊ณตํ์ต๋๋ค.', data: result })
+ } catch (e) {
+ next(e)
+ }
+ },
+
+ getMyMissions: (req: Request, res: Response, next: NextFunction): void => {
+ try {
+ const { status, page, size } = req.query as Record
+ const result = memberService.getMyMissions(req.memberId!, status, page, size)
+ res.status(200).json({ success: true, code: 'S200', message: '๋ฏธ์
๋ชฉ๋ก ์กฐํ์ ์ฑ๊ณตํ์ต๋๋ค.', data: result })
+ } catch (e) {
+ next(e)
+ }
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/controllers/mission.controller.ts" "b/\353\217\204\354\226\217/week4/src/controllers/mission.controller.ts"
new file mode 100644
index 0000000..12719e2
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/controllers/mission.controller.ts"
@@ -0,0 +1,14 @@
+import { Request, Response, NextFunction } from 'express'
+import { missionService } from '../services/mission.service.js'
+
+export const missionController = {
+ challengeMission: (req: Request, res: Response, next: NextFunction): void => {
+ try {
+ const missionId = parseInt(String(req.params.missionId), 10)
+ const result = missionService.challengeMission(missionId, req.memberId!)
+ res.status(201).json({ success: true, code: 'S201', message: '๋ฏธ์
๋์ ์ด ์์๋์์ต๋๋ค.', data: result })
+ } catch (e) {
+ next(e)
+ }
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/controllers/store.controller.ts" "b/\353\217\204\354\226\217/week4/src/controllers/store.controller.ts"
new file mode 100644
index 0000000..df9ec46
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/controllers/store.controller.ts"
@@ -0,0 +1,14 @@
+import { Request, Response, NextFunction } from 'express'
+import { storeService } from '../services/store.service.js'
+
+export const storeController = {
+ createReview: (req: Request, res: Response, next: NextFunction): void => {
+ try {
+ const storeId = parseInt(String(req.params.storeId), 10)
+ const result = storeService.createReview(storeId, req.memberId!, req.body)
+ res.status(201).json({ success: true, code: 'S201', message: '๋ฆฌ๋ทฐ๊ฐ ๋ฑ๋ก๋์์ต๋๋ค.', data: result })
+ } catch (e) {
+ next(e)
+ }
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/db/index.ts" "b/\353\217\204\354\226\217/week4/src/db/index.ts"
new file mode 100644
index 0000000..0fba219
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/db/index.ts"
@@ -0,0 +1,32 @@
+import type { Member, Store, Review, Mission, MemberMission } from '../types/index.js'
+
+// ์ธ๋ฉ๋ชจ๋ฆฌ DB (week5์์ ์ค์ DB๋ก ๊ต์ฒด ์์ )
+let memberIdSeq = 1
+let reviewIdSeq = 1
+let missionIdSeq = 3
+let memberMissionIdSeq = 1
+let storeIdSeq = 3
+
+export const db = {
+ members: [] as Member[],
+
+ stores: [
+ { id: 1, name: '๋ง์๋ ์๋น', address: '์์ธ์ ๊ฐ๋จ๊ตฌ' },
+ { id: 2, name: '์นดํ UMC', address: '์์ธ์ ๋งํฌ๊ตฌ' },
+ ] as Store[],
+
+ reviews: [] as Review[],
+
+ missions: [
+ { id: 1, storeId: 1, title: '์ฒซ ๋ฐฉ๋ฌธ ๋ฏธ์
', reward: 500, deadline: '2026-12-31', missionSpec: '์์ ์ฃผ๋ฌธ ํ ๋ฆฌ๋ทฐ ๋จ๊ธฐ๊ธฐ' },
+ { id: 2, storeId: 2, title: '์นดํ ๋ฐฉ๋ฌธ ๋ฏธ์
', reward: 300, deadline: '2026-12-31', missionSpec: '์๋ฃ ์ฃผ๋ฌธํ๊ธฐ' },
+ ] as Mission[],
+
+ memberMissions: [] as MemberMission[],
+
+ nextMemberId: () => memberIdSeq++,
+ nextReviewId: () => reviewIdSeq++,
+ nextMissionId: () => missionIdSeq++,
+ nextMemberMissionId: () => memberMissionIdSeq++,
+ nextStoreId: () => storeIdSeq++,
+}
diff --git "a/\353\217\204\354\226\217/week4/src/index.ts" "b/\353\217\204\354\226\217/week4/src/index.ts"
new file mode 100644
index 0000000..092d041
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/index.ts"
@@ -0,0 +1,24 @@
+import express from 'express'
+import { memberRouter } from './routes/member.route.js'
+import { storeRouter } from './routes/store.route.js'
+import { missionRouter } from './routes/mission.route.js'
+import { errorMiddleware } from './middleware/error.middleware.js'
+
+const app = express()
+const port = 3000
+
+app.use(express.json())
+
+app.use('/api/v1/members', memberRouter)
+app.use('/api/v1/stores', storeRouter)
+app.use('/api/v1/missions', missionRouter)
+
+app.get('/', (_req, res) => {
+ res.send('UMC 4์ฃผ์ฐจ ์๋ฒ ์คํ ์ค!')
+})
+
+app.use(errorMiddleware)
+
+app.listen(port, () => {
+ console.log(`Server is running on port ${port}`)
+})
diff --git "a/\353\217\204\354\226\217/week4/src/middleware/auth.middleware.ts" "b/\353\217\204\354\226\217/week4/src/middleware/auth.middleware.ts"
new file mode 100644
index 0000000..83b898e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/middleware/auth.middleware.ts"
@@ -0,0 +1,23 @@
+import { Request, Response, NextFunction } from 'express'
+import jwt from 'jsonwebtoken'
+
+const JWT_SECRET = process.env.JWT_SECRET ?? 'umc-week4-secret'
+
+export const authMiddleware = (req: Request, res: Response, next: NextFunction): void => {
+ const authHeader = req.headers.authorization
+
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
+ res.status(401).json({ success: false, code: 'E401', message: '์ธ์ฆ ํ ํฐ์ด ํ์ํฉ๋๋ค.' })
+ return
+ }
+
+ const token = authHeader.split(' ')[1]
+
+ try {
+ const decoded = jwt.verify(token, JWT_SECRET) as { memberId: number }
+ req.memberId = decoded.memberId
+ next()
+ } catch {
+ res.status(401).json({ success: false, code: 'E401', message: '์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค.' })
+ }
+}
diff --git "a/\353\217\204\354\226\217/week4/src/middleware/error.middleware.ts" "b/\353\217\204\354\226\217/week4/src/middleware/error.middleware.ts"
new file mode 100644
index 0000000..6a8897b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/middleware/error.middleware.ts"
@@ -0,0 +1,16 @@
+import { Request, Response, NextFunction } from 'express'
+
+interface AppError extends Error {
+ status?: number
+}
+
+export const errorMiddleware = (err: AppError, req: Request, res: Response, _next: NextFunction): void => {
+ console.error(`[Error] ${err.message}`)
+
+ const status = err.status ?? 500
+ res.status(status).json({
+ success: false,
+ code: `E${status}`,
+ message: err.message || '์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.',
+ })
+}
diff --git "a/\353\217\204\354\226\217/week4/src/repositories/member.repository.ts" "b/\353\217\204\354\226\217/week4/src/repositories/member.repository.ts"
new file mode 100644
index 0000000..1eaba8f
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/repositories/member.repository.ts"
@@ -0,0 +1,43 @@
+import { db } from '../db/index.js'
+import type { Member, MemberMission, PageResult } from '../types/index.js'
+
+export const memberRepository = {
+ findByEmail: (email: string): Member | undefined =>
+ db.members.find((m) => m.email === email),
+
+ findById: (id: number): Member | undefined =>
+ db.members.find((m) => m.id === id),
+
+ save: (member: Omit): Member => {
+ const newMember: Member = {
+ ...member,
+ id: db.nextMemberId(),
+ point: 0,
+ createdAt: new Date().toISOString(),
+ }
+ db.members.push(newMember)
+ return newMember
+ },
+
+ findMissionsByMemberId: (
+ memberId: number,
+ status: string | undefined,
+ page: string | undefined,
+ size: string | undefined,
+ ): PageResult => {
+ let missions = db.memberMissions.filter((mm) => mm.memberId === memberId)
+
+ if (status) {
+ missions = missions.filter((mm) => mm.status === status)
+ }
+
+ const pageNum = parseInt(page ?? '1', 10)
+ const sizeNum = parseInt(size ?? '10', 10)
+ const totalCount = missions.length
+ const totalPages = Math.max(1, Math.ceil(totalCount / sizeNum))
+ const start = (pageNum - 1) * sizeNum
+ const paged = missions.slice(start, start + sizeNum)
+
+ return { missions: paged, totalPages, currentPage: pageNum, isLast: pageNum >= totalPages }
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/repositories/mission.repository.ts" "b/\353\217\204\354\226\217/week4/src/repositories/mission.repository.ts"
new file mode 100644
index 0000000..4594c37
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/repositories/mission.repository.ts"
@@ -0,0 +1,20 @@
+import { db } from '../db/index.js'
+import type { Mission, MemberMission } from '../types/index.js'
+
+export const missionRepository = {
+ findById: (id: number): Mission | undefined =>
+ db.missions.find((m) => m.id === id),
+
+ findMemberMission: (memberId: number, missionId: number): MemberMission | undefined =>
+ db.memberMissions.find((mm) => mm.memberId === memberId && mm.missionId === missionId),
+
+ saveMemberMission: (memberMission: Omit): MemberMission => {
+ const newMM: MemberMission = {
+ ...memberMission,
+ id: db.nextMemberMissionId(),
+ createdAt: new Date().toISOString(),
+ }
+ db.memberMissions.push(newMM)
+ return newMM
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/repositories/store.repository.ts" "b/\353\217\204\354\226\217/week4/src/repositories/store.repository.ts"
new file mode 100644
index 0000000..cfb2e41
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/repositories/store.repository.ts"
@@ -0,0 +1,17 @@
+import { db } from '../db/index.js'
+import type { Store, Review } from '../types/index.js'
+
+export const storeRepository = {
+ findById: (id: number): Store | undefined =>
+ db.stores.find((s) => s.id === id),
+
+ saveReview: (review: Omit): Review => {
+ const newReview: Review = {
+ ...review,
+ id: db.nextReviewId(),
+ createdAt: new Date().toISOString(),
+ }
+ db.reviews.push(newReview)
+ return newReview
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/routes/member.route.ts" "b/\353\217\204\354\226\217/week4/src/routes/member.route.ts"
new file mode 100644
index 0000000..047749e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/routes/member.route.ts"
@@ -0,0 +1,10 @@
+import express from 'express'
+import { memberController } from '../controllers/member.controller.js'
+import { authMiddleware } from '../middleware/auth.middleware.js'
+
+export const memberRouter = express.Router()
+
+memberRouter.post('/', memberController.signUp)
+memberRouter.post('/login', memberController.login)
+memberRouter.get('/me', authMiddleware, memberController.getMyPage)
+memberRouter.get('/me/missions', authMiddleware, memberController.getMyMissions)
diff --git "a/\353\217\204\354\226\217/week4/src/routes/mission.route.ts" "b/\353\217\204\354\226\217/week4/src/routes/mission.route.ts"
new file mode 100644
index 0000000..e05c73e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/routes/mission.route.ts"
@@ -0,0 +1,7 @@
+import express from 'express'
+import { missionController } from '../controllers/mission.controller.js'
+import { authMiddleware } from '../middleware/auth.middleware.js'
+
+export const missionRouter = express.Router()
+
+missionRouter.post('/:missionId/challenges', authMiddleware, missionController.challengeMission)
diff --git "a/\353\217\204\354\226\217/week4/src/routes/store.route.ts" "b/\353\217\204\354\226\217/week4/src/routes/store.route.ts"
new file mode 100644
index 0000000..a239a4e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/routes/store.route.ts"
@@ -0,0 +1,7 @@
+import express from 'express'
+import { storeController } from '../controllers/store.controller.js'
+import { authMiddleware } from '../middleware/auth.middleware.js'
+
+export const storeRouter = express.Router()
+
+storeRouter.post('/:storeId/reviews', authMiddleware, storeController.createReview)
diff --git "a/\353\217\204\354\226\217/week4/src/services/member.service.ts" "b/\353\217\204\354\226\217/week4/src/services/member.service.ts"
new file mode 100644
index 0000000..8b81eca
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/services/member.service.ts"
@@ -0,0 +1,76 @@
+import bcrypt from 'bcryptjs'
+import jwt from 'jsonwebtoken'
+import { memberRepository } from '../repositories/member.repository.js'
+import type { MemberMission, PageResult } from '../types/index.js'
+
+const JWT_SECRET = process.env.JWT_SECRET ?? 'umc-week4-secret'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const memberService = {
+ signUp: async (body: Record) => {
+ const { name, nickname, email, password } = body as {
+ name?: string
+ nickname?: string
+ email?: string
+ password?: string
+ }
+
+ if (!name || !nickname || !email || !password) {
+ throw makeError('ํ์ ํญ๋ชฉ(name, nickname, email, password)์ ๋ชจ๋ ์
๋ ฅํด ์ฃผ์ธ์.', 400)
+ }
+
+ if (memberRepository.findByEmail(email)) {
+ throw makeError('์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.', 409)
+ }
+
+ const hashedPassword = await bcrypt.hash(password, 10)
+ const member = memberRepository.save({ name, nickname, email, password: hashedPassword })
+ const token = jwt.sign({ memberId: member.id }, JWT_SECRET, { expiresIn: '7d' })
+
+ return { memberId: member.id, name: member.name, nickname: member.nickname, token }
+ },
+
+ login: async (body: Record) => {
+ const { email, password } = body as { email?: string; password?: string }
+
+ if (!email || !password) {
+ throw makeError('์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.', 400)
+ }
+
+ const member = memberRepository.findByEmail(email)
+ if (!member) {
+ throw makeError('์ด๋ฉ์ผ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.', 401)
+ }
+
+ const isValid = await bcrypt.compare(password, member.password)
+ if (!isValid) {
+ throw makeError('์ด๋ฉ์ผ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.', 401)
+ }
+
+ const token = jwt.sign({ memberId: member.id }, JWT_SECRET, { expiresIn: '7d' })
+ return { memberId: member.id, name: member.name, nickname: member.nickname, token }
+ },
+
+ getMyPage: (memberId: number) => {
+ const member = memberRepository.findById(memberId)
+ if (!member) {
+ throw makeError('ํ์์ ์ฐพ์ ์ ์์ต๋๋ค.', 404)
+ }
+ const { password: _, ...safeData } = member
+ return safeData
+ },
+
+ getMyMissions: (
+ memberId: number,
+ status: string | undefined,
+ page: string | undefined,
+ size: string | undefined,
+ ): PageResult => {
+ return memberRepository.findMissionsByMemberId(memberId, status, page, size)
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/services/mission.service.ts" "b/\353\217\204\354\226\217/week4/src/services/mission.service.ts"
new file mode 100644
index 0000000..d9f0068
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/services/mission.service.ts"
@@ -0,0 +1,24 @@
+import { missionRepository } from '../repositories/mission.repository.js'
+import type { MemberMission } from '../types/index.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const missionService = {
+ challengeMission: (missionId: number, memberId: number): MemberMission => {
+ const mission = missionRepository.findById(missionId)
+ if (!mission) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๋ฏธ์
์
๋๋ค.', 404)
+ }
+
+ const existing = missionRepository.findMemberMission(memberId, missionId)
+ if (existing) {
+ throw makeError('์ด๋ฏธ ๋์ ์ค์ด๊ฑฐ๋ ์๋ฃํ ๋ฏธ์
์
๋๋ค.', 409)
+ }
+
+ return missionRepository.saveMemberMission({ memberId, missionId, status: 'CHALLENGING' })
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/services/store.service.ts" "b/\353\217\204\354\226\217/week4/src/services/store.service.ts"
new file mode 100644
index 0000000..94a740b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/services/store.service.ts"
@@ -0,0 +1,29 @@
+import { storeRepository } from '../repositories/store.repository.js'
+import type { Review } from '../types/index.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const storeService = {
+ createReview: (storeId: number, memberId: number, body: Record): Review => {
+ const { content, score } = body as { content?: string; score?: number }
+
+ if (!content || score === undefined) {
+ throw makeError('๋ด์ฉ(content)๊ณผ ๋ณ์ (score)์ ์
๋ ฅํด ์ฃผ์ธ์.', 400)
+ }
+
+ if (score < 1 || score > 5) {
+ throw makeError('๋ณ์ ์ 1~5 ์ฌ์ด์ฌ์ผ ํฉ๋๋ค.', 400)
+ }
+
+ const store = storeRepository.findById(storeId)
+ if (!store) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.', 404)
+ }
+
+ return storeRepository.saveReview({ storeId, memberId, content, score })
+ },
+}
diff --git "a/\353\217\204\354\226\217/week4/src/types/index.ts" "b/\353\217\204\354\226\217/week4/src/types/index.ts"
new file mode 100644
index 0000000..f128546
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/src/types/index.ts"
@@ -0,0 +1,57 @@
+export interface Member {
+ id: number
+ name: string
+ nickname: string
+ email: string
+ password: string
+ point: number
+ createdAt: string
+}
+
+export interface Store {
+ id: number
+ name: string
+ address: string
+}
+
+export interface Review {
+ id: number
+ storeId: number
+ memberId: number
+ content: string
+ score: number
+ createdAt: string
+}
+
+export interface Mission {
+ id: number
+ storeId: number
+ title: string
+ reward: number
+ deadline: string
+ missionSpec: string
+}
+
+export interface MemberMission {
+ id: number
+ memberId: number
+ missionId: number
+ status: 'CHALLENGING' | 'COMPLETE'
+ createdAt: string
+}
+
+export interface PageResult {
+ missions: T[]
+ totalPages: number
+ currentPage: number
+ isLast: boolean
+}
+
+// Express Request์ memberId ์ฃผ์
์ ์ํ ํ์
ํ์ฅ
+declare global {
+ namespace Express {
+ interface Request {
+ memberId?: number
+ }
+ }
+}
diff --git "a/\353\217\204\354\226\217/week4/tsconfig.json" "b/\353\217\204\354\226\217/week4/tsconfig.json"
new file mode 100644
index 0000000..1725aa8
--- /dev/null
+++ "b/\353\217\204\354\226\217/week4/tsconfig.json"
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "outDir": "dist",
+ "rootDir": "src"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git "a/\353\217\204\354\226\217/week5/.gitignore" "b/\353\217\204\354\226\217/week5/.gitignore"
new file mode 100644
index 0000000..81e6f7c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/.gitignore"
@@ -0,0 +1,19 @@
+# dependency directories
+node_modules/
+
+# build output
+dist/
+
+# dotenv environment variable files
+.env
+.env.local
+.env.development
+.env.production
+.env.*
+
+# macOS
+.DS_Store
+
+# logs
+*.log
+npm-debug.log*
diff --git "a/\353\217\204\354\226\217/week5/POSTMAN_GUIDE.md" "b/\353\217\204\354\226\217/week5/POSTMAN_GUIDE.md"
new file mode 100644
index 0000000..c77064e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/POSTMAN_GUIDE.md"
@@ -0,0 +1,327 @@
+# Postman API ํ
์คํธ ๊ฐ์ด๋ โ Week 5 UMC Mission Service
+
+## ๋ชฉ์ฐจ
+1. [Workspace & Collection ์์ฑ](#1-workspace--collection-์์ฑ)
+2. [ํ๊ฒฝ ๋ณ์ ์ค์ ](#2-ํ๊ฒฝ-๋ณ์-์ค์ )
+3. [API ์์ฒญ ๋ชฉ๋ก](#3-api-์์ฒญ-๋ชฉ๋ก)
+ - [์๋ฒ ์ํ ํ์ธ](#31-์๋ฒ-์ํ-ํ์ธ)
+ - [ํ์๊ฐ์
](#32-ํ์๊ฐ์
)
+ - [๊ฐ๊ฒ ๋ฑ๋ก](#33-๊ฐ๊ฒ-๋ฑ๋ก)
+ - [๋ฆฌ๋ทฐ ์์ฑ](#34-๋ฆฌ๋ทฐ-์์ฑ)
+ - [๋ฏธ์
์์ฑ](#35-๋ฏธ์
-์์ฑ)
+ - [๋ฏธ์
๋์ ](#36-๋ฏธ์
-๋์ )
+4. [์๋ฌ ์ผ์ด์ค ํ
์คํธ](#4-์๋ฌ-์ผ์ด์ค-ํ
์คํธ)
+5. [์๋ต ํ์ ์ ๋ฆฌ](#5-์๋ต-ํ์-์ ๋ฆฌ)
+
+---
+
+## 1. Workspace & Collection ์์ฑ
+
+1. Postman ์ผ์ชฝ ์๋จ **[Workspaces]** โ ๋ด ์์
๊ณต๊ฐ์ผ๋ก ์ด๋
+2. ์ผ์ชฝ ๋ฉ๋ด **[Collections]** ์ `+` ๋ฒํผ ํด๋ฆญ
+3. ์ด๋ฆ์ `UMC-Week5-Mission-Service`๋ก ๋ณ๊ฒฝ
+4. ํ์์ ํด๋๋ฅผ ๋ง๋ค์ด ๋๋ฉ์ธ๋ณ๋ก ์์ฒญ์ ๋ถ๋ฅํ๋ฉด ํธ๋ฆฌํฉ๋๋ค:
+ - `Members` โ ํ์ ๊ด๋ จ
+ - `Stores` โ ๊ฐ๊ฒ ๊ด๋ จ
+ - `Reviews` โ ๋ฆฌ๋ทฐ ๊ด๋ จ
+ - `Missions` โ ๋ฏธ์
๊ด๋ จ
+
+---
+
+## 2. ํ๊ฒฝ ๋ณ์ ์ค์
+
+์๋ฒ ์ฃผ์๋ฅผ ๋งค๋ฒ ์
๋ ฅํ์ง ์๋๋ก ํ๊ฒฝ ๋ณ์๋ฅผ ๋ฑ๋กํฉ๋๋ค.
+
+1. ์ผ์ชฝ ๋ฉ๋ด **[Environments]** โ `+` ๋ฒํผ ํด๋ฆญ
+2. ํ๊ฒฝ ์ด๋ฆ: `Local`
+3. ์๋ ๋ณ์ ๋ฑ๋ก ํ **Save**
+
+| Variable | Initial Value | ์ค๋ช
|
+|---|---|---|
+| `host` | `http://localhost:3000` | ์๋ฒ ์ฃผ์ (`.env`์ `PORT=3000` ๊ธฐ์ค) |
+
+4. ์ฐ์ธก ์๋จ ๋๋กญ๋ค์ด์์ **Local** ์ ํ
+
+> ์์ฒญ URL์์ `{{host}}`๋ก ์ฐธ์กฐํฉ๋๋ค. ์: `{{host}}/api/v1/members/signup`
+
+---
+
+## 3. API ์์ฒญ ๋ชฉ๋ก
+
+### 3.1 ์๋ฒ ์ํ ํ์ธ
+
+| ํญ๋ชฉ | ๋ด์ฉ |
+|---|---|
+| **Method** | `GET` |
+| **URL** | `{{host}}/` |
+| **Body** | ์์ |
+| **๊ธฐ๋ ์๋ต** | `200 OK` |
+
+**์๋ต ์์**
+```json
+"Hello World!"
+```
+
+---
+
+### 3.2 ํ์๊ฐ์
+
+| ํญ๋ชฉ | ๋ด์ฉ |
+|---|---|
+| **Method** | `POST` |
+| **URL** | `{{host}}/api/v1/members/signup` |
+| **Headers** | `Content-Type: application/json` |
+| **๊ธฐ๋ ์๋ต** | `201 Created` |
+
+**Request Body (raw โ JSON)**
+```json
+{
+ "name": "์ธ๋
",
+ "nickname": "unyeon",
+ "email": "unyeon@umc.com",
+ "password": "password123!",
+ "phoneNum": "010-1234-5678",
+ "birth": "2000-01-01",
+ "gender": "FEMALE",
+ "address": "์์ธํน๋ณ์ ๊ฐ๋จ๊ตฌ",
+ "specAddress": "101๋ 202ํธ"
+}
+```
+
+> `name`, `nickname`์ ํ์๊ฐ์
๋๋ค. ๋๋จธ์ง๋ ์ ํ ์ฌํญ์
๋๋ค.
+> `gender` ํ์ฉ๊ฐ: `"MALE"` | `"FEMALE"` | `"OTHER"`
+> `birth`, `deadLine` ๋ ์ง ํ์: `"YYYY-MM-DD"`
+
+**์ฑ๊ณต ์๋ต ์์**
+```json
+{
+ "success": true,
+ "data": {
+ "memberId": 1,
+ "name": "์ธ๋
",
+ "nickname": "unyeon",
+ "email": "unyeon@umc.com",
+ "phoneNum": "010-1234-5678",
+ "status": "ACTIVE"
+ }
+}
+```
+
+---
+
+### 3.3 ๊ฐ๊ฒ ๋ฑ๋ก
+
+| ํญ๋ชฉ | ๋ด์ฉ |
+|---|---|
+| **Method** | `POST` |
+| **URL** | `{{host}}/api/v1/stores` |
+| **Headers** | `Content-Type: application/json` |
+| **๊ธฐ๋ ์๋ต** | `201 Created` |
+
+**Request Body (raw โ JSON)**
+```json
+{
+ "regionId": 1,
+ "foodCategoryId": 1,
+ "name": "๋ง์๋ ์นํจ์ง",
+ "description": "๋ฐ์ญํ๊ณ ๋ง์๋ ์นํจ์ ํ๋งคํฉ๋๋ค.",
+ "address": "์์ธํน๋ณ์ ๋งํฌ๊ตฌ ํ๋์
๊ตฌ์ญ 1๋ฒ ์ถ๊ตฌ",
+ "lat": 37.5563,
+ "lng": 126.9239
+}
+```
+
+> `regionId`, `foodCategoryId`, `name`, `address`๋ ํ์๊ฐ์
๋๋ค.
+> `regionId`์ `foodCategoryId`๋ DB์ ๋ฏธ๋ฆฌ ์กด์ฌํ๋ ๊ฐ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
+
+**์ฑ๊ณต ์๋ต ์์**
+```json
+{
+ "success": true,
+ "data": {
+ "storeId": 1,
+ "name": "๋ง์๋ ์นํจ์ง",
+ "address": "์์ธํน๋ณ์ ๋งํฌ๊ตฌ ํ๋์
๊ตฌ์ญ 1๋ฒ ์ถ๊ตฌ",
+ "regionId": 1
+ }
+}
+```
+
+---
+
+### 3.4 ๋ฆฌ๋ทฐ ์์ฑ
+
+| ํญ๋ชฉ | ๋ด์ฉ |
+|---|---|
+| **Method** | `POST` |
+| **URL** | `{{host}}/api/v1/stores/:storeId/reviews` |
+| **URL ์์** | `{{host}}/api/v1/stores/1/reviews` |
+| **Headers** | `Content-Type: application/json` |
+| **๊ธฐ๋ ์๋ต** | `201 Created` |
+
+**Request Body (raw โ JSON)**
+```json
+{
+ "memberId": 1,
+ "content": "์นํจ์ด ์ ๋ง ๋ฐ์ญํ๊ณ ๋ง์์์ด์! ๋ ๋ฐฉ๋ฌธํ ๊ฒ ๊ฐ์์.",
+ "score": 5
+}
+```
+
+> `memberId`, `content`, `score`๋ ๋ชจ๋ ํ์๊ฐ์
๋๋ค.
+> `score` ํ์ฉ ๋ฒ์: `1` ~ `5` (์ ์)
+
+**์ฑ๊ณต ์๋ต ์์**
+```json
+{
+ "success": true,
+ "data": {
+ "reviewId": 1,
+ "memberId": 1,
+ "storeId": 1,
+ "content": "์นํจ์ด ์ ๋ง ๋ฐ์ญํ๊ณ ๋ง์์์ด์! ๋ ๋ฐฉ๋ฌธํ ๊ฒ ๊ฐ์์.",
+ "score": 5,
+ "createdAt": "2026-04-18T12:00:00.000Z"
+ }
+}
+```
+
+---
+
+### 3.5 ๋ฏธ์
์์ฑ
+
+| ํญ๋ชฉ | ๋ด์ฉ |
+|---|---|
+| **Method** | `POST` |
+| **URL** | `{{host}}/api/v1/stores/:storeId/missions` |
+| **URL ์์** | `{{host}}/api/v1/stores/1/missions` |
+| **Headers** | `Content-Type: application/json` |
+| **๊ธฐ๋ ์๋ต** | `201 Created` |
+
+**Request Body (raw โ JSON)**
+```json
+{
+ "title": "์นํจ 3๋ฒ ์ฃผ๋ฌธํ๊ธฐ",
+ "reward": 500,
+ "spec": "ํ ๋ฌ ๋ด์ ์นํจ์ 3๋ฒ ์ฃผ๋ฌธํ๋ฉด 500ํฌ์ธํธ ์ ๋ฆฝ!",
+ "deadLine": "2026-05-31"
+}
+```
+
+> `title`, `reward`๋ ํ์๊ฐ์
๋๋ค.
+> `reward`๋ ์ง๊ธํ ํฌ์ธํธ ์๋์
๋๋ค.
+
+**์ฑ๊ณต ์๋ต ์์**
+```json
+{
+ "success": true,
+ "data": {
+ "missionId": 1,
+ "storeId": 1,
+ "title": "์นํจ 3๋ฒ ์ฃผ๋ฌธํ๊ธฐ",
+ "reward": 500,
+ "spec": "ํ ๋ฌ ๋ด์ ์นํจ์ 3๋ฒ ์ฃผ๋ฌธํ๋ฉด 500ํฌ์ธํธ ์ ๋ฆฝ!",
+ "deadLine": "2026-05-31T00:00:00.000Z"
+ }
+}
+```
+
+---
+
+### 3.6 ๋ฏธ์
๋์
+
+| ํญ๋ชฉ | ๋ด์ฉ |
+|---|---|
+| **Method** | `POST` |
+| **URL** | `{{host}}/api/v1/missions/:missionId/challenge` |
+| **URL ์์** | `{{host}}/api/v1/missions/1/challenge` |
+| **Headers** | `Content-Type: application/json` |
+| **๊ธฐ๋ ์๋ต** | `201 Created` |
+
+**Request Body (raw โ JSON)**
+```json
+{
+ "memberId": 1
+}
+```
+
+**์ฑ๊ณต ์๋ต ์์**
+```json
+{
+ "success": true,
+ "data": {
+ "memberMissionId": 1,
+ "memberId": 1,
+ "missionId": 1,
+ "status": "CHALLENGING"
+ }
+}
+```
+
+---
+
+## 4. ์๋ฌ ์ผ์ด์ค ํ
์คํธ
+
+๊ฐ ์์ฒญ์ **๋ณต์ (Duplicate)** ํด์ ์๋ฌ ์ผ์ด์ค ์ ์ฉ ์์ฒญ์ผ๋ก ์ ์ฅํด ๋๋ฉด ์ข์ต๋๋ค.
+
+### ํ์๊ฐ์
์๋ฌ
+
+| ์ผ์ด์ค | ๋ฐฉ๋ฒ | ๊ธฐ๋ ์๋ต |
+|---|---|---|
+| ์ด๋ฉ์ผ ์ค๋ณต | ๋์ผํ `email`๋ก ๋ ๋ฒ ์์ฒญ | `409 Conflict` |
+| ํ์๊ฐ ๋๋ฝ | `name` ๋๋ `nickname` ์ ๊ฑฐ | `400 Bad Request` ๋๋ DB ์๋ฌ |
+
+### ๋ฆฌ๋ทฐ ์์ฑ ์๋ฌ
+
+| ์ผ์ด์ค | ๋ฐฉ๋ฒ | ๊ธฐ๋ ์๋ต |
+|---|---|---|
+| ์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ | URL์ `storeId`๋ฅผ `99999`๋ก ๋ณ๊ฒฝ | `404 Not Found` |
+| ์ ์ ๋ฒ์ ์ด๊ณผ | `"score": 6` ๋๋ `"score": 0` | `400 Bad Request` |
+
+### ๋ฏธ์
๋์ ์๋ฌ
+
+| ์ผ์ด์ค | ๋ฐฉ๋ฒ | ๊ธฐ๋ ์๋ต |
+|---|---|---|
+| ์กด์ฌํ์ง ์๋ ๋ฏธ์
| URL์ `missionId`๋ฅผ `99999`๋ก ๋ณ๊ฒฝ | `404 Not Found` |
+| ์ค๋ณต ๋์ | ๋์ผํ `memberId`๋ก ๊ฐ์ ๋ฏธ์
์ ๋ ๋ฒ ์์ฒญ | `409 Conflict` |
+
+### ๋ฏธ์
/๋ฆฌ๋ทฐ ๊ณตํต ์๋ฌ
+
+| ์ผ์ด์ค | ๋ฐฉ๋ฒ | ๊ธฐ๋ ์๋ต |
+|---|---|---|
+| ์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์ ๋ฏธ์
์์ฑ | URL์ `storeId`๋ฅผ `99999`๋ก ๋ณ๊ฒฝ | `404 Not Found` |
+
+---
+
+## 5. ์๋ต ํ์ ์ ๋ฆฌ
+
+๋ชจ๋ API๋ ์๋ ๋ ๊ฐ์ง ํ์ ์ค ํ๋๋ก ์๋ตํฉ๋๋ค.
+
+**์ฑ๊ณต ์**
+```json
+{
+ "success": true,
+ "data": { ... }
+}
+```
+
+**์คํจ ์**
+```json
+{
+ "success": false,
+ "code": "E404",
+ "message": "์๋ฌ ๋ฉ์์ง"
+}
+```
+
+| ์ํ ์ฝ๋ | ์ฝ๋ ํ์ | ์ํฉ |
+|---|---|---|
+| `201 Created` | โ | ๋ฆฌ์์ค ์์ฑ ์ฑ๊ณต |
+| `400 Bad Request` | `E400` | ์๋ชป๋ ์
๋ ฅ๊ฐ (์: score ๋ฒ์ ์ด๊ณผ) |
+| `404 Not Found` | `E404` | ์กด์ฌํ์ง ์๋ ๋ฆฌ์์ค |
+| `409 Conflict` | `E409` | ์ค๋ณต ๋ฐ์ดํฐ (์ด๋ฉ์ผ, ๋ฏธ์
์ค๋ณต ๋์ ) |
+| `500 Internal Server Error` | `E500` | ์๋ฒ/DB ์ค๋ฅ |
+
+---
+
+> **์๋ต ์ ์ฅ ํ:** ์ฑ๊ณต ์๋ต์ด ์์ ๋ ์๋ต์ฐฝ ์ฐ์ธก **[Save Response]** โ **[Save as example]** ์ ํด๋ฆญํ๋ฉด Postman Documentation ํญ์ ์์ ์๋ต์ด ์๋์ผ๋ก ๊ธฐ๋ก๋ฉ๋๋ค. ์๋ฌ ์ผ์ด์ค๋ ํจ๊ป ์ ์ฅํด๋๋ฉด ํ์๊ณผ ๊ณต์ ํ๊ธฐ ํธ๋ฆฌํฉ๋๋ค.
diff --git "a/\353\217\204\354\226\217/week5/package-lock.json" "b/\353\217\204\354\226\217/week5/package-lock.json"
new file mode 100644
index 0000000..08b27e8
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/package-lock.json"
@@ -0,0 +1,2077 @@
+{
+ "name": "umc-week5",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "umc-week5",
+ "version": "1.0.0",
+ "dependencies": {
+ "bcryptjs": "^2.4.3",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.5",
+ "express": "^4.19.2",
+ "http-status-codes": "^2.3.0",
+ "mysql2": "^3.9.7"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cors": "^2.8.17",
+ "@types/dotenv": "^8.2.0",
+ "@types/express": "^4.17.21",
+ "@types/node": "^22.0.0",
+ "nodemon": "^3.1.0",
+ "tsx": "^4.19.3",
+ "typescript": "^5.8.3"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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/dotenv": {
+ "version": "8.2.3",
+ "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.3.tgz",
+ "integrity": "sha512-g2FXjlDX/cYuc5CiQvyU/6kkbP1JtmGzh0obW50zD7OKeILVL0NSpPWLXVfqoAGQjom2/SLLx9zHq0KXvD6mbw==",
+ "deprecated": "This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dotenv": "*"
+ }
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "^1"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.8",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz",
+ "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "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/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/node": {
+ "version": "22.19.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
+ "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "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": {
+ "@types/node": "*"
+ }
+ },
+ "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": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/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/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/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "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.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "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": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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/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/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.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "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/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "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.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "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.14.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/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.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "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.2",
+ "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/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "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/get-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-status-codes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+ "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": {
+ "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/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/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": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/mysql2": {
+ "version": "3.22.1",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.1.tgz",
+ "integrity": "sha512-48+9UXehKyxxiP2pqCxUq+MSFvX+v41jwsSpFDQO/jAoFuAELutBGJUhWJnDbe82/OBlIhSBMC82WeonmznT/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.2",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.2",
+ "long": "^5.3.2",
+ "lru.min": "^1.1.4",
+ "named-placeholders": "^1.1.6",
+ "sql-escaper": "^1.3.3"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 8"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.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.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "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/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.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "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.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "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/sql-escaper": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz",
+ "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=2.0.0",
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/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/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "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/typescript": {
+ "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": {
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "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/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/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"
+ }
+ }
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/package.json" "b/\353\217\204\354\226\217/week5/package.json"
new file mode 100644
index 0000000..69d2ef2
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/package.json"
@@ -0,0 +1,31 @@
+{
+ "name": "umc-week5",
+ "version": "1.0.0",
+ "description": "UMC 5์ฃผ์ฐจ - Express + TypeScript + MySQL API ์๋ฒ",
+ "main": "src/index.ts",
+ "type": "module",
+ "scripts": {
+ "start": "tsx src/index.ts",
+ "dev": "nodemon --exec tsx src/index.ts",
+ "build": "tsc",
+ "start:prod": "node dist/index.js"
+ },
+ "dependencies": {
+ "bcryptjs": "^2.4.3",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.5",
+ "express": "^4.19.2",
+ "http-status-codes": "^2.3.0",
+ "mysql2": "^3.9.7"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cors": "^2.8.17",
+ "@types/dotenv": "^8.2.0",
+ "@types/express": "^4.17.21",
+ "@types/node": "^22.0.0",
+ "nodemon": "^3.1.0",
+ "tsx": "^4.19.3",
+ "typescript": "^5.8.3"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/reset_db.sql" "b/\353\217\204\354\226\217/week5/reset_db.sql"
new file mode 100644
index 0000000..454f20b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/reset_db.sql"
@@ -0,0 +1,215 @@
+-- ============================================================
+-- DB ์ด๊ธฐํ ๋ฐ ์ฌ์์ฑ ์คํฌ๋ฆฝํธ
+-- ============================================================
+
+CREATE DATABASE IF NOT EXISTS umc_mission DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE umc_mission;
+
+-- FK ์ฒดํฌ ๋นํ์ฑํ ํ ์ ์ฒด DROP
+SET FOREIGN_KEY_CHECKS = 0;
+
+DROP TABLE IF EXISTS review_image;
+DROP TABLE IF EXISTS review;
+DROP TABLE IF EXISTS member_mission;
+DROP TABLE IF EXISTS mission;
+DROP TABLE IF EXISTS store_hours;
+DROP TABLE IF EXISTS store_image;
+DROP TABLE IF EXISTS store;
+DROP TABLE IF EXISTS member_prefer;
+DROP TABLE IF EXISTS member_agree;
+DROP TABLE IF EXISTS member;
+DROP TABLE IF EXISTS terms;
+DROP TABLE IF EXISTS food_category;
+DROP TABLE IF EXISTS region;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- ============================================================
+-- ํ
์ด๋ธ ์ฌ์์ฑ
+-- ============================================================
+
+CREATE TABLE region (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์ง์ญ๋ช
',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE food_category (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์นดํ
๊ณ ๋ฆฌ๋ช
(ํ์, ์ค์, ์ผ์ ๋ฑ)',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE terms (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ title VARCHAR(100) NOT NULL COMMENT '์ฝ๊ด ์ ๋ชฉ',
+ content TEXT NOT NULL COMMENT '์ฝ๊ด ๋ด์ฉ',
+ optional BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'TRUE: ์ ํ ๋์, FALSE: ํ์ ๋์',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE member (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ social_type VARCHAR(20) NULL,
+ social_id VARCHAR(100) NULL,
+ email VARCHAR(100) NULL,
+ password VARCHAR(255) NULL,
+ name VARCHAR(50) NOT NULL,
+ nickname VARCHAR(50) NOT NULL,
+ profile_image_url VARCHAR(500) NULL,
+ phone_num VARCHAR(20) NULL,
+ phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
+ birth DATE NULL,
+ gender ENUM('MALE', 'FEMALE', 'OTHER') NULL,
+ address VARCHAR(200) NULL,
+ spec_address VARCHAR(200) NULL,
+ point INT NOT NULL DEFAULT 0,
+ status ENUM('ACTIVE', 'INACTIVE', 'BANNED') NOT NULL DEFAULT 'ACTIVE',
+ inactive_date DATETIME NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_email (email),
+ UNIQUE KEY uq_member_social (social_type, social_id)
+);
+
+CREATE TABLE member_agree (
+ member_id BIGINT NOT NULL,
+ terms_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, terms_id),
+ CONSTRAINT fk_member_agree_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_agree_terms FOREIGN KEY (terms_id) REFERENCES terms (id)
+);
+
+CREATE TABLE member_prefer (
+ member_id BIGINT NOT NULL,
+ food_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, food_id),
+ CONSTRAINT fk_member_prefer_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_prefer_food FOREIGN KEY (food_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ region_id BIGINT NOT NULL,
+ food_category_id BIGINT NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ description TEXT NULL,
+ lat DECIMAL(10,7) NULL,
+ lng DECIMAL(10,7) NULL,
+ address VARCHAR(200) NOT NULL,
+ status ENUM('OPEN', 'CLOSED', 'PENDING') NOT NULL DEFAULT 'OPEN',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_region FOREIGN KEY (region_id) REFERENCES region (id),
+ CONSTRAINT fk_store_category FOREIGN KEY (food_category_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_image_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE store_hours (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ day_of_week VARCHAR(3) NOT NULL,
+ open_time TIME NOT NULL,
+ close_time TIME NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_store_hours_day (store_id, day_of_week),
+ CONSTRAINT fk_store_hours_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ title VARCHAR(200) NOT NULL,
+ reward INT NOT NULL DEFAULT 0,
+ spec VARCHAR(500) NULL,
+ dead_line DATE NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_mission_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE member_mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ mission_id BIGINT NOT NULL,
+ status ENUM('CHALLENGING', 'COMPLETE') NOT NULL DEFAULT 'CHALLENGING',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_mission (member_id, mission_id),
+ CONSTRAINT fk_member_mission_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_mission_mission FOREIGN KEY (mission_id) REFERENCES mission (id)
+);
+
+CREATE TABLE review (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ store_id BIGINT NOT NULL,
+ member_mission_id BIGINT NULL,
+ content TEXT NOT NULL,
+ score DECIMAL(2,1) NOT NULL,
+ owner_reply VARCHAR(500) NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_review_store FOREIGN KEY (store_id) REFERENCES store (id),
+ CONSTRAINT fk_review_member_mission FOREIGN KEY (member_mission_id) REFERENCES member_mission (id),
+ CONSTRAINT chk_review_score CHECK (score BETWEEN 1.0 AND 5.0)
+);
+
+CREATE TABLE review_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ review_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_image_review FOREIGN KEY (review_id) REFERENCES review (id)
+);
+
+-- ============================================================
+-- ์๋ ๋ฐ์ดํฐ (API ํ
์คํธ์ฉ ์ด๊ธฐ ๋ฐ์ดํฐ)
+-- ============================================================
+
+INSERT INTO region (name) VALUES
+ ('์์ธ'),
+ ('๊ฒฝ๊ธฐ'),
+ ('์ธ์ฒ'),
+ ('๋ถ์ฐ'),
+ ('๋๊ตฌ');
+
+INSERT INTO food_category (name) VALUES
+ ('ํ์'),
+ ('์ค์'),
+ ('์ผ์'),
+ ('์์'),
+ ('๋ถ์'),
+ ('์นดํ/๋์ ํธ'),
+ ('์นํจ'),
+ ('ํผ์'),
+ ('ํจ์คํธํธ๋');
diff --git "a/\353\217\204\354\226\217/week5/src/db.config.ts" "b/\353\217\204\354\226\217/week5/src/db.config.ts"
new file mode 100644
index 0000000..a9c5115
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/db.config.ts"
@@ -0,0 +1,15 @@
+import mysql from 'mysql2/promise'
+import dotenv from 'dotenv'
+
+dotenv.config()
+
+export const pool = mysql.createPool({
+ host: process.env.DB_HOST ?? 'localhost',
+ user: process.env.DB_USER ?? 'root',
+ port: parseInt(process.env.DB_PORT ?? '3306'),
+ database: process.env.DB_NAME ?? 'umc_mission',
+ password: process.env.DB_PASSWORD ?? '',
+ waitForConnections: true,
+ connectionLimit: 10,
+ queueLimit: 0,
+})
diff --git "a/\353\217\204\354\226\217/week5/src/index.ts" "b/\353\217\204\354\226\217/week5/src/index.ts"
new file mode 100644
index 0000000..4cc7f93
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/index.ts"
@@ -0,0 +1,47 @@
+import dotenv from 'dotenv'
+import express, { Express, Request, Response } from 'express'
+import cors from 'cors'
+
+// ์ปจํธ๋กค๋ฌ import
+import { handleCreateStore } from './modules/stores/controllers/store.controller.js'
+import { handleCreateReview } from './modules/reviews/controllers/review.controller.js'
+import { handleCreateMission, handleChallengeMission } from './modules/missions/controllers/mission.controller.js'
+import { handleSignUp } from './modules/members/controllers/member.controller.js'
+
+// ์๋ฌ ๋ฏธ๋ค์จ์ด import
+import { errorMiddleware } from './middleware/error.middleware.js'
+
+// 1. ํ๊ฒฝ ๋ณ์ ์ค์ (๊ฐ์ฅ ๋จผ์ ํธ์ถ)
+dotenv.config()
+
+const app: Express = express()
+const port = process.env.PORT ?? 3000
+
+// 2. ๋ฏธ๋ค์จ์ด ์ค์
+app.use(cors())
+app.use(express.json())
+app.use(express.urlencoded({ extended: false }))
+
+// 3. ๋ผ์ฐํฐ ๋ฑ๋ก
+app.get('/', (_req: Request, res: Response) => {
+ res.send('UMC 5์ฃผ์ฐจ ์๋ฒ ์คํ ์ค!')
+})
+
+// ํ์
+app.post('/api/v1/members/signup', handleSignUp)
+
+// ๊ฐ๊ฒ
+app.post('/api/v1/stores', handleCreateStore)
+app.post('/api/v1/stores/:storeId/reviews', handleCreateReview)
+app.post('/api/v1/stores/:storeId/missions', handleCreateMission)
+
+// ๋ฏธ์
+app.post('/api/v1/missions/:missionId/challenge', handleChallengeMission)
+
+// 4. ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ (๋ฐ๋์ ๋ผ์ฐํฐ ๋ฑ๋ก ์ดํ์ ์์น)
+app.use(errorMiddleware)
+
+// 5. ์๋ฒ ์์
+app.listen(port, () => {
+ console.log(`[server]: Server is running at http://localhost:${port}`)
+})
diff --git "a/\353\217\204\354\226\217/week5/src/middleware/error.middleware.ts" "b/\353\217\204\354\226\217/week5/src/middleware/error.middleware.ts"
new file mode 100644
index 0000000..ec8849d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/middleware/error.middleware.ts"
@@ -0,0 +1,21 @@
+import { Request, Response, NextFunction } from 'express'
+
+interface AppError extends Error {
+ status?: number
+}
+
+export const errorMiddleware = (
+ err: AppError,
+ _req: Request,
+ res: Response,
+ _next: NextFunction,
+): void => {
+ console.error(`[Error] ${err.message}`)
+
+ const status = err.status ?? 500
+ res.status(status).json({
+ success: false,
+ code: `E${status}`,
+ message: err.message ?? '์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.',
+ })
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/members/controllers/member.controller.ts" "b/\353\217\204\354\226\217/week5/src/modules/members/controllers/member.controller.ts"
new file mode 100644
index 0000000..3494ab7
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/members/controllers/member.controller.ts"
@@ -0,0 +1,18 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { MemberSignUpRequest } from '../dtos/member.dto.js'
+import { signUp } from '../services/member.service.js'
+
+// POST /api/v1/members/signup
+export const handleSignUp = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const result = await signUp(req.body as MemberSignUpRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/members/dtos/member.dto.ts" "b/\353\217\204\354\226\217/week5/src/modules/members/dtos/member.dto.ts"
new file mode 100644
index 0000000..b0cbf3f
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/members/dtos/member.dto.ts"
@@ -0,0 +1,45 @@
+// ํ์๊ฐ์
์์ฒญ ์ธํฐํ์ด์ค
+export interface MemberSignUpRequest {
+ name: string
+ nickname: string
+ email?: string
+ password?: string
+ phoneNum?: string
+ birth?: string // "YYYY-MM-DD"
+ gender?: string // "MALE" | "FEMALE" | "OTHER"
+ address?: string
+ specAddress?: string
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToMember = (body: MemberSignUpRequest) => {
+ return {
+ name: body.name,
+ nickname: body.nickname,
+ email: body.email ?? null,
+ phoneNum: body.phoneNum ?? null,
+ birth: body.birth ? new Date(body.birth) : null,
+ gender: body.gender ?? null,
+ address: body.address ?? null,
+ specAddress: body.specAddress ?? null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromMember = (member: {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+}) => {
+ return {
+ memberId: member.id,
+ name: member.name,
+ nickname: member.nickname,
+ email: member.email,
+ phoneNum: member.phone_num,
+ status: member.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/members/repositories/member.repository.ts" "b/\353\217\204\354\226\217/week5/src/modules/members/repositories/member.repository.ts"
new file mode 100644
index 0000000..a6c73b1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/members/repositories/member.repository.ts"
@@ -0,0 +1,72 @@
+import { ResultSetHeader, RowDataPacket } from 'mysql2'
+import { pool } from '../../../db.config.js'
+
+// ์ด๋ฉ์ผ ์ค๋ณต ํ์ธ
+export const findMemberByEmail = async (email: string): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ 'SELECT id FROM member WHERE email = ?',
+ [email],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ํ์ ์ถ๊ฐ
+export const addMember = async (data: {
+ name: string
+ nickname: string
+ email: string | null
+ hashedPassword: string | null
+ phoneNum: string | null
+ birth: Date | null
+ gender: string | null
+ address: string | null
+ specAddress: string | null
+}): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [result] = await conn.query(
+ `INSERT INTO member
+ (name, nickname, email, password, phone_num, birth, gender, address, spec_address)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ [
+ data.name,
+ data.nickname,
+ data.email,
+ data.hashedPassword,
+ data.phoneNum,
+ data.birth,
+ data.gender,
+ data.address,
+ data.specAddress,
+ ],
+ )
+ return result.insertId
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ํ์ ์กฐํ
+export const getMemberById = async (memberId: number): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ 'SELECT * FROM member WHERE id = ?',
+ [memberId],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/members/services/member.service.ts" "b/\353\217\204\354\226\217/week5/src/modules/members/services/member.service.ts"
new file mode 100644
index 0000000..ed1b3ad
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/members/services/member.service.ts"
@@ -0,0 +1,50 @@
+import bcrypt from 'bcryptjs'
+import { MemberSignUpRequest, bodyToMember, responseFromMember } from '../dtos/member.dto.js'
+import {
+ findMemberByEmail,
+ addMember,
+ getMemberById,
+} from '../repositories/member.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const signUp = async (data: MemberSignUpRequest) => {
+ // ํ์๊ฐ ๊ฒ์ฆ
+ if (!data.name || !data.nickname) {
+ throw makeError('name๊ณผ nickname์ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค.', 400)
+ }
+
+ // ์ด๋ฉ์ผ ์ค๋ณต ๊ฒ์ฆ
+ if (data.email) {
+ const existing = await findMemberByEmail(data.email)
+ if (existing) {
+ throw makeError('์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์
๋๋ค.', 409)
+ }
+ }
+
+ // ๋น๋ฐ๋ฒํธ ํด์ฑ (password๊ฐ ์๋ ๊ฒฝ์ฐ์๋ง)
+ const hashedPassword = data.password
+ ? await bcrypt.hash(data.password, 10)
+ : null
+
+ const memberData = bodyToMember(data)
+ const memberId = await addMember({ ...memberData, hashedPassword })
+
+ const member = await getMemberById(memberId)
+ if (!member) {
+ throw makeError('ํ์ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromMember(member as {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/missions/controllers/mission.controller.ts" "b/\353\217\204\354\226\217/week5/src/modules/missions/controllers/mission.controller.ts"
new file mode 100644
index 0000000..aa93baa
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/missions/controllers/mission.controller.ts"
@@ -0,0 +1,34 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { MissionCreateRequest, MissionChallengeRequest } from '../dtos/mission.dto.js'
+import { createMission, challengeMission } from '../services/mission.service.js'
+
+// POST /api/v1/stores/:storeId/missions
+export const handleCreateMission = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const storeId = parseInt(req.params['storeId'] ?? '0', 10)
+ const result = await createMission(storeId, req.body as MissionCreateRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
+
+// POST /api/v1/missions/:missionId/challenge
+export const handleChallengeMission = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const missionId = parseInt(req.params['missionId'] ?? '0', 10)
+ const result = await challengeMission(missionId, req.body as MissionChallengeRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/missions/dtos/mission.dto.ts" "b/\353\217\204\354\226\217/week5/src/modules/missions/dtos/mission.dto.ts"
new file mode 100644
index 0000000..8786670
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/missions/dtos/mission.dto.ts"
@@ -0,0 +1,57 @@
+// ๋ฏธ์
์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionCreateRequest {
+ title: string
+ reward: number
+ spec?: string
+ deadLine?: string // "YYYY-MM-DD"
+}
+
+// ๋ฏธ์
๋์ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionChallengeRequest {
+ memberId: number
+ status: 'CHALLENGING' | 'COMPLETE'
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ (๋ฏธ์
์ถ๊ฐ)
+export const bodyToMission = (body: MissionCreateRequest) => {
+ return {
+ title: body.title,
+ reward: body.reward,
+ spec: body.spec ?? null,
+ deadLine: body.deadLine ? new Date(body.deadLine) : null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
)
+export const responseFromMission = (mission: {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+}) => {
+ return {
+ missionId: mission.id,
+ storeId: mission.store_id,
+ title: mission.title,
+ reward: mission.reward,
+ spec: mission.spec,
+ deadLine: mission.dead_line,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
๋์ )
+export const responseFromMemberMission = (mm: {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+}) => {
+ return {
+ memberMissionId: mm.id,
+ memberId: mm.member_id,
+ missionId: mm.mission_id,
+ status: mm.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/missions/repositories/mission.repository.ts" "b/\353\217\204\354\226\217/week5/src/modules/missions/repositories/mission.repository.ts"
new file mode 100644
index 0000000..65a6e13
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/missions/repositories/mission.repository.ts"
@@ -0,0 +1,100 @@
+import { ResultSetHeader, RowDataPacket } from 'mysql2'
+import { pool } from '../../../db.config.js'
+
+// ๋ฏธ์
์ถ๊ฐ
+export const addMission = async (data: {
+ storeId: number
+ title: string
+ reward: number
+ spec: string | null
+ deadLine: Date | null
+}): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [result] = await conn.query(
+ `INSERT INTO mission (store_id, title, reward, spec, dead_line)
+ VALUES (?, ?, ?, ?, ?)`,
+ [data.storeId, data.title, data.reward, data.spec, data.deadLine],
+ )
+ return result.insertId
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ๋ฏธ์
์กฐํ
+export const getMissionById = async (missionId: number): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ 'SELECT * FROM mission WHERE id = ?',
+ [missionId],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์ธ์ง ํ์ธ
+export const findMemberMission = async (
+ memberId: number,
+ missionId: number,
+): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ `SELECT * FROM member_mission
+ WHERE member_id = ? AND mission_id = ?`,
+ [memberId, missionId],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ๋ฏธ์
๋์ ์ถ๊ฐ
+export const addMemberMission = async (
+ memberId: number,
+ missionId: number,
+ status: string,
+): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [result] = await conn.query(
+ `INSERT INTO member_mission (member_id, mission_id, status)
+ VALUES (?, ?, ?)`,
+ [memberId, missionId, status],
+ )
+ return result.insertId
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ๋ฐฉ๊ธ ์ถ๊ฐํ ๋ฏธ์
๋์ ๊ธฐ๋ก ์กฐํ
+export const getMemberMissionById = async (
+ memberMissionId: number,
+): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ 'SELECT * FROM member_mission WHERE id = ?',
+ [memberMissionId],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/missions/services/mission.service.ts" "b/\353\217\204\354\226\217/week5/src/modules/missions/services/mission.service.ts"
new file mode 100644
index 0000000..65869d4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/missions/services/mission.service.ts"
@@ -0,0 +1,84 @@
+import {
+ MissionCreateRequest,
+ MissionChallengeRequest,
+ bodyToMission,
+ responseFromMission,
+ responseFromMemberMission,
+} from '../dtos/mission.dto.js'
+import {
+ addMission,
+ getMissionById,
+ findMemberMission,
+ addMemberMission,
+ getMemberMissionById,
+} from '../repositories/mission.repository.js'
+import { findStoreById } from '../../stores/repositories/store.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+// ๋ฏธ์
์ถ๊ฐ
+export const createMission = async (storeId: number, data: MissionCreateRequest) => {
+ // ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ
+ const store = await findStoreById(storeId)
+ if (!store) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.', 404)
+ }
+
+ const missionData = bodyToMission(data)
+ const missionId = await addMission({ ...missionData, storeId })
+
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw makeError('๋ฏธ์
์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromMission(mission as {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+ })
+}
+
+// ๋ฏธ์
๋์ ํ๊ธฐ
+export const challengeMission = async (
+ missionId: number,
+ data: MissionChallengeRequest,
+) => {
+ // ๋ฏธ์
์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๋ฏธ์
์
๋๋ค.', 404)
+ }
+
+ // ํ์๊ฐ ๊ฒ์ฆ
+ if (!data.status) {
+ throw makeError('status๋ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค. (CHALLENGING ๋๋ COMPLETE)', 400)
+ }
+
+ // ์ด๋ฏธ ๋์ ์ค์ธ์ง ๊ฒ์ฆ
+ const existing = await findMemberMission(data.memberId, missionId)
+ if (existing) {
+ throw makeError('์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.', 409)
+ }
+
+ const memberMissionId = await addMemberMission(data.memberId, missionId, data.status)
+
+ const memberMission = await getMemberMissionById(memberMissionId)
+ if (!memberMission) {
+ throw makeError('๋ฏธ์
๋์ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromMemberMission(memberMission as {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/reviews/controllers/review.controller.ts" "b/\353\217\204\354\226\217/week5/src/modules/reviews/controllers/review.controller.ts"
new file mode 100644
index 0000000..8828e3c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/reviews/controllers/review.controller.ts"
@@ -0,0 +1,19 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { ReviewCreateRequest } from '../dtos/review.dto.js'
+import { createReview } from '../services/review.service.js'
+
+// POST /api/v1/stores/:storeId/reviews
+export const handleCreateReview = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const storeId = parseInt(req.params['storeId'] ?? '0', 10)
+ const result = await createReview(storeId, req.body as ReviewCreateRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/reviews/dtos/review.dto.ts" "b/\353\217\204\354\226\217/week5/src/modules/reviews/dtos/review.dto.ts"
new file mode 100644
index 0000000..0009736
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/reviews/dtos/review.dto.ts"
@@ -0,0 +1,34 @@
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface ReviewCreateRequest {
+ memberId: number
+ content: string
+ score: number
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToReview = (body: ReviewCreateRequest) => {
+ return {
+ memberId: body.memberId,
+ content: body.content,
+ score: body.score,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromReview = (review: {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+}) => {
+ return {
+ reviewId: review.id,
+ memberId: review.member_id,
+ storeId: review.store_id,
+ content: review.content,
+ score: review.score,
+ createdAt: review.created_at,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/reviews/repositories/review.repository.ts" "b/\353\217\204\354\226\217/week5/src/modules/reviews/repositories/review.repository.ts"
new file mode 100644
index 0000000..618fb02
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/reviews/repositories/review.repository.ts"
@@ -0,0 +1,40 @@
+import { ResultSetHeader, RowDataPacket } from 'mysql2'
+import { pool } from '../../../db.config.js'
+
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ
+export const addReview = async (data: {
+ memberId: number
+ storeId: number
+ content: string
+ score: number
+}): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [result] = await conn.query(
+ `INSERT INTO review (member_id, store_id, content, score)
+ VALUES (?, ?, ?, ?)`,
+ [data.memberId, data.storeId, data.content, data.score],
+ )
+ return result.insertId
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ๋ฐฉ๊ธ ์ถ๊ฐํ ๋ฆฌ๋ทฐ ์กฐํ
+export const getReviewById = async (reviewId: number): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ 'SELECT * FROM review WHERE id = ?',
+ [reviewId],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/reviews/services/review.service.ts" "b/\353\217\204\354\226\217/week5/src/modules/reviews/services/review.service.ts"
new file mode 100644
index 0000000..45a67a5
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/reviews/services/review.service.ts"
@@ -0,0 +1,39 @@
+import { ReviewCreateRequest, bodyToReview, responseFromReview } from '../dtos/review.dto.js'
+import { addReview, getReviewById } from '../repositories/review.repository.js'
+import { findStoreById } from '../../stores/repositories/store.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const createReview = async (storeId: number, data: ReviewCreateRequest) => {
+ // ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ
+ const store = await findStoreById(storeId)
+ if (!store) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.', 404)
+ }
+
+ // ๋ณ์ ์ ํจ์ฑ ๊ฒ์ฌ
+ if (data.score < 1 || data.score > 5) {
+ throw makeError('๋ณ์ ์ 1~5 ์ฌ์ด์ฌ์ผ ํฉ๋๋ค.', 400)
+ }
+
+ const reviewData = bodyToReview(data)
+ const reviewId = await addReview({ ...reviewData, storeId })
+
+ const review = await getReviewById(reviewId)
+ if (!review) {
+ throw makeError('๋ฆฌ๋ทฐ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromReview(review as {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+ })
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/stores/controllers/store.controller.ts" "b/\353\217\204\354\226\217/week5/src/modules/stores/controllers/store.controller.ts"
new file mode 100644
index 0000000..b767414
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/stores/controllers/store.controller.ts"
@@ -0,0 +1,18 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { StoreCreateRequest } from '../dtos/store.dto.js'
+import { createStore } from '../services/store.service.js'
+
+// POST /api/v1/stores
+export const handleCreateStore = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const result = await createStore(req.body as StoreCreateRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/stores/dtos/store.dto.ts" "b/\353\217\204\354\226\217/week5/src/modules/stores/dtos/store.dto.ts"
new file mode 100644
index 0000000..fe0d839
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/stores/dtos/store.dto.ts"
@@ -0,0 +1,38 @@
+// ๊ฐ๊ฒ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface StoreCreateRequest {
+ regionId: number
+ foodCategoryId: number
+ name: string
+ description?: string
+ address: string
+ lat?: number
+ lng?: number
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToStore = (body: StoreCreateRequest) => {
+ return {
+ regionId: body.regionId,
+ foodCategoryId: body.foodCategoryId,
+ name: body.name,
+ description: body.description ?? null,
+ address: body.address,
+ lat: body.lat ?? null,
+ lng: body.lng ?? null,
+ }
+}
+
+// DB ์กฐํ ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromStore = (store: {
+ id: number
+ name: string
+ address: string
+ region_id: number
+}) => {
+ return {
+ storeId: store.id,
+ name: store.name,
+ address: store.address,
+ regionId: store.region_id,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/stores/repositories/store.repository.ts" "b/\353\217\204\354\226\217/week5/src/modules/stores/repositories/store.repository.ts"
new file mode 100644
index 0000000..5869ef5
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/stores/repositories/store.repository.ts"
@@ -0,0 +1,67 @@
+import { ResultSetHeader, RowDataPacket } from 'mysql2'
+import { pool } from '../../../db.config.js'
+
+// ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ํ์ธ
+export const findStoreById = async (storeId: number): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ 'SELECT * FROM store WHERE id = ?',
+ [storeId],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ๊ฐ๊ฒ ์ถ๊ฐ
+export const addStore = async (data: {
+ regionId: number
+ foodCategoryId: number
+ name: string
+ description: string | null
+ address: string
+ lat: number | null
+ lng: number | null
+}): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [result] = await conn.query(
+ `INSERT INTO store (region_id, food_category_id, name, description, address, lat, lng)
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ [
+ data.regionId,
+ data.foodCategoryId,
+ data.name,
+ data.description,
+ data.address,
+ data.lat,
+ data.lng,
+ ],
+ )
+ return result.insertId
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
+
+// ๋ฐฉ๊ธ ์ถ๊ฐํ ๊ฐ๊ฒ ์ ๋ณด ์กฐํ
+export const getStoreById = async (storeId: number): Promise => {
+ const conn = await pool.getConnection()
+ try {
+ const [rows] = await conn.query(
+ 'SELECT * FROM store WHERE id = ?',
+ [storeId],
+ )
+ return rows[0] ?? null
+ } catch (err) {
+ throw new Error(`DB ์ค๋ฅ: ${err}`)
+ } finally {
+ conn.release()
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/src/modules/stores/services/store.service.ts" "b/\353\217\204\354\226\217/week5/src/modules/stores/services/store.service.ts"
new file mode 100644
index 0000000..3dcf178
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/src/modules/stores/services/store.service.ts"
@@ -0,0 +1,25 @@
+import { StoreCreateRequest, bodyToStore, responseFromStore } from '../dtos/store.dto.js'
+import { addStore, getStoreById } from '../repositories/store.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const createStore = async (data: StoreCreateRequest) => {
+ const storeData = bodyToStore(data)
+ const storeId = await addStore(storeData)
+
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw makeError('๊ฐ๊ฒ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromStore(store as {
+ id: number
+ name: string
+ address: string
+ region_id: number
+ })
+}
diff --git "a/\353\217\204\354\226\217/week5/todolist.json" "b/\353\217\204\354\226\217/week5/todolist.json"
new file mode 100644
index 0000000..830b572
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/todolist.json"
@@ -0,0 +1,353 @@
+{
+ "chapter": "Chapter 5. API ๋ฐ ํ๋ก์ ํธ ์ค์ ๊ธฐ์ด",
+ "branch": "feature/chapter-05",
+ "week4_reference": "../week4",
+ "keywords": [
+ {
+ "term": "ํ๊ฒฝ ๋ณ์ (Environment Variables)",
+ "summary": "DB ๋น๋ฐ๋ฒํธยทAPI Key ๋ฑ ๋ฏผ๊ฐํ ๊ฐ์ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ณ .env ํ์ผ๋ก ๊ด๋ฆฌ. dotenv ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ก๋ํ๋ฉฐ .gitignore์ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํจ.",
+ "example": "process.env.DB_PASSWORD"
+ },
+ {
+ "term": "CORS (Cross-Origin Resource Sharing)",
+ "summary": "๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ Origin(๋๋ฉ์ธยทํฌํธ)์ ์๋ฒ์ ์์ฒญํ ๋ ๋ฐ์ํ๋ ๋ณด์ ์ ์ฑ
. Express์์๋ cors ๋ฏธ๋ค์จ์ด๋ก ํ์ฉ ์ฒ๋ฆฌ.",
+ "example": "app.use(cors())"
+ },
+ {
+ "term": "DB Connection Pool",
+ "summary": "๋งค ์์ฒญ๋ง๋ค DB ์ปค๋ฅ์
์ ์๋ก ์์ฑยทํด์ ํ์ง ์๊ณ , ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋ ์ปค๋ฅ์
ํ์์ ๋น๋ ค ์ฐ๋ ๋ฐฉ์. mysql2์ createPool()๋ก ๊ตฌํ. finally ๋ธ๋ก์์ conn.release()๋ก ๋ฐํํด์ผ ํจ.",
+ "example": "const pool = mysql.createPool({ connectionLimit: 10 })"
+ },
+ {
+ "term": "๋น๋๊ธฐ (async / await)",
+ "summary": "DB ์ฟผ๋ฆฌยท์ธ๋ถ API ํธ์ถ์ฒ๋ผ ์๋ต ๋๊ธฐ๊ฐ ํ์ํ ์์
์ Promise ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌ. async ํจ์ ์์์ await๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ฉฐ, ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์ฝ๊ธฐ ์ฝ๊ฒ ์์ฑ ๊ฐ๋ฅ.",
+ "example": "const [rows] = await pool.query('SELECT ...')"
+ },
+ {
+ "term": "try / catch / finally",
+ "summary": "๋น๋๊ธฐ ์์
์์ ๋ฐ์ํ ์ ์๋ ์๋ฌ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฒ๋ฆฌ. try: ์ ์ ๋ก์ง, catch: ์๋ฌ ์ฒ๋ฆฌ, finally: ์ปค๋ฅ์
๋ฐํ(conn.release()) ๋ฑ ํญ์ ์คํ๋์ด์ผ ํ๋ ์ ๋ฆฌ ์ฝ๋.",
+ "example": "try { ... } catch(err) { throw new Error(...) } finally { conn.release() }"
+ },
+ {
+ "term": "Interface (์ธํฐํ์ด์ค)",
+ "summary": "TypeScript์์ ๊ฐ์ฒด์ ํํ(์์ฑยทํ์
)๋ฅผ ์ ์ํ๋ ์ค๊ณ๋. DTOยทRepository ๋ฐํ ํ์
๋ฑ์ ํ์ฉ. ?๋ฅผ ๋ถ์ด๋ฉด ์ ํ์ ํ๋กํผํฐ.",
+ "example": "export interface StoreCreateRequest { regionId: number; name: string; address: string; }"
+ },
+ {
+ "term": "Type Assertion (as ํค์๋)",
+ "summary": "TypeScript ์ปดํ์ผ๋ฌ์๊ฒ '์ด ๊ฐ์ ์ด ํ์
์ด์ผ'๋ผ๊ณ ๊ฐ์ ๋ก ์๋ ค์ฃผ๋ ๋ฌธ๋ฒ. req.body๋ ๊ธฐ๋ณธ์ ์ผ๋ก any์ด๋ฏ๋ก, as ๋ก ์ ์ํ ์ธํฐํ์ด์ค ํ์
์ผ๋ก ๋ณํํด ์ฌ์ฉ.",
+ "example": "req.body as StoreCreateRequest"
+ }
+ ],
+ "project_structure": {
+ "root": "week5/",
+ "note": "week4์ in-memory DB โ MySQL ์ค์ DB ์ฐ๊ฒฐ๋ก ์ ํ. ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค ๊ตฌ์กฐ ์ฑํ.",
+ "files": [
+ "src/index.ts - Express ์ฑ ์ง์
์ , ๋ฏธ๋ค์จ์ดยท๋ผ์ฐํฐ ๋ฑ๋ก",
+ "src/db.config.ts - MySQL Connection Pool ์ค์ ",
+ "src/modules/stores/controllers/store.controller.ts",
+ "src/modules/stores/services/store.service.ts",
+ "src/modules/stores/repositories/store.repository.ts",
+ "src/modules/stores/dtos/store.dto.ts",
+ "src/modules/reviews/controllers/review.controller.ts",
+ "src/modules/reviews/services/review.service.ts",
+ "src/modules/reviews/repositories/review.repository.ts",
+ "src/modules/reviews/dtos/review.dto.ts",
+ "src/modules/missions/controllers/mission.controller.ts",
+ "src/modules/missions/services/mission.service.ts",
+ "src/modules/missions/repositories/mission.repository.ts",
+ "src/modules/missions/dtos/mission.dto.ts",
+ ".env - DB ์ ์ ์ ๋ณดยทPORT (gitignore ํ์)",
+ ".gitignore",
+ "package.json",
+ "tsconfig.json",
+ "schema.sql - week4 schema.sql ์ฌ์ฌ์ฉ (ํ
์ด๋ธ ์ด๋ฏธ ์ ์๋จ)"
+ ]
+ },
+ "todos": [
+ {
+ "id": 1,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "GitHub ์ด์ ์์ฑ ๋ฐ ๋ธ๋์น ๋ถ๊ธฐ",
+ "status": "todo",
+ "details": [
+ "GitHub ์ ์ฅ์ Issues ํญ์์ ๋ผ๋ฒจ ์ ๋ฆฌ: bug, docs, feature, refactor",
+ "์ด์ ์ ๋ชฉ: '[feat] Chapter 5 - API ๊ตฌํ (๊ฐ๊ฒ ์ถ๊ฐ / ๋ฆฌ๋ทฐ / ๋ฏธ์
)'",
+ "Assignee: ๋ณธ์ธ, Label: feature ๋ก ์ด์ ์์ฑ",
+ "์ด์์์ 'Create a branch' ํด๋ฆญ โ feature/chapter-05 ๋ธ๋์น ์์ฑ",
+ "๋ก์ปฌ์์: git fetch origin && git checkout feature/chapter-05"
+ ]
+ },
+ {
+ "id": 2,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "Postman ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ ํ์ธ",
+ "status": "todo",
+ "details": [
+ "Postman ์ค์น (https://www.postman.com/downloads/)",
+ "Params / Authorization / Headers / Body ํญ ์ญํ ์ดํด",
+ "Body > raw > JSON ์ ํ ๋ฐฉ๋ฒ ์์ง",
+ "๋์ค์ API ํ
์คํธ ์ ์คํฌ๋ฆฐ์ท ์ ์ฅํ ์ค๋น"
+ ]
+ },
+ {
+ "id": 3,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "week5 ํด๋ ์ด๊ธฐํ ๋ฐ ์์กด์ฑ ์ค์น",
+ "status": "todo",
+ "details": [
+ "cd week5 && npm init -y",
+ "npm install express cors dotenv http-status-codes mysql2",
+ "npm install -D typescript @types/node @types/express @types/cors @types/dotenv nodemon tsx",
+ "npx tsc --init ํ tsconfig.json ์์ (rootDir: ./src, outDir: ./dist, module: NodeNext, strict: true ๋ฑ)",
+ "week4/tsconfig.json์ ์ฐธ๊ณ ํด module/moduleResolution ์ค์ ์ผ์น์ํค๊ธฐ",
+ "package.json scripts ์ถ๊ฐ: start / dev (nodemon --exec tsx src/index.ts)"
+ ]
+ },
+ {
+ "id": 4,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": ".env ํ์ผ ๋ฐ .gitignore ์์ฑ",
+ "status": "todo",
+ "details": [
+ ".gitignore์ node_modules/ / .env / .env.* ์ถ๊ฐ",
+ ".env์ PORT=3000, DB_HOST=localhost, DB_PORT=3306, DB_USER=root, DB_PASSWORD=๋น๋ฐ๋ฒํธ, DB_NAME=umc_mission ์์ฑ",
+ "DB_NAME์ week4/schema.sql ๊ธฐ์ค umc_mission ์ฌ์ฉ (์ด๋ฏธ ํ
์ด๋ธ ์์)"
+ ]
+ },
+ {
+ "id": 5,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/db.config.ts ์์ฑ - MySQL Connection Pool",
+ "status": "todo",
+ "details": [
+ "mysql2/promise์ createPool ์ฌ์ฉ",
+ "ํ๊ฒฝ ๋ณ์(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME)๋ก ์ค์ ",
+ "connectionLimit: 10, waitForConnections: true",
+ "week4 schema.sql์ DB(umc_mission)์ ์ฐ๊ฒฐ"
+ ],
+ "reference_week4": "week4/src/db/index.ts ๊ตฌ์กฐ ์ฐธ๊ณ (๋จ, in-memoryโMySQL๋ก ๋ณ๊ฒฝ)"
+ },
+ {
+ "id": 6,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/index.ts ์์ฑ - Express ์ฑ ์ง์
์ ",
+ "status": "todo",
+ "details": [
+ "dotenv.config() ์ต์๋จ ํธ์ถ",
+ "cors(), express.json(), express.urlencoded() ๋ฏธ๋ค์จ์ด ๋ฑ๋ก",
+ "๊ฐ ๋ชจ๋ ๋ผ์ฐํฐ ๋ฑ๋ก: /api/v1/stores, /api/v1/reviews, /api/v1/missions",
+ "์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ๋ฏธ๋ค์จ์ด ๋ฑ๋ก (์๋์ด ๋ฏธ์
: JSON ์๋ฌ ์๋ต)",
+ "app.listen(process.env.PORT || 3000)"
+ ],
+ "reference_week4": "week4/src/index.ts ๊ตฌ์กฐ ๊ทธ๋๋ก ํ์ฉ, corsยทdotenv ์ถ๊ฐ"
+ },
+ {
+ "id": 7,
+ "phase": "DB ์ค๋น",
+ "title": "MySQL์ week5์ฉ ํ
์ด๋ธ ํ์ธ ๋ฐ ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
",
+ "status": "todo",
+ "details": [
+ "week4/schema.sql๋ก ํ
์ด๋ธ ์์ฑ (์ด๋ฏธ ๋์ด์์ผ๋ฉด ์๋ต)",
+ "food_category ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO food_category(name) VALUES ('ํ์'),('์ค์'),('์ผ์'),('์์'),('์นํจ'),('๋ถ์'),('๊ณ ๊ธฐ/๊ตฌ์ด'),('๋์๋ฝ'),('์์'),('ํจ์คํธํธ๋'),('๋ค์ ํธ'),('์์์ํธ๋')",
+ "region ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO region(name) VALUES ('์์ธ'),('๊ฒฝ๊ธฐ'),('๋ถ์ฐ')...",
+ "member ๋๋ฏธ๋ฐ์ดํฐ 1๊ฑด ์ฝ์
(API ํ
์คํธ์ฉ ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ ]
+ },
+ {
+ "id": 8,
+ "phase": "API ๊ตฌํ - 1-1",
+ "title": "[ํ์] ํน์ ์ง์ญ์ ๊ฐ๊ฒ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores",
+ "request_body": {
+ "regionId": "number",
+ "foodCategoryId": "number",
+ "name": "string",
+ "description": "string (์ ํ)",
+ "address": "string",
+ "lat": "number (์ ํ)",
+ "lng": "number (์ ํ)"
+ },
+ "details": [
+ "store.dto.ts - StoreCreateRequest ์ธํฐํ์ด์ค ์ ์",
+ "store.dto.ts - bodyToStore() ๋ณํ ํจ์ ์์ฑ",
+ "store.repository.ts - addStore(): INSERT INTO store(...) VALUES(?)",
+ "store.service.ts - createStore(data): addStore ํธ์ถ ํ ๊ฒฐ๊ณผ ๋ฐํ",
+ "store.controller.ts - handleCreateStore: bodyToStore(req.body as ...) โ service ํธ์ถ โ 201 ์๋ต",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก: app.post('/api/v1/stores', handleCreateStore)"
+ ],
+ "reference_week4": "week4/src/controllers/store.controller.ts ํจํด ์ฐธ๊ณ "
+ },
+ {
+ "id": 9,
+ "phase": "API ๊ตฌํ - 1-2",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/stores/:storeId/reviews",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)",
+ "content": "string",
+ "score": "number (1.0~5.0)"
+ },
+ "details": [
+ "review.dto.ts - ReviewCreateRequest ์ธํฐํ์ด์ค (content, score, memberId)",
+ "review.dto.ts - bodyToReview() ๋ณํ ํจ์",
+ "review.repository.ts - addReview(): INSERT INTO review(...)",
+ "store.repository.ts (๋๋ review.repository.ts) - findStoreById(): SELECT * FROM store WHERE id=?",
+ "review.service.ts - createReview(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addReview ํธ์ถ",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.')",
+ "review.controller.ts - handleCreateReview: storeId = parseInt(req.params.storeId) โ service ํธ์ถ",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋ฆฌ๋ทฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ ๊ฐ๊ฒ๊ฐ ์กด์ฌํ๋์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/services/store.service.ts์ createReview ํจํด, week4 ์คํค๋ง์ review ํ
์ด๋ธ"
+ },
+ {
+ "id": 10,
+ "phase": "API ๊ตฌํ - 1-3",
+ "title": "๊ฐ๊ฒ์ ๋ฏธ์
์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores/:storeId/missions",
+ "request_body": {
+ "title": "string",
+ "reward": "number",
+ "spec": "string (์ ํ)",
+ "deadLine": "string (YYYY-MM-DD, ์ ํ)"
+ },
+ "details": [
+ "mission.dto.ts - MissionCreateRequest ์ธํฐํ์ด์ค",
+ "mission.dto.ts - bodyToMission() ๋ณํ ํจ์ (deadLine โ Date ๋ณํ)",
+ "mission.repository.ts - addMission(): INSERT INTO mission(store_id, title, reward, spec, dead_line)",
+ "mission.service.ts - createMission(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addMission",
+ "mission.controller.ts - handleCreateMission",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4/src/repositories/mission.repository.ts ํจํด, week4 schema.sql์ mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 11,
+ "phase": "API ๊ตฌํ - 1-4",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฏธ์
์ ๋์ ์ค์ธ ๋ฏธ์
์ ์ถ๊ฐ(๋ฏธ์
๋์ ํ๊ธฐ) API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/missions/:missionId/challenge",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ },
+ "details": [
+ "mission.dto.ts - MissionChallengeRequest ์ธํฐํ์ด์ค ({ memberId: number })",
+ "mission.repository.ts - findMemberMission(): SELECT * FROM member_mission WHERE member_id=? AND mission_id=?",
+ "mission.repository.ts - addMemberMission(): INSERT INTO member_mission(member_id, mission_id, status) VALUES(?,?,'CHALLENGING')",
+ "mission.service.ts - challengeMission(missionId, data): ์ค๋ณต ๋์ ๊ฒ์ฆ โ addMemberMission",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.')",
+ "mission.controller.ts - handleChallengeMission: missionId = parseInt(req.params.missionId) โ service",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋์ ํ๋ ค๋ ๋ฏธ์
์ด ์ด๋ฏธ ๋์ ์ค์ด์ง๋ ์์์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/repositories/mission.repository.ts, week4 schema.sql์ member_mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 12,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
2๋ฒ] Controller โ Service โ Repository โ DB ์์ฒญ ํ๋ฆ ์ ๋ฆฌ",
+ "status": "todo",
+ "details": [
+ "์: POST /api/v1/stores/:storeId/reviews ์์ฒญ ํ๋ฆ์ ์์๋๋ก ์์ฑ",
+ "1. ์ฌ์ฉ์๊ฐ POST /api/v1/stores/1/reviews ์์ฒญ ์ ์ก",
+ "2. index.ts์ ๋ผ์ฐํฐ๊ฐ handleCreateReview ์ปจํธ๋กค๋ฌ ํธ์ถ",
+ "3. Controller: req.body๋ฅผ ReviewCreateRequest ํ์
์ผ๋ก ๋ณํ(bodyToReview), storeId ํ์ฑ",
+ "4. Service: ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ(findStoreById) โ ์์ผ๋ฉด Error throw",
+ "5. Repository: INSERT INTO review ์ฟผ๋ฆฌ ์คํ โ insertId ๋ฐํ",
+ "6. Service: ๊ฒฐ๊ณผ๋ฅผ DTO๋ก ๋ณํํด Controller์ ๋ฐํ",
+ "7. Controller: 201 JSON ์๋ต ์ ์ก",
+ "์ํฌ๋ถ์ ์์ฝ ์ ๋ฆฌ ์น์
์ ์ด ๋ด์ฉ ํฌํจ"
+ ]
+ },
+ {
+ "id": 13,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
3๋ฒ] ํ์๊ฐ์
API์ bcrypt ๋น๋ฐ๋ฒํธ ํด์ฑ ์ถ๊ฐ",
+ "status": "todo",
+ "details": [
+ "npm install bcryptjs && npm install -D @types/bcryptjs (week4์ ์ด๋ฏธ ์ค์น๋จ)",
+ "member.dto.ts - MemberSignUpRequest ์ธํฐํ์ด์ค์ password ํ๋ ์ถ๊ฐ",
+ "member.repository.ts - addMember(): INSERT INTO member(..., password) VALUES(...)",
+ "member.service.ts - signUp(): const hashedPw = await bcrypt.hash(data.password, 10) โ addMember์ ์ ๋ฌ",
+ "member.controller.ts - handleSignUp ์์ฑ",
+ "POST /api/v1/members/signup ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4์์ bcryptjs ์ด๋ฏธ ์ฌ์ฉ ์ค - week4/package.json ์ฐธ๊ณ "
+ },
+ {
+ "id": 14,
+ "phase": "์๋์ด ๋ฏธ์
",
+ "title": "[์๋์ด] ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ - JSON ํํ ์๋ฌ ์๋ต",
+ "status": "todo",
+ "details": [
+ "src/middleware/error.middleware.ts ์์ฑ",
+ "ErrorRequestHandler ํ์
์ฌ์ฉ: (err, req, res, next) => void",
+ "res.status(err.status || 500).json({ success: false, message: err.message || '์๋ฒ ์๋ฌ' })",
+ "index.ts ๋งจ ๋ง์ง๋ง์ app.use(errorMiddleware) ๋ฑ๋ก",
+ "Controller์์ try-catch ์ ๊ฑฐํ๊ฑฐ๋ next(err) ์ฌ์ฉ์ผ๋ก ๋ณ๊ฒฝ",
+ "๊ธฐ์กด HTML ์๋ฌ ์๋ต โ JSON ์๋ฌ ์๋ต์ผ๋ก ๊ฐ์ "
+ ],
+ "reference_week4": "week4/src/middleware/error.middleware.ts ๊ทธ๋๋ก ํ์ฉ ๊ฐ๋ฅ"
+ },
+ {
+ "id": 15,
+ "phase": "ํ
์คํธ",
+ "title": "Postman / curl๋ก ๊ฐ API ํธ์ถ ๋ฐ ์คํฌ๋ฆฐ์ท ์ ์ฅ",
+ "status": "todo",
+ "details": [
+ "npm run dev ๋ก ์๋ฒ ์คํ",
+ "API 1-1: POST /api/v1/stores - ๊ฐ๊ฒ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: POST /api/v1/stores/:storeId/reviews - ๋ฆฌ๋ทฐ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: ์กด์ฌํ์ง ์๋ storeId๋ก ์์ฒญ โ ์๋ฌ ์๋ต ์คํฌ๋ฆฐ์ท",
+ "API 1-3: POST /api/v1/stores/:storeId/missions - ๋ฏธ์
์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: POST /api/v1/missions/:missionId/challenge - ๋์ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: ๋์ผ ๋ฏธ์
์ฌ๋์ โ '์ด๋ฏธ ๋์ ์ค' ์๋ฌ ์คํฌ๋ฆฐ์ท",
+ "DB์์ SELECT๋ก ๋ฐ์ดํฐ ์ฝ์
ํ์ธ ์คํฌ๋ฆฐ์ท"
+ ]
+ },
+ {
+ "id": 16,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "feature/chapter-05 ๋ธ๋์น์ push ๋ฐ PR ์์ฑ",
+ "status": "todo",
+ "details": [
+ "git add . && git commit -m 'feat: 5์ฃผ์ฐจ ๋ฏธ์
- API ๊ตฌํ (๊ฐ๊ฒ/๋ฆฌ๋ทฐ/๋ฏธ์
)'",
+ "git push origin feature/chapter-05",
+ "GitHub์์ PR ์์ฑ (main ๋ธ๋์น์ mergeํ์ง ๋ง ๊ฒ!)",
+ "์ํฌ๋ถ์ ๋ฏธ์
๊ธฐ๋ก๋์ GitHub ๋งํฌ ์ ์ถ",
+ "GitHub ์ด์ Close"
+ ]
+ },
+ {
+ "id": 17,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "ํต์ฌ ํค์๋ ๋ฐ ์์ฝ ์ ๋ฆฌ ์์ฑ",
+ "status": "todo",
+ "details": [
+ "์ด ํ์ผ ์๋จ์ keywords ์น์
์ ์ฐธ๊ณ ํด ์ํฌ๋ถ์ ๊ธฐ์
",
+ "์์ฝ ์ ๋ฆฌ: ControllerโServiceโRepositoryโDB ํ๋ฆ ์ค๋ช
",
+ "์ํด๋ฆฌ ์คํฌ๋ผ ์ง๋ฌธ ๋ต๋ณ: DTO ์์ด ์ฌ์ฉํ ๋์ ๋ฌธ์ ์ / Service Layer ํ์์ฑ"
+ ]
+ }
+ ],
+ "required_apis_summary": {
+ "must_implement": ["1-2 (๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ)", "1-4 (๋ฏธ์
๋์ ํ๊ธฐ)"],
+ "minimum_count": "ํ์ 2๊ฐ ํฌํจ ์ด 3๊ฐ ์ด์",
+ "senior_mission": "4๊ฐ ์ ๋ถ + JSON ์๋ฌ ์๋ต ๊ฐ์ "
+ },
+ "key_differences_from_week4": {
+ "database": "in-memory(week4) โ MySQL Connection Pool(week5)",
+ "modules": "flat ๊ตฌ์กฐ(week4) โ ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค(week5: src/modules/{๋๋ฉ์ธ}/)",
+ "env": ".env ํ์ผ ์ถ๊ฐ (dotenv ์ฌ์ฉ)",
+ "cors": "cors ๋ฏธ๋ค์จ์ด ์ถ๊ฐ",
+ "error_response": "HTML ์๋ฌ(๊ธฐ๋ณธ) โ JSON ์๋ฌ(์๋์ด ๋ฏธ์
๊ฐ์ )"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week5/tsconfig.json" "b/\353\217\204\354\226\217/week5/tsconfig.json"
new file mode 100644
index 0000000..1143499
--- /dev/null
+++ "b/\353\217\204\354\226\217/week5/tsconfig.json"
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "target": "ESNext",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "lib": ["ESNext"],
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "sourceMap": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git "a/\353\217\204\354\226\217/week6/.gitignore" "b/\353\217\204\354\226\217/week6/.gitignore"
new file mode 100644
index 0000000..928abd6
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/.gitignore"
@@ -0,0 +1,21 @@
+# dependency directories
+node_modules/
+
+# build output
+dist/
+
+# dotenv environment variable files
+.env
+.env.local
+.env.development
+.env.production
+.env.*
+
+# macOS
+.DS_Store
+
+# logs
+*.log
+npm-debug.log*
+
+/src/generated/prisma
diff --git "a/\353\217\204\354\226\217/week6/package-lock.json" "b/\353\217\204\354\226\217/week6/package-lock.json"
new file mode 100644
index 0000000..f9315aa
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/package-lock.json"
@@ -0,0 +1,3122 @@
+{
+ "name": "week6",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/node": "^25.6.0",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0"
+ }
+ },
+ "node_modules/@electric-sql/pglite": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz",
+ "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@electric-sql/pglite-socket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz",
+ "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "pglite-server": "dist/scripts/server.js"
+ },
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@electric-sql/pglite-tools": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz",
+ "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.11",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
+ "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@prisma/adapter-mariadb": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.8.0.tgz",
+ "integrity": "sha512-mWsgcfbUjxB3qSzRlLs8E03vsKrqXzYK2zpx3e8u6wIgeHJM/sE46cuOGcYvHiZGmeQLCd3xL6YSSGM9QOLI6w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/driver-adapter-utils": "7.8.0",
+ "mariadb": "3.4.5"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz",
+ "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/client-runtime-utils": "7.8.0"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/client-runtime-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz",
+ "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/config": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz",
+ "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.3.4",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.20.0",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz",
+ "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/dev": {
+ "version": "0.24.3",
+ "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz",
+ "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "@electric-sql/pglite": "0.4.1",
+ "@electric-sql/pglite-socket": "0.1.1",
+ "@electric-sql/pglite-tools": "0.3.1",
+ "@hono/node-server": "1.19.11",
+ "@prisma/get-platform": "7.2.0",
+ "@prisma/query-plan-executor": "7.2.0",
+ "@prisma/streams-local": "0.1.2",
+ "foreground-child": "3.3.1",
+ "get-port-please": "3.2.0",
+ "hono": "^4.12.8",
+ "http-status-codes": "2.3.0",
+ "pathe": "2.0.3",
+ "proper-lockfile": "4.1.2",
+ "remeda": "2.33.4",
+ "std-env": "3.10.0",
+ "valibot": "1.2.0",
+ "zeptomatch": "2.1.0"
+ }
+ },
+ "node_modules/@prisma/driver-adapter-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz",
+ "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz",
+ "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/fetch-engine": "7.8.0",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz",
+ "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz",
+ "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
+ "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.2.0"
+ }
+ },
+ "node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+ "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/query-plan-executor": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
+ "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/streams-local": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz",
+ "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ajv": "^8.12.0",
+ "better-result": "^2.7.0",
+ "env-paths": "^3.0.0",
+ "proper-lockfile": "^4.1.2"
+ },
+ "engines": {
+ "bun": ">=1.3.6",
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@prisma/studio-core": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz",
+ "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@radix-ui/react-toggle": "1.1.10",
+ "chart.js": "4.5.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0",
+ "pnpm": "8"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
+ "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "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": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "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/node": {
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
+ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.19.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "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/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "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": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "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/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/better-result": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.0.tgz",
+ "integrity": "sha512-NHwGDGVbRlWDOce3CwcfGIrcNR9zY37ut3SVwQVfv57DZdVhxjhA4mfaHN1n8QwWnRAR4iErpW1X/eaiaUaFYg==",
+ "devOptional": 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": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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/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/c12": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
+ "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^5.0.0",
+ "confbox": "^0.2.4",
+ "defu": "^6.1.6",
+ "dotenv": "^17.3.1",
+ "exsolve": "^1.0.8",
+ "giget": "^3.2.0",
+ "jiti": "^2.6.1",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^2.1.0",
+ "pkg-types": "^2.3.0",
+ "rc9": "^3.0.1"
+ },
+ "peerDependencies": {
+ "magicast": "*"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": 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": {
+ "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/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
+ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/dotenv": {
+ "version": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+ "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/effect": {
+ "version": "3.20.0",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz",
+ "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
+ "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "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": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "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": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/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-port-please": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
+ "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "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-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/giget": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
+ "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
+ "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==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/grammex": {
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
+ "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/graphmatch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
+ "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.15",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz",
+ "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-status-codes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "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/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/mariadb": {
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz",
+ "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==",
+ "license": "LGPL-2.1-or-later",
+ "dependencies": {
+ "@types/geojson": "^7946.0.16",
+ "@types/node": "^24.0.13",
+ "denque": "^2.1.0",
+ "iconv-lite": "^0.6.3",
+ "lru-cache": "^10.4.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/mariadb/node_modules/@types/node": {
+ "version": "24.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
+ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/mariadb/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/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": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/mysql2": {
+ "version": "3.15.3",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
+ "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.0",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "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/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/nodemon/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/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/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "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-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
+ "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.4",
+ "exsolve": "^1.0.8",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/postgres": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
+ "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
+ "devOptional": true,
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/porsager"
+ }
+ },
+ "node_modules/prisma": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz",
+ "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "7.8.0",
+ "@prisma/dev": "0.24.3",
+ "@prisma/engines": "7.8.0",
+ "@prisma/studio-core": "0.27.3",
+ "mysql2": "3.15.3",
+ "postgres": "3.4.7"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "better-sqlite3": ">=9.0.0",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "better-sqlite3": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "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/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "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": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/rc9": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
+ "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.6",
+ "destr": "^2.0.5"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.5"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/remeda": {
+ "version": "2.33.4",
+ "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
+ "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/remeda"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
+ "devOptional": true
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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==",
+ "devOptional": 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==",
+ "devOptional": 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",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "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/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "devOptional": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "devOptional": true,
+ "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/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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.19.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
+ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
+ "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/valibot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "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/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/zeptomatch": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
+ "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "grammex": "^3.1.11",
+ "graphmatch": "^1.1.0"
+ }
+ }
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/package.json" "b/\353\217\204\354\226\217/week6/package.json"
new file mode 100644
index 0000000..4771fb6
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/package.json"
@@ -0,0 +1,23 @@
+{
+ "scripts": {
+ "dev": "nodemon --ext ts,prisma --ignore src/generated --exec \"npx prisma generate && tsx src/index.ts\""
+ },
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/node": "^25.6.0",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/prisma.config.ts" "b/\353\217\204\354\226\217/week6/prisma.config.ts"
new file mode 100644
index 0000000..5170cc4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/prisma.config.ts"
@@ -0,0 +1,12 @@
+///
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+const { DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME } = process.env;
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ datasource: {
+ url: `mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT ?? 3306}/${DB_NAME}`,
+ },
+});
diff --git "a/\353\217\204\354\226\217/week6/prisma/migrations/20260428071132_init_database/migration.sql" "b/\353\217\204\354\226\217/week6/prisma/migrations/20260428071132_init_database/migration.sql"
new file mode 100644
index 0000000..31b12a1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/prisma/migrations/20260428071132_init_database/migration.sql"
@@ -0,0 +1,39 @@
+-- CreateTable
+CREATE TABLE `user` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `email` VARCHAR(255) NOT NULL,
+ `name` VARCHAR(100) NOT NULL,
+ `gender` VARCHAR(15) NOT NULL,
+ `birth` DATE NOT NULL,
+ `address` VARCHAR(255) NOT NULL,
+ `detail_address` VARCHAR(255) NULL,
+ `phone_number` VARCHAR(15) NOT NULL,
+
+ UNIQUE INDEX `email`(`email`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `food_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_favor_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `user_id` INTEGER NOT NULL,
+ `food_category_id` INTEGER NOT NULL,
+
+ INDEX `f_category_id`(`food_category_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_food_category_id_fkey` FOREIGN KEY (`food_category_id`) REFERENCES `food_category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week6/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql" "b/\353\217\204\354\226\217/week6/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
new file mode 100644
index 0000000..99445d4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
@@ -0,0 +1,25 @@
+-- CreateTable
+CREATE TABLE `store` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_store_review` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `user_id` INTEGER NOT NULL,
+ `content` TEXT NOT NULL,
+
+ INDEX `store_id`(`store_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week6/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql" "b/\353\217\204\354\226\217/week6/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
new file mode 100644
index 0000000..a6dd8b0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
@@ -0,0 +1,33 @@
+-- CreateTable
+CREATE TABLE `mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `title` VARCHAR(200) NOT NULL,
+ `reward` INTEGER NOT NULL,
+ `spec` TEXT NULL,
+ `dead_line` DATETIME(3) NULL,
+
+ INDEX `store_id`(`store_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `member_mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `member_id` INTEGER NOT NULL,
+ `mission_id` INTEGER NOT NULL,
+ `status` VARCHAR(15) NOT NULL,
+
+ INDEX `member_id`(`member_id`),
+ INDEX `mission_id`(`mission_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `mission` ADD CONSTRAINT `mission_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_member_id_fkey` FOREIGN KEY (`member_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week6/prisma/migrations/migration_lock.toml" "b/\353\217\204\354\226\217/week6/prisma/migrations/migration_lock.toml"
new file mode 100644
index 0000000..592fc0b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/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 (e.g., Git)
+provider = "mysql"
diff --git "a/\353\217\204\354\226\217/week6/prisma/schema.prisma" "b/\353\217\204\354\226\217/week6/prisma/schema.prisma"
new file mode 100644
index 0000000..a5758e0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/prisma/schema.prisma"
@@ -0,0 +1,100 @@
+generator client {
+ provider = "prisma-client"
+ output = "../src/generated/prisma"
+}
+
+datasource db {
+ provider = "mysql"
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique(map: "email") @db.VarChar(255)
+ name String @db.VarChar(100)
+ gender String @db.VarChar(15)
+ birth DateTime @db.Date
+ address String @db.VarChar(255)
+ detailAddress String? @map("detail_address") @db.VarChar(255)
+ phoneNumber String @map("phone_number") @db.VarChar(15)
+
+ userFavorCategories UserFavorCategory[]
+ reviews UserStoreReview[]
+ memberMissions MemberMission[]
+
+ @@map("user")
+}
+
+model FoodCategory {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ userFavorCategories UserFavorCategory[]
+
+ @@map("food_category")
+}
+
+model UserFavorCategory {
+ id Int @id @default(autoincrement())
+ userId Int @map("user_id")
+ foodCategoryId Int @map("food_category_id")
+ user User @relation(fields: [userId], references: [id])
+ foodCategory FoodCategory @relation(fields: [foodCategoryId], references: [id])
+
+ @@index([foodCategoryId], map: "f_category_id")
+ @@index([userId], map: "user_id")
+ @@map("user_favor_category")
+}
+
+model Store {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ reviews UserStoreReview[]
+ missions Mission[]
+
+ @@map("store")
+}
+
+model UserStoreReview {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ userId Int @map("user_id")
+ content String @db.Text
+
+ store Store @relation(fields: [storeId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+
+ @@index([storeId], map: "store_id")
+ @@index([userId], map: "user_id")
+ @@map("user_store_review")
+}
+
+model Mission {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ title String @db.VarChar(200)
+ reward Int
+ spec String? @db.Text
+ deadLine DateTime? @map("dead_line")
+
+ store Store @relation(fields: [storeId], references: [id])
+ memberMissions MemberMission[]
+
+ @@index([storeId], map: "store_id")
+ @@map("mission")
+}
+
+model MemberMission {
+ id Int @id @default(autoincrement())
+ userId Int @map("member_id")
+ missionId Int @map("mission_id")
+ status String @db.VarChar(15)
+
+ user User @relation(fields: [userId], references: [id])
+ mission Mission @relation(fields: [missionId], references: [id])
+
+ @@index([userId], map: "member_id")
+ @@index([missionId], map: "mission_id")
+ @@map("member_mission")
+}
+
diff --git "a/\353\217\204\354\226\217/week6/reset_db.sql" "b/\353\217\204\354\226\217/week6/reset_db.sql"
new file mode 100644
index 0000000..454f20b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/reset_db.sql"
@@ -0,0 +1,215 @@
+-- ============================================================
+-- DB ์ด๊ธฐํ ๋ฐ ์ฌ์์ฑ ์คํฌ๋ฆฝํธ
+-- ============================================================
+
+CREATE DATABASE IF NOT EXISTS umc_mission DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE umc_mission;
+
+-- FK ์ฒดํฌ ๋นํ์ฑํ ํ ์ ์ฒด DROP
+SET FOREIGN_KEY_CHECKS = 0;
+
+DROP TABLE IF EXISTS review_image;
+DROP TABLE IF EXISTS review;
+DROP TABLE IF EXISTS member_mission;
+DROP TABLE IF EXISTS mission;
+DROP TABLE IF EXISTS store_hours;
+DROP TABLE IF EXISTS store_image;
+DROP TABLE IF EXISTS store;
+DROP TABLE IF EXISTS member_prefer;
+DROP TABLE IF EXISTS member_agree;
+DROP TABLE IF EXISTS member;
+DROP TABLE IF EXISTS terms;
+DROP TABLE IF EXISTS food_category;
+DROP TABLE IF EXISTS region;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- ============================================================
+-- ํ
์ด๋ธ ์ฌ์์ฑ
+-- ============================================================
+
+CREATE TABLE region (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์ง์ญ๋ช
',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE food_category (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์นดํ
๊ณ ๋ฆฌ๋ช
(ํ์, ์ค์, ์ผ์ ๋ฑ)',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE terms (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ title VARCHAR(100) NOT NULL COMMENT '์ฝ๊ด ์ ๋ชฉ',
+ content TEXT NOT NULL COMMENT '์ฝ๊ด ๋ด์ฉ',
+ optional BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'TRUE: ์ ํ ๋์, FALSE: ํ์ ๋์',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE member (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ social_type VARCHAR(20) NULL,
+ social_id VARCHAR(100) NULL,
+ email VARCHAR(100) NULL,
+ password VARCHAR(255) NULL,
+ name VARCHAR(50) NOT NULL,
+ nickname VARCHAR(50) NOT NULL,
+ profile_image_url VARCHAR(500) NULL,
+ phone_num VARCHAR(20) NULL,
+ phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
+ birth DATE NULL,
+ gender ENUM('MALE', 'FEMALE', 'OTHER') NULL,
+ address VARCHAR(200) NULL,
+ spec_address VARCHAR(200) NULL,
+ point INT NOT NULL DEFAULT 0,
+ status ENUM('ACTIVE', 'INACTIVE', 'BANNED') NOT NULL DEFAULT 'ACTIVE',
+ inactive_date DATETIME NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_email (email),
+ UNIQUE KEY uq_member_social (social_type, social_id)
+);
+
+CREATE TABLE member_agree (
+ member_id BIGINT NOT NULL,
+ terms_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, terms_id),
+ CONSTRAINT fk_member_agree_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_agree_terms FOREIGN KEY (terms_id) REFERENCES terms (id)
+);
+
+CREATE TABLE member_prefer (
+ member_id BIGINT NOT NULL,
+ food_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, food_id),
+ CONSTRAINT fk_member_prefer_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_prefer_food FOREIGN KEY (food_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ region_id BIGINT NOT NULL,
+ food_category_id BIGINT NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ description TEXT NULL,
+ lat DECIMAL(10,7) NULL,
+ lng DECIMAL(10,7) NULL,
+ address VARCHAR(200) NOT NULL,
+ status ENUM('OPEN', 'CLOSED', 'PENDING') NOT NULL DEFAULT 'OPEN',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_region FOREIGN KEY (region_id) REFERENCES region (id),
+ CONSTRAINT fk_store_category FOREIGN KEY (food_category_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_image_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE store_hours (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ day_of_week VARCHAR(3) NOT NULL,
+ open_time TIME NOT NULL,
+ close_time TIME NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_store_hours_day (store_id, day_of_week),
+ CONSTRAINT fk_store_hours_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ title VARCHAR(200) NOT NULL,
+ reward INT NOT NULL DEFAULT 0,
+ spec VARCHAR(500) NULL,
+ dead_line DATE NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_mission_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE member_mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ mission_id BIGINT NOT NULL,
+ status ENUM('CHALLENGING', 'COMPLETE') NOT NULL DEFAULT 'CHALLENGING',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_mission (member_id, mission_id),
+ CONSTRAINT fk_member_mission_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_mission_mission FOREIGN KEY (mission_id) REFERENCES mission (id)
+);
+
+CREATE TABLE review (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ store_id BIGINT NOT NULL,
+ member_mission_id BIGINT NULL,
+ content TEXT NOT NULL,
+ score DECIMAL(2,1) NOT NULL,
+ owner_reply VARCHAR(500) NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_review_store FOREIGN KEY (store_id) REFERENCES store (id),
+ CONSTRAINT fk_review_member_mission FOREIGN KEY (member_mission_id) REFERENCES member_mission (id),
+ CONSTRAINT chk_review_score CHECK (score BETWEEN 1.0 AND 5.0)
+);
+
+CREATE TABLE review_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ review_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_image_review FOREIGN KEY (review_id) REFERENCES review (id)
+);
+
+-- ============================================================
+-- ์๋ ๋ฐ์ดํฐ (API ํ
์คํธ์ฉ ์ด๊ธฐ ๋ฐ์ดํฐ)
+-- ============================================================
+
+INSERT INTO region (name) VALUES
+ ('์์ธ'),
+ ('๊ฒฝ๊ธฐ'),
+ ('์ธ์ฒ'),
+ ('๋ถ์ฐ'),
+ ('๋๊ตฌ');
+
+INSERT INTO food_category (name) VALUES
+ ('ํ์'),
+ ('์ค์'),
+ ('์ผ์'),
+ ('์์'),
+ ('๋ถ์'),
+ ('์นดํ/๋์ ํธ'),
+ ('์นํจ'),
+ ('ํผ์'),
+ ('ํจ์คํธํธ๋');
diff --git "a/\353\217\204\354\226\217/week6/src/db.config.ts" "b/\353\217\204\354\226\217/week6/src/db.config.ts"
new file mode 100644
index 0000000..7b88907
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/db.config.ts"
@@ -0,0 +1,17 @@
+import "dotenv/config";
+import { PrismaClient } from "./generated/prisma/client.js";
+import { PrismaMariaDb } from "@prisma/adapter-mariadb";
+
+const adapter = new PrismaMariaDb({
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASSWORD,
+ database: process.env.DB_NAME,
+ port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 3306,
+ connectionLimit: 10,
+});
+
+export const prisma = new PrismaClient({
+ adapter,
+ log: ["query", "info", "error", "warn"],
+});
diff --git "a/\353\217\204\354\226\217/week6/src/index.ts" "b/\353\217\204\354\226\217/week6/src/index.ts"
new file mode 100644
index 0000000..1e525f9
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/index.ts"
@@ -0,0 +1,55 @@
+import dotenv from 'dotenv'
+import express, { Express, Request, Response } from 'express'
+import cors from 'cors'
+
+// ์ปจํธ๋กค๋ฌ import
+import { handleCreateStore, handleListStoreReviews } from './modules/stores/controllers/store.controller.js'
+import { handleListUserReviews } from './modules/reviews/controllers/review.controller.js'
+import { handleCreateReview } from './modules/reviews/controllers/review.controller.js'
+import { handleCreateMission, handleChallengeMission, handleListStoreMissions, handleListOngoingMissions, handleCompleteMission } from './modules/missions/controllers/mission.controller.js'
+import { handleSignUp } from './modules/members/controllers/member.controller.js'
+
+// ์๋ฌ ๋ฏธ๋ค์จ์ด import
+import { errorMiddleware } from './middleware/error.middleware.js'
+
+// 1. ํ๊ฒฝ ๋ณ์ ์ค์ (๊ฐ์ฅ ๋จผ์ ํธ์ถ)
+dotenv.config()
+
+const app: Express = express()
+const port = process.env.PORT ?? 3000
+
+// 2. ๋ฏธ๋ค์จ์ด ์ค์
+app.use(cors())
+app.use(express.json())
+app.use(express.urlencoded({ extended: false }))
+
+// 3. ๋ผ์ฐํฐ ๋ฑ๋ก
+app.get('/', (_req: Request, res: Response) => {
+ res.send('UMC 6์ฃผ์ฐจ ์๋ฒ ์คํ ์ค!')
+})
+
+// ํ์
+app.post('/api/v1/members/signup', handleSignUp)
+app.get('/api/v1/users/:userId/reviews', handleListUserReviews)
+app.get('/api/v1/users/:userId/missions', handleListOngoingMissions)
+
+// ๊ฐ๊ฒ
+app.post('/api/v1/stores', handleCreateStore)
+app.post('/api/v1/stores/:storeId/reviews', handleCreateReview)
+app.get('/api/v1/stores/:storeId/reviews', handleListStoreReviews)
+app.get('/api/v1/stores/:storeId/missions', handleListStoreMissions)
+app.post('/api/v1/stores/:storeId/missions', handleCreateMission)
+
+// ๋ฏธ์
+app.post('/api/v1/missions/:missionId/challenge', handleChallengeMission)
+app.patch('/api/v1/users/:userId/missions/:missionId', handleCompleteMission)
+
+
+
+// 4. ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ (๋ฐ๋์ ๋ผ์ฐํฐ ๋ฑ๋ก ์ดํ์ ์์น)
+app.use(errorMiddleware)
+
+// 5. ์๋ฒ ์์
+app.listen(port, () => {
+ console.log(`[server]: Server is running at http://localhost:${port}`)
+})
diff --git "a/\353\217\204\354\226\217/week6/src/middleware/error.middleware.ts" "b/\353\217\204\354\226\217/week6/src/middleware/error.middleware.ts"
new file mode 100644
index 0000000..ec8849d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/middleware/error.middleware.ts"
@@ -0,0 +1,21 @@
+import { Request, Response, NextFunction } from 'express'
+
+interface AppError extends Error {
+ status?: number
+}
+
+export const errorMiddleware = (
+ err: AppError,
+ _req: Request,
+ res: Response,
+ _next: NextFunction,
+): void => {
+ console.error(`[Error] ${err.message}`)
+
+ const status = err.status ?? 500
+ res.status(status).json({
+ success: false,
+ code: `E${status}`,
+ message: err.message ?? '์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.',
+ })
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/members/controllers/member.controller.ts" "b/\353\217\204\354\226\217/week6/src/modules/members/controllers/member.controller.ts"
new file mode 100644
index 0000000..3494ab7
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/members/controllers/member.controller.ts"
@@ -0,0 +1,18 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { MemberSignUpRequest } from '../dtos/member.dto.js'
+import { signUp } from '../services/member.service.js'
+
+// POST /api/v1/members/signup
+export const handleSignUp = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const result = await signUp(req.body as MemberSignUpRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/members/dtos/member.dto.ts" "b/\353\217\204\354\226\217/week6/src/modules/members/dtos/member.dto.ts"
new file mode 100644
index 0000000..b0cbf3f
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/members/dtos/member.dto.ts"
@@ -0,0 +1,45 @@
+// ํ์๊ฐ์
์์ฒญ ์ธํฐํ์ด์ค
+export interface MemberSignUpRequest {
+ name: string
+ nickname: string
+ email?: string
+ password?: string
+ phoneNum?: string
+ birth?: string // "YYYY-MM-DD"
+ gender?: string // "MALE" | "FEMALE" | "OTHER"
+ address?: string
+ specAddress?: string
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToMember = (body: MemberSignUpRequest) => {
+ return {
+ name: body.name,
+ nickname: body.nickname,
+ email: body.email ?? null,
+ phoneNum: body.phoneNum ?? null,
+ birth: body.birth ? new Date(body.birth) : null,
+ gender: body.gender ?? null,
+ address: body.address ?? null,
+ specAddress: body.specAddress ?? null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromMember = (member: {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+}) => {
+ return {
+ memberId: member.id,
+ name: member.name,
+ nickname: member.nickname,
+ email: member.email,
+ phoneNum: member.phone_num,
+ status: member.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/members/repositories/member.repository.ts" "b/\353\217\204\354\226\217/week6/src/modules/members/repositories/member.repository.ts"
new file mode 100644
index 0000000..549030e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/members/repositories/member.repository.ts"
@@ -0,0 +1,26 @@
+import { prisma } from '../../../db.config.js'
+
+// ์ ์ ์์ฑ
+export const addUser = async (data: any) => {
+ const exists = await prisma.user.findFirst({ where: { email: data.email } })
+ if (exists) return null
+
+ const created = await prisma.user.create({ data })
+ return created.id
+}
+
+// ์ ์ ์กฐํ (์์ผ๋ฉด ์์ธ throw)
+export const getUser = async (userId: number) =>
+ prisma.user.findFirstOrThrow({ where: { id: userId } })
+
+// ์ ํธ ์์ ์นดํ
๊ณ ๋ฆฌ ๋ฑ๋ก
+export const setPreference = async (userId: number, foodCategoryId: number) =>
+ prisma.userFavorCategory.create({ data: { userId, foodCategoryId } })
+
+// ์ ํธ ์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํ (JOIN ํฌํจ)
+export const getUserPreferencesByUserId = async (userId: number) =>
+ prisma.userFavorCategory.findMany({
+ where: { userId },
+ include: { foodCategory: true },
+ orderBy: { foodCategoryId: 'asc' },
+ })
diff --git "a/\353\217\204\354\226\217/week6/src/modules/members/services/member.service.ts" "b/\353\217\204\354\226\217/week6/src/modules/members/services/member.service.ts"
new file mode 100644
index 0000000..f5a0e0e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/members/services/member.service.ts"
@@ -0,0 +1,42 @@
+import bcrypt from 'bcryptjs'
+import { MemberSignUpRequest, bodyToMember, responseFromMember } from '../dtos/member.dto.js'
+import {
+ addUser,
+ getUser,
+} from '../repositories/member.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const signUp = async (data: MemberSignUpRequest) => {
+ // ํ์๊ฐ ๊ฒ์ฆ
+ if (!data.name || !data.nickname) {
+ throw makeError('name๊ณผ nickname์ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค.', 400)
+ }
+
+ // ๋น๋ฐ๋ฒํธ ํด์ฑ (password๊ฐ ์๋ ๊ฒฝ์ฐ์๋ง)
+ const hashedPassword = data.password
+ ? await bcrypt.hash(data.password, 10)
+ : null
+
+ const memberData = bodyToMember(data)
+ const memberId = await addUser({ ...memberData, hashedPassword })
+
+ if (memberId === null) {
+ throw makeError('์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์
๋๋ค.', 409)
+ }
+
+ const member = await getUser(memberId)
+
+ return responseFromMember(member as {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/missions/controllers/mission.controller.ts" "b/\353\217\204\354\226\217/week6/src/modules/missions/controllers/mission.controller.ts"
new file mode 100644
index 0000000..52d7cc8
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/missions/controllers/mission.controller.ts"
@@ -0,0 +1,67 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { MissionCreateRequest, MissionChallengeRequest } from '../dtos/mission.dto.js'
+import { createMission, challengeMission, listStoreMissions, listOngoingMissions, finishMission } from '../services/mission.service.js'
+
+// GET /api/v1/users/:userId/missions
+export const handleListOngoingMissions = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = parseInt(String(req.params['userId'] ?? '0'), 10)
+ const cursor = typeof req.query['cursor'] === 'string' ? parseInt(req.query['cursor'], 10) : 0
+ res.status(200).json(await listOngoingMissions(userId, cursor))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// GET /api/v1/stores/:storeId/missions
+export const handleListStoreMissions = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const storeId = parseInt(String(req.params['storeId'] ?? '0'), 10)
+ const cursor = typeof req.query['cursor'] === 'string' ? parseInt(req.query['cursor'], 10) : 0
+ res.status(200).json(await listStoreMissions(storeId, cursor))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// POST /api/v1/stores/:storeId/missions
+export const handleCreateMission = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const storeId = parseInt(String(req.params['storeId'] ?? '0'), 10)
+ const result = await createMission(storeId, req.body as MissionCreateRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
+
+// POST /api/v1/missions/:missionId/challenge
+export const handleChallengeMission = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const missionId = parseInt(String(req.params['missionId'] ?? '0'), 10)
+ const result = await challengeMission(missionId, req.body as MissionChallengeRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
+
+// PATCH /api/v1/users/:userId/missions/:missionId
+export const handleCompleteMission = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = parseInt(String(req.params['userId'] ?? '0'), 10)
+ const missionId = parseInt(String(req.params['missionId'] ?? '0'), 10)
+ res.status(200).json(await finishMission(userId, missionId))
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/missions/dtos/mission.dto.ts" "b/\353\217\204\354\226\217/week6/src/modules/missions/dtos/mission.dto.ts"
new file mode 100644
index 0000000..8786670
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/missions/dtos/mission.dto.ts"
@@ -0,0 +1,57 @@
+// ๋ฏธ์
์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionCreateRequest {
+ title: string
+ reward: number
+ spec?: string
+ deadLine?: string // "YYYY-MM-DD"
+}
+
+// ๋ฏธ์
๋์ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionChallengeRequest {
+ memberId: number
+ status: 'CHALLENGING' | 'COMPLETE'
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ (๋ฏธ์
์ถ๊ฐ)
+export const bodyToMission = (body: MissionCreateRequest) => {
+ return {
+ title: body.title,
+ reward: body.reward,
+ spec: body.spec ?? null,
+ deadLine: body.deadLine ? new Date(body.deadLine) : null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
)
+export const responseFromMission = (mission: {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+}) => {
+ return {
+ missionId: mission.id,
+ storeId: mission.store_id,
+ title: mission.title,
+ reward: mission.reward,
+ spec: mission.spec,
+ deadLine: mission.dead_line,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
๋์ )
+export const responseFromMemberMission = (mm: {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+}) => {
+ return {
+ memberMissionId: mm.id,
+ memberId: mm.member_id,
+ missionId: mm.mission_id,
+ status: mm.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/missions/repositories/mission.repository.ts" "b/\353\217\204\354\226\217/week6/src/modules/missions/repositories/mission.repository.ts"
new file mode 100644
index 0000000..bee3e62
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/missions/repositories/mission.repository.ts"
@@ -0,0 +1,72 @@
+import { prisma } from '../../../db.config.js'
+
+// ๋ฏธ์
์ถ๊ฐ
+export const addMission = async (data: {
+ storeId: number
+ title: string
+ reward: number
+ spec: string | null
+ deadLine: Date | null
+}): Promise => {
+ const created = await prisma.mission.create({
+ data: {
+ storeId: data.storeId,
+ title: data.title,
+ reward: data.reward,
+ spec: data.spec,
+ deadLine: data.deadLine,
+ },
+ })
+ return created.id
+}
+
+// ๋ฏธ์
์กฐํ
+export const getMissionById = async (missionId: number) =>
+ prisma.mission.findFirst({ where: { id: missionId } })
+
+// ์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์ธ์ง ํ์ธ
+export const findMemberMission = async (memberId: number, missionId: number) =>
+ prisma.memberMission.findFirst({
+ where: { userId: memberId, missionId },
+ })
+
+// ๋ฏธ์
๋์ ์ถ๊ฐ
+export const addMemberMission = async (
+ memberId: number,
+ missionId: number,
+ status: string,
+): Promise => {
+ const created = await prisma.memberMission.create({
+ data: { userId: memberId, missionId, status },
+ })
+ return created.id
+}
+
+// ๋ฏธ์
๋์ ๊ธฐ๋ก ์กฐํ
+export const getMemberMissionById = async (memberMissionId: number) =>
+ prisma.memberMission.findFirst({ where: { id: memberMissionId } })
+
+// ํน์ ๊ฐ๊ฒ์ ๋ฏธ์
๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getStoreMissions = async (storeId: number, cursor: number) =>
+ prisma.mission.findMany({
+ where: { storeId, id: { gt: cursor } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+
+// ์ ์ ๊ฐ ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก ์กฐํ
+export const getOngoingMissions = async (userId: number, cursor: number) =>
+ prisma.memberMission.findMany({
+ where: { userId, status: '์งํ์ค', id: { gt: cursor } },
+ include: { mission: { include: { store: true } } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+// ์งํ ์ค์ธ ๋ฏธ์
์ ์๋ฃ๋ก ๋ณ๊ฒฝ
+export const completeMission = async (userId: number, missionId: number) =>
+ prisma.memberMission.updateMany({
+ where: { userId, missionId, status: '์งํ์ค' },
+ data: { status: '์๋ฃ' },
+ })
\ No newline at end of file
diff --git "a/\353\217\204\354\226\217/week6/src/modules/missions/services/mission.service.ts" "b/\353\217\204\354\226\217/week6/src/modules/missions/services/mission.service.ts"
new file mode 100644
index 0000000..b706590
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/missions/services/mission.service.ts"
@@ -0,0 +1,116 @@
+import {
+ MissionCreateRequest,
+ MissionChallengeRequest,
+ bodyToMission,
+ responseFromMission,
+ responseFromMemberMission,
+} from '../dtos/mission.dto.js'
+import {
+ addMission,
+ getMissionById,
+ findMemberMission,
+ addMemberMission,
+ getMemberMissionById,
+ getStoreMissions,
+ getOngoingMissions,
+ completeMission,
+} from '../repositories/mission.repository.js'
+import { getStoreById } from '../../stores/repositories/store.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+// ๋ฏธ์
์ถ๊ฐ
+export const createMission = async (storeId: number, data: MissionCreateRequest) => {
+ // ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.', 404)
+ }
+
+ const missionData = bodyToMission(data)
+ const missionId = await addMission({ ...missionData, storeId })
+
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw makeError('๋ฏธ์
์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromMission(mission as unknown as {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+ })
+}
+
+// ํน์ ๊ฐ๊ฒ์ ๋ฏธ์
๋ชฉ๋ก
+export const listStoreMissions = async (storeId: number, cursor: number) => {
+ const missions = await getStoreMissions(storeId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+// ๋ด๊ฐ ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก
+export const listOngoingMissions = async (userId: number, cursor: number) => {
+ const missions = await getOngoingMissions(userId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+// ์งํ ์ค์ธ ๋ฏธ์
์ ์๋ฃ๋ก ๋ณ๊ฒฝ
+export const finishMission = async (userId: number, missionId: number) => {
+ const result = await completeMission(userId, missionId)
+ if (result.count === 0) {
+ throw makeError('์งํ ์ค์ธ ๋ฏธ์
์ด ์์ต๋๋ค.', 404)
+ }
+ return { message: '๋ฏธ์
์ด ์๋ฃ ์ฒ๋ฆฌ๋์ต๋๋ค.' }
+}
+
+// ๋ฏธ์
๋์ ํ๊ธฐ
+export const challengeMission = async (
+ missionId: number,
+ data: MissionChallengeRequest,
+) => {
+ // ๋ฏธ์
์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๋ฏธ์
์
๋๋ค.', 404)
+ }
+
+ // ํ์๊ฐ ๊ฒ์ฆ
+ if (!data.status) {
+ throw makeError('status๋ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค. (CHALLENGING ๋๋ COMPLETE)', 400)
+ }
+
+ // ์ด๋ฏธ ๋์ ์ค์ธ์ง ๊ฒ์ฆ
+ const existing = await findMemberMission(data.memberId, missionId)
+ if (existing) {
+ throw makeError('์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.', 409)
+ }
+
+ const memberMissionId = await addMemberMission(data.memberId, missionId, data.status)
+
+ const memberMission = await getMemberMissionById(memberMissionId)
+ if (!memberMission) {
+ throw makeError('๋ฏธ์
๋์ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromMemberMission(memberMission as unknown as {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/reviews/controllers/review.controller.ts" "b/\353\217\204\354\226\217/week6/src/modules/reviews/controllers/review.controller.ts"
new file mode 100644
index 0000000..9cef30f
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/reviews/controllers/review.controller.ts"
@@ -0,0 +1,30 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { ReviewCreateRequest } from '../dtos/review.dto.js'
+import { createReview, listUserReviews } from '../services/review.service.js'
+
+// GET /api/v1/users/:userId/reviews
+export const handleListUserReviews = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = parseInt(req.params['userId'] ?? '0', 10)
+ const cursor = typeof req.query['cursor'] === 'string' ? parseInt(req.query['cursor'], 10) : 0
+ res.status(200).json(await listUserReviews(userId, cursor))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// POST /api/v1/stores/:storeId/reviews
+export const handleCreateReview = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const storeId = parseInt(req.params['storeId'] ?? '0', 10)
+ const result = await createReview(storeId, req.body as ReviewCreateRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/reviews/dtos/review.dto.ts" "b/\353\217\204\354\226\217/week6/src/modules/reviews/dtos/review.dto.ts"
new file mode 100644
index 0000000..0738b1c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/reviews/dtos/review.dto.ts"
@@ -0,0 +1,43 @@
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface ReviewCreateRequest {
+ memberId: number
+ content: string
+ score: number
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToReview = (body: ReviewCreateRequest) => {
+ return {
+ memberId: body.memberId,
+ content: body.content,
+ score: body.score,
+ }
+}
+
+// ๋ฆฌ๋ทฐ ๋ชฉ๋ก โ ์๋ต ํํ๋ก ๋ณํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const responseFromUserReviews = (reviews: any[]) => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromReview = (review: {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+}) => {
+ return {
+ reviewId: review.id,
+ memberId: review.member_id,
+ storeId: review.store_id,
+ content: review.content,
+ score: review.score,
+ createdAt: review.created_at,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/reviews/repositories/review.repository.ts" "b/\353\217\204\354\226\217/week6/src/modules/reviews/repositories/review.repository.ts"
new file mode 100644
index 0000000..adca307
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/reviews/repositories/review.repository.ts"
@@ -0,0 +1,30 @@
+import { prisma } from '../../../db.config.js'
+
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ
+export const addReview = async (data: {
+ memberId: number
+ storeId: number
+ content: string
+}): Promise => {
+ const created = await prisma.userStoreReview.create({
+ data: {
+ userId: data.memberId,
+ storeId: data.storeId,
+ content: data.content,
+ },
+ })
+ return created.id
+}
+
+// ๋ฆฌ๋ทฐ ์กฐํ
+export const getReviewById = async (reviewId: number) =>
+ prisma.userStoreReview.findFirst({ where: { id: reviewId } })
+
+// ๋ด๊ฐ ์์ฑํ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getUserReviews = async (userId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ where: { userId, id: { gt: cursor } },
+ include: { store: true },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week6/src/modules/reviews/services/review.service.ts" "b/\353\217\204\354\226\217/week6/src/modules/reviews/services/review.service.ts"
new file mode 100644
index 0000000..bdd952f
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/reviews/services/review.service.ts"
@@ -0,0 +1,44 @@
+import { ReviewCreateRequest, bodyToReview, responseFromReview, responseFromUserReviews } from '../dtos/review.dto.js'
+import { addReview, getReviewById, getUserReviews } from '../repositories/review.repository.js'
+import { getStoreById } from '../../stores/repositories/store.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const listUserReviews = async (userId: number, cursor: number) => {
+ const reviews = await getUserReviews(userId, cursor)
+ return responseFromUserReviews(reviews)
+}
+
+export const createReview = async (storeId: number, data: ReviewCreateRequest) => {
+ // ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw makeError('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.', 404)
+ }
+
+ // ๋ณ์ ์ ํจ์ฑ ๊ฒ์ฌ
+ if (data.score < 1 || data.score > 5) {
+ throw makeError('๋ณ์ ์ 1~5 ์ฌ์ด์ฌ์ผ ํฉ๋๋ค.', 400)
+ }
+
+ const reviewData = bodyToReview(data)
+ const reviewId = await addReview({ ...reviewData, storeId })
+
+ const review = await getReviewById(reviewId)
+ if (!review) {
+ throw makeError('๋ฆฌ๋ทฐ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromReview(review as {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+ })
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/stores/controllers/store.controller.ts" "b/\353\217\204\354\226\217/week6/src/modules/stores/controllers/store.controller.ts"
new file mode 100644
index 0000000..f626991
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/stores/controllers/store.controller.ts"
@@ -0,0 +1,29 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { StoreCreateRequest } from '../dtos/store.dto.js'
+import { createStore, listStoreReviews } from '../services/store.service.js'
+
+// POST /api/v1/stores
+export const handleCreateStore = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const result = await createStore(req.body as StoreCreateRequest)
+ res.status(StatusCodes.CREATED).json({ success: true, data: result })
+ } catch (err) {
+ next(err)
+ }
+}
+
+// GET /api/v1/stores/:storeId/reviews
+export const handleListStoreReviews = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const storeId = parseInt(req.params.storeId, 10)
+ const cursor = typeof req.query.cursor === 'string' ? parseInt(req.query.cursor, 10) : 0
+ res.status(200).json(await listStoreReviews(storeId, cursor))
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/stores/dtos/store.dto.ts" "b/\353\217\204\354\226\217/week6/src/modules/stores/dtos/store.dto.ts"
new file mode 100644
index 0000000..6922309
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/stores/dtos/store.dto.ts"
@@ -0,0 +1,49 @@
+// ๊ฐ๊ฒ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface StoreCreateRequest {
+ regionId: number
+ foodCategoryId: number
+ name: string
+ description?: string
+ address: string
+ lat?: number
+ lng?: number
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToStore = (body: StoreCreateRequest) => {
+ return {
+ regionId: body.regionId,
+ foodCategoryId: body.foodCategoryId,
+ name: body.name,
+ description: body.description ?? null,
+ address: body.address,
+ lat: body.lat ?? null,
+ lng: body.lng ?? null,
+ }
+}
+
+// ๋ฆฌ๋ทฐ ๋ชฉ๋ก โ ์๋ต ํํ๋ก ๋ณํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const responseFromReviews = (reviews: any[]) => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews,
+ pagination: {
+ cursor: last ? last.id : null,
+ },
+ }
+}
+
+// DB ์กฐํ ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromStore = (store: {
+ id: number
+ name: string
+ address: string
+ region_id: number
+}) => {
+ return {
+ storeId: store.id,
+ name: store.name,
+ address: store.address,
+ regionId: store.region_id,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/src/modules/stores/repositories/store.repository.ts" "b/\353\217\204\354\226\217/week6/src/modules/stores/repositories/store.repository.ts"
new file mode 100644
index 0000000..9a9ac1e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/stores/repositories/store.repository.ts"
@@ -0,0 +1,28 @@
+import { prisma } from '../../../db.config.js'
+
+// ๊ฐ๊ฒ ์ถ๊ฐ
+export const addStore = async (data: { name: string }): Promise => {
+ const created = await prisma.store.create({ data })
+ return created.id
+}
+
+// ๊ฐ๊ฒ ์กฐํ
+export const getStoreById = async (storeId: number) =>
+ prisma.store.findFirst({ where: { id: storeId } })
+
+// ๊ฐ๊ฒ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getAllStoreReviews = async (storeId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ select: {
+ id: true,
+ content: true,
+ store: true,
+ user: true,
+ },
+ where: {
+ storeId,
+ id: { gt: cursor },
+ },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week6/src/modules/stores/services/store.service.ts" "b/\353\217\204\354\226\217/week6/src/modules/stores/services/store.service.ts"
new file mode 100644
index 0000000..94e88f8
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/src/modules/stores/services/store.service.ts"
@@ -0,0 +1,30 @@
+import { StoreCreateRequest, bodyToStore, responseFromStore, responseFromReviews } from '../dtos/store.dto.js'
+import { addStore, getStoreById, getAllStoreReviews } from '../repositories/store.repository.js'
+
+const makeError = (message: string, status: number): Error & { status: number } => {
+ const err = new Error(message) as Error & { status: number }
+ err.status = status
+ return err
+}
+
+export const listStoreReviews = async (storeId: number, cursor: number) => {
+ const reviews = await getAllStoreReviews(storeId, cursor)
+ return responseFromReviews(reviews)
+}
+
+export const createStore = async (data: StoreCreateRequest) => {
+ const storeData = bodyToStore(data)
+ const storeId = await addStore(storeData)
+
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw makeError('๊ฐ๊ฒ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.', 500)
+ }
+
+ return responseFromStore(store as {
+ id: number
+ name: string
+ address: string
+ region_id: number
+ })
+}
diff --git "a/\353\217\204\354\226\217/week6/todolist.json" "b/\353\217\204\354\226\217/week6/todolist.json"
new file mode 100644
index 0000000..830b572
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/todolist.json"
@@ -0,0 +1,353 @@
+{
+ "chapter": "Chapter 5. API ๋ฐ ํ๋ก์ ํธ ์ค์ ๊ธฐ์ด",
+ "branch": "feature/chapter-05",
+ "week4_reference": "../week4",
+ "keywords": [
+ {
+ "term": "ํ๊ฒฝ ๋ณ์ (Environment Variables)",
+ "summary": "DB ๋น๋ฐ๋ฒํธยทAPI Key ๋ฑ ๋ฏผ๊ฐํ ๊ฐ์ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ณ .env ํ์ผ๋ก ๊ด๋ฆฌ. dotenv ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ก๋ํ๋ฉฐ .gitignore์ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํจ.",
+ "example": "process.env.DB_PASSWORD"
+ },
+ {
+ "term": "CORS (Cross-Origin Resource Sharing)",
+ "summary": "๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ Origin(๋๋ฉ์ธยทํฌํธ)์ ์๋ฒ์ ์์ฒญํ ๋ ๋ฐ์ํ๋ ๋ณด์ ์ ์ฑ
. Express์์๋ cors ๋ฏธ๋ค์จ์ด๋ก ํ์ฉ ์ฒ๋ฆฌ.",
+ "example": "app.use(cors())"
+ },
+ {
+ "term": "DB Connection Pool",
+ "summary": "๋งค ์์ฒญ๋ง๋ค DB ์ปค๋ฅ์
์ ์๋ก ์์ฑยทํด์ ํ์ง ์๊ณ , ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋ ์ปค๋ฅ์
ํ์์ ๋น๋ ค ์ฐ๋ ๋ฐฉ์. mysql2์ createPool()๋ก ๊ตฌํ. finally ๋ธ๋ก์์ conn.release()๋ก ๋ฐํํด์ผ ํจ.",
+ "example": "const pool = mysql.createPool({ connectionLimit: 10 })"
+ },
+ {
+ "term": "๋น๋๊ธฐ (async / await)",
+ "summary": "DB ์ฟผ๋ฆฌยท์ธ๋ถ API ํธ์ถ์ฒ๋ผ ์๋ต ๋๊ธฐ๊ฐ ํ์ํ ์์
์ Promise ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌ. async ํจ์ ์์์ await๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ฉฐ, ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์ฝ๊ธฐ ์ฝ๊ฒ ์์ฑ ๊ฐ๋ฅ.",
+ "example": "const [rows] = await pool.query('SELECT ...')"
+ },
+ {
+ "term": "try / catch / finally",
+ "summary": "๋น๋๊ธฐ ์์
์์ ๋ฐ์ํ ์ ์๋ ์๋ฌ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฒ๋ฆฌ. try: ์ ์ ๋ก์ง, catch: ์๋ฌ ์ฒ๋ฆฌ, finally: ์ปค๋ฅ์
๋ฐํ(conn.release()) ๋ฑ ํญ์ ์คํ๋์ด์ผ ํ๋ ์ ๋ฆฌ ์ฝ๋.",
+ "example": "try { ... } catch(err) { throw new Error(...) } finally { conn.release() }"
+ },
+ {
+ "term": "Interface (์ธํฐํ์ด์ค)",
+ "summary": "TypeScript์์ ๊ฐ์ฒด์ ํํ(์์ฑยทํ์
)๋ฅผ ์ ์ํ๋ ์ค๊ณ๋. DTOยทRepository ๋ฐํ ํ์
๋ฑ์ ํ์ฉ. ?๋ฅผ ๋ถ์ด๋ฉด ์ ํ์ ํ๋กํผํฐ.",
+ "example": "export interface StoreCreateRequest { regionId: number; name: string; address: string; }"
+ },
+ {
+ "term": "Type Assertion (as ํค์๋)",
+ "summary": "TypeScript ์ปดํ์ผ๋ฌ์๊ฒ '์ด ๊ฐ์ ์ด ํ์
์ด์ผ'๋ผ๊ณ ๊ฐ์ ๋ก ์๋ ค์ฃผ๋ ๋ฌธ๋ฒ. req.body๋ ๊ธฐ๋ณธ์ ์ผ๋ก any์ด๋ฏ๋ก, as ๋ก ์ ์ํ ์ธํฐํ์ด์ค ํ์
์ผ๋ก ๋ณํํด ์ฌ์ฉ.",
+ "example": "req.body as StoreCreateRequest"
+ }
+ ],
+ "project_structure": {
+ "root": "week5/",
+ "note": "week4์ in-memory DB โ MySQL ์ค์ DB ์ฐ๊ฒฐ๋ก ์ ํ. ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค ๊ตฌ์กฐ ์ฑํ.",
+ "files": [
+ "src/index.ts - Express ์ฑ ์ง์
์ , ๋ฏธ๋ค์จ์ดยท๋ผ์ฐํฐ ๋ฑ๋ก",
+ "src/db.config.ts - MySQL Connection Pool ์ค์ ",
+ "src/modules/stores/controllers/store.controller.ts",
+ "src/modules/stores/services/store.service.ts",
+ "src/modules/stores/repositories/store.repository.ts",
+ "src/modules/stores/dtos/store.dto.ts",
+ "src/modules/reviews/controllers/review.controller.ts",
+ "src/modules/reviews/services/review.service.ts",
+ "src/modules/reviews/repositories/review.repository.ts",
+ "src/modules/reviews/dtos/review.dto.ts",
+ "src/modules/missions/controllers/mission.controller.ts",
+ "src/modules/missions/services/mission.service.ts",
+ "src/modules/missions/repositories/mission.repository.ts",
+ "src/modules/missions/dtos/mission.dto.ts",
+ ".env - DB ์ ์ ์ ๋ณดยทPORT (gitignore ํ์)",
+ ".gitignore",
+ "package.json",
+ "tsconfig.json",
+ "schema.sql - week4 schema.sql ์ฌ์ฌ์ฉ (ํ
์ด๋ธ ์ด๋ฏธ ์ ์๋จ)"
+ ]
+ },
+ "todos": [
+ {
+ "id": 1,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "GitHub ์ด์ ์์ฑ ๋ฐ ๋ธ๋์น ๋ถ๊ธฐ",
+ "status": "todo",
+ "details": [
+ "GitHub ์ ์ฅ์ Issues ํญ์์ ๋ผ๋ฒจ ์ ๋ฆฌ: bug, docs, feature, refactor",
+ "์ด์ ์ ๋ชฉ: '[feat] Chapter 5 - API ๊ตฌํ (๊ฐ๊ฒ ์ถ๊ฐ / ๋ฆฌ๋ทฐ / ๋ฏธ์
)'",
+ "Assignee: ๋ณธ์ธ, Label: feature ๋ก ์ด์ ์์ฑ",
+ "์ด์์์ 'Create a branch' ํด๋ฆญ โ feature/chapter-05 ๋ธ๋์น ์์ฑ",
+ "๋ก์ปฌ์์: git fetch origin && git checkout feature/chapter-05"
+ ]
+ },
+ {
+ "id": 2,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "Postman ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ ํ์ธ",
+ "status": "todo",
+ "details": [
+ "Postman ์ค์น (https://www.postman.com/downloads/)",
+ "Params / Authorization / Headers / Body ํญ ์ญํ ์ดํด",
+ "Body > raw > JSON ์ ํ ๋ฐฉ๋ฒ ์์ง",
+ "๋์ค์ API ํ
์คํธ ์ ์คํฌ๋ฆฐ์ท ์ ์ฅํ ์ค๋น"
+ ]
+ },
+ {
+ "id": 3,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "week5 ํด๋ ์ด๊ธฐํ ๋ฐ ์์กด์ฑ ์ค์น",
+ "status": "todo",
+ "details": [
+ "cd week5 && npm init -y",
+ "npm install express cors dotenv http-status-codes mysql2",
+ "npm install -D typescript @types/node @types/express @types/cors @types/dotenv nodemon tsx",
+ "npx tsc --init ํ tsconfig.json ์์ (rootDir: ./src, outDir: ./dist, module: NodeNext, strict: true ๋ฑ)",
+ "week4/tsconfig.json์ ์ฐธ๊ณ ํด module/moduleResolution ์ค์ ์ผ์น์ํค๊ธฐ",
+ "package.json scripts ์ถ๊ฐ: start / dev (nodemon --exec tsx src/index.ts)"
+ ]
+ },
+ {
+ "id": 4,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": ".env ํ์ผ ๋ฐ .gitignore ์์ฑ",
+ "status": "todo",
+ "details": [
+ ".gitignore์ node_modules/ / .env / .env.* ์ถ๊ฐ",
+ ".env์ PORT=3000, DB_HOST=localhost, DB_PORT=3306, DB_USER=root, DB_PASSWORD=๋น๋ฐ๋ฒํธ, DB_NAME=umc_mission ์์ฑ",
+ "DB_NAME์ week4/schema.sql ๊ธฐ์ค umc_mission ์ฌ์ฉ (์ด๋ฏธ ํ
์ด๋ธ ์์)"
+ ]
+ },
+ {
+ "id": 5,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/db.config.ts ์์ฑ - MySQL Connection Pool",
+ "status": "todo",
+ "details": [
+ "mysql2/promise์ createPool ์ฌ์ฉ",
+ "ํ๊ฒฝ ๋ณ์(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME)๋ก ์ค์ ",
+ "connectionLimit: 10, waitForConnections: true",
+ "week4 schema.sql์ DB(umc_mission)์ ์ฐ๊ฒฐ"
+ ],
+ "reference_week4": "week4/src/db/index.ts ๊ตฌ์กฐ ์ฐธ๊ณ (๋จ, in-memoryโMySQL๋ก ๋ณ๊ฒฝ)"
+ },
+ {
+ "id": 6,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/index.ts ์์ฑ - Express ์ฑ ์ง์
์ ",
+ "status": "todo",
+ "details": [
+ "dotenv.config() ์ต์๋จ ํธ์ถ",
+ "cors(), express.json(), express.urlencoded() ๋ฏธ๋ค์จ์ด ๋ฑ๋ก",
+ "๊ฐ ๋ชจ๋ ๋ผ์ฐํฐ ๋ฑ๋ก: /api/v1/stores, /api/v1/reviews, /api/v1/missions",
+ "์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ๋ฏธ๋ค์จ์ด ๋ฑ๋ก (์๋์ด ๋ฏธ์
: JSON ์๋ฌ ์๋ต)",
+ "app.listen(process.env.PORT || 3000)"
+ ],
+ "reference_week4": "week4/src/index.ts ๊ตฌ์กฐ ๊ทธ๋๋ก ํ์ฉ, corsยทdotenv ์ถ๊ฐ"
+ },
+ {
+ "id": 7,
+ "phase": "DB ์ค๋น",
+ "title": "MySQL์ week5์ฉ ํ
์ด๋ธ ํ์ธ ๋ฐ ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
",
+ "status": "todo",
+ "details": [
+ "week4/schema.sql๋ก ํ
์ด๋ธ ์์ฑ (์ด๋ฏธ ๋์ด์์ผ๋ฉด ์๋ต)",
+ "food_category ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO food_category(name) VALUES ('ํ์'),('์ค์'),('์ผ์'),('์์'),('์นํจ'),('๋ถ์'),('๊ณ ๊ธฐ/๊ตฌ์ด'),('๋์๋ฝ'),('์์'),('ํจ์คํธํธ๋'),('๋ค์ ํธ'),('์์์ํธ๋')",
+ "region ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO region(name) VALUES ('์์ธ'),('๊ฒฝ๊ธฐ'),('๋ถ์ฐ')...",
+ "member ๋๋ฏธ๋ฐ์ดํฐ 1๊ฑด ์ฝ์
(API ํ
์คํธ์ฉ ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ ]
+ },
+ {
+ "id": 8,
+ "phase": "API ๊ตฌํ - 1-1",
+ "title": "[ํ์] ํน์ ์ง์ญ์ ๊ฐ๊ฒ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores",
+ "request_body": {
+ "regionId": "number",
+ "foodCategoryId": "number",
+ "name": "string",
+ "description": "string (์ ํ)",
+ "address": "string",
+ "lat": "number (์ ํ)",
+ "lng": "number (์ ํ)"
+ },
+ "details": [
+ "store.dto.ts - StoreCreateRequest ์ธํฐํ์ด์ค ์ ์",
+ "store.dto.ts - bodyToStore() ๋ณํ ํจ์ ์์ฑ",
+ "store.repository.ts - addStore(): INSERT INTO store(...) VALUES(?)",
+ "store.service.ts - createStore(data): addStore ํธ์ถ ํ ๊ฒฐ๊ณผ ๋ฐํ",
+ "store.controller.ts - handleCreateStore: bodyToStore(req.body as ...) โ service ํธ์ถ โ 201 ์๋ต",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก: app.post('/api/v1/stores', handleCreateStore)"
+ ],
+ "reference_week4": "week4/src/controllers/store.controller.ts ํจํด ์ฐธ๊ณ "
+ },
+ {
+ "id": 9,
+ "phase": "API ๊ตฌํ - 1-2",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/stores/:storeId/reviews",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)",
+ "content": "string",
+ "score": "number (1.0~5.0)"
+ },
+ "details": [
+ "review.dto.ts - ReviewCreateRequest ์ธํฐํ์ด์ค (content, score, memberId)",
+ "review.dto.ts - bodyToReview() ๋ณํ ํจ์",
+ "review.repository.ts - addReview(): INSERT INTO review(...)",
+ "store.repository.ts (๋๋ review.repository.ts) - findStoreById(): SELECT * FROM store WHERE id=?",
+ "review.service.ts - createReview(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addReview ํธ์ถ",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.')",
+ "review.controller.ts - handleCreateReview: storeId = parseInt(req.params.storeId) โ service ํธ์ถ",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋ฆฌ๋ทฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ ๊ฐ๊ฒ๊ฐ ์กด์ฌํ๋์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/services/store.service.ts์ createReview ํจํด, week4 ์คํค๋ง์ review ํ
์ด๋ธ"
+ },
+ {
+ "id": 10,
+ "phase": "API ๊ตฌํ - 1-3",
+ "title": "๊ฐ๊ฒ์ ๋ฏธ์
์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores/:storeId/missions",
+ "request_body": {
+ "title": "string",
+ "reward": "number",
+ "spec": "string (์ ํ)",
+ "deadLine": "string (YYYY-MM-DD, ์ ํ)"
+ },
+ "details": [
+ "mission.dto.ts - MissionCreateRequest ์ธํฐํ์ด์ค",
+ "mission.dto.ts - bodyToMission() ๋ณํ ํจ์ (deadLine โ Date ๋ณํ)",
+ "mission.repository.ts - addMission(): INSERT INTO mission(store_id, title, reward, spec, dead_line)",
+ "mission.service.ts - createMission(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addMission",
+ "mission.controller.ts - handleCreateMission",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4/src/repositories/mission.repository.ts ํจํด, week4 schema.sql์ mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 11,
+ "phase": "API ๊ตฌํ - 1-4",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฏธ์
์ ๋์ ์ค์ธ ๋ฏธ์
์ ์ถ๊ฐ(๋ฏธ์
๋์ ํ๊ธฐ) API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/missions/:missionId/challenge",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ },
+ "details": [
+ "mission.dto.ts - MissionChallengeRequest ์ธํฐํ์ด์ค ({ memberId: number })",
+ "mission.repository.ts - findMemberMission(): SELECT * FROM member_mission WHERE member_id=? AND mission_id=?",
+ "mission.repository.ts - addMemberMission(): INSERT INTO member_mission(member_id, mission_id, status) VALUES(?,?,'CHALLENGING')",
+ "mission.service.ts - challengeMission(missionId, data): ์ค๋ณต ๋์ ๊ฒ์ฆ โ addMemberMission",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.')",
+ "mission.controller.ts - handleChallengeMission: missionId = parseInt(req.params.missionId) โ service",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋์ ํ๋ ค๋ ๋ฏธ์
์ด ์ด๋ฏธ ๋์ ์ค์ด์ง๋ ์์์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/repositories/mission.repository.ts, week4 schema.sql์ member_mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 12,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
2๋ฒ] Controller โ Service โ Repository โ DB ์์ฒญ ํ๋ฆ ์ ๋ฆฌ",
+ "status": "todo",
+ "details": [
+ "์: POST /api/v1/stores/:storeId/reviews ์์ฒญ ํ๋ฆ์ ์์๋๋ก ์์ฑ",
+ "1. ์ฌ์ฉ์๊ฐ POST /api/v1/stores/1/reviews ์์ฒญ ์ ์ก",
+ "2. index.ts์ ๋ผ์ฐํฐ๊ฐ handleCreateReview ์ปจํธ๋กค๋ฌ ํธ์ถ",
+ "3. Controller: req.body๋ฅผ ReviewCreateRequest ํ์
์ผ๋ก ๋ณํ(bodyToReview), storeId ํ์ฑ",
+ "4. Service: ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ(findStoreById) โ ์์ผ๋ฉด Error throw",
+ "5. Repository: INSERT INTO review ์ฟผ๋ฆฌ ์คํ โ insertId ๋ฐํ",
+ "6. Service: ๊ฒฐ๊ณผ๋ฅผ DTO๋ก ๋ณํํด Controller์ ๋ฐํ",
+ "7. Controller: 201 JSON ์๋ต ์ ์ก",
+ "์ํฌ๋ถ์ ์์ฝ ์ ๋ฆฌ ์น์
์ ์ด ๋ด์ฉ ํฌํจ"
+ ]
+ },
+ {
+ "id": 13,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
3๋ฒ] ํ์๊ฐ์
API์ bcrypt ๋น๋ฐ๋ฒํธ ํด์ฑ ์ถ๊ฐ",
+ "status": "todo",
+ "details": [
+ "npm install bcryptjs && npm install -D @types/bcryptjs (week4์ ์ด๋ฏธ ์ค์น๋จ)",
+ "member.dto.ts - MemberSignUpRequest ์ธํฐํ์ด์ค์ password ํ๋ ์ถ๊ฐ",
+ "member.repository.ts - addMember(): INSERT INTO member(..., password) VALUES(...)",
+ "member.service.ts - signUp(): const hashedPw = await bcrypt.hash(data.password, 10) โ addMember์ ์ ๋ฌ",
+ "member.controller.ts - handleSignUp ์์ฑ",
+ "POST /api/v1/members/signup ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4์์ bcryptjs ์ด๋ฏธ ์ฌ์ฉ ์ค - week4/package.json ์ฐธ๊ณ "
+ },
+ {
+ "id": 14,
+ "phase": "์๋์ด ๋ฏธ์
",
+ "title": "[์๋์ด] ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ - JSON ํํ ์๋ฌ ์๋ต",
+ "status": "todo",
+ "details": [
+ "src/middleware/error.middleware.ts ์์ฑ",
+ "ErrorRequestHandler ํ์
์ฌ์ฉ: (err, req, res, next) => void",
+ "res.status(err.status || 500).json({ success: false, message: err.message || '์๋ฒ ์๋ฌ' })",
+ "index.ts ๋งจ ๋ง์ง๋ง์ app.use(errorMiddleware) ๋ฑ๋ก",
+ "Controller์์ try-catch ์ ๊ฑฐํ๊ฑฐ๋ next(err) ์ฌ์ฉ์ผ๋ก ๋ณ๊ฒฝ",
+ "๊ธฐ์กด HTML ์๋ฌ ์๋ต โ JSON ์๋ฌ ์๋ต์ผ๋ก ๊ฐ์ "
+ ],
+ "reference_week4": "week4/src/middleware/error.middleware.ts ๊ทธ๋๋ก ํ์ฉ ๊ฐ๋ฅ"
+ },
+ {
+ "id": 15,
+ "phase": "ํ
์คํธ",
+ "title": "Postman / curl๋ก ๊ฐ API ํธ์ถ ๋ฐ ์คํฌ๋ฆฐ์ท ์ ์ฅ",
+ "status": "todo",
+ "details": [
+ "npm run dev ๋ก ์๋ฒ ์คํ",
+ "API 1-1: POST /api/v1/stores - ๊ฐ๊ฒ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: POST /api/v1/stores/:storeId/reviews - ๋ฆฌ๋ทฐ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: ์กด์ฌํ์ง ์๋ storeId๋ก ์์ฒญ โ ์๋ฌ ์๋ต ์คํฌ๋ฆฐ์ท",
+ "API 1-3: POST /api/v1/stores/:storeId/missions - ๋ฏธ์
์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: POST /api/v1/missions/:missionId/challenge - ๋์ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: ๋์ผ ๋ฏธ์
์ฌ๋์ โ '์ด๋ฏธ ๋์ ์ค' ์๋ฌ ์คํฌ๋ฆฐ์ท",
+ "DB์์ SELECT๋ก ๋ฐ์ดํฐ ์ฝ์
ํ์ธ ์คํฌ๋ฆฐ์ท"
+ ]
+ },
+ {
+ "id": 16,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "feature/chapter-05 ๋ธ๋์น์ push ๋ฐ PR ์์ฑ",
+ "status": "todo",
+ "details": [
+ "git add . && git commit -m 'feat: 5์ฃผ์ฐจ ๋ฏธ์
- API ๊ตฌํ (๊ฐ๊ฒ/๋ฆฌ๋ทฐ/๋ฏธ์
)'",
+ "git push origin feature/chapter-05",
+ "GitHub์์ PR ์์ฑ (main ๋ธ๋์น์ mergeํ์ง ๋ง ๊ฒ!)",
+ "์ํฌ๋ถ์ ๋ฏธ์
๊ธฐ๋ก๋์ GitHub ๋งํฌ ์ ์ถ",
+ "GitHub ์ด์ Close"
+ ]
+ },
+ {
+ "id": 17,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "ํต์ฌ ํค์๋ ๋ฐ ์์ฝ ์ ๋ฆฌ ์์ฑ",
+ "status": "todo",
+ "details": [
+ "์ด ํ์ผ ์๋จ์ keywords ์น์
์ ์ฐธ๊ณ ํด ์ํฌ๋ถ์ ๊ธฐ์
",
+ "์์ฝ ์ ๋ฆฌ: ControllerโServiceโRepositoryโDB ํ๋ฆ ์ค๋ช
",
+ "์ํด๋ฆฌ ์คํฌ๋ผ ์ง๋ฌธ ๋ต๋ณ: DTO ์์ด ์ฌ์ฉํ ๋์ ๋ฌธ์ ์ / Service Layer ํ์์ฑ"
+ ]
+ }
+ ],
+ "required_apis_summary": {
+ "must_implement": ["1-2 (๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ)", "1-4 (๋ฏธ์
๋์ ํ๊ธฐ)"],
+ "minimum_count": "ํ์ 2๊ฐ ํฌํจ ์ด 3๊ฐ ์ด์",
+ "senior_mission": "4๊ฐ ์ ๋ถ + JSON ์๋ฌ ์๋ต ๊ฐ์ "
+ },
+ "key_differences_from_week4": {
+ "database": "in-memory(week4) โ MySQL Connection Pool(week5)",
+ "modules": "flat ๊ตฌ์กฐ(week4) โ ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค(week5: src/modules/{๋๋ฉ์ธ}/)",
+ "env": ".env ํ์ผ ์ถ๊ฐ (dotenv ์ฌ์ฉ)",
+ "cors": "cors ๋ฏธ๋ค์จ์ด ์ถ๊ฐ",
+ "error_response": "HTML ์๋ฌ(๊ธฐ๋ณธ) โ JSON ์๋ฌ(์๋์ด ๋ฏธ์
๊ฐ์ )"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week6/tsconfig.json" "b/\353\217\204\354\226\217/week6/tsconfig.json"
new file mode 100644
index 0000000..d25f8a3
--- /dev/null
+++ "b/\353\217\204\354\226\217/week6/tsconfig.json"
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "target": "ESNext",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "lib": ["ESNext"],
+ "types": ["node"],
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "sourceMap": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git "a/\353\217\204\354\226\217/week7/.gitignore" "b/\353\217\204\354\226\217/week7/.gitignore"
new file mode 100644
index 0000000..7206753
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/.gitignore"
@@ -0,0 +1,22 @@
+# dependency directories
+node_modules/
+
+# build output
+dist/
+
+# dotenv environment variable files
+.env
+.env.local
+.env.development
+.env.production
+.env.*
+.claude*
+
+# macOS
+.DS_Store
+
+# logs
+*.log
+npm-debug.log*
+
+/src/generated/prisma
diff --git "a/\353\217\204\354\226\217/week7/README.md" "b/\353\217\204\354\226\217/week7/README.md"
new file mode 100644
index 0000000..bb278f1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/README.md"
@@ -0,0 +1 @@
+# umc-node-study
\ No newline at end of file
diff --git "a/\353\217\204\354\226\217/week7/package-lock.json" "b/\353\217\204\354\226\217/week7/package-lock.json"
new file mode 100644
index 0000000..df28c13
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/package-lock.json"
@@ -0,0 +1,3250 @@
+{
+ "name": "umc-node-study",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0",
+ "morgan": "^1.10.1"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/morgan": "^1.9.10",
+ "@types/node": "^25.6.0",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0",
+ "typescript": "^6.0.3"
+ }
+ },
+ "node_modules/@electric-sql/pglite": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz",
+ "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@electric-sql/pglite-socket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz",
+ "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "pglite-server": "dist/scripts/server.js"
+ },
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@electric-sql/pglite-tools": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz",
+ "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.11",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
+ "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@prisma/adapter-mariadb": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.8.0.tgz",
+ "integrity": "sha512-mWsgcfbUjxB3qSzRlLs8E03vsKrqXzYK2zpx3e8u6wIgeHJM/sE46cuOGcYvHiZGmeQLCd3xL6YSSGM9QOLI6w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/driver-adapter-utils": "7.8.0",
+ "mariadb": "3.4.5"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz",
+ "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/client-runtime-utils": "7.8.0"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/client-runtime-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz",
+ "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/config": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz",
+ "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.3.4",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.20.0",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz",
+ "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/dev": {
+ "version": "0.24.3",
+ "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz",
+ "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "@electric-sql/pglite": "0.4.1",
+ "@electric-sql/pglite-socket": "0.1.1",
+ "@electric-sql/pglite-tools": "0.3.1",
+ "@hono/node-server": "1.19.11",
+ "@prisma/get-platform": "7.2.0",
+ "@prisma/query-plan-executor": "7.2.0",
+ "@prisma/streams-local": "0.1.2",
+ "foreground-child": "3.3.1",
+ "get-port-please": "3.2.0",
+ "hono": "^4.12.8",
+ "http-status-codes": "2.3.0",
+ "pathe": "2.0.3",
+ "proper-lockfile": "4.1.2",
+ "remeda": "2.33.4",
+ "std-env": "3.10.0",
+ "valibot": "1.2.0",
+ "zeptomatch": "2.1.0"
+ }
+ },
+ "node_modules/@prisma/driver-adapter-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz",
+ "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz",
+ "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/fetch-engine": "7.8.0",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz",
+ "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz",
+ "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
+ "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.2.0"
+ }
+ },
+ "node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+ "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/query-plan-executor": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
+ "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/streams-local": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz",
+ "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ajv": "^8.12.0",
+ "better-result": "^2.7.0",
+ "env-paths": "^3.0.0",
+ "proper-lockfile": "^4.1.2"
+ },
+ "engines": {
+ "bun": ">=1.3.6",
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@prisma/studio-core": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz",
+ "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@radix-ui/react-toggle": "1.1.10",
+ "chart.js": "4.5.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0",
+ "pnpm": "8"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
+ "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "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/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",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
+ "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": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "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/morgan": {
+ "version": "1.9.10",
+ "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz",
+ "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
+ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.19.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "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/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "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": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "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/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/better-result": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.0.tgz",
+ "integrity": "sha512-NHwGDGVbRlWDOce3CwcfGIrcNR9zY37ut3SVwQVfv57DZdVhxjhA4mfaHN1n8QwWnRAR4iErpW1X/eaiaUaFYg==",
+ "devOptional": 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": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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/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/c12": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
+ "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^5.0.0",
+ "confbox": "^0.2.4",
+ "defu": "^6.1.6",
+ "dotenv": "^17.3.1",
+ "exsolve": "^1.0.8",
+ "giget": "^3.2.0",
+ "jiti": "^2.6.1",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^2.1.0",
+ "pkg-types": "^2.3.0",
+ "rc9": "^3.0.1"
+ },
+ "peerDependencies": {
+ "magicast": "*"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": 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": {
+ "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/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/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/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
+ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/dotenv": {
+ "version": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+ "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/effect": {
+ "version": "3.20.0",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz",
+ "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
+ "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "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": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "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": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/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-port-please": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
+ "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "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-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/giget": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
+ "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
+ "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==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/grammex": {
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
+ "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/graphmatch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
+ "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.15",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz",
+ "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-status-codes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "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/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/mariadb": {
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz",
+ "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==",
+ "license": "LGPL-2.1-or-later",
+ "dependencies": {
+ "@types/geojson": "^7946.0.16",
+ "@types/node": "^24.0.13",
+ "denque": "^2.1.0",
+ "iconv-lite": "^0.6.3",
+ "lru-cache": "^10.4.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/mariadb/node_modules/@types/node": {
+ "version": "24.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
+ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/mariadb/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/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": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/morgan": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
+ "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
+ "license": "MIT",
+ "dependencies": {
+ "basic-auth": "~2.0.1",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/morgan/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/morgan/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/morgan/node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/mysql2": {
+ "version": "3.15.3",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
+ "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.0",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "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/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/nodemon/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/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/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "license": "MIT",
+ "engines": {
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "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-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
+ "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.4",
+ "exsolve": "^1.0.8",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/postgres": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
+ "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
+ "devOptional": true,
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/porsager"
+ }
+ },
+ "node_modules/prisma": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz",
+ "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "7.8.0",
+ "@prisma/dev": "0.24.3",
+ "@prisma/engines": "7.8.0",
+ "@prisma/studio-core": "0.27.3",
+ "mysql2": "3.15.3",
+ "postgres": "3.4.7"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "better-sqlite3": ">=9.0.0",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "better-sqlite3": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "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/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "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": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/rc9": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
+ "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.6",
+ "destr": "^2.0.5"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.5"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/remeda": {
+ "version": "2.33.4",
+ "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
+ "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/remeda"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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/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/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
+ "devOptional": true
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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==",
+ "devOptional": 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==",
+ "devOptional": 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",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "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/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "devOptional": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "devOptional": true,
+ "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/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "devOptional": 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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.19.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
+ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
+ "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/valibot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "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/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/zeptomatch": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
+ "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "grammex": "^3.1.11",
+ "graphmatch": "^1.1.0"
+ }
+ }
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/package.json" "b/\353\217\204\354\226\217/week7/package.json"
new file mode 100644
index 0000000..8502f59
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/package.json"
@@ -0,0 +1,28 @@
+{
+ "scripts": {
+ "dev": "nodemon --ext ts,prisma --ignore src/generated --exec \"npx prisma generate && tsx src/index.ts\""
+ },
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0",
+ "morgan": "^1.10.1"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/morgan": "^1.9.10",
+ "@types/node": "^25.6.0",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0",
+ "typescript": "^6.0.3"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/prisma.config.ts" "b/\353\217\204\354\226\217/week7/prisma.config.ts"
new file mode 100644
index 0000000..5170cc4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/prisma.config.ts"
@@ -0,0 +1,12 @@
+///
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+const { DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME } = process.env;
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ datasource: {
+ url: `mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT ?? 3306}/${DB_NAME}`,
+ },
+});
diff --git "a/\353\217\204\354\226\217/week7/prisma/migrations/20260428071132_init_database/migration.sql" "b/\353\217\204\354\226\217/week7/prisma/migrations/20260428071132_init_database/migration.sql"
new file mode 100644
index 0000000..31b12a1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/prisma/migrations/20260428071132_init_database/migration.sql"
@@ -0,0 +1,39 @@
+-- CreateTable
+CREATE TABLE `user` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `email` VARCHAR(255) NOT NULL,
+ `name` VARCHAR(100) NOT NULL,
+ `gender` VARCHAR(15) NOT NULL,
+ `birth` DATE NOT NULL,
+ `address` VARCHAR(255) NOT NULL,
+ `detail_address` VARCHAR(255) NULL,
+ `phone_number` VARCHAR(15) NOT NULL,
+
+ UNIQUE INDEX `email`(`email`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `food_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_favor_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `user_id` INTEGER NOT NULL,
+ `food_category_id` INTEGER NOT NULL,
+
+ INDEX `f_category_id`(`food_category_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_food_category_id_fkey` FOREIGN KEY (`food_category_id`) REFERENCES `food_category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week7/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql" "b/\353\217\204\354\226\217/week7/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
new file mode 100644
index 0000000..99445d4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
@@ -0,0 +1,25 @@
+-- CreateTable
+CREATE TABLE `store` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_store_review` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `user_id` INTEGER NOT NULL,
+ `content` TEXT NOT NULL,
+
+ INDEX `store_id`(`store_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week7/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql" "b/\353\217\204\354\226\217/week7/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
new file mode 100644
index 0000000..a6dd8b0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
@@ -0,0 +1,33 @@
+-- CreateTable
+CREATE TABLE `mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `title` VARCHAR(200) NOT NULL,
+ `reward` INTEGER NOT NULL,
+ `spec` TEXT NULL,
+ `dead_line` DATETIME(3) NULL,
+
+ INDEX `store_id`(`store_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `member_mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `member_id` INTEGER NOT NULL,
+ `mission_id` INTEGER NOT NULL,
+ `status` VARCHAR(15) NOT NULL,
+
+ INDEX `member_id`(`member_id`),
+ INDEX `mission_id`(`mission_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `mission` ADD CONSTRAINT `mission_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_member_id_fkey` FOREIGN KEY (`member_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week7/prisma/migrations/migration_lock.toml" "b/\353\217\204\354\226\217/week7/prisma/migrations/migration_lock.toml"
new file mode 100644
index 0000000..592fc0b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/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 (e.g., Git)
+provider = "mysql"
diff --git "a/\353\217\204\354\226\217/week7/prisma/schema.prisma" "b/\353\217\204\354\226\217/week7/prisma/schema.prisma"
new file mode 100644
index 0000000..a5758e0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/prisma/schema.prisma"
@@ -0,0 +1,100 @@
+generator client {
+ provider = "prisma-client"
+ output = "../src/generated/prisma"
+}
+
+datasource db {
+ provider = "mysql"
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique(map: "email") @db.VarChar(255)
+ name String @db.VarChar(100)
+ gender String @db.VarChar(15)
+ birth DateTime @db.Date
+ address String @db.VarChar(255)
+ detailAddress String? @map("detail_address") @db.VarChar(255)
+ phoneNumber String @map("phone_number") @db.VarChar(15)
+
+ userFavorCategories UserFavorCategory[]
+ reviews UserStoreReview[]
+ memberMissions MemberMission[]
+
+ @@map("user")
+}
+
+model FoodCategory {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ userFavorCategories UserFavorCategory[]
+
+ @@map("food_category")
+}
+
+model UserFavorCategory {
+ id Int @id @default(autoincrement())
+ userId Int @map("user_id")
+ foodCategoryId Int @map("food_category_id")
+ user User @relation(fields: [userId], references: [id])
+ foodCategory FoodCategory @relation(fields: [foodCategoryId], references: [id])
+
+ @@index([foodCategoryId], map: "f_category_id")
+ @@index([userId], map: "user_id")
+ @@map("user_favor_category")
+}
+
+model Store {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ reviews UserStoreReview[]
+ missions Mission[]
+
+ @@map("store")
+}
+
+model UserStoreReview {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ userId Int @map("user_id")
+ content String @db.Text
+
+ store Store @relation(fields: [storeId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+
+ @@index([storeId], map: "store_id")
+ @@index([userId], map: "user_id")
+ @@map("user_store_review")
+}
+
+model Mission {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ title String @db.VarChar(200)
+ reward Int
+ spec String? @db.Text
+ deadLine DateTime? @map("dead_line")
+
+ store Store @relation(fields: [storeId], references: [id])
+ memberMissions MemberMission[]
+
+ @@index([storeId], map: "store_id")
+ @@map("mission")
+}
+
+model MemberMission {
+ id Int @id @default(autoincrement())
+ userId Int @map("member_id")
+ missionId Int @map("mission_id")
+ status String @db.VarChar(15)
+
+ user User @relation(fields: [userId], references: [id])
+ mission Mission @relation(fields: [missionId], references: [id])
+
+ @@index([userId], map: "member_id")
+ @@index([missionId], map: "mission_id")
+ @@map("member_mission")
+}
+
diff --git "a/\353\217\204\354\226\217/week7/reset_db.sql" "b/\353\217\204\354\226\217/week7/reset_db.sql"
new file mode 100644
index 0000000..454f20b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/reset_db.sql"
@@ -0,0 +1,215 @@
+-- ============================================================
+-- DB ์ด๊ธฐํ ๋ฐ ์ฌ์์ฑ ์คํฌ๋ฆฝํธ
+-- ============================================================
+
+CREATE DATABASE IF NOT EXISTS umc_mission DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE umc_mission;
+
+-- FK ์ฒดํฌ ๋นํ์ฑํ ํ ์ ์ฒด DROP
+SET FOREIGN_KEY_CHECKS = 0;
+
+DROP TABLE IF EXISTS review_image;
+DROP TABLE IF EXISTS review;
+DROP TABLE IF EXISTS member_mission;
+DROP TABLE IF EXISTS mission;
+DROP TABLE IF EXISTS store_hours;
+DROP TABLE IF EXISTS store_image;
+DROP TABLE IF EXISTS store;
+DROP TABLE IF EXISTS member_prefer;
+DROP TABLE IF EXISTS member_agree;
+DROP TABLE IF EXISTS member;
+DROP TABLE IF EXISTS terms;
+DROP TABLE IF EXISTS food_category;
+DROP TABLE IF EXISTS region;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- ============================================================
+-- ํ
์ด๋ธ ์ฌ์์ฑ
+-- ============================================================
+
+CREATE TABLE region (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์ง์ญ๋ช
',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE food_category (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์นดํ
๊ณ ๋ฆฌ๋ช
(ํ์, ์ค์, ์ผ์ ๋ฑ)',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE terms (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ title VARCHAR(100) NOT NULL COMMENT '์ฝ๊ด ์ ๋ชฉ',
+ content TEXT NOT NULL COMMENT '์ฝ๊ด ๋ด์ฉ',
+ optional BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'TRUE: ์ ํ ๋์, FALSE: ํ์ ๋์',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE member (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ social_type VARCHAR(20) NULL,
+ social_id VARCHAR(100) NULL,
+ email VARCHAR(100) NULL,
+ password VARCHAR(255) NULL,
+ name VARCHAR(50) NOT NULL,
+ nickname VARCHAR(50) NOT NULL,
+ profile_image_url VARCHAR(500) NULL,
+ phone_num VARCHAR(20) NULL,
+ phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
+ birth DATE NULL,
+ gender ENUM('MALE', 'FEMALE', 'OTHER') NULL,
+ address VARCHAR(200) NULL,
+ spec_address VARCHAR(200) NULL,
+ point INT NOT NULL DEFAULT 0,
+ status ENUM('ACTIVE', 'INACTIVE', 'BANNED') NOT NULL DEFAULT 'ACTIVE',
+ inactive_date DATETIME NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_email (email),
+ UNIQUE KEY uq_member_social (social_type, social_id)
+);
+
+CREATE TABLE member_agree (
+ member_id BIGINT NOT NULL,
+ terms_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, terms_id),
+ CONSTRAINT fk_member_agree_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_agree_terms FOREIGN KEY (terms_id) REFERENCES terms (id)
+);
+
+CREATE TABLE member_prefer (
+ member_id BIGINT NOT NULL,
+ food_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, food_id),
+ CONSTRAINT fk_member_prefer_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_prefer_food FOREIGN KEY (food_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ region_id BIGINT NOT NULL,
+ food_category_id BIGINT NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ description TEXT NULL,
+ lat DECIMAL(10,7) NULL,
+ lng DECIMAL(10,7) NULL,
+ address VARCHAR(200) NOT NULL,
+ status ENUM('OPEN', 'CLOSED', 'PENDING') NOT NULL DEFAULT 'OPEN',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_region FOREIGN KEY (region_id) REFERENCES region (id),
+ CONSTRAINT fk_store_category FOREIGN KEY (food_category_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_image_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE store_hours (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ day_of_week VARCHAR(3) NOT NULL,
+ open_time TIME NOT NULL,
+ close_time TIME NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_store_hours_day (store_id, day_of_week),
+ CONSTRAINT fk_store_hours_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ title VARCHAR(200) NOT NULL,
+ reward INT NOT NULL DEFAULT 0,
+ spec VARCHAR(500) NULL,
+ dead_line DATE NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_mission_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE member_mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ mission_id BIGINT NOT NULL,
+ status ENUM('CHALLENGING', 'COMPLETE') NOT NULL DEFAULT 'CHALLENGING',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_mission (member_id, mission_id),
+ CONSTRAINT fk_member_mission_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_mission_mission FOREIGN KEY (mission_id) REFERENCES mission (id)
+);
+
+CREATE TABLE review (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ store_id BIGINT NOT NULL,
+ member_mission_id BIGINT NULL,
+ content TEXT NOT NULL,
+ score DECIMAL(2,1) NOT NULL,
+ owner_reply VARCHAR(500) NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_review_store FOREIGN KEY (store_id) REFERENCES store (id),
+ CONSTRAINT fk_review_member_mission FOREIGN KEY (member_mission_id) REFERENCES member_mission (id),
+ CONSTRAINT chk_review_score CHECK (score BETWEEN 1.0 AND 5.0)
+);
+
+CREATE TABLE review_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ review_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_image_review FOREIGN KEY (review_id) REFERENCES review (id)
+);
+
+-- ============================================================
+-- ์๋ ๋ฐ์ดํฐ (API ํ
์คํธ์ฉ ์ด๊ธฐ ๋ฐ์ดํฐ)
+-- ============================================================
+
+INSERT INTO region (name) VALUES
+ ('์์ธ'),
+ ('๊ฒฝ๊ธฐ'),
+ ('์ธ์ฒ'),
+ ('๋ถ์ฐ'),
+ ('๋๊ตฌ');
+
+INSERT INTO food_category (name) VALUES
+ ('ํ์'),
+ ('์ค์'),
+ ('์ผ์'),
+ ('์์'),
+ ('๋ถ์'),
+ ('์นดํ/๋์ ํธ'),
+ ('์นํจ'),
+ ('ํผ์'),
+ ('ํจ์คํธํธ๋');
diff --git "a/\353\217\204\354\226\217/week7/src/db.config.ts" "b/\353\217\204\354\226\217/week7/src/db.config.ts"
new file mode 100644
index 0000000..7b88907
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/db.config.ts"
@@ -0,0 +1,17 @@
+import "dotenv/config";
+import { PrismaClient } from "./generated/prisma/client.js";
+import { PrismaMariaDb } from "@prisma/adapter-mariadb";
+
+const adapter = new PrismaMariaDb({
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASSWORD,
+ database: process.env.DB_NAME,
+ port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 3306,
+ connectionLimit: 10,
+});
+
+export const prisma = new PrismaClient({
+ adapter,
+ log: ["query", "info", "error", "warn"],
+});
diff --git "a/\353\217\204\354\226\217/week7/src/index.ts" "b/\353\217\204\354\226\217/week7/src/index.ts"
new file mode 100644
index 0000000..142e52c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/index.ts"
@@ -0,0 +1,59 @@
+import dotenv from 'dotenv'
+import express, { Express, Request, Response } from 'express'
+import cors from 'cors'
+import morgan from 'morgan'
+import cookieParser from 'cookie-parser'
+
+// ์ปจํธ๋กค๋ฌ import
+import { handleCreateStore, handleListStoreReviews } from './modules/stores/controllers/store.controller.js'
+import { handleListUserReviews } from './modules/reviews/controllers/review.controller.js'
+import { handleCreateReview } from './modules/reviews/controllers/review.controller.js'
+import { handleCreateMission, handleChallengeMission, handleListStoreMissions, handleListOngoingMissions, handleCompleteMission } from './modules/missions/controllers/mission.controller.js'
+import { handleSignUp } from './modules/members/controllers/member.controller.js'
+
+// ์๋ฌ ๋ฏธ๋ค์จ์ด import
+import { errorMiddleware } from './middleware/error.middleware.js'
+
+// 1. ํ๊ฒฝ ๋ณ์ ์ค์ (๊ฐ์ฅ ๋จผ์ ํธ์ถ)
+dotenv.config()
+
+const app: Express = express()
+const port = process.env.PORT ?? 3000
+
+// 2. ๋ฏธ๋ค์จ์ด ์ค์
+app.use(morgan('dev'))
+app.use(cookieParser())
+app.use(cors())
+app.use(express.json())
+app.use(express.urlencoded({ extended: false }))
+
+// 3. ๋ผ์ฐํฐ ๋ฑ๋ก
+app.get('/', (_req: Request, res: Response) => {
+ res.send('UMC 7์ฃผ์ฐจ ์๋ฒ ์คํ ์ค!')
+})
+
+// ํ์
+app.post('/api/v1/members/signup', handleSignUp)
+app.get('/api/v1/users/:userId/reviews', handleListUserReviews)
+app.get('/api/v1/users/:userId/missions', handleListOngoingMissions)
+
+// ๊ฐ๊ฒ
+app.post('/api/v1/stores', handleCreateStore)
+app.post('/api/v1/stores/:storeId/reviews', handleCreateReview)
+app.get('/api/v1/stores/:storeId/reviews', handleListStoreReviews)
+app.get('/api/v1/stores/:storeId/missions', handleListStoreMissions)
+app.post('/api/v1/stores/:storeId/missions', handleCreateMission)
+
+// ๋ฏธ์
+app.post('/api/v1/missions/:missionId/challenge', handleChallengeMission)
+app.patch('/api/v1/users/:userId/missions/:missionId', handleCompleteMission)
+
+
+
+// 4. ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ (๋ฐ๋์ ๋ผ์ฐํฐ ๋ฑ๋ก ์ดํ์ ์์น)
+app.use(errorMiddleware)
+
+// 5. ์๋ฒ ์์
+app.listen(port, () => {
+ console.log(`[server]: Server is running at http://localhost:${port}`)
+})
diff --git "a/\353\217\204\354\226\217/week7/src/middleware/error.middleware.ts" "b/\353\217\204\354\226\217/week7/src/middleware/error.middleware.ts"
new file mode 100644
index 0000000..abd0e3a
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/middleware/error.middleware.ts"
@@ -0,0 +1,38 @@
+import { Request, Response, NextFunction } from 'express'
+import { BaseError } from '../utils/errors.js'
+
+export const errorMiddleware = (
+ err: unknown,
+ _req: Request,
+ res: Response,
+ _next: NextFunction,
+): void => {
+ if (err instanceof BaseError) {
+ res.status(err.status).json({
+ isSuccess: false,
+ code: err.code,
+ message: err.message,
+ result: null,
+ })
+ return
+ }
+
+ // Prisma unique constraint ์๋ฌ
+ if (typeof err === 'object' && err !== null && (err as { code?: string }).code === 'P2002') {
+ res.status(409).json({
+ isSuccess: false,
+ code: 'USER4002',
+ message: '์ด๋ฏธ ์กด์ฌํ๋ ๋ฐ์ดํฐ์
๋๋ค.',
+ result: null,
+ })
+ return
+ }
+
+ console.error(err)
+ res.status(500).json({
+ isSuccess: false,
+ code: 'COMMON500',
+ message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.',
+ result: null,
+ })
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/members/controllers/member.controller.ts" "b/\353\217\204\354\226\217/week7/src/modules/members/controllers/member.controller.ts"
new file mode 100644
index 0000000..39bd45c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/members/controllers/member.controller.ts"
@@ -0,0 +1,19 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { MemberSignUpRequest } from '../dtos/member.dto.js'
+import { signUp } from '../services/member.service.js'
+import { BaseResponse } from '../../../utils/response.js'
+
+// POST /api/v1/members/signup
+export const handleSignUp = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const result = await signUp(req.body as MemberSignUpRequest)
+ res.status(StatusCodes.CREATED).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/members/dtos/member.dto.ts" "b/\353\217\204\354\226\217/week7/src/modules/members/dtos/member.dto.ts"
new file mode 100644
index 0000000..b0cbf3f
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/members/dtos/member.dto.ts"
@@ -0,0 +1,45 @@
+// ํ์๊ฐ์
์์ฒญ ์ธํฐํ์ด์ค
+export interface MemberSignUpRequest {
+ name: string
+ nickname: string
+ email?: string
+ password?: string
+ phoneNum?: string
+ birth?: string // "YYYY-MM-DD"
+ gender?: string // "MALE" | "FEMALE" | "OTHER"
+ address?: string
+ specAddress?: string
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToMember = (body: MemberSignUpRequest) => {
+ return {
+ name: body.name,
+ nickname: body.nickname,
+ email: body.email ?? null,
+ phoneNum: body.phoneNum ?? null,
+ birth: body.birth ? new Date(body.birth) : null,
+ gender: body.gender ?? null,
+ address: body.address ?? null,
+ specAddress: body.specAddress ?? null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromMember = (member: {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+}) => {
+ return {
+ memberId: member.id,
+ name: member.name,
+ nickname: member.nickname,
+ email: member.email,
+ phoneNum: member.phone_num,
+ status: member.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/members/repositories/member.repository.ts" "b/\353\217\204\354\226\217/week7/src/modules/members/repositories/member.repository.ts"
new file mode 100644
index 0000000..549030e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/members/repositories/member.repository.ts"
@@ -0,0 +1,26 @@
+import { prisma } from '../../../db.config.js'
+
+// ์ ์ ์์ฑ
+export const addUser = async (data: any) => {
+ const exists = await prisma.user.findFirst({ where: { email: data.email } })
+ if (exists) return null
+
+ const created = await prisma.user.create({ data })
+ return created.id
+}
+
+// ์ ์ ์กฐํ (์์ผ๋ฉด ์์ธ throw)
+export const getUser = async (userId: number) =>
+ prisma.user.findFirstOrThrow({ where: { id: userId } })
+
+// ์ ํธ ์์ ์นดํ
๊ณ ๋ฆฌ ๋ฑ๋ก
+export const setPreference = async (userId: number, foodCategoryId: number) =>
+ prisma.userFavorCategory.create({ data: { userId, foodCategoryId } })
+
+// ์ ํธ ์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํ (JOIN ํฌํจ)
+export const getUserPreferencesByUserId = async (userId: number) =>
+ prisma.userFavorCategory.findMany({
+ where: { userId },
+ include: { foodCategory: true },
+ orderBy: { foodCategoryId: 'asc' },
+ })
diff --git "a/\353\217\204\354\226\217/week7/src/modules/members/services/member.service.ts" "b/\353\217\204\354\226\217/week7/src/modules/members/services/member.service.ts"
new file mode 100644
index 0000000..2cf928d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/members/services/member.service.ts"
@@ -0,0 +1,39 @@
+import bcrypt from 'bcryptjs'
+import { MemberSignUpRequest, bodyToMember, responseFromMember } from '../dtos/member.dto.js'
+import { addUser, getUser } from '../repositories/member.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const signUp = async (data: MemberSignUpRequest) => {
+ if (!data.name || !data.nickname) {
+ throw new BaseError(
+ ErrorCode.MEMBER_REQUIRED_FIELD.message,
+ ErrorCode.MEMBER_REQUIRED_FIELD.status,
+ ErrorCode.MEMBER_REQUIRED_FIELD.code,
+ )
+ }
+
+ const hashedPassword = data.password ? await bcrypt.hash(data.password, 10) : null
+
+ const memberData = bodyToMember(data)
+ const memberId = await addUser({ ...memberData, hashedPassword })
+
+ if (memberId === null) {
+ throw new BaseError(
+ ErrorCode.DUPLICATE_EMAIL.message,
+ ErrorCode.DUPLICATE_EMAIL.status,
+ ErrorCode.DUPLICATE_EMAIL.code,
+ )
+ }
+
+ const member = await getUser(memberId)
+
+ return responseFromMember(member as {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/missions/controllers/mission.controller.ts" "b/\353\217\204\354\226\217/week7/src/modules/missions/controllers/mission.controller.ts"
new file mode 100644
index 0000000..e3057f1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/missions/controllers/mission.controller.ts"
@@ -0,0 +1,71 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { MissionCreateRequest, MissionChallengeRequest } from '../dtos/mission.dto.js'
+import { createMission, challengeMission, listStoreMissions, listOngoingMissions, finishMission } from '../services/mission.service.js'
+import { BaseResponse } from '../../../utils/response.js'
+
+// GET /api/v1/users/:userId/missions
+export const handleListOngoingMissions = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = parseInt(String(req.params['userId'] ?? '0'), 10)
+ const cursor = typeof req.query['cursor'] === 'string' ? parseInt(req.query['cursor'], 10) : 0
+ const result = await listOngoingMissions(userId, cursor)
+ res.status(StatusCodes.OK).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// GET /api/v1/stores/:storeId/missions
+export const handleListStoreMissions = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const storeId = parseInt(String(req.params['storeId'] ?? '0'), 10)
+ const cursor = typeof req.query['cursor'] === 'string' ? parseInt(req.query['cursor'], 10) : 0
+ const result = await listStoreMissions(storeId, cursor)
+ res.status(StatusCodes.OK).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// POST /api/v1/stores/:storeId/missions
+export const handleCreateMission = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const storeId = parseInt(String(req.params['storeId'] ?? '0'), 10)
+ const result = await createMission(storeId, req.body as MissionCreateRequest)
+ res.status(StatusCodes.CREATED).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// POST /api/v1/missions/:missionId/challenge
+export const handleChallengeMission = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const missionId = parseInt(String(req.params['missionId'] ?? '0'), 10)
+ const result = await challengeMission(missionId, req.body as MissionChallengeRequest)
+ res.status(StatusCodes.CREATED).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// PATCH /api/v1/users/:userId/missions/:missionId
+export const handleCompleteMission = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = parseInt(String(req.params['userId'] ?? '0'), 10)
+ const missionId = parseInt(String(req.params['missionId'] ?? '0'), 10)
+ const result = await finishMission(userId, missionId)
+ res.status(StatusCodes.OK).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/missions/dtos/mission.dto.ts" "b/\353\217\204\354\226\217/week7/src/modules/missions/dtos/mission.dto.ts"
new file mode 100644
index 0000000..8786670
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/missions/dtos/mission.dto.ts"
@@ -0,0 +1,57 @@
+// ๋ฏธ์
์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionCreateRequest {
+ title: string
+ reward: number
+ spec?: string
+ deadLine?: string // "YYYY-MM-DD"
+}
+
+// ๋ฏธ์
๋์ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionChallengeRequest {
+ memberId: number
+ status: 'CHALLENGING' | 'COMPLETE'
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ (๋ฏธ์
์ถ๊ฐ)
+export const bodyToMission = (body: MissionCreateRequest) => {
+ return {
+ title: body.title,
+ reward: body.reward,
+ spec: body.spec ?? null,
+ deadLine: body.deadLine ? new Date(body.deadLine) : null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
)
+export const responseFromMission = (mission: {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+}) => {
+ return {
+ missionId: mission.id,
+ storeId: mission.store_id,
+ title: mission.title,
+ reward: mission.reward,
+ spec: mission.spec,
+ deadLine: mission.dead_line,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
๋์ )
+export const responseFromMemberMission = (mm: {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+}) => {
+ return {
+ memberMissionId: mm.id,
+ memberId: mm.member_id,
+ missionId: mm.mission_id,
+ status: mm.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/missions/repositories/mission.repository.ts" "b/\353\217\204\354\226\217/week7/src/modules/missions/repositories/mission.repository.ts"
new file mode 100644
index 0000000..bee3e62
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/missions/repositories/mission.repository.ts"
@@ -0,0 +1,72 @@
+import { prisma } from '../../../db.config.js'
+
+// ๋ฏธ์
์ถ๊ฐ
+export const addMission = async (data: {
+ storeId: number
+ title: string
+ reward: number
+ spec: string | null
+ deadLine: Date | null
+}): Promise => {
+ const created = await prisma.mission.create({
+ data: {
+ storeId: data.storeId,
+ title: data.title,
+ reward: data.reward,
+ spec: data.spec,
+ deadLine: data.deadLine,
+ },
+ })
+ return created.id
+}
+
+// ๋ฏธ์
์กฐํ
+export const getMissionById = async (missionId: number) =>
+ prisma.mission.findFirst({ where: { id: missionId } })
+
+// ์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์ธ์ง ํ์ธ
+export const findMemberMission = async (memberId: number, missionId: number) =>
+ prisma.memberMission.findFirst({
+ where: { userId: memberId, missionId },
+ })
+
+// ๋ฏธ์
๋์ ์ถ๊ฐ
+export const addMemberMission = async (
+ memberId: number,
+ missionId: number,
+ status: string,
+): Promise => {
+ const created = await prisma.memberMission.create({
+ data: { userId: memberId, missionId, status },
+ })
+ return created.id
+}
+
+// ๋ฏธ์
๋์ ๊ธฐ๋ก ์กฐํ
+export const getMemberMissionById = async (memberMissionId: number) =>
+ prisma.memberMission.findFirst({ where: { id: memberMissionId } })
+
+// ํน์ ๊ฐ๊ฒ์ ๋ฏธ์
๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getStoreMissions = async (storeId: number, cursor: number) =>
+ prisma.mission.findMany({
+ where: { storeId, id: { gt: cursor } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+
+// ์ ์ ๊ฐ ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก ์กฐํ
+export const getOngoingMissions = async (userId: number, cursor: number) =>
+ prisma.memberMission.findMany({
+ where: { userId, status: '์งํ์ค', id: { gt: cursor } },
+ include: { mission: { include: { store: true } } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+// ์งํ ์ค์ธ ๋ฏธ์
์ ์๋ฃ๋ก ๋ณ๊ฒฝ
+export const completeMission = async (userId: number, missionId: number) =>
+ prisma.memberMission.updateMany({
+ where: { userId, missionId, status: '์งํ์ค' },
+ data: { status: '์๋ฃ' },
+ })
\ No newline at end of file
diff --git "a/\353\217\204\354\226\217/week7/src/modules/missions/services/mission.service.ts" "b/\353\217\204\354\226\217/week7/src/modules/missions/services/mission.service.ts"
new file mode 100644
index 0000000..92018a3
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/missions/services/mission.service.ts"
@@ -0,0 +1,128 @@
+import {
+ MissionCreateRequest,
+ MissionChallengeRequest,
+ bodyToMission,
+ responseFromMission,
+ responseFromMemberMission,
+} from '../dtos/mission.dto.js'
+import {
+ addMission,
+ getMissionById,
+ findMemberMission,
+ addMemberMission,
+ getMemberMissionById,
+ getStoreMissions,
+ getOngoingMissions,
+ completeMission,
+} from '../repositories/mission.repository.js'
+import { getStoreById } from '../../stores/repositories/store.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const createMission = async (storeId: number, data: MissionCreateRequest) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(
+ ErrorCode.STORE_NOT_FOUND.message,
+ ErrorCode.STORE_NOT_FOUND.status,
+ ErrorCode.STORE_NOT_FOUND.code,
+ )
+ }
+
+ const missionData = bodyToMission(data)
+ const missionId = await addMission({ ...missionData, storeId })
+
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw new BaseError(
+ ErrorCode.MISSION_CREATE_FAILED.message,
+ ErrorCode.MISSION_CREATE_FAILED.status,
+ ErrorCode.MISSION_CREATE_FAILED.code,
+ )
+ }
+
+ return responseFromMission(mission as unknown as {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+ })
+}
+
+export const listStoreMissions = async (storeId: number, cursor: number) => {
+ const missions = await getStoreMissions(storeId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+export const listOngoingMissions = async (userId: number, cursor: number) => {
+ const missions = await getOngoingMissions(userId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+export const finishMission = async (userId: number, missionId: number) => {
+ const result = await completeMission(userId, missionId)
+ if (result.count === 0) {
+ throw new BaseError(
+ ErrorCode.ONGOING_MISSION_NOT_FOUND.message,
+ ErrorCode.ONGOING_MISSION_NOT_FOUND.status,
+ ErrorCode.ONGOING_MISSION_NOT_FOUND.code,
+ )
+ }
+ return { message: '๋ฏธ์
์ด ์๋ฃ ์ฒ๋ฆฌ๋์ต๋๋ค.' }
+}
+
+export const challengeMission = async (missionId: number, data: MissionChallengeRequest) => {
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw new BaseError(
+ ErrorCode.MISSION_NOT_FOUND.message,
+ ErrorCode.MISSION_NOT_FOUND.status,
+ ErrorCode.MISSION_NOT_FOUND.code,
+ )
+ }
+
+ if (!data.status) {
+ throw new BaseError(
+ ErrorCode.MISSION_STATUS_REQUIRED.message,
+ ErrorCode.MISSION_STATUS_REQUIRED.status,
+ ErrorCode.MISSION_STATUS_REQUIRED.code,
+ )
+ }
+
+ const existing = await findMemberMission(data.memberId, missionId)
+ if (existing) {
+ throw new BaseError(
+ ErrorCode.MISSION_ALREADY_CHALLENGING.message,
+ ErrorCode.MISSION_ALREADY_CHALLENGING.status,
+ ErrorCode.MISSION_ALREADY_CHALLENGING.code,
+ )
+ }
+
+ const memberMissionId = await addMemberMission(data.memberId, missionId, data.status)
+
+ const memberMission = await getMemberMissionById(memberMissionId)
+ if (!memberMission) {
+ throw new BaseError(
+ ErrorCode.MISSION_CHALLENGE_FAILED.message,
+ ErrorCode.MISSION_CHALLENGE_FAILED.status,
+ ErrorCode.MISSION_CHALLENGE_FAILED.code,
+ )
+ }
+
+ return responseFromMemberMission(memberMission as unknown as {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/reviews/controllers/review.controller.ts" "b/\353\217\204\354\226\217/week7/src/modules/reviews/controllers/review.controller.ts"
new file mode 100644
index 0000000..9d73016
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/reviews/controllers/review.controller.ts"
@@ -0,0 +1,32 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { ReviewCreateRequest } from '../dtos/review.dto.js'
+import { createReview, listUserReviews } from '../services/review.service.js'
+import { BaseResponse } from '../../../utils/response.js'
+
+// GET /api/v1/users/:userId/reviews
+export const handleListUserReviews = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const userId = parseInt(String(req.params['userId'] ?? '0'), 10)
+ const cursor = typeof req.query['cursor'] === 'string' ? parseInt(req.query['cursor'], 10) : 0
+ const result = await listUserReviews(userId, cursor)
+ res.status(StatusCodes.OK).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// POST /api/v1/stores/:storeId/reviews
+export const handleCreateReview = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const storeId = parseInt(String(req.params['storeId'] ?? '0'), 10)
+ const result = await createReview(storeId, req.body as ReviewCreateRequest)
+ res.status(StatusCodes.CREATED).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/reviews/dtos/review.dto.ts" "b/\353\217\204\354\226\217/week7/src/modules/reviews/dtos/review.dto.ts"
new file mode 100644
index 0000000..0738b1c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/reviews/dtos/review.dto.ts"
@@ -0,0 +1,43 @@
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface ReviewCreateRequest {
+ memberId: number
+ content: string
+ score: number
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToReview = (body: ReviewCreateRequest) => {
+ return {
+ memberId: body.memberId,
+ content: body.content,
+ score: body.score,
+ }
+}
+
+// ๋ฆฌ๋ทฐ ๋ชฉ๋ก โ ์๋ต ํํ๋ก ๋ณํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const responseFromUserReviews = (reviews: any[]) => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromReview = (review: {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+}) => {
+ return {
+ reviewId: review.id,
+ memberId: review.member_id,
+ storeId: review.store_id,
+ content: review.content,
+ score: review.score,
+ createdAt: review.created_at,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/reviews/repositories/review.repository.ts" "b/\353\217\204\354\226\217/week7/src/modules/reviews/repositories/review.repository.ts"
new file mode 100644
index 0000000..adca307
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/reviews/repositories/review.repository.ts"
@@ -0,0 +1,30 @@
+import { prisma } from '../../../db.config.js'
+
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ
+export const addReview = async (data: {
+ memberId: number
+ storeId: number
+ content: string
+}): Promise => {
+ const created = await prisma.userStoreReview.create({
+ data: {
+ userId: data.memberId,
+ storeId: data.storeId,
+ content: data.content,
+ },
+ })
+ return created.id
+}
+
+// ๋ฆฌ๋ทฐ ์กฐํ
+export const getReviewById = async (reviewId: number) =>
+ prisma.userStoreReview.findFirst({ where: { id: reviewId } })
+
+// ๋ด๊ฐ ์์ฑํ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getUserReviews = async (userId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ where: { userId, id: { gt: cursor } },
+ include: { store: true },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week7/src/modules/reviews/services/review.service.ts" "b/\353\217\204\354\226\217/week7/src/modules/reviews/services/review.service.ts"
new file mode 100644
index 0000000..13b2281
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/reviews/services/review.service.ts"
@@ -0,0 +1,50 @@
+import { ReviewCreateRequest, bodyToReview, responseFromReview, responseFromUserReviews } from '../dtos/review.dto.js'
+import { addReview, getReviewById, getUserReviews } from '../repositories/review.repository.js'
+import { getStoreById } from '../../stores/repositories/store.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const listUserReviews = async (userId: number, cursor: number) => {
+ const reviews = await getUserReviews(userId, cursor)
+ return responseFromUserReviews(reviews)
+}
+
+export const createReview = async (storeId: number, data: ReviewCreateRequest) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(
+ ErrorCode.STORE_NOT_FOUND.message,
+ ErrorCode.STORE_NOT_FOUND.status,
+ ErrorCode.STORE_NOT_FOUND.code,
+ )
+ }
+
+ if (data.score < 1 || data.score > 5) {
+ throw new BaseError(
+ ErrorCode.INVALID_SCORE.message,
+ ErrorCode.INVALID_SCORE.status,
+ ErrorCode.INVALID_SCORE.code,
+ )
+ }
+
+ const reviewData = bodyToReview(data)
+ const reviewId = await addReview({ ...reviewData, storeId })
+
+ const review = await getReviewById(reviewId)
+ if (!review) {
+ throw new BaseError(
+ ErrorCode.REVIEW_CREATE_FAILED.message,
+ ErrorCode.REVIEW_CREATE_FAILED.status,
+ ErrorCode.REVIEW_CREATE_FAILED.code,
+ )
+ }
+
+ return responseFromReview(review as {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+ })
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/stores/controllers/store.controller.ts" "b/\353\217\204\354\226\217/week7/src/modules/stores/controllers/store.controller.ts"
new file mode 100644
index 0000000..ff13fac
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/stores/controllers/store.controller.ts"
@@ -0,0 +1,31 @@
+import { Request, Response, NextFunction } from 'express'
+import { StatusCodes } from 'http-status-codes'
+import { StoreCreateRequest } from '../dtos/store.dto.js'
+import { createStore, listStoreReviews } from '../services/store.service.js'
+import { BaseResponse } from '../../../utils/response.js'
+
+// POST /api/v1/stores
+export const handleCreateStore = async (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+): Promise => {
+ try {
+ const result = await createStore(req.body as StoreCreateRequest)
+ res.status(StatusCodes.CREATED).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
+
+// GET /api/v1/stores/:storeId/reviews
+export const handleListStoreReviews = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const storeId = parseInt(String(req.params['storeId'] ?? '0'), 10)
+ const cursor = typeof req.query.cursor === 'string' ? parseInt(req.query.cursor, 10) : 0
+ const result = await listStoreReviews(storeId, cursor)
+ res.status(StatusCodes.OK).json(BaseResponse(result))
+ } catch (err) {
+ next(err)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/stores/dtos/store.dto.ts" "b/\353\217\204\354\226\217/week7/src/modules/stores/dtos/store.dto.ts"
new file mode 100644
index 0000000..6922309
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/stores/dtos/store.dto.ts"
@@ -0,0 +1,49 @@
+// ๊ฐ๊ฒ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface StoreCreateRequest {
+ regionId: number
+ foodCategoryId: number
+ name: string
+ description?: string
+ address: string
+ lat?: number
+ lng?: number
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToStore = (body: StoreCreateRequest) => {
+ return {
+ regionId: body.regionId,
+ foodCategoryId: body.foodCategoryId,
+ name: body.name,
+ description: body.description ?? null,
+ address: body.address,
+ lat: body.lat ?? null,
+ lng: body.lng ?? null,
+ }
+}
+
+// ๋ฆฌ๋ทฐ ๋ชฉ๋ก โ ์๋ต ํํ๋ก ๋ณํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const responseFromReviews = (reviews: any[]) => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews,
+ pagination: {
+ cursor: last ? last.id : null,
+ },
+ }
+}
+
+// DB ์กฐํ ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromStore = (store: {
+ id: number
+ name: string
+ address: string
+ region_id: number
+}) => {
+ return {
+ storeId: store.id,
+ name: store.name,
+ address: store.address,
+ regionId: store.region_id,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/modules/stores/repositories/store.repository.ts" "b/\353\217\204\354\226\217/week7/src/modules/stores/repositories/store.repository.ts"
new file mode 100644
index 0000000..9a9ac1e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/stores/repositories/store.repository.ts"
@@ -0,0 +1,28 @@
+import { prisma } from '../../../db.config.js'
+
+// ๊ฐ๊ฒ ์ถ๊ฐ
+export const addStore = async (data: { name: string }): Promise => {
+ const created = await prisma.store.create({ data })
+ return created.id
+}
+
+// ๊ฐ๊ฒ ์กฐํ
+export const getStoreById = async (storeId: number) =>
+ prisma.store.findFirst({ where: { id: storeId } })
+
+// ๊ฐ๊ฒ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getAllStoreReviews = async (storeId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ select: {
+ id: true,
+ content: true,
+ store: true,
+ user: true,
+ },
+ where: {
+ storeId,
+ id: { gt: cursor },
+ },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week7/src/modules/stores/services/store.service.ts" "b/\353\217\204\354\226\217/week7/src/modules/stores/services/store.service.ts"
new file mode 100644
index 0000000..91f2c48
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/modules/stores/services/store.service.ts"
@@ -0,0 +1,30 @@
+import { StoreCreateRequest, bodyToStore, responseFromStore, responseFromReviews } from '../dtos/store.dto.js'
+import { addStore, getStoreById, getAllStoreReviews } from '../repositories/store.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const listStoreReviews = async (storeId: number, cursor: number) => {
+ const reviews = await getAllStoreReviews(storeId, cursor)
+ return responseFromReviews(reviews)
+}
+
+export const createStore = async (data: StoreCreateRequest) => {
+ const storeData = bodyToStore(data)
+ const storeId = await addStore(storeData)
+
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(
+ ErrorCode.STORE_CREATE_FAILED.message,
+ ErrorCode.STORE_CREATE_FAILED.status,
+ ErrorCode.STORE_CREATE_FAILED.code,
+ )
+ }
+
+ return responseFromStore(store as {
+ id: number
+ name: string
+ address: string
+ region_id: number
+ })
+}
diff --git "a/\353\217\204\354\226\217/week7/src/utils/errorCode.ts" "b/\353\217\204\354\226\217/week7/src/utils/errorCode.ts"
new file mode 100644
index 0000000..1a271dd
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/utils/errorCode.ts"
@@ -0,0 +1,26 @@
+export const ErrorCode = {
+ // ๊ณตํต
+ INTERNAL_ERROR: { status: 500, code: 'COMMON500', message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.' },
+ INVALID_INPUT: { status: 400, code: 'COMMON400', message: '์๋ชป๋ ์
๋ ฅ๊ฐ์
๋๋ค.' },
+
+ // ํ์
+ USER_NOT_FOUND: { status: 404, code: 'USER4001', message: '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.' },
+ DUPLICATE_EMAIL: { status: 409, code: 'USER4002', message: '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.' },
+ MEMBER_REQUIRED_FIELD: { status: 400, code: 'USER4003', message: 'name๊ณผ nickname์ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค.' },
+
+ // ๊ฐ๊ฒ
+ STORE_NOT_FOUND: { status: 404, code: 'STORE4001', message: '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.' },
+ STORE_CREATE_FAILED: { status: 500, code: 'STORE5001', message: '๊ฐ๊ฒ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+
+ // ๋ฆฌ๋ทฐ
+ INVALID_SCORE: { status: 400, code: 'REVIEW4001', message: '๋ณ์ ์ 1~5 ์ฌ์ด์ฌ์ผ ํฉ๋๋ค.' },
+ REVIEW_CREATE_FAILED: { status: 500, code: 'REVIEW5001', message: '๋ฆฌ๋ทฐ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+
+ // ๋ฏธ์
+ MISSION_NOT_FOUND: { status: 404, code: 'MISSION4001', message: '์กด์ฌํ์ง ์๋ ๋ฏธ์
์
๋๋ค.' },
+ MISSION_ALREADY_CHALLENGING: { status: 409, code: 'MISSION4002', message: '์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.' },
+ ONGOING_MISSION_NOT_FOUND: { status: 404, code: 'MISSION4003', message: '์งํ ์ค์ธ ๋ฏธ์
์ด ์์ต๋๋ค.' },
+ MISSION_STATUS_REQUIRED: { status: 400, code: 'MISSION4004', message: 'status๋ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค. (CHALLENGING ๋๋ COMPLETE)' },
+ MISSION_CREATE_FAILED: { status: 500, code: 'MISSION5001', message: '๋ฏธ์
์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+ MISSION_CHALLENGE_FAILED: { status: 500, code: 'MISSION5002', message: '๋ฏธ์
๋์ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+} as const
diff --git "a/\353\217\204\354\226\217/week7/src/utils/errors.ts" "b/\353\217\204\354\226\217/week7/src/utils/errors.ts"
new file mode 100644
index 0000000..c237d36
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/utils/errors.ts"
@@ -0,0 +1,11 @@
+export class BaseError extends Error {
+ status: number
+ code: string
+
+ constructor(message: string, statusCode = 500, code = 'INTERNAL_ERROR') {
+ super(message)
+ this.name = 'BaseError'
+ this.status = statusCode
+ this.code = code
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/src/utils/response.ts" "b/\353\217\204\354\226\217/week7/src/utils/response.ts"
new file mode 100644
index 0000000..6adf287
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/src/utils/response.ts"
@@ -0,0 +1,8 @@
+export const BaseResponse = (result: T, message = '์ฑ๊ณต์
๋๋ค.', code = 'COMMON200') => {
+ return {
+ isSuccess: true,
+ code,
+ message,
+ result,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/todolist.json" "b/\353\217\204\354\226\217/week7/todolist.json"
new file mode 100644
index 0000000..830b572
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/todolist.json"
@@ -0,0 +1,353 @@
+{
+ "chapter": "Chapter 5. API ๋ฐ ํ๋ก์ ํธ ์ค์ ๊ธฐ์ด",
+ "branch": "feature/chapter-05",
+ "week4_reference": "../week4",
+ "keywords": [
+ {
+ "term": "ํ๊ฒฝ ๋ณ์ (Environment Variables)",
+ "summary": "DB ๋น๋ฐ๋ฒํธยทAPI Key ๋ฑ ๋ฏผ๊ฐํ ๊ฐ์ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ณ .env ํ์ผ๋ก ๊ด๋ฆฌ. dotenv ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ก๋ํ๋ฉฐ .gitignore์ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํจ.",
+ "example": "process.env.DB_PASSWORD"
+ },
+ {
+ "term": "CORS (Cross-Origin Resource Sharing)",
+ "summary": "๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ Origin(๋๋ฉ์ธยทํฌํธ)์ ์๋ฒ์ ์์ฒญํ ๋ ๋ฐ์ํ๋ ๋ณด์ ์ ์ฑ
. Express์์๋ cors ๋ฏธ๋ค์จ์ด๋ก ํ์ฉ ์ฒ๋ฆฌ.",
+ "example": "app.use(cors())"
+ },
+ {
+ "term": "DB Connection Pool",
+ "summary": "๋งค ์์ฒญ๋ง๋ค DB ์ปค๋ฅ์
์ ์๋ก ์์ฑยทํด์ ํ์ง ์๊ณ , ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋ ์ปค๋ฅ์
ํ์์ ๋น๋ ค ์ฐ๋ ๋ฐฉ์. mysql2์ createPool()๋ก ๊ตฌํ. finally ๋ธ๋ก์์ conn.release()๋ก ๋ฐํํด์ผ ํจ.",
+ "example": "const pool = mysql.createPool({ connectionLimit: 10 })"
+ },
+ {
+ "term": "๋น๋๊ธฐ (async / await)",
+ "summary": "DB ์ฟผ๋ฆฌยท์ธ๋ถ API ํธ์ถ์ฒ๋ผ ์๋ต ๋๊ธฐ๊ฐ ํ์ํ ์์
์ Promise ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌ. async ํจ์ ์์์ await๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ฉฐ, ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์ฝ๊ธฐ ์ฝ๊ฒ ์์ฑ ๊ฐ๋ฅ.",
+ "example": "const [rows] = await pool.query('SELECT ...')"
+ },
+ {
+ "term": "try / catch / finally",
+ "summary": "๋น๋๊ธฐ ์์
์์ ๋ฐ์ํ ์ ์๋ ์๋ฌ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฒ๋ฆฌ. try: ์ ์ ๋ก์ง, catch: ์๋ฌ ์ฒ๋ฆฌ, finally: ์ปค๋ฅ์
๋ฐํ(conn.release()) ๋ฑ ํญ์ ์คํ๋์ด์ผ ํ๋ ์ ๋ฆฌ ์ฝ๋.",
+ "example": "try { ... } catch(err) { throw new Error(...) } finally { conn.release() }"
+ },
+ {
+ "term": "Interface (์ธํฐํ์ด์ค)",
+ "summary": "TypeScript์์ ๊ฐ์ฒด์ ํํ(์์ฑยทํ์
)๋ฅผ ์ ์ํ๋ ์ค๊ณ๋. DTOยทRepository ๋ฐํ ํ์
๋ฑ์ ํ์ฉ. ?๋ฅผ ๋ถ์ด๋ฉด ์ ํ์ ํ๋กํผํฐ.",
+ "example": "export interface StoreCreateRequest { regionId: number; name: string; address: string; }"
+ },
+ {
+ "term": "Type Assertion (as ํค์๋)",
+ "summary": "TypeScript ์ปดํ์ผ๋ฌ์๊ฒ '์ด ๊ฐ์ ์ด ํ์
์ด์ผ'๋ผ๊ณ ๊ฐ์ ๋ก ์๋ ค์ฃผ๋ ๋ฌธ๋ฒ. req.body๋ ๊ธฐ๋ณธ์ ์ผ๋ก any์ด๋ฏ๋ก, as ๋ก ์ ์ํ ์ธํฐํ์ด์ค ํ์
์ผ๋ก ๋ณํํด ์ฌ์ฉ.",
+ "example": "req.body as StoreCreateRequest"
+ }
+ ],
+ "project_structure": {
+ "root": "week5/",
+ "note": "week4์ in-memory DB โ MySQL ์ค์ DB ์ฐ๊ฒฐ๋ก ์ ํ. ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค ๊ตฌ์กฐ ์ฑํ.",
+ "files": [
+ "src/index.ts - Express ์ฑ ์ง์
์ , ๋ฏธ๋ค์จ์ดยท๋ผ์ฐํฐ ๋ฑ๋ก",
+ "src/db.config.ts - MySQL Connection Pool ์ค์ ",
+ "src/modules/stores/controllers/store.controller.ts",
+ "src/modules/stores/services/store.service.ts",
+ "src/modules/stores/repositories/store.repository.ts",
+ "src/modules/stores/dtos/store.dto.ts",
+ "src/modules/reviews/controllers/review.controller.ts",
+ "src/modules/reviews/services/review.service.ts",
+ "src/modules/reviews/repositories/review.repository.ts",
+ "src/modules/reviews/dtos/review.dto.ts",
+ "src/modules/missions/controllers/mission.controller.ts",
+ "src/modules/missions/services/mission.service.ts",
+ "src/modules/missions/repositories/mission.repository.ts",
+ "src/modules/missions/dtos/mission.dto.ts",
+ ".env - DB ์ ์ ์ ๋ณดยทPORT (gitignore ํ์)",
+ ".gitignore",
+ "package.json",
+ "tsconfig.json",
+ "schema.sql - week4 schema.sql ์ฌ์ฌ์ฉ (ํ
์ด๋ธ ์ด๋ฏธ ์ ์๋จ)"
+ ]
+ },
+ "todos": [
+ {
+ "id": 1,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "GitHub ์ด์ ์์ฑ ๋ฐ ๋ธ๋์น ๋ถ๊ธฐ",
+ "status": "todo",
+ "details": [
+ "GitHub ์ ์ฅ์ Issues ํญ์์ ๋ผ๋ฒจ ์ ๋ฆฌ: bug, docs, feature, refactor",
+ "์ด์ ์ ๋ชฉ: '[feat] Chapter 5 - API ๊ตฌํ (๊ฐ๊ฒ ์ถ๊ฐ / ๋ฆฌ๋ทฐ / ๋ฏธ์
)'",
+ "Assignee: ๋ณธ์ธ, Label: feature ๋ก ์ด์ ์์ฑ",
+ "์ด์์์ 'Create a branch' ํด๋ฆญ โ feature/chapter-05 ๋ธ๋์น ์์ฑ",
+ "๋ก์ปฌ์์: git fetch origin && git checkout feature/chapter-05"
+ ]
+ },
+ {
+ "id": 2,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "Postman ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ ํ์ธ",
+ "status": "todo",
+ "details": [
+ "Postman ์ค์น (https://www.postman.com/downloads/)",
+ "Params / Authorization / Headers / Body ํญ ์ญํ ์ดํด",
+ "Body > raw > JSON ์ ํ ๋ฐฉ๋ฒ ์์ง",
+ "๋์ค์ API ํ
์คํธ ์ ์คํฌ๋ฆฐ์ท ์ ์ฅํ ์ค๋น"
+ ]
+ },
+ {
+ "id": 3,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "week5 ํด๋ ์ด๊ธฐํ ๋ฐ ์์กด์ฑ ์ค์น",
+ "status": "todo",
+ "details": [
+ "cd week5 && npm init -y",
+ "npm install express cors dotenv http-status-codes mysql2",
+ "npm install -D typescript @types/node @types/express @types/cors @types/dotenv nodemon tsx",
+ "npx tsc --init ํ tsconfig.json ์์ (rootDir: ./src, outDir: ./dist, module: NodeNext, strict: true ๋ฑ)",
+ "week4/tsconfig.json์ ์ฐธ๊ณ ํด module/moduleResolution ์ค์ ์ผ์น์ํค๊ธฐ",
+ "package.json scripts ์ถ๊ฐ: start / dev (nodemon --exec tsx src/index.ts)"
+ ]
+ },
+ {
+ "id": 4,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": ".env ํ์ผ ๋ฐ .gitignore ์์ฑ",
+ "status": "todo",
+ "details": [
+ ".gitignore์ node_modules/ / .env / .env.* ์ถ๊ฐ",
+ ".env์ PORT=3000, DB_HOST=localhost, DB_PORT=3306, DB_USER=root, DB_PASSWORD=๋น๋ฐ๋ฒํธ, DB_NAME=umc_mission ์์ฑ",
+ "DB_NAME์ week4/schema.sql ๊ธฐ์ค umc_mission ์ฌ์ฉ (์ด๋ฏธ ํ
์ด๋ธ ์์)"
+ ]
+ },
+ {
+ "id": 5,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/db.config.ts ์์ฑ - MySQL Connection Pool",
+ "status": "todo",
+ "details": [
+ "mysql2/promise์ createPool ์ฌ์ฉ",
+ "ํ๊ฒฝ ๋ณ์(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME)๋ก ์ค์ ",
+ "connectionLimit: 10, waitForConnections: true",
+ "week4 schema.sql์ DB(umc_mission)์ ์ฐ๊ฒฐ"
+ ],
+ "reference_week4": "week4/src/db/index.ts ๊ตฌ์กฐ ์ฐธ๊ณ (๋จ, in-memoryโMySQL๋ก ๋ณ๊ฒฝ)"
+ },
+ {
+ "id": 6,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/index.ts ์์ฑ - Express ์ฑ ์ง์
์ ",
+ "status": "todo",
+ "details": [
+ "dotenv.config() ์ต์๋จ ํธ์ถ",
+ "cors(), express.json(), express.urlencoded() ๋ฏธ๋ค์จ์ด ๋ฑ๋ก",
+ "๊ฐ ๋ชจ๋ ๋ผ์ฐํฐ ๋ฑ๋ก: /api/v1/stores, /api/v1/reviews, /api/v1/missions",
+ "์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ๋ฏธ๋ค์จ์ด ๋ฑ๋ก (์๋์ด ๋ฏธ์
: JSON ์๋ฌ ์๋ต)",
+ "app.listen(process.env.PORT || 3000)"
+ ],
+ "reference_week4": "week4/src/index.ts ๊ตฌ์กฐ ๊ทธ๋๋ก ํ์ฉ, corsยทdotenv ์ถ๊ฐ"
+ },
+ {
+ "id": 7,
+ "phase": "DB ์ค๋น",
+ "title": "MySQL์ week5์ฉ ํ
์ด๋ธ ํ์ธ ๋ฐ ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
",
+ "status": "todo",
+ "details": [
+ "week4/schema.sql๋ก ํ
์ด๋ธ ์์ฑ (์ด๋ฏธ ๋์ด์์ผ๋ฉด ์๋ต)",
+ "food_category ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO food_category(name) VALUES ('ํ์'),('์ค์'),('์ผ์'),('์์'),('์นํจ'),('๋ถ์'),('๊ณ ๊ธฐ/๊ตฌ์ด'),('๋์๋ฝ'),('์์'),('ํจ์คํธํธ๋'),('๋ค์ ํธ'),('์์์ํธ๋')",
+ "region ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO region(name) VALUES ('์์ธ'),('๊ฒฝ๊ธฐ'),('๋ถ์ฐ')...",
+ "member ๋๋ฏธ๋ฐ์ดํฐ 1๊ฑด ์ฝ์
(API ํ
์คํธ์ฉ ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ ]
+ },
+ {
+ "id": 8,
+ "phase": "API ๊ตฌํ - 1-1",
+ "title": "[ํ์] ํน์ ์ง์ญ์ ๊ฐ๊ฒ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores",
+ "request_body": {
+ "regionId": "number",
+ "foodCategoryId": "number",
+ "name": "string",
+ "description": "string (์ ํ)",
+ "address": "string",
+ "lat": "number (์ ํ)",
+ "lng": "number (์ ํ)"
+ },
+ "details": [
+ "store.dto.ts - StoreCreateRequest ์ธํฐํ์ด์ค ์ ์",
+ "store.dto.ts - bodyToStore() ๋ณํ ํจ์ ์์ฑ",
+ "store.repository.ts - addStore(): INSERT INTO store(...) VALUES(?)",
+ "store.service.ts - createStore(data): addStore ํธ์ถ ํ ๊ฒฐ๊ณผ ๋ฐํ",
+ "store.controller.ts - handleCreateStore: bodyToStore(req.body as ...) โ service ํธ์ถ โ 201 ์๋ต",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก: app.post('/api/v1/stores', handleCreateStore)"
+ ],
+ "reference_week4": "week4/src/controllers/store.controller.ts ํจํด ์ฐธ๊ณ "
+ },
+ {
+ "id": 9,
+ "phase": "API ๊ตฌํ - 1-2",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/stores/:storeId/reviews",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)",
+ "content": "string",
+ "score": "number (1.0~5.0)"
+ },
+ "details": [
+ "review.dto.ts - ReviewCreateRequest ์ธํฐํ์ด์ค (content, score, memberId)",
+ "review.dto.ts - bodyToReview() ๋ณํ ํจ์",
+ "review.repository.ts - addReview(): INSERT INTO review(...)",
+ "store.repository.ts (๋๋ review.repository.ts) - findStoreById(): SELECT * FROM store WHERE id=?",
+ "review.service.ts - createReview(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addReview ํธ์ถ",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.')",
+ "review.controller.ts - handleCreateReview: storeId = parseInt(req.params.storeId) โ service ํธ์ถ",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋ฆฌ๋ทฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ ๊ฐ๊ฒ๊ฐ ์กด์ฌํ๋์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/services/store.service.ts์ createReview ํจํด, week4 ์คํค๋ง์ review ํ
์ด๋ธ"
+ },
+ {
+ "id": 10,
+ "phase": "API ๊ตฌํ - 1-3",
+ "title": "๊ฐ๊ฒ์ ๋ฏธ์
์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores/:storeId/missions",
+ "request_body": {
+ "title": "string",
+ "reward": "number",
+ "spec": "string (์ ํ)",
+ "deadLine": "string (YYYY-MM-DD, ์ ํ)"
+ },
+ "details": [
+ "mission.dto.ts - MissionCreateRequest ์ธํฐํ์ด์ค",
+ "mission.dto.ts - bodyToMission() ๋ณํ ํจ์ (deadLine โ Date ๋ณํ)",
+ "mission.repository.ts - addMission(): INSERT INTO mission(store_id, title, reward, spec, dead_line)",
+ "mission.service.ts - createMission(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addMission",
+ "mission.controller.ts - handleCreateMission",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4/src/repositories/mission.repository.ts ํจํด, week4 schema.sql์ mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 11,
+ "phase": "API ๊ตฌํ - 1-4",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฏธ์
์ ๋์ ์ค์ธ ๋ฏธ์
์ ์ถ๊ฐ(๋ฏธ์
๋์ ํ๊ธฐ) API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/missions/:missionId/challenge",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ },
+ "details": [
+ "mission.dto.ts - MissionChallengeRequest ์ธํฐํ์ด์ค ({ memberId: number })",
+ "mission.repository.ts - findMemberMission(): SELECT * FROM member_mission WHERE member_id=? AND mission_id=?",
+ "mission.repository.ts - addMemberMission(): INSERT INTO member_mission(member_id, mission_id, status) VALUES(?,?,'CHALLENGING')",
+ "mission.service.ts - challengeMission(missionId, data): ์ค๋ณต ๋์ ๊ฒ์ฆ โ addMemberMission",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.')",
+ "mission.controller.ts - handleChallengeMission: missionId = parseInt(req.params.missionId) โ service",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋์ ํ๋ ค๋ ๋ฏธ์
์ด ์ด๋ฏธ ๋์ ์ค์ด์ง๋ ์์์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/repositories/mission.repository.ts, week4 schema.sql์ member_mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 12,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
2๋ฒ] Controller โ Service โ Repository โ DB ์์ฒญ ํ๋ฆ ์ ๋ฆฌ",
+ "status": "todo",
+ "details": [
+ "์: POST /api/v1/stores/:storeId/reviews ์์ฒญ ํ๋ฆ์ ์์๋๋ก ์์ฑ",
+ "1. ์ฌ์ฉ์๊ฐ POST /api/v1/stores/1/reviews ์์ฒญ ์ ์ก",
+ "2. index.ts์ ๋ผ์ฐํฐ๊ฐ handleCreateReview ์ปจํธ๋กค๋ฌ ํธ์ถ",
+ "3. Controller: req.body๋ฅผ ReviewCreateRequest ํ์
์ผ๋ก ๋ณํ(bodyToReview), storeId ํ์ฑ",
+ "4. Service: ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ(findStoreById) โ ์์ผ๋ฉด Error throw",
+ "5. Repository: INSERT INTO review ์ฟผ๋ฆฌ ์คํ โ insertId ๋ฐํ",
+ "6. Service: ๊ฒฐ๊ณผ๋ฅผ DTO๋ก ๋ณํํด Controller์ ๋ฐํ",
+ "7. Controller: 201 JSON ์๋ต ์ ์ก",
+ "์ํฌ๋ถ์ ์์ฝ ์ ๋ฆฌ ์น์
์ ์ด ๋ด์ฉ ํฌํจ"
+ ]
+ },
+ {
+ "id": 13,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
3๋ฒ] ํ์๊ฐ์
API์ bcrypt ๋น๋ฐ๋ฒํธ ํด์ฑ ์ถ๊ฐ",
+ "status": "todo",
+ "details": [
+ "npm install bcryptjs && npm install -D @types/bcryptjs (week4์ ์ด๋ฏธ ์ค์น๋จ)",
+ "member.dto.ts - MemberSignUpRequest ์ธํฐํ์ด์ค์ password ํ๋ ์ถ๊ฐ",
+ "member.repository.ts - addMember(): INSERT INTO member(..., password) VALUES(...)",
+ "member.service.ts - signUp(): const hashedPw = await bcrypt.hash(data.password, 10) โ addMember์ ์ ๋ฌ",
+ "member.controller.ts - handleSignUp ์์ฑ",
+ "POST /api/v1/members/signup ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4์์ bcryptjs ์ด๋ฏธ ์ฌ์ฉ ์ค - week4/package.json ์ฐธ๊ณ "
+ },
+ {
+ "id": 14,
+ "phase": "์๋์ด ๋ฏธ์
",
+ "title": "[์๋์ด] ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ - JSON ํํ ์๋ฌ ์๋ต",
+ "status": "todo",
+ "details": [
+ "src/middleware/error.middleware.ts ์์ฑ",
+ "ErrorRequestHandler ํ์
์ฌ์ฉ: (err, req, res, next) => void",
+ "res.status(err.status || 500).json({ success: false, message: err.message || '์๋ฒ ์๋ฌ' })",
+ "index.ts ๋งจ ๋ง์ง๋ง์ app.use(errorMiddleware) ๋ฑ๋ก",
+ "Controller์์ try-catch ์ ๊ฑฐํ๊ฑฐ๋ next(err) ์ฌ์ฉ์ผ๋ก ๋ณ๊ฒฝ",
+ "๊ธฐ์กด HTML ์๋ฌ ์๋ต โ JSON ์๋ฌ ์๋ต์ผ๋ก ๊ฐ์ "
+ ],
+ "reference_week4": "week4/src/middleware/error.middleware.ts ๊ทธ๋๋ก ํ์ฉ ๊ฐ๋ฅ"
+ },
+ {
+ "id": 15,
+ "phase": "ํ
์คํธ",
+ "title": "Postman / curl๋ก ๊ฐ API ํธ์ถ ๋ฐ ์คํฌ๋ฆฐ์ท ์ ์ฅ",
+ "status": "todo",
+ "details": [
+ "npm run dev ๋ก ์๋ฒ ์คํ",
+ "API 1-1: POST /api/v1/stores - ๊ฐ๊ฒ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: POST /api/v1/stores/:storeId/reviews - ๋ฆฌ๋ทฐ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: ์กด์ฌํ์ง ์๋ storeId๋ก ์์ฒญ โ ์๋ฌ ์๋ต ์คํฌ๋ฆฐ์ท",
+ "API 1-3: POST /api/v1/stores/:storeId/missions - ๋ฏธ์
์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: POST /api/v1/missions/:missionId/challenge - ๋์ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: ๋์ผ ๋ฏธ์
์ฌ๋์ โ '์ด๋ฏธ ๋์ ์ค' ์๋ฌ ์คํฌ๋ฆฐ์ท",
+ "DB์์ SELECT๋ก ๋ฐ์ดํฐ ์ฝ์
ํ์ธ ์คํฌ๋ฆฐ์ท"
+ ]
+ },
+ {
+ "id": 16,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "feature/chapter-05 ๋ธ๋์น์ push ๋ฐ PR ์์ฑ",
+ "status": "todo",
+ "details": [
+ "git add . && git commit -m 'feat: 5์ฃผ์ฐจ ๋ฏธ์
- API ๊ตฌํ (๊ฐ๊ฒ/๋ฆฌ๋ทฐ/๋ฏธ์
)'",
+ "git push origin feature/chapter-05",
+ "GitHub์์ PR ์์ฑ (main ๋ธ๋์น์ mergeํ์ง ๋ง ๊ฒ!)",
+ "์ํฌ๋ถ์ ๋ฏธ์
๊ธฐ๋ก๋์ GitHub ๋งํฌ ์ ์ถ",
+ "GitHub ์ด์ Close"
+ ]
+ },
+ {
+ "id": 17,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "ํต์ฌ ํค์๋ ๋ฐ ์์ฝ ์ ๋ฆฌ ์์ฑ",
+ "status": "todo",
+ "details": [
+ "์ด ํ์ผ ์๋จ์ keywords ์น์
์ ์ฐธ๊ณ ํด ์ํฌ๋ถ์ ๊ธฐ์
",
+ "์์ฝ ์ ๋ฆฌ: ControllerโServiceโRepositoryโDB ํ๋ฆ ์ค๋ช
",
+ "์ํด๋ฆฌ ์คํฌ๋ผ ์ง๋ฌธ ๋ต๋ณ: DTO ์์ด ์ฌ์ฉํ ๋์ ๋ฌธ์ ์ / Service Layer ํ์์ฑ"
+ ]
+ }
+ ],
+ "required_apis_summary": {
+ "must_implement": ["1-2 (๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ)", "1-4 (๋ฏธ์
๋์ ํ๊ธฐ)"],
+ "minimum_count": "ํ์ 2๊ฐ ํฌํจ ์ด 3๊ฐ ์ด์",
+ "senior_mission": "4๊ฐ ์ ๋ถ + JSON ์๋ฌ ์๋ต ๊ฐ์ "
+ },
+ "key_differences_from_week4": {
+ "database": "in-memory(week4) โ MySQL Connection Pool(week5)",
+ "modules": "flat ๊ตฌ์กฐ(week4) โ ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค(week5: src/modules/{๋๋ฉ์ธ}/)",
+ "env": ".env ํ์ผ ์ถ๊ฐ (dotenv ์ฌ์ฉ)",
+ "cors": "cors ๋ฏธ๋ค์จ์ด ์ถ๊ฐ",
+ "error_response": "HTML ์๋ฌ(๊ธฐ๋ณธ) โ JSON ์๋ฌ(์๋์ด ๋ฏธ์
๊ฐ์ )"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week7/tsconfig.json" "b/\353\217\204\354\226\217/week7/tsconfig.json"
new file mode 100644
index 0000000..d25f8a3
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/tsconfig.json"
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "target": "ESNext",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "lib": ["ESNext"],
+ "types": ["node"],
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "sourceMap": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git "a/\353\217\204\354\226\217/week7/week7-commands.md" "b/\353\217\204\354\226\217/week7/week7-commands.md"
new file mode 100644
index 0000000..37beced
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/week7-commands.md"
@@ -0,0 +1,116 @@
+# Week 7 ์คํ ๋ช
๋ น์ด ์์ ์ ๋ฆฌ
+
+> ์๋ ์์๋๋ก ์คํํ์ธ์. ๊ฐ ๋จ๊ณ ์๋ฃ ํ ๋ค์ ๋จ๊ณ๋ก ์งํํฉ๋๋ค.
+
+---
+
+## 1๋จ๊ณ. ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ ์ด๋
+
+```bash
+cd
+```
+
+> week6 ์ฝ๋๊ฐ ์๋ ํ๋ก์ ํธ ๋ฃจํธ ํด๋๋ก ์ด๋ํฉ๋๋ค.
+
+---
+
+## 2๋จ๊ณ. ํ์ ๋ฏธ๋ค์จ์ด ํจํค์ง ์ค์น
+
+```bash
+npm install morgan cookie-parser
+```
+
+---
+
+## 3๋จ๊ณ. ์ค์น ํ์ธ
+
+```bash
+cat package.json
+```
+
+> `dependencies`์ `morgan`๊ณผ `cookie-parser`๊ฐ ์ถ๊ฐ๋์๋์ง ํ์ธํฉ๋๋ค.
+
+---
+
+## 4๋จ๊ณ. Git ์ด๊ธฐํ ๋ฐ ์๊ฒฉ ์ ์ฅ์ ์ฐ๊ฒฐ
+
+> GitHub์์ ์ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ๋จผ์ ๋ง๋ ํ ์คํํฉ๋๋ค.
+
+```bash
+git init
+git remote add origin https://github.com//.git
+```
+
+---
+
+## 5๋จ๊ณ. ์์
๋ธ๋์น ์์ฑ ๋ฐ ์ด๋
+
+```bash
+git checkout -b feature/chapter-07
+```
+
+> **์ฃผ์:** `main` ๋ธ๋์น์๋ ์ ๋ pushํ์ง ์์ต๋๋ค. (Week 11 CI/CD ์ ์ฉ)
+
+---
+
+## 6๋จ๊ณ. ์ฝ๋ ์์ฑ ํ ์คํ
์ด์ง
+
+> `index.js`, `BaseResponse`, `BaseError`, `errorCode.js` ๋ฑ ์์ /์์ฑ ํ ์คํ
+
+```bash
+git add .
+```
+
+---
+
+## 7๋จ๊ณ. ์ปค๋ฐ
+
+```bash
+git commit -m "feat: add middleware, standard response, and error handling"
+```
+
+---
+
+## 8๋จ๊ณ. feature ๋ธ๋์น์ push
+
+```bash
+git push origin feature/chapter-07
+```
+
+---
+
+## (์ ํ) Prisma ๊ด๋ จ ๋ช
๋ น์ด
+
+### ์คํค๋ง ๋ณ๊ฒฝ ํ ๋ง์ด๊ทธ๋ ์ด์
์คํ
+
+```bash
+npx prisma migrate dev --name
+```
+
+### DB ์ํ์ ์คํค๋ง ๋๊ธฐํ ํ์ธ
+
+```bash
+npx prisma db pull
+```
+
+### Prisma Studio (GUI๋ก DB ํ์ธ)
+
+```bash
+npx prisma studio
+```
+
+---
+
+## ์ ์ฒด ์์ ์์ฝ
+
+| ์์ | ๋ช
๋ น์ด | ์ค๋ช
|
+|---|---|---|
+| 1 | `cd ` | ํ๋ก์ ํธ ํด๋ ์ด๋ |
+| 2 | `npm install morgan cookie-parser` | ๋ฏธ๋ค์จ์ด ์ค์น |
+| 3 | `cat package.json` | ์ค์น ํ์ธ |
+| 4 | `git init` | Git ์ด๊ธฐํ |
+| 5 | `git remote add origin ` | ์๊ฒฉ ์ ์ฅ์ ์ฐ๊ฒฐ |
+| 6 | `git checkout -b feature/chapter-07` | ์์
๋ธ๋์น ์์ฑ |
+| 7 | `git add .` | ๋ณ๊ฒฝ ํ์ผ ์คํ
์ด์ง |
+| 8 | `git commit -m "..."` | ์ปค๋ฐ |
+| 9 | `git push origin feature/chapter-07` | ๋ธ๋์น์ push |
diff --git "a/\353\217\204\354\226\217/week7/week7-workbook.md" "b/\353\217\204\354\226\217/week7/week7-workbook.md"
new file mode 100644
index 0000000..c235612
--- /dev/null
+++ "b/\353\217\204\354\226\217/week7/week7-workbook.md"
@@ -0,0 +1,282 @@
+# Week 7 ์ํฌ๋ถ - ๋ฏธ๋ค์จ์ด, ํ์ค ์๋ต/์๋ฌ ์ฒ๋ฆฌ, Git ์ ๋ต
+
+> **ํ๋ก์ ํธ ์ฐธ์กฐ:** week6_directory_contents ๊ธฐ๋ฐ์ผ๋ก week7 ๋ฆฌํฉํ ๋ง
+
+---
+
+## Step 1. ํ์ ๋ฏธ๋ค์จ์ด ์ค์น ๋ฐ ์ค์
+
+### ์ค์น ํจํค์ง
+
+```bash
+npm install morgan cookie-parser
+```
+
+### `index.js` ์ ์ฉ ์์
+
+```js
+const express = require('express');
+const morgan = require('morgan');
+const cookieParser = require('cookie-parser');
+
+const app = express();
+
+// ์์ฒญ ๋ก๊น
๋ฏธ๋ค์จ์ด
+app.use(morgan('dev'));
+
+// ์ฟ ํค ํ์ฑ ๋ฏธ๋ค์จ์ด
+app.use(cookieParser());
+
+// JSON ๋ฐ URL-encoded body ํ์ฑ
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+```
+
+### ๋ฏธ๋ค์จ์ด ์ญํ ์ค๋ช
+
+| ๋ฏธ๋ค์จ์ด | ์ญํ |
+|---|---|
+| `morgan('dev')` | HTTP ์์ฒญ/์๋ต ๋ก๊ทธ๋ฅผ ํฐ๋ฏธ๋์ ์ถ๋ ฅ (๊ฐ๋ฐ ํ๊ฒฝ์ฉ) |
+| `cookieParser()` | `req.cookies` ๊ฐ์ฒด๋ก ์ฟ ํค ๊ฐ ํ์ฑ |
+| `express.json()` | `Content-Type: application/json` ์์ฒญ ๋ฐ๋ ํ์ฑ |
+| `express.urlencoded()` | HTML form ๋ฐ์ดํฐ ํ์ฑ |
+
+---
+
+## Step 2. ํ์ค API ์ฑ๊ณต ์๋ต ํ์ ํต์ผ
+
+### ์๋ต ํ์ ์ ์
+
+๋ชจ๋ API์ ์ฑ๊ณต ์๋ต์ ์๋ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฆ
๋๋ค:
+
+```json
+{
+ "isSuccess": true,
+ "code": "COMMON200",
+ "message": "์ฑ๊ณต์
๋๋ค.",
+ "result": { }
+}
+```
+
+| ํ๋ | ํ์
| ์ค๋ช
|
+|---|---|---|
+| `isSuccess` | boolean | ์์ฒญ ์ฑ๊ณต ์ฌ๋ถ |
+| `code` | string | ์๋ต ์ฝ๋ (์: `COMMON200`, `USER404`) |
+| `message` | string | ์ฌ๋์ด ์ฝ์ ์ ์๋ ๋ฉ์์ง |
+| `result` | object \| array | ์ค์ ๋ฐ์ดํฐ |
+
+### BaseResponse ์ ํธ๋ฆฌํฐ ๊ตฌํ ์์
+
+```js
+// src/utils/response.js
+
+const BaseResponse = (result, message = '์ฑ๊ณต์
๋๋ค.', code = 'COMMON200') => {
+ return {
+ isSuccess: true,
+ code,
+ message,
+ result,
+ };
+};
+
+module.exports = { BaseResponse };
+```
+
+### ์ปจํธ๋กค๋ฌ์์ ์ฌ์ฉ
+
+```js
+const { BaseResponse } = require('../utils/response');
+
+// Before (๊ธฐ์กด ๋ฐฉ์)
+res.status(200).json(user);
+
+// After (ํ์ค ์๋ต ์ ์ฉ)
+res.status(200).json(BaseResponse(user));
+```
+
+---
+
+## Step 3. ์ค์ ์ง์ค์ ์ปค์คํ
์๋ฌ ์ฒ๋ฆฌ
+
+### BaseError ํด๋์ค ์ ์
+
+```js
+// src/utils/errors.js
+
+class BaseError extends Error {
+ constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
+ super(message);
+ this.name = 'BaseError';
+ this.status = statusCode;
+ this.code = code;
+ }
+}
+
+module.exports = { BaseError };
+```
+
+### ์๋ฌ ์ฝ๋ ์์ ์์
+
+```js
+// src/utils/errorCode.js
+
+const ErrorCode = {
+ USER_NOT_FOUND: { status: 404, code: 'USER4001', message: '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.' },
+ DUPLICATE_EMAIL: { status: 409, code: 'USER4002', message: '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.' },
+ INTERNAL_ERROR: { status: 500, code: 'COMMON500', message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.' },
+};
+
+module.exports = { ErrorCode };
+```
+
+### ๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ฌ ๋ฏธ๋ค์จ์ด (`index.js` ํ๋จ์ ์ถ๊ฐ)
+
+```js
+// ๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ฌ - ๋ฐ๋์ ๋ค๋ฅธ app.use() ์๋์ ์์นํด์ผ ํจ
+app.use((err, req, res, next) => {
+ if (err instanceof BaseError) {
+ return res.status(err.status).json({
+ isSuccess: false,
+ code: err.code,
+ message: err.message,
+ result: null,
+ });
+ }
+
+ // Prisma unique constraint ์๋ฌ ์ฒ๋ฆฌ
+ if (err.code === 'P2002') {
+ return res.status(409).json({
+ isSuccess: false,
+ code: 'USER4002',
+ message: '์ด๋ฏธ ์กด์ฌํ๋ ๋ฐ์ดํฐ์
๋๋ค.',
+ result: null,
+ });
+ }
+
+ console.error(err);
+ return res.status(500).json({
+ isSuccess: false,
+ code: 'COMMON500',
+ message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.',
+ result: null,
+ });
+});
+```
+
+### ์ปจํธ๋กค๋ฌ์์ ์๋ฌ ๋์ง๊ธฐ
+
+```js
+const { BaseError } = require('../utils/errors');
+const { ErrorCode } = require('../utils/errorCode');
+
+// Before (๊ธฐ์กด ๋ฐฉ์)
+res.status(404).send('User Not Found');
+
+// After (next()๋ก ์๋ฌ ์์)
+const user = await prisma.user.findUnique({ where: { id } });
+if (!user) {
+ return next(new BaseError(
+ ErrorCode.USER_NOT_FOUND.message,
+ ErrorCode.USER_NOT_FOUND.status,
+ ErrorCode.USER_NOT_FOUND.code
+ ));
+}
+```
+
+---
+
+## Step 4. GitHub ๋ธ๋์น ์ ๋ต
+
+### ๋ธ๋์น ๊ท์น
+
+| ๋ธ๋์น | ์ฉ๋ |
+|---|---|
+| `main` | Week 11 CI/CD ํ์ดํ๋ผ์ธ ์ ์ฉ โ **์ง์ push ๊ธ์ง** |
+| `feature/chapter-07` | Week 7 ์์
๋ธ๋์น |
+
+### ์ฃผ์์ฌํญ
+
+> `main` ๋ธ๋์น๋ Week 11 CI/CD ์ค์ ์ ์ํด ๋ณดํธ๋์ด ์์ต๋๋ค.
+> ๋ชจ๋ ์ฝ๋๋ ๋ฐ๋์ `feature/chapter-07` ๋ธ๋์น์ push ํ์ธ์.
+
+---
+
+## Step 5. Prisma ORM vs Raw SQL (mysql2) ๋น๊ต ๋ถ์
+
+### ๋น๊ตํ
+
+| ํญ๋ชฉ | Prisma ORM | Raw SQL (mysql2) |
+|---|---|---|
+| **๋ฌธ๋ฒ** | TypeScript ๊ธฐ๋ฐ ํ์
์์ API | ์ง์ SQL ๋ฌธ์์ด ์์ฑ |
+| **๊ฐ๋
์ฑ** | ๋์ (์ง๊ด์ ๋ฉ์๋ ์ฒด์ด๋) | ๋ฎ์ (SQL ์๋ จ๋ ํ์) |
+| **ํ์
์์ ์ฑ** | ์๋ ํ์
์ถ๋ก ์ง์ | ์์ (์ง์ ์บ์คํ
ํ์) |
+| **์ฑ๋ฅ ํ๋** | ์ ํ์ | ์์ ํ SQL ์ ์ด ๊ฐ๋ฅ |
+| **๋ง์ด๊ทธ๋ ์ด์
** | `prisma migrate dev` ์๋ํ | ์๋ ๊ด๋ฆฌ |
+| **ํ์ต ๋น์ฉ** | Prisma ๋ฌธ๋ฒ ํ์ต ํ์ | SQL ์ง์๋ง ์์ผ๋ฉด ๋จ |
+| **๋ณต์ก ์ฟผ๋ฆฌ** | ํ๊ณ ์์ (rawQuery ํผ์ฉ) | ๋ฌด์ ํ |
+
+---
+
+### `prisma migrate dev` ์ฅ๋จ์ ๋ถ์
+
+#### ์ฅ์
+
+- **์๋ ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ ์์ฑ:** `schema.prisma` ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ์งํด SQL ํ์ผ ์๋ ์์ฑ
+- **ํ์คํ ๋ฆฌ ๊ด๋ฆฌ:** `prisma/migrations/` ํด๋์ ๋ณ๊ฒฝ ์ด๋ ฅ ๋์ ๋ณด๊ด
+- **๊ฐ๋ฐ ํธ์์ฑ:** ์คํค๋ง โ DB ๋๊ธฐํ๋ฅผ ๋ช
๋ น์ด ํ ์ค๋ก ์ฒ๋ฆฌ
+- **Seed ์ฐ๋:** `prisma db seed`์ ์ฐ๊ณํ์ฌ ์ด๊ธฐ ๋ฐ์ดํฐ ์ฝ์
๊ฐ๋ฅ
+
+#### ๋จ์
+
+- **ํ ํ์
์ ์ถฉ๋ ์ํ:** ์ฌ๋ฌ ์ฌ๋์ด `schema.prisma`๋ฅผ ๋์์ ์์ ํ๋ฉด migration ์ถฉ๋ ๋ฐ์
+- **ํ๋ก๋์
๋ถ์ ํฉ:** ์ด์ ํ๊ฒฝ์์๋ `prisma migrate deploy` ์ฌ์ฉ ๊ถ์ฅ (`dev`๋ ๊ฐ๋ฐ ์ ์ฉ)
+- **์๋ํ์ ๋ถํฌ๋ช
์ฑ:** ์์ฑ๋ SQL์ ๊ฒํ ํ์ง ์์ผ๋ฉด ์๋์น ์์ ๋ฐ์ดํฐ ์์ค ์ํ
+
+---
+
+### ํ์
์ Migration ์ถฉ๋ ๋ฐฉ์ง ์ ๋ต
+
+1. **์คํค๋ง ๋ด๋น์ ๋จ์ผํ:** `schema.prisma` ์์ ์ ํ ๋ช
์ด ๋ด๋นํ๊ฑฐ๋ PR ๋ฆฌ๋ทฐ๋ฅผ ํ์๋ก ์ค์
+2. **๋ธ๋์น ์ ๋ต ์ค์:** ๊ธฐ๋ฅ ๋ธ๋์น์์ ์คํค๋ง ์์ ํ PR ๋จธ์ง ์์ ์งํค๊ธฐ
+3. **migration ํ์ผ ์ปค๋ฐ ํ์:** `prisma/migrations/` ํด๋๋ฅผ `.gitignore`์ ์ถ๊ฐํ์ง ๋ง๊ณ ํญ์ ์ปค๋ฐ
+4. **๋จธ์ง ์ `prisma migrate dev` ์ฌ์คํ:** ๋จธ์ง ํ ๋ก์ปฌ์์ ๋ฐ๋์ ์ฌ์คํํ์ฌ ๋๊ธฐํ ํ์ธ
+5. **ํ ๋ด DB ์ํ ๊ณต์ :** ๋ง์ด๊ทธ๋ ์ด์
์คํ ์ฌ๋ถ๋ฅผ ํ ์ฑ๋(์ฌ๋ ๋ฑ)์ ๊ณต์
+
+---
+
+### ๊ฐ์ธ ์ ํธ๋: Prisma vs mysql2
+
+**์ ํ: Prisma ORM**
+
+**์ด์ :**
+
+- TypeScript ํ๊ฒฝ์์ ํ์
์๋ ์์ฑ๊ณผ ์ปดํ์ผ ํ์ ์๋ฌ ๊ฒ์ถ์ด ๊ฐ๋ฅํด ๋ฐํ์ ๋ฒ๊ทธ ๊ฐ์
+- ๋ณต์กํ JOIN๋ณด๋ค ๋จ์ CRUD ์์ฃผ์ธ ํ์ฌ ํ๋ก์ ํธ ๊ท๋ชจ์ ์ถฉ๋ถ
+- ๋ง์ด๊ทธ๋ ์ด์
์๋ํ๋ก ํ ์ ์ฒด์ DB ์ํ๋ฅผ ์ผ๊ด๋๊ฒ ์ ์ง ๊ฐ๋ฅ
+- `prisma studio`๋ฅผ ํตํ GUI ๋ฐ์ดํฐ ํ์ธ์ด ๊ฐ๋ฐ ์์ฐ์ฑ์ ๋์
+
+**mysql2๊ฐ ๋ ์ ํฉํ ๊ฒฝ์ฐ:**
+
+- ๋ณต์กํ ์ง๊ณ ์ฟผ๋ฆฌ๋ ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋ง์ ์๋น์ค
+- ๊ทน๋์ ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ ๋๊ท๋ชจ ํธ๋ํฝ ํ๊ฒฝ
+
+---
+
+## ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ ์ ์
+
+```
+project/
+โโโ index.js
+โโโ prisma/
+โ โโโ schema.prisma
+โ โโโ migrations/
+โโโ src/
+โ โโโ controllers/
+โ โโโ services/
+โ โโโ repositories/
+โ โโโ utils/
+โ โโโ response.js โ BaseResponse
+โ โโโ errors.js โ BaseError
+โ โโโ errorCode.js โ ์๋ฌ ์ฝ๋ ์์
+โโโ package.json
+```
diff --git "a/\353\217\204\354\226\217/week8/.gitignore" "b/\353\217\204\354\226\217/week8/.gitignore"
new file mode 100644
index 0000000..7206753
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/.gitignore"
@@ -0,0 +1,22 @@
+# dependency directories
+node_modules/
+
+# build output
+dist/
+
+# dotenv environment variable files
+.env
+.env.local
+.env.development
+.env.production
+.env.*
+.claude*
+
+# macOS
+.DS_Store
+
+# logs
+*.log
+npm-debug.log*
+
+/src/generated/prisma
diff --git "a/\353\217\204\354\226\217/week8/README.md" "b/\353\217\204\354\226\217/week8/README.md"
new file mode 100644
index 0000000..bb278f1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/README.md"
@@ -0,0 +1 @@
+# umc-node-study
\ No newline at end of file
diff --git "a/\353\217\204\354\226\217/week8/package-lock.json" "b/\353\217\204\354\226\217/week8/package-lock.json"
new file mode 100644
index 0000000..181b1c6
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/package-lock.json"
@@ -0,0 +1,4842 @@
+{
+ "name": "week8",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0",
+ "morgan": "^1.10.1",
+ "swagger-ui-express": "^5.0.1",
+ "tsoa": "^6.6.0"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/morgan": "^1.9.10",
+ "@types/node": "^25.6.0",
+ "@types/swagger-ui-express": "^4.1.8",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0",
+ "typescript": "^6.0.3"
+ }
+ },
+ "node_modules/@electric-sql/pglite": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz",
+ "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@electric-sql/pglite-socket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz",
+ "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "pglite-server": "dist/scripts/server.js"
+ },
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@electric-sql/pglite-tools": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz",
+ "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hapi/accept": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz",
+ "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/ammo": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz",
+ "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/b64": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz",
+ "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/boom": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz",
+ "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/bounce": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.2.tgz",
+ "integrity": "sha512-d0XmlTi3H9HFDHhQLjg4F4auL1EY3Wqj7j7/hGDhFFe6xAbnm3qiGrXeT93zZnPH8gH+SKAFYiRzu26xkXcH3g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/bourne": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
+ "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/call": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz",
+ "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/catbox": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz",
+ "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/podium": "^5.0.0",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/catbox-memory": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.2.tgz",
+ "integrity": "sha512-H1l4ugoFW/ZRkqeFrIo8p1rWN0PA4MDTfu4JmcoNDvnY975o29mqoZblqFTotxNHlEkMPpIiIBJTV+Mbi+aF0g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/content": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.2.tgz",
+ "integrity": "sha512-OKyCOTjNR1hftwSjk9ueyAQTw8AwapvzBrPIWMGn39vhR5PmqLdYFmLc35bsSBye7gSMnlkXfc679bUdMIcRyQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.0"
+ }
+ },
+ "node_modules/@hapi/cryptiles": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.3.tgz",
+ "integrity": "sha512-r6VKalpbMHz4ci3gFjFysBmhwCg70RpYZy6OkjEpdXzAYnYFX5XsW7n4YMJvuIYpnMwLxGUjK/cBhA7X3JDvXw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@hapi/file": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz",
+ "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/hapi": {
+ "version": "21.4.9",
+ "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.4.9.tgz",
+ "integrity": "sha512-YnecZOVx2AD08VvPl0ZaFS0MjEHqg+InGRmBRli731ct+VwI++dpu3BIYA1Z4SMr6HUAnpyvbQ1aq5woe3fBWg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/accept": "^6.0.3",
+ "@hapi/ammo": "^6.0.1",
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bounce": "^3.0.2",
+ "@hapi/call": "^9.0.1",
+ "@hapi/catbox": "^12.1.1",
+ "@hapi/catbox-memory": "^6.0.2",
+ "@hapi/heavy": "^8.0.1",
+ "@hapi/hoek": "^11.0.7",
+ "@hapi/mimos": "^7.0.1",
+ "@hapi/podium": "^5.0.2",
+ "@hapi/shot": "^6.0.2",
+ "@hapi/somever": "^4.1.1",
+ "@hapi/statehood": "^8.2.1",
+ "@hapi/subtext": "^8.1.3",
+ "@hapi/teamwork": "^6.0.1",
+ "@hapi/topo": "^6.0.2",
+ "@hapi/validate": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=14.15.0"
+ }
+ },
+ "node_modules/@hapi/heavy": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz",
+ "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/hoek": {
+ "version": "11.0.7",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz",
+ "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/iron": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz",
+ "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/b64": "^6.0.1",
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/cryptiles": "^6.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/mimos": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz",
+ "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "mime-db": "^1.52.0"
+ }
+ },
+ "node_modules/@hapi/nigel": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz",
+ "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/vise": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@hapi/pez": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.1.tgz",
+ "integrity": "sha512-yg2OS1tC0S1sHXvhUtWsfRn6lrKl9jKtRhZ+EI0woOW/gqX5vM2PZ1459ypCvCYDRLJ9nIyueeEH5MJV1ZDqIg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/b64": "^6.0.1",
+ "@hapi/boom": "^10.0.1",
+ "@hapi/content": "^6.0.1",
+ "@hapi/hoek": "^11.0.7",
+ "@hapi/nigel": "^5.0.1"
+ }
+ },
+ "node_modules/@hapi/podium": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.2.tgz",
+ "integrity": "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/teamwork": "^6.0.0",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/shot": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.2.tgz",
+ "integrity": "sha512-WKK1ShfJTrL1oXC0skoIZQYzvLsyMDEF8lfcWuQBjpjCN29qivr9U36ld1z0nt6edvzv28etNMOqUF4klnHryw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/somever": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz",
+ "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/bounce": "^3.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/statehood": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.2.1.tgz",
+ "integrity": "sha512-xf72TG/QINW26jUu+uL5H+crE1o8GplIgfPWwPZhnAGJzetIVAQEQYvzq+C0aEVHg5/lMMtQ+L9UryuSa5Yjkg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bounce": "^3.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/cryptiles": "^6.0.1",
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/iron": "^7.0.1",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/subtext": {
+ "version": "8.1.3",
+ "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.3.tgz",
+ "integrity": "sha512-WTpEZQjBP3UJ3gGunNl3w5Ao1EOJsuu2vttZ2KEcG+csSLxc0dI6VIkl2md2jDlHiQ2ARAoqdSUScy05A/NHtA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/content": "^6.0.2",
+ "@hapi/file": "^3.0.0",
+ "@hapi/hoek": "^11.0.7",
+ "@hapi/pez": "^6.1.1",
+ "@hapi/wreck": "^18.1.1"
+ }
+ },
+ "node_modules/@hapi/teamwork": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.1.tgz",
+ "integrity": "sha512-52OXRslUfYwXAOG8k58f2h2ngXYQGP0x5RPOo+eWA/FtyLgHjGMrE3+e9LSXP/0q2YfHAK5wj9aA9DTy1K+kyQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@hapi/topo": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz",
+ "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/validate": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz",
+ "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/topo": "^6.0.1"
+ }
+ },
+ "node_modules/@hapi/vise": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz",
+ "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/wreck": {
+ "version": "18.1.2",
+ "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.2.tgz",
+ "integrity": "sha512-3dMnV2pfhQiyEqu8DL3VBmxkdLiRDiiUDuG79Dp+UK1gL9ZxAfDOUhB6k3D5MLqcgJJ1IARyGFhwoc1NITr/pg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.11",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
+ "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@prisma/adapter-mariadb": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.8.0.tgz",
+ "integrity": "sha512-mWsgcfbUjxB3qSzRlLs8E03vsKrqXzYK2zpx3e8u6wIgeHJM/sE46cuOGcYvHiZGmeQLCd3xL6YSSGM9QOLI6w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/driver-adapter-utils": "7.8.0",
+ "mariadb": "3.4.5"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz",
+ "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/client-runtime-utils": "7.8.0"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/client-runtime-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz",
+ "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/config": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz",
+ "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.3.4",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.20.0",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz",
+ "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/dev": {
+ "version": "0.24.3",
+ "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz",
+ "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "@electric-sql/pglite": "0.4.1",
+ "@electric-sql/pglite-socket": "0.1.1",
+ "@electric-sql/pglite-tools": "0.3.1",
+ "@hono/node-server": "1.19.11",
+ "@prisma/get-platform": "7.2.0",
+ "@prisma/query-plan-executor": "7.2.0",
+ "@prisma/streams-local": "0.1.2",
+ "foreground-child": "3.3.1",
+ "get-port-please": "3.2.0",
+ "hono": "^4.12.8",
+ "http-status-codes": "2.3.0",
+ "pathe": "2.0.3",
+ "proper-lockfile": "4.1.2",
+ "remeda": "2.33.4",
+ "std-env": "3.10.0",
+ "valibot": "1.2.0",
+ "zeptomatch": "2.1.0"
+ }
+ },
+ "node_modules/@prisma/driver-adapter-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz",
+ "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz",
+ "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/fetch-engine": "7.8.0",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz",
+ "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz",
+ "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
+ "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.2.0"
+ }
+ },
+ "node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+ "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/query-plan-executor": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
+ "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/streams-local": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz",
+ "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ajv": "^8.12.0",
+ "better-result": "^2.7.0",
+ "env-paths": "^3.0.0",
+ "proper-lockfile": "^4.1.2"
+ },
+ "engines": {
+ "bun": ">=1.3.6",
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@prisma/studio-core": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz",
+ "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@radix-ui/react-toggle": "1.1.10",
+ "chart.js": "4.5.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0",
+ "pnpm": "8"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
+ "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@scarf/scarf": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsoa/cli": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@tsoa/cli/-/cli-6.6.0.tgz",
+ "integrity": "sha512-thSW0EiqjkF7HspcPIVIy0ZX65VqbWALHbxwl8Sk83j2kakOMq+fJvfo8FcBAWlMki+JDH7CO5iaAaSLHbeqtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tsoa/runtime": "^6.6.0",
+ "@types/multer": "^1.4.12",
+ "fs-extra": "^11.2.0",
+ "glob": "^10.3.10",
+ "handlebars": "^4.7.8",
+ "merge-anything": "^5.1.7",
+ "minimatch": "^9.0.1",
+ "ts-deepmerge": "^7.0.2",
+ "typescript": "^5.7.2",
+ "validator": "^13.12.0",
+ "yaml": "^2.6.1",
+ "yargs": "^17.7.1"
+ },
+ "bin": {
+ "tsoa": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "yarn": ">=1.9.4"
+ }
+ },
+ "node_modules/@tsoa/cli/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/@tsoa/cli/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@tsoa/cli/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@tsoa/cli/node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/@tsoa/runtime": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@tsoa/runtime/-/runtime-6.6.0.tgz",
+ "integrity": "sha512-+rF2gdL8CX+jQ82/IBc+MRJFNAvWPoBBl77HHJv3ESVMqbKhlhlo97JHmKyFbLcX6XOJN8zl8gfQpAEJN4SOMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hapi": "^21.3.12",
+ "@types/koa": "^2.15.0",
+ "@types/multer": "^1.4.12",
+ "express": "^4.21.2",
+ "reflect-metadata": "^0.2.2",
+ "validator": "^13.12.0"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "yarn": ">=1.9.4"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/body-parser": {
+ "version": "1.20.5",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+ "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.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.15.1",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/debug/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/@tsoa/runtime/node_modules/express": {
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.5",
+ "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.15.1",
+ "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/@tsoa/runtime/node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "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.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/@tsoa/runtime/node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/@tsoa/runtime/node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@types/accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/content-disposition": {
+ "version": "0.5.9",
+ "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz",
+ "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==",
+ "license": "MIT"
+ },
+ "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",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/cookies": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz",
+ "integrity": "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/express": "*",
+ "@types/keygrip": "*",
+ "@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": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/http-assert": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz",
+ "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==",
+ "license": "MIT"
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/@types/keygrip": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz",
+ "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/koa": {
+ "version": "2.15.2",
+ "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.2.tgz",
+ "integrity": "sha512-CB+iyjjh1uS5N6/CKwXvw0qA7USMS2WVc4Tjf660yCjhdvqzNr8gdFcIawB41zGGptOQ+d1fnpaQWIIUXYxR3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/accepts": "*",
+ "@types/content-disposition": "*",
+ "@types/cookies": "*",
+ "@types/http-assert": "*",
+ "@types/http-errors": "*",
+ "@types/keygrip": "*",
+ "@types/koa-compose": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/koa-compose": {
+ "version": "3.2.9",
+ "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.9.tgz",
+ "integrity": "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/koa": "*"
+ }
+ },
+ "node_modules/@types/morgan": {
+ "version": "1.9.10",
+ "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz",
+ "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/multer": {
+ "version": "1.4.13",
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
+ "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
+ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.19.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/swagger-ui-express": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
+ "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "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/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/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/better-result": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.0.tgz",
+ "integrity": "sha512-NHwGDGVbRlWDOce3CwcfGIrcNR9zY37ut3SVwQVfv57DZdVhxjhA4mfaHN1n8QwWnRAR4iErpW1X/eaiaUaFYg==",
+ "devOptional": 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": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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/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/c12": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
+ "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^5.0.0",
+ "confbox": "^0.2.4",
+ "defu": "^6.1.6",
+ "dotenv": "^17.3.1",
+ "exsolve": "^1.0.8",
+ "giget": "^3.2.0",
+ "jiti": "^2.6.1",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^2.1.0",
+ "pkg-types": "^2.3.0",
+ "rc9": "^3.0.1"
+ },
+ "peerDependencies": {
+ "magicast": "*"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": 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": {
+ "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/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "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/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==",
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/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/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
+ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/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": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+ "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/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
+ "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/effect": {
+ "version": "3.20.0",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz",
+ "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "license": "MIT"
+ },
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
+ "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "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/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": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "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": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "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/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/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==",
+ "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-port-please": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
+ "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "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-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/giget": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
+ "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "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/glob/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/glob/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/grammex": {
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
+ "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/graphmatch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
+ "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.9",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
+ "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "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.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.15",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz",
+ "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-status-codes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "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-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",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
+ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/mariadb": {
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz",
+ "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==",
+ "license": "LGPL-2.1-or-later",
+ "dependencies": {
+ "@types/geojson": "^7946.0.16",
+ "@types/node": "^24.0.13",
+ "denque": "^2.1.0",
+ "iconv-lite": "^0.6.3",
+ "lru-cache": "^10.4.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/mariadb/node_modules/@types/node": {
+ "version": "24.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
+ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/mariadb/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/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": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-anything": {
+ "version": "5.1.7",
+ "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
+ "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/morgan": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
+ "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
+ "license": "MIT",
+ "dependencies": {
+ "basic-auth": "~2.0.1",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/morgan/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/morgan/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/morgan/node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/mysql2": {
+ "version": "3.15.3",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
+ "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.0",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "license": "MIT"
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "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/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/nodemon/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/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/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "license": "MIT",
+ "engines": {
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
+ "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-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
+ "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.4",
+ "exsolve": "^1.0.8",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/postgres": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
+ "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
+ "devOptional": true,
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/porsager"
+ }
+ },
+ "node_modules/prisma": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz",
+ "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "7.8.0",
+ "@prisma/dev": "0.24.3",
+ "@prisma/engines": "7.8.0",
+ "@prisma/studio-core": "0.27.3",
+ "mysql2": "3.15.3",
+ "postgres": "3.4.7"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "better-sqlite3": ">=9.0.0",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "better-sqlite3": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "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/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "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": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/rc9": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
+ "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.6",
+ "destr": "^2.0.5"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.5"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/reflect-metadata": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
+ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/remeda": {
+ "version": "2.33.4",
+ "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
+ "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/remeda"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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/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/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
+ "devOptional": true
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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==",
+ "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==",
+ "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",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "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/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "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/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/swagger-ui-dist": {
+ "version": "5.32.6",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz",
+ "integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@scarf/scarf": "=1.4.0"
+ }
+ },
+ "node_modules/swagger-ui-express": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
+ "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
+ "license": "MIT",
+ "dependencies": {
+ "swagger-ui-dist": ">=5.0.0"
+ },
+ "engines": {
+ "node": ">= v0.10.32"
+ },
+ "peerDependencies": {
+ "express": ">=4.0.0 || >=5.0.0-beta"
+ }
+ },
+ "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/ts-deepmerge": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz",
+ "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14.13.1"
+ }
+ },
+ "node_modules/tsoa": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/tsoa/-/tsoa-6.6.0.tgz",
+ "integrity": "sha512-7FudRojmbEpbSQ3t1pyG5EjV3scF7/X75giQt1q+tnuGjjJppB8BOEmIdCK/G8S5Dqnmpwz5Q3vxluKozpIW9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@tsoa/cli": "^6.6.0",
+ "@tsoa/runtime": "^6.6.0"
+ },
+ "bin": {
+ "tsoa": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "yarn": ">=1.9.4"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.19.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
+ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "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/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/valibot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.15.35",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
+ "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
+ "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/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "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/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "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==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/zeptomatch": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
+ "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "grammex": "^3.1.11",
+ "graphmatch": "^1.1.0"
+ }
+ }
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/package.json" "b/\353\217\204\354\226\217/week8/package.json"
new file mode 100644
index 0000000..9bce09c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/package.json"
@@ -0,0 +1,33 @@
+{
+ "scripts": {
+ "build": "tsoa spec-and-routes && tsc",
+ "start": "tsoa spec-and-routes && tsx src/index.ts",
+ "dev": "tsoa spec-and-routes && nodemon --ext ts,prisma --ignore src/generated --exec \"npx prisma generate && tsx src/index.ts\""
+ },
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0",
+ "morgan": "^1.10.1",
+ "swagger-ui-express": "^5.0.1",
+ "tsoa": "^6.6.0"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/morgan": "^1.9.10",
+ "@types/node": "^25.6.0",
+ "@types/swagger-ui-express": "^4.1.8",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0",
+ "typescript": "^6.0.3"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/prisma.config.ts" "b/\353\217\204\354\226\217/week8/prisma.config.ts"
new file mode 100644
index 0000000..5170cc4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/prisma.config.ts"
@@ -0,0 +1,12 @@
+///
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+const { DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME } = process.env;
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ datasource: {
+ url: `mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT ?? 3306}/${DB_NAME}`,
+ },
+});
diff --git "a/\353\217\204\354\226\217/week8/prisma/migrations/20260428071132_init_database/migration.sql" "b/\353\217\204\354\226\217/week8/prisma/migrations/20260428071132_init_database/migration.sql"
new file mode 100644
index 0000000..31b12a1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/prisma/migrations/20260428071132_init_database/migration.sql"
@@ -0,0 +1,39 @@
+-- CreateTable
+CREATE TABLE `user` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `email` VARCHAR(255) NOT NULL,
+ `name` VARCHAR(100) NOT NULL,
+ `gender` VARCHAR(15) NOT NULL,
+ `birth` DATE NOT NULL,
+ `address` VARCHAR(255) NOT NULL,
+ `detail_address` VARCHAR(255) NULL,
+ `phone_number` VARCHAR(15) NOT NULL,
+
+ UNIQUE INDEX `email`(`email`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `food_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_favor_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `user_id` INTEGER NOT NULL,
+ `food_category_id` INTEGER NOT NULL,
+
+ INDEX `f_category_id`(`food_category_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_food_category_id_fkey` FOREIGN KEY (`food_category_id`) REFERENCES `food_category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week8/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql" "b/\353\217\204\354\226\217/week8/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
new file mode 100644
index 0000000..99445d4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
@@ -0,0 +1,25 @@
+-- CreateTable
+CREATE TABLE `store` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_store_review` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `user_id` INTEGER NOT NULL,
+ `content` TEXT NOT NULL,
+
+ INDEX `store_id`(`store_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week8/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql" "b/\353\217\204\354\226\217/week8/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
new file mode 100644
index 0000000..a6dd8b0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
@@ -0,0 +1,33 @@
+-- CreateTable
+CREATE TABLE `mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `title` VARCHAR(200) NOT NULL,
+ `reward` INTEGER NOT NULL,
+ `spec` TEXT NULL,
+ `dead_line` DATETIME(3) NULL,
+
+ INDEX `store_id`(`store_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `member_mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `member_id` INTEGER NOT NULL,
+ `mission_id` INTEGER NOT NULL,
+ `status` VARCHAR(15) NOT NULL,
+
+ INDEX `member_id`(`member_id`),
+ INDEX `mission_id`(`mission_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `mission` ADD CONSTRAINT `mission_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_member_id_fkey` FOREIGN KEY (`member_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week8/prisma/migrations/migration_lock.toml" "b/\353\217\204\354\226\217/week8/prisma/migrations/migration_lock.toml"
new file mode 100644
index 0000000..592fc0b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/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 (e.g., Git)
+provider = "mysql"
diff --git "a/\353\217\204\354\226\217/week8/prisma/schema.prisma" "b/\353\217\204\354\226\217/week8/prisma/schema.prisma"
new file mode 100644
index 0000000..a5758e0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/prisma/schema.prisma"
@@ -0,0 +1,100 @@
+generator client {
+ provider = "prisma-client"
+ output = "../src/generated/prisma"
+}
+
+datasource db {
+ provider = "mysql"
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique(map: "email") @db.VarChar(255)
+ name String @db.VarChar(100)
+ gender String @db.VarChar(15)
+ birth DateTime @db.Date
+ address String @db.VarChar(255)
+ detailAddress String? @map("detail_address") @db.VarChar(255)
+ phoneNumber String @map("phone_number") @db.VarChar(15)
+
+ userFavorCategories UserFavorCategory[]
+ reviews UserStoreReview[]
+ memberMissions MemberMission[]
+
+ @@map("user")
+}
+
+model FoodCategory {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ userFavorCategories UserFavorCategory[]
+
+ @@map("food_category")
+}
+
+model UserFavorCategory {
+ id Int @id @default(autoincrement())
+ userId Int @map("user_id")
+ foodCategoryId Int @map("food_category_id")
+ user User @relation(fields: [userId], references: [id])
+ foodCategory FoodCategory @relation(fields: [foodCategoryId], references: [id])
+
+ @@index([foodCategoryId], map: "f_category_id")
+ @@index([userId], map: "user_id")
+ @@map("user_favor_category")
+}
+
+model Store {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ reviews UserStoreReview[]
+ missions Mission[]
+
+ @@map("store")
+}
+
+model UserStoreReview {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ userId Int @map("user_id")
+ content String @db.Text
+
+ store Store @relation(fields: [storeId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+
+ @@index([storeId], map: "store_id")
+ @@index([userId], map: "user_id")
+ @@map("user_store_review")
+}
+
+model Mission {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ title String @db.VarChar(200)
+ reward Int
+ spec String? @db.Text
+ deadLine DateTime? @map("dead_line")
+
+ store Store @relation(fields: [storeId], references: [id])
+ memberMissions MemberMission[]
+
+ @@index([storeId], map: "store_id")
+ @@map("mission")
+}
+
+model MemberMission {
+ id Int @id @default(autoincrement())
+ userId Int @map("member_id")
+ missionId Int @map("mission_id")
+ status String @db.VarChar(15)
+
+ user User @relation(fields: [userId], references: [id])
+ mission Mission @relation(fields: [missionId], references: [id])
+
+ @@index([userId], map: "member_id")
+ @@index([missionId], map: "mission_id")
+ @@map("member_mission")
+}
+
diff --git "a/\353\217\204\354\226\217/week8/reset_db.sql" "b/\353\217\204\354\226\217/week8/reset_db.sql"
new file mode 100644
index 0000000..454f20b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/reset_db.sql"
@@ -0,0 +1,215 @@
+-- ============================================================
+-- DB ์ด๊ธฐํ ๋ฐ ์ฌ์์ฑ ์คํฌ๋ฆฝํธ
+-- ============================================================
+
+CREATE DATABASE IF NOT EXISTS umc_mission DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE umc_mission;
+
+-- FK ์ฒดํฌ ๋นํ์ฑํ ํ ์ ์ฒด DROP
+SET FOREIGN_KEY_CHECKS = 0;
+
+DROP TABLE IF EXISTS review_image;
+DROP TABLE IF EXISTS review;
+DROP TABLE IF EXISTS member_mission;
+DROP TABLE IF EXISTS mission;
+DROP TABLE IF EXISTS store_hours;
+DROP TABLE IF EXISTS store_image;
+DROP TABLE IF EXISTS store;
+DROP TABLE IF EXISTS member_prefer;
+DROP TABLE IF EXISTS member_agree;
+DROP TABLE IF EXISTS member;
+DROP TABLE IF EXISTS terms;
+DROP TABLE IF EXISTS food_category;
+DROP TABLE IF EXISTS region;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- ============================================================
+-- ํ
์ด๋ธ ์ฌ์์ฑ
+-- ============================================================
+
+CREATE TABLE region (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์ง์ญ๋ช
',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE food_category (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์นดํ
๊ณ ๋ฆฌ๋ช
(ํ์, ์ค์, ์ผ์ ๋ฑ)',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE terms (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ title VARCHAR(100) NOT NULL COMMENT '์ฝ๊ด ์ ๋ชฉ',
+ content TEXT NOT NULL COMMENT '์ฝ๊ด ๋ด์ฉ',
+ optional BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'TRUE: ์ ํ ๋์, FALSE: ํ์ ๋์',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE member (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ social_type VARCHAR(20) NULL,
+ social_id VARCHAR(100) NULL,
+ email VARCHAR(100) NULL,
+ password VARCHAR(255) NULL,
+ name VARCHAR(50) NOT NULL,
+ nickname VARCHAR(50) NOT NULL,
+ profile_image_url VARCHAR(500) NULL,
+ phone_num VARCHAR(20) NULL,
+ phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
+ birth DATE NULL,
+ gender ENUM('MALE', 'FEMALE', 'OTHER') NULL,
+ address VARCHAR(200) NULL,
+ spec_address VARCHAR(200) NULL,
+ point INT NOT NULL DEFAULT 0,
+ status ENUM('ACTIVE', 'INACTIVE', 'BANNED') NOT NULL DEFAULT 'ACTIVE',
+ inactive_date DATETIME NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_email (email),
+ UNIQUE KEY uq_member_social (social_type, social_id)
+);
+
+CREATE TABLE member_agree (
+ member_id BIGINT NOT NULL,
+ terms_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, terms_id),
+ CONSTRAINT fk_member_agree_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_agree_terms FOREIGN KEY (terms_id) REFERENCES terms (id)
+);
+
+CREATE TABLE member_prefer (
+ member_id BIGINT NOT NULL,
+ food_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, food_id),
+ CONSTRAINT fk_member_prefer_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_prefer_food FOREIGN KEY (food_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ region_id BIGINT NOT NULL,
+ food_category_id BIGINT NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ description TEXT NULL,
+ lat DECIMAL(10,7) NULL,
+ lng DECIMAL(10,7) NULL,
+ address VARCHAR(200) NOT NULL,
+ status ENUM('OPEN', 'CLOSED', 'PENDING') NOT NULL DEFAULT 'OPEN',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_region FOREIGN KEY (region_id) REFERENCES region (id),
+ CONSTRAINT fk_store_category FOREIGN KEY (food_category_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_image_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE store_hours (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ day_of_week VARCHAR(3) NOT NULL,
+ open_time TIME NOT NULL,
+ close_time TIME NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_store_hours_day (store_id, day_of_week),
+ CONSTRAINT fk_store_hours_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ title VARCHAR(200) NOT NULL,
+ reward INT NOT NULL DEFAULT 0,
+ spec VARCHAR(500) NULL,
+ dead_line DATE NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_mission_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE member_mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ mission_id BIGINT NOT NULL,
+ status ENUM('CHALLENGING', 'COMPLETE') NOT NULL DEFAULT 'CHALLENGING',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_mission (member_id, mission_id),
+ CONSTRAINT fk_member_mission_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_mission_mission FOREIGN KEY (mission_id) REFERENCES mission (id)
+);
+
+CREATE TABLE review (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ store_id BIGINT NOT NULL,
+ member_mission_id BIGINT NULL,
+ content TEXT NOT NULL,
+ score DECIMAL(2,1) NOT NULL,
+ owner_reply VARCHAR(500) NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_review_store FOREIGN KEY (store_id) REFERENCES store (id),
+ CONSTRAINT fk_review_member_mission FOREIGN KEY (member_mission_id) REFERENCES member_mission (id),
+ CONSTRAINT chk_review_score CHECK (score BETWEEN 1.0 AND 5.0)
+);
+
+CREATE TABLE review_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ review_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_image_review FOREIGN KEY (review_id) REFERENCES review (id)
+);
+
+-- ============================================================
+-- ์๋ ๋ฐ์ดํฐ (API ํ
์คํธ์ฉ ์ด๊ธฐ ๋ฐ์ดํฐ)
+-- ============================================================
+
+INSERT INTO region (name) VALUES
+ ('์์ธ'),
+ ('๊ฒฝ๊ธฐ'),
+ ('์ธ์ฒ'),
+ ('๋ถ์ฐ'),
+ ('๋๊ตฌ');
+
+INSERT INTO food_category (name) VALUES
+ ('ํ์'),
+ ('์ค์'),
+ ('์ผ์'),
+ ('์์'),
+ ('๋ถ์'),
+ ('์นดํ/๋์ ํธ'),
+ ('์นํจ'),
+ ('ํผ์'),
+ ('ํจ์คํธํธ๋');
diff --git "a/\353\217\204\354\226\217/week8/src/db.config.ts" "b/\353\217\204\354\226\217/week8/src/db.config.ts"
new file mode 100644
index 0000000..7b88907
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/db.config.ts"
@@ -0,0 +1,17 @@
+import "dotenv/config";
+import { PrismaClient } from "./generated/prisma/client.js";
+import { PrismaMariaDb } from "@prisma/adapter-mariadb";
+
+const adapter = new PrismaMariaDb({
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASSWORD,
+ database: process.env.DB_NAME,
+ port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 3306,
+ connectionLimit: 10,
+});
+
+export const prisma = new PrismaClient({
+ adapter,
+ log: ["query", "info", "error", "warn"],
+});
diff --git "a/\353\217\204\354\226\217/week8/src/generated/routes.ts" "b/\353\217\204\354\226\217/week8/src/generated/routes.ts"
new file mode 100644
index 0000000..020aa77
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/generated/routes.ts"
@@ -0,0 +1,622 @@
+/* tslint:disable */
+/* eslint-disable */
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import type { TsoaRoute } from '@tsoa/runtime';
+import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { StoreController } from './../modules/stores/controllers/store.controller';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { MissionController } from './../modules/missions/controllers/mission.controller';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { UserController } from './../modules/members/controllers/user.controller';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { MemberController } from './../modules/members/controllers/member.controller';
+import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express';
+
+
+
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+const models: TsoaRoute.Models = {
+ "StoreCreateResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "storeId": {"dataType":"double","required":true},
+ "name": {"dataType":"string","required":true},
+ "address": {"dataType":"string","required":true},
+ "regionId": {"dataType":"double","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_StoreCreateResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"StoreCreateResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "StoreCreateRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "regionId": {"dataType":"double","required":true},
+ "foodCategoryId": {"dataType":"double","required":true},
+ "name": {"dataType":"string","required":true},
+ "description": {"dataType":"string"},
+ "address": {"dataType":"string","required":true},
+ "lat": {"dataType":"double"},
+ "lng": {"dataType":"double"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewItem": {
+ "dataType": "refObject",
+ "properties": {
+ "reviewId": {"dataType":"double","required":true},
+ "memberId": {"dataType":"double","required":true},
+ "storeId": {"dataType":"double","required":true},
+ "content": {"dataType":"string","required":true},
+ "score": {"dataType":"double","required":true},
+ "createdAt": {"dataType":"datetime","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"ReviewItem"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_ReviewListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"ReviewListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewCreateResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "reviewId": {"dataType":"double","required":true},
+ "memberId": {"dataType":"double","required":true},
+ "storeId": {"dataType":"double","required":true},
+ "content": {"dataType":"string","required":true},
+ "score": {"dataType":"double","required":true},
+ "createdAt": {"dataType":"datetime","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_ReviewCreateResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"ReviewCreateResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewCreateRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "memberId": {"dataType":"double","required":true},
+ "content": {"dataType":"string","required":true},
+ "score": {"dataType":"double","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionCreateResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "missionId": {"dataType":"double","required":true},
+ "storeId": {"dataType":"double","required":true},
+ "title": {"dataType":"string","required":true},
+ "reward": {"dataType":"double","required":true},
+ "spec": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "deadLine": {"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "StoreMissionListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"MissionCreateResponse"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_StoreMissionListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"StoreMissionListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_MissionCreateResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"MissionCreateResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionCreateRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "title": {"dataType":"string","required":true},
+ "reward": {"dataType":"double","required":true},
+ "spec": {"dataType":"string"},
+ "deadLine": {"dataType":"string"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionChallengeResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "memberMissionId": {"dataType":"double","required":true},
+ "memberId": {"dataType":"double","required":true},
+ "missionId": {"dataType":"double","required":true},
+ "status": {"dataType":"string","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_MissionChallengeResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"MissionChallengeResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionChallengeRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "memberId": {"dataType":"double","required":true},
+ "status": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["CHALLENGING"]},{"dataType":"enum","enums":["COMPLETE"]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "UserReviewListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"ReviewCreateResponse"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_UserReviewListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"UserReviewListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "OngoingMissionListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"MissionCreateResponse"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_OngoingMissionListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"OngoingMissionListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MemberSignUpResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "memberId": {"dataType":"double","required":true},
+ "name": {"dataType":"string","required":true},
+ "nickname": {"dataType":"string","required":true},
+ "email": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "phoneNum": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "status": {"dataType":"string","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_MemberSignUpResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"MemberSignUpResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MemberSignUpRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "name": {"dataType":"string","required":true},
+ "nickname": {"dataType":"string","required":true},
+ "email": {"dataType":"string"},
+ "password": {"dataType":"string"},
+ "phoneNum": {"dataType":"string"},
+ "birth": {"dataType":"string"},
+ "gender": {"dataType":"string"},
+ "address": {"dataType":"string"},
+ "specAddress": {"dataType":"string"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+};
+const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true});
+
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+
+
+
+export function RegisterRoutes(app: Router) {
+
+ // ###########################################################################################################
+ // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look
+ // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa
+ // ###########################################################################################################
+
+
+
+ const argsStoreController_handleCreateStore: Record = {
+ body: {"in":"body","name":"body","required":true,"ref":"StoreCreateRequest"},
+ };
+ app.post('/stores',
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleCreateStore)),
+
+ async function StoreController_handleCreateStore(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleCreateStore, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCreateStore',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleListStoreReviews: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/stores/:storeId/reviews',
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleListStoreReviews)),
+
+ async function StoreController_handleListStoreReviews(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleListStoreReviews, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListStoreReviews',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleCreateReview: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ body: {"in":"body","name":"body","required":true,"ref":"ReviewCreateRequest"},
+ };
+ app.post('/stores/:storeId/reviews',
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleCreateReview)),
+
+ async function StoreController_handleCreateReview(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleCreateReview, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCreateReview',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleListStoreMissions: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/stores/:storeId/missions',
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleListStoreMissions)),
+
+ async function StoreController_handleListStoreMissions(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleListStoreMissions, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListStoreMissions',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleCreateMission: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ body: {"in":"body","name":"body","required":true,"ref":"MissionCreateRequest"},
+ };
+ app.post('/stores/:storeId/missions',
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleCreateMission)),
+
+ async function StoreController_handleCreateMission(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleCreateMission, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCreateMission',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsMissionController_handleChallengeMission: Record = {
+ missionId: {"in":"path","name":"missionId","required":true,"dataType":"double"},
+ body: {"in":"body","name":"body","required":true,"ref":"MissionChallengeRequest"},
+ };
+ app.post('/missions/:missionId/challenge',
+ ...(fetchMiddlewares(MissionController)),
+ ...(fetchMiddlewares(MissionController.prototype.handleChallengeMission)),
+
+ async function MissionController_handleChallengeMission(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsMissionController_handleChallengeMission, request, response });
+
+ const controller = new MissionController();
+
+ await templateService.apiHandler({
+ methodName: 'handleChallengeMission',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsUserController_handleListUserReviews: Record = {
+ userId: {"in":"path","name":"userId","required":true,"dataType":"double"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/users/:userId/reviews',
+ ...(fetchMiddlewares(UserController)),
+ ...(fetchMiddlewares(UserController.prototype.handleListUserReviews)),
+
+ async function UserController_handleListUserReviews(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleListUserReviews, request, response });
+
+ const controller = new UserController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListUserReviews',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsUserController_handleListOngoingMissions: Record = {
+ userId: {"in":"path","name":"userId","required":true,"dataType":"double"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/users/:userId/missions',
+ ...(fetchMiddlewares(UserController)),
+ ...(fetchMiddlewares(UserController.prototype.handleListOngoingMissions)),
+
+ async function UserController_handleListOngoingMissions(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleListOngoingMissions, request, response });
+
+ const controller = new UserController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListOngoingMissions',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsUserController_handleCompleteMission: Record = {
+ userId: {"in":"path","name":"userId","required":true,"dataType":"double"},
+ missionId: {"in":"path","name":"missionId","required":true,"dataType":"double"},
+ };
+ app.patch('/users/:userId/missions/:missionId',
+ ...(fetchMiddlewares(UserController)),
+ ...(fetchMiddlewares(UserController.prototype.handleCompleteMission)),
+
+ async function UserController_handleCompleteMission(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleCompleteMission, request, response });
+
+ const controller = new UserController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCompleteMission',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsMemberController_handleSignUp: Record = {
+ body: {"in":"body","name":"body","required":true,"ref":"MemberSignUpRequest"},
+ };
+ app.post('/members/signup',
+ ...(fetchMiddlewares(MemberController)),
+ ...(fetchMiddlewares(MemberController.prototype.handleSignUp)),
+
+ async function MemberController_handleSignUp(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsMemberController_handleSignUp, request, response });
+
+ const controller = new MemberController();
+
+ await templateService.apiHandler({
+ methodName: 'handleSignUp',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+}
+
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
diff --git "a/\353\217\204\354\226\217/week8/src/index.ts" "b/\353\217\204\354\226\217/week8/src/index.ts"
new file mode 100644
index 0000000..90404e2
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/index.ts"
@@ -0,0 +1,44 @@
+import dotenv from 'dotenv'
+import express, { Express, Request, Response, NextFunction } from 'express'
+import cors from 'cors'
+import morgan from 'morgan'
+import cookieParser from 'cookie-parser'
+import swaggerUi from 'swagger-ui-express'
+import path from 'path'
+import fs from 'fs'
+import { RegisterRoutes } from './generated/routes.js'
+import { errorMiddleware } from './middleware/error.middleware.js'
+
+// 1. ํ๊ฒฝ ๋ณ์ ์ค์ (๊ฐ์ฅ ๋จผ์ ํธ์ถ)
+dotenv.config()
+
+const app: Express = express()
+const port = process.env.PORT ?? 3000
+
+// 2. ๋ฏธ๋ค์จ์ด ์ค์
+app.use(morgan('dev'))
+app.use(cookieParser())
+app.use(cors())
+app.use(express.json())
+app.use(express.urlencoded({ extended: false }))
+
+// 3. TSOA๊ฐ ์์ฑํ ๋ผ์ฐํธ ๋ฑ๋ก
+const router = express.Router()
+RegisterRoutes(router)
+app.use('/api/v1', router)
+
+// 4. Swagger UI ์ฐ๊ฒฐ (dist/swagger.json ์ด ์กด์ฌํ ๋๋ง)
+const swaggerPath = path.resolve('dist/swagger.json')
+if (fs.existsSync(swaggerPath)) {
+ const swaggerFile = JSON.parse(fs.readFileSync(swaggerPath, 'utf8'))
+ app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
+}
+
+// 5. ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ (๋ฐ๋์ ๋ผ์ฐํฐ ๋ฑ๋ก ์ดํ์ ์์น)
+app.use(errorMiddleware)
+
+// 6. ์๋ฒ ์์
+app.listen(port, () => {
+ console.log(`[server]: Server is running at http://localhost:${port}`)
+ console.log(`[docs]: Swagger UI at http://localhost:${port}/docs`)
+})
diff --git "a/\353\217\204\354\226\217/week8/src/middleware/error.middleware.ts" "b/\353\217\204\354\226\217/week8/src/middleware/error.middleware.ts"
new file mode 100644
index 0000000..abd0e3a
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/middleware/error.middleware.ts"
@@ -0,0 +1,38 @@
+import { Request, Response, NextFunction } from 'express'
+import { BaseError } from '../utils/errors.js'
+
+export const errorMiddleware = (
+ err: unknown,
+ _req: Request,
+ res: Response,
+ _next: NextFunction,
+): void => {
+ if (err instanceof BaseError) {
+ res.status(err.status).json({
+ isSuccess: false,
+ code: err.code,
+ message: err.message,
+ result: null,
+ })
+ return
+ }
+
+ // Prisma unique constraint ์๋ฌ
+ if (typeof err === 'object' && err !== null && (err as { code?: string }).code === 'P2002') {
+ res.status(409).json({
+ isSuccess: false,
+ code: 'USER4002',
+ message: '์ด๋ฏธ ์กด์ฌํ๋ ๋ฐ์ดํฐ์
๋๋ค.',
+ result: null,
+ })
+ return
+ }
+
+ console.error(err)
+ res.status(500).json({
+ isSuccess: false,
+ code: 'COMMON500',
+ message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.',
+ result: null,
+ })
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/members/controllers/member.controller.ts" "b/\353\217\204\354\226\217/week8/src/modules/members/controllers/member.controller.ts"
new file mode 100644
index 0000000..6c42629
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/members/controllers/member.controller.ts"
@@ -0,0 +1,24 @@
+import { Body, Controller, Post, Route, Tags, Response as TsoaResponse, SuccessResponse } from 'tsoa'
+import { MemberSignUpRequest, MemberSignUpResponse } from '../dtos/member.dto.js'
+import { signUp } from '../services/member.service.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('members')
+@Tags('Member')
+export class MemberController extends Controller {
+ /**
+ * ํ์๊ฐ์
API
+ * @summary ์๋ก์ด ํ์์ ๋ฑ๋กํฉ๋๋ค.
+ */
+ @Post('signup')
+ @SuccessResponse(201, 'ํ์๊ฐ์
์ฑ๊ณต')
+ @TsoaResponse>(409, '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ (USER4002)')
+ @TsoaResponse>(400, 'name ๋๋ nickname ๋๋ฝ (USER4003)')
+ public async handleSignUp(
+ @Body() body: MemberSignUpRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await signUp(body)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/members/controllers/user.controller.ts" "b/\353\217\204\354\226\217/week8/src/modules/members/controllers/user.controller.ts"
new file mode 100644
index 0000000..b36650d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/members/controllers/user.controller.ts"
@@ -0,0 +1,71 @@
+import { Controller, Get, Patch, Path, Query, Route, Tags, Response as TsoaResponse } from 'tsoa'
+import { listUserReviews } from '../../reviews/services/review.service.js'
+import { listOngoingMissions, finishMission } from '../../missions/services/mission.service.js'
+import { UserReviewListResponse } from '../../reviews/dtos/review.dto.js'
+import { OngoingMissionListResponse, MissionChallengeResponse } from '../../missions/dtos/mission.dto.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('users')
+@Tags('User')
+export class UserController extends Controller {
+ /**
+ * ๋ด ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ API
+ * @summary ํน์ ์ฌ์ฉ์๊ฐ ์์ฑํ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+ */
+ @Get('{userId}/reviews')
+ @TsoaResponse>(404, '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ (USER4001)')
+ public async handleListUserReviews(
+ /**
+ * ๋ฆฌ๋ทฐ๋ฅผ ์กฐํํ ์ฌ์ฉ์ ID
+ */
+ @Path() userId: number,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const result = await listUserReviews(userId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก ์กฐํ API
+ * @summary ํน์ ์ฌ์ฉ์๊ฐ ํ์ฌ ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+ */
+ @Get('{userId}/missions')
+ @TsoaResponse>(404, '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ (USER4001)')
+ public async handleListOngoingMissions(
+ /**
+ * ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํ ์ฌ์ฉ์ ID
+ */
+ @Path() userId: number,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const result = await listOngoingMissions(userId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ๋ฏธ์
์๋ฃ ์ฒ๋ฆฌ API
+ * @summary ์งํ ์ค์ธ ๋ฏธ์
์ ์๋ฃ ์ํ๋ก ๋ณ๊ฒฝํฉ๋๋ค.
+ */
+ @Patch('{userId}/missions/{missionId}')
+ @TsoaResponse>(404, '์งํ ์ค์ธ ๋ฏธ์
์์ (MISSION4003)')
+ @TsoaResponse>(404, '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ (USER4001)')
+ public async handleCompleteMission(
+ /**
+ * ๋ฏธ์
์ ์๋ฃํ ์ฌ์ฉ์ ID
+ */
+ @Path() userId: number,
+ /**
+ * ์๋ฃํ ๋ฏธ์
ID
+ */
+ @Path() missionId: number,
+ ): Promise> {
+ const result = await finishMission(userId, missionId)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/members/dtos/member.dto.ts" "b/\353\217\204\354\226\217/week8/src/modules/members/dtos/member.dto.ts"
new file mode 100644
index 0000000..9f8cfd0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/members/dtos/member.dto.ts"
@@ -0,0 +1,85 @@
+// ํ์๊ฐ์
์์ฒญ ์ธํฐํ์ด์ค
+export interface MemberSignUpRequest {
+ /** ํ์ ์ด๋ฆ */
+ name: string
+ /** ๋๋ค์ */
+ nickname: string
+ /**
+ * ์ด๋ฉ์ผ
+ * @example "test@example.com"
+ */
+ email?: string
+ /**
+ * ๋น๋ฐ๋ฒํธ
+ * @example "qwer1234!"
+ */
+ password?: string
+ /**
+ * ์ ํ๋ฒํธ
+ * @example "010-1234-5678"
+ */
+ phoneNum?: string
+ /**
+ * ์๋
์์ผ (YYYY-MM-DD)
+ * @example "2000-01-01"
+ */
+ birth?: string
+ /**
+ * ์ฑ๋ณ
+ * @example "FEMALE"
+ */
+ gender?: string
+ /** ์ฃผ์ */
+ address?: string
+ /** ์์ธ ์ฃผ์ */
+ specAddress?: string
+}
+
+// ํ์๊ฐ์
์๋ต ์ธํฐํ์ด์ค
+export interface MemberSignUpResponse {
+ /** ์์ฑ๋ ํ์ ID */
+ memberId: number
+ /** ํ์ ์ด๋ฆ */
+ name: string
+ /** ๋๋ค์ */
+ nickname: string
+ /** ์ด๋ฉ์ผ */
+ email: string | null
+ /** ์ ํ๋ฒํธ */
+ phoneNum: string | null
+ /** ํ์ ์ํ */
+ status: string
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToMember = (body: MemberSignUpRequest) => {
+ return {
+ name: body.name,
+ nickname: body.nickname,
+ email: body.email ?? null,
+ phoneNum: body.phoneNum ?? null,
+ birth: body.birth ? new Date(body.birth) : null,
+ gender: body.gender ?? null,
+ address: body.address ?? null,
+ specAddress: body.specAddress ?? null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromMember = (member: {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+}) => {
+ return {
+ memberId: member.id,
+ name: member.name,
+ nickname: member.nickname,
+ email: member.email,
+ phoneNum: member.phone_num,
+ status: member.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/members/repositories/member.repository.ts" "b/\353\217\204\354\226\217/week8/src/modules/members/repositories/member.repository.ts"
new file mode 100644
index 0000000..549030e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/members/repositories/member.repository.ts"
@@ -0,0 +1,26 @@
+import { prisma } from '../../../db.config.js'
+
+// ์ ์ ์์ฑ
+export const addUser = async (data: any) => {
+ const exists = await prisma.user.findFirst({ where: { email: data.email } })
+ if (exists) return null
+
+ const created = await prisma.user.create({ data })
+ return created.id
+}
+
+// ์ ์ ์กฐํ (์์ผ๋ฉด ์์ธ throw)
+export const getUser = async (userId: number) =>
+ prisma.user.findFirstOrThrow({ where: { id: userId } })
+
+// ์ ํธ ์์ ์นดํ
๊ณ ๋ฆฌ ๋ฑ๋ก
+export const setPreference = async (userId: number, foodCategoryId: number) =>
+ prisma.userFavorCategory.create({ data: { userId, foodCategoryId } })
+
+// ์ ํธ ์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํ (JOIN ํฌํจ)
+export const getUserPreferencesByUserId = async (userId: number) =>
+ prisma.userFavorCategory.findMany({
+ where: { userId },
+ include: { foodCategory: true },
+ orderBy: { foodCategoryId: 'asc' },
+ })
diff --git "a/\353\217\204\354\226\217/week8/src/modules/members/services/member.service.ts" "b/\353\217\204\354\226\217/week8/src/modules/members/services/member.service.ts"
new file mode 100644
index 0000000..2cf928d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/members/services/member.service.ts"
@@ -0,0 +1,39 @@
+import bcrypt from 'bcryptjs'
+import { MemberSignUpRequest, bodyToMember, responseFromMember } from '../dtos/member.dto.js'
+import { addUser, getUser } from '../repositories/member.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const signUp = async (data: MemberSignUpRequest) => {
+ if (!data.name || !data.nickname) {
+ throw new BaseError(
+ ErrorCode.MEMBER_REQUIRED_FIELD.message,
+ ErrorCode.MEMBER_REQUIRED_FIELD.status,
+ ErrorCode.MEMBER_REQUIRED_FIELD.code,
+ )
+ }
+
+ const hashedPassword = data.password ? await bcrypt.hash(data.password, 10) : null
+
+ const memberData = bodyToMember(data)
+ const memberId = await addUser({ ...memberData, hashedPassword })
+
+ if (memberId === null) {
+ throw new BaseError(
+ ErrorCode.DUPLICATE_EMAIL.message,
+ ErrorCode.DUPLICATE_EMAIL.status,
+ ErrorCode.DUPLICATE_EMAIL.code,
+ )
+ }
+
+ const member = await getUser(memberId)
+
+ return responseFromMember(member as {
+ id: number
+ name: string
+ nickname: string
+ email: string | null
+ phone_num: string | null
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/missions/controllers/mission.controller.ts" "b/\353\217\204\354\226\217/week8/src/modules/missions/controllers/mission.controller.ts"
new file mode 100644
index 0000000..df4b236
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/missions/controllers/mission.controller.ts"
@@ -0,0 +1,28 @@
+import { Body, Controller, Path, Post, Route, Tags, Response as TsoaResponse, SuccessResponse } from 'tsoa'
+import { MissionChallengeRequest, MissionChallengeResponse } from '../dtos/mission.dto.js'
+import { challengeMission } from '../services/mission.service.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('missions')
+@Tags('Mission')
+export class MissionController extends Controller {
+ /**
+ * ๋ฏธ์
๋์ API
+ * @summary ํน์ ๋ฏธ์
์ ๋์ (์ฐธ์ฌ)ํฉ๋๋ค.
+ */
+ @Post('{missionId}/challenge')
+ @SuccessResponse(201, '๋ฏธ์
๋์ ์ฑ๊ณต')
+ @TsoaResponse>(409, '์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
(MISSION4002)')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๋ฏธ์
(MISSION4001)')
+ public async handleChallengeMission(
+ /**
+ * ๋์ ํ ๋ฏธ์
ID
+ */
+ @Path() missionId: number,
+ @Body() body: MissionChallengeRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await challengeMission(missionId, body)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/missions/dtos/mission.dto.ts" "b/\353\217\204\354\226\217/week8/src/modules/missions/dtos/mission.dto.ts"
new file mode 100644
index 0000000..d6bc435
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/missions/dtos/mission.dto.ts"
@@ -0,0 +1,120 @@
+// ๋ฏธ์
์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionCreateRequest {
+ /** ๋ฏธ์
์ ๋ชฉ */
+ title: string
+ /**
+ * ํฌ์ธํธ ๋ณด์
+ * @example 500
+ */
+ reward: number
+ /** ๋ฏธ์
์์ธ ์ค๋ช
*/
+ spec?: string
+ /**
+ * ๋ง๊ฐ์ผ (YYYY-MM-DD)
+ * @example "2026-12-31"
+ */
+ deadLine?: string
+}
+
+// ๋ฏธ์
๋์ ์์ฒญ ์ธํฐํ์ด์ค
+export interface MissionChallengeRequest {
+ /**
+ * ๋์ ํ ํ์ ID
+ * @example 1
+ */
+ memberId: number
+ /**
+ * ๋ฏธ์
์ํ
+ * @example "CHALLENGING"
+ */
+ status: 'CHALLENGING' | 'COMPLETE'
+}
+
+// ๋ฏธ์
์์ฑ ์๋ต ์ธํฐํ์ด์ค
+export interface MissionCreateResponse {
+ /** ์์ฑ๋ ๋ฏธ์
ID */
+ missionId: number
+ /** ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๋ฏธ์
์ ๋ชฉ */
+ title: string
+ /** ํฌ์ธํธ ๋ณด์ */
+ reward: number
+ /** ๋ฏธ์
์ค๋ช
*/
+ spec: string | null
+ /** ๋ง๊ฐ์ผ */
+ deadLine: Date | null
+}
+
+// ๋ฏธ์
๋์ ์๋ต ์ธํฐํ์ด์ค
+export interface MissionChallengeResponse {
+ /** ํ์-๋ฏธ์
๋งคํ ID */
+ memberMissionId: number
+ /** ํ์ ID */
+ memberId: number
+ /** ๋ฏธ์
ID */
+ missionId: number
+ /** ๋ฏธ์
์ํ */
+ status: string
+}
+
+// ์งํ ์ค ๋ฏธ์
๋ชฉ๋ก ์๋ต ์ธํฐํ์ด์ค
+export interface OngoingMissionListResponse {
+ data: MissionCreateResponse[]
+ pagination: {
+ /** ๋ค์ ํ์ด์ง ์ปค์ */
+ cursor: number | null
+ }
+}
+
+// ๊ฐ๊ฒ ๋ฏธ์
๋ชฉ๋ก ์๋ต ์ธํฐํ์ด์ค
+export interface StoreMissionListResponse {
+ data: MissionCreateResponse[]
+ pagination: {
+ cursor: number | null
+ }
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ (๋ฏธ์
์ถ๊ฐ)
+export const bodyToMission = (body: MissionCreateRequest) => {
+ return {
+ title: body.title,
+ reward: body.reward,
+ spec: body.spec ?? null,
+ deadLine: body.deadLine ? new Date(body.deadLine) : null,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
)
+export const responseFromMission = (mission: {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+}) => {
+ return {
+ missionId: mission.id,
+ storeId: mission.store_id,
+ title: mission.title,
+ reward: mission.reward,
+ spec: mission.spec,
+ deadLine: mission.dead_line,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (๋ฏธ์
๋์ )
+export const responseFromMemberMission = (mm: {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+}) => {
+ return {
+ memberMissionId: mm.id,
+ memberId: mm.member_id,
+ missionId: mm.mission_id,
+ status: mm.status,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/missions/repositories/mission.repository.ts" "b/\353\217\204\354\226\217/week8/src/modules/missions/repositories/mission.repository.ts"
new file mode 100644
index 0000000..bee3e62
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/missions/repositories/mission.repository.ts"
@@ -0,0 +1,72 @@
+import { prisma } from '../../../db.config.js'
+
+// ๋ฏธ์
์ถ๊ฐ
+export const addMission = async (data: {
+ storeId: number
+ title: string
+ reward: number
+ spec: string | null
+ deadLine: Date | null
+}): Promise => {
+ const created = await prisma.mission.create({
+ data: {
+ storeId: data.storeId,
+ title: data.title,
+ reward: data.reward,
+ spec: data.spec,
+ deadLine: data.deadLine,
+ },
+ })
+ return created.id
+}
+
+// ๋ฏธ์
์กฐํ
+export const getMissionById = async (missionId: number) =>
+ prisma.mission.findFirst({ where: { id: missionId } })
+
+// ์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์ธ์ง ํ์ธ
+export const findMemberMission = async (memberId: number, missionId: number) =>
+ prisma.memberMission.findFirst({
+ where: { userId: memberId, missionId },
+ })
+
+// ๋ฏธ์
๋์ ์ถ๊ฐ
+export const addMemberMission = async (
+ memberId: number,
+ missionId: number,
+ status: string,
+): Promise => {
+ const created = await prisma.memberMission.create({
+ data: { userId: memberId, missionId, status },
+ })
+ return created.id
+}
+
+// ๋ฏธ์
๋์ ๊ธฐ๋ก ์กฐํ
+export const getMemberMissionById = async (memberMissionId: number) =>
+ prisma.memberMission.findFirst({ where: { id: memberMissionId } })
+
+// ํน์ ๊ฐ๊ฒ์ ๋ฏธ์
๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getStoreMissions = async (storeId: number, cursor: number) =>
+ prisma.mission.findMany({
+ where: { storeId, id: { gt: cursor } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+
+// ์ ์ ๊ฐ ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก ์กฐํ
+export const getOngoingMissions = async (userId: number, cursor: number) =>
+ prisma.memberMission.findMany({
+ where: { userId, status: '์งํ์ค', id: { gt: cursor } },
+ include: { mission: { include: { store: true } } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+// ์งํ ์ค์ธ ๋ฏธ์
์ ์๋ฃ๋ก ๋ณ๊ฒฝ
+export const completeMission = async (userId: number, missionId: number) =>
+ prisma.memberMission.updateMany({
+ where: { userId, missionId, status: '์งํ์ค' },
+ data: { status: '์๋ฃ' },
+ })
\ No newline at end of file
diff --git "a/\353\217\204\354\226\217/week8/src/modules/missions/services/mission.service.ts" "b/\353\217\204\354\226\217/week8/src/modules/missions/services/mission.service.ts"
new file mode 100644
index 0000000..92018a3
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/missions/services/mission.service.ts"
@@ -0,0 +1,128 @@
+import {
+ MissionCreateRequest,
+ MissionChallengeRequest,
+ bodyToMission,
+ responseFromMission,
+ responseFromMemberMission,
+} from '../dtos/mission.dto.js'
+import {
+ addMission,
+ getMissionById,
+ findMemberMission,
+ addMemberMission,
+ getMemberMissionById,
+ getStoreMissions,
+ getOngoingMissions,
+ completeMission,
+} from '../repositories/mission.repository.js'
+import { getStoreById } from '../../stores/repositories/store.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const createMission = async (storeId: number, data: MissionCreateRequest) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(
+ ErrorCode.STORE_NOT_FOUND.message,
+ ErrorCode.STORE_NOT_FOUND.status,
+ ErrorCode.STORE_NOT_FOUND.code,
+ )
+ }
+
+ const missionData = bodyToMission(data)
+ const missionId = await addMission({ ...missionData, storeId })
+
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw new BaseError(
+ ErrorCode.MISSION_CREATE_FAILED.message,
+ ErrorCode.MISSION_CREATE_FAILED.status,
+ ErrorCode.MISSION_CREATE_FAILED.code,
+ )
+ }
+
+ return responseFromMission(mission as unknown as {
+ id: number
+ store_id: number
+ title: string
+ reward: number
+ spec: string | null
+ dead_line: Date | null
+ })
+}
+
+export const listStoreMissions = async (storeId: number, cursor: number) => {
+ const missions = await getStoreMissions(storeId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+export const listOngoingMissions = async (userId: number, cursor: number) => {
+ const missions = await getOngoingMissions(userId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+export const finishMission = async (userId: number, missionId: number) => {
+ const result = await completeMission(userId, missionId)
+ if (result.count === 0) {
+ throw new BaseError(
+ ErrorCode.ONGOING_MISSION_NOT_FOUND.message,
+ ErrorCode.ONGOING_MISSION_NOT_FOUND.status,
+ ErrorCode.ONGOING_MISSION_NOT_FOUND.code,
+ )
+ }
+ return { message: '๋ฏธ์
์ด ์๋ฃ ์ฒ๋ฆฌ๋์ต๋๋ค.' }
+}
+
+export const challengeMission = async (missionId: number, data: MissionChallengeRequest) => {
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw new BaseError(
+ ErrorCode.MISSION_NOT_FOUND.message,
+ ErrorCode.MISSION_NOT_FOUND.status,
+ ErrorCode.MISSION_NOT_FOUND.code,
+ )
+ }
+
+ if (!data.status) {
+ throw new BaseError(
+ ErrorCode.MISSION_STATUS_REQUIRED.message,
+ ErrorCode.MISSION_STATUS_REQUIRED.status,
+ ErrorCode.MISSION_STATUS_REQUIRED.code,
+ )
+ }
+
+ const existing = await findMemberMission(data.memberId, missionId)
+ if (existing) {
+ throw new BaseError(
+ ErrorCode.MISSION_ALREADY_CHALLENGING.message,
+ ErrorCode.MISSION_ALREADY_CHALLENGING.status,
+ ErrorCode.MISSION_ALREADY_CHALLENGING.code,
+ )
+ }
+
+ const memberMissionId = await addMemberMission(data.memberId, missionId, data.status)
+
+ const memberMission = await getMemberMissionById(memberMissionId)
+ if (!memberMission) {
+ throw new BaseError(
+ ErrorCode.MISSION_CHALLENGE_FAILED.message,
+ ErrorCode.MISSION_CHALLENGE_FAILED.status,
+ ErrorCode.MISSION_CHALLENGE_FAILED.code,
+ )
+ }
+
+ return responseFromMemberMission(memberMission as unknown as {
+ id: number
+ member_id: number
+ mission_id: number
+ status: string
+ })
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/reviews/controllers/review.controller.ts" "b/\353\217\204\354\226\217/week8/src/modules/reviews/controllers/review.controller.ts"
new file mode 100644
index 0000000..77d8ccf
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/reviews/controllers/review.controller.ts"
@@ -0,0 +1,7 @@
+// review ๊ด๋ จ ์๋ํฌ์ธํธ๋ StoreController(๊ฐ๊ฒ ๋ฆฌ๋ทฐ ์์ฑ/์กฐํ)์
+// UserController(์ฌ์ฉ์ ๋ฆฌ๋ทฐ ๋ชฉ๋ก)๋ก ๋ถ์ฐ ๊ด๋ฆฌํฉ๋๋ค.
+// - POST /api/v1/stores/:storeId/reviews โ StoreController
+// - GET /api/v1/stores/:storeId/reviews โ StoreController
+// - GET /api/v1/users/:userId/reviews โ UserController
+export {}
+
diff --git "a/\353\217\204\354\226\217/week8/src/modules/reviews/dtos/review.dto.ts" "b/\353\217\204\354\226\217/week8/src/modules/reviews/dtos/review.dto.ts"
new file mode 100644
index 0000000..ea89781
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/reviews/dtos/review.dto.ts"
@@ -0,0 +1,80 @@
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface ReviewCreateRequest {
+ /**
+ * ์์ฑ์ ํ์ ID
+ * @example 1
+ */
+ memberId: number
+ /**
+ * ๋ฆฌ๋ทฐ ๋ด์ฉ
+ * @example "์์์ด ์ ๋ง ๋ง์์์ด์!"
+ */
+ content: string
+ /**
+ * ๋ณ์ (1~5)
+ * @example 4.5
+ */
+ score: number
+}
+
+// ๋ฆฌ๋ทฐ ์์ฑ ์๋ต ์ธํฐํ์ด์ค
+export interface ReviewCreateResponse {
+ /** ์์ฑ๋ ๋ฆฌ๋ทฐ ID */
+ reviewId: number
+ /** ์์ฑ์ ํ์ ID */
+ memberId: number
+ /** ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๋ฆฌ๋ทฐ ๋ด์ฉ */
+ content: string
+ /** ๋ณ์ */
+ score: number
+ /** ์์ฑ์ผ์ */
+ createdAt: Date
+}
+
+// ์ฌ์ฉ์ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์๋ต ์ธํฐํ์ด์ค
+export interface UserReviewListResponse {
+ data: ReviewCreateResponse[]
+ pagination: {
+ /** ๋ค์ ํ์ด์ง ์ปค์ */
+ cursor: number | null
+ }
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToReview = (body: ReviewCreateRequest) => {
+ return {
+ memberId: body.memberId,
+ content: body.content,
+ score: body.score,
+ }
+}
+
+// ๋ฆฌ๋ทฐ ๋ชฉ๋ก โ ์๋ต ํํ๋ก ๋ณํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const responseFromUserReviews = (reviews: any[]) => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews,
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromReview = (review: {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+}) => {
+ return {
+ reviewId: review.id,
+ memberId: review.member_id,
+ storeId: review.store_id,
+ content: review.content,
+ score: review.score,
+ createdAt: review.created_at,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/reviews/repositories/review.repository.ts" "b/\353\217\204\354\226\217/week8/src/modules/reviews/repositories/review.repository.ts"
new file mode 100644
index 0000000..adca307
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/reviews/repositories/review.repository.ts"
@@ -0,0 +1,30 @@
+import { prisma } from '../../../db.config.js'
+
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ
+export const addReview = async (data: {
+ memberId: number
+ storeId: number
+ content: string
+}): Promise => {
+ const created = await prisma.userStoreReview.create({
+ data: {
+ userId: data.memberId,
+ storeId: data.storeId,
+ content: data.content,
+ },
+ })
+ return created.id
+}
+
+// ๋ฆฌ๋ทฐ ์กฐํ
+export const getReviewById = async (reviewId: number) =>
+ prisma.userStoreReview.findFirst({ where: { id: reviewId } })
+
+// ๋ด๊ฐ ์์ฑํ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getUserReviews = async (userId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ where: { userId, id: { gt: cursor } },
+ include: { store: true },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week8/src/modules/reviews/services/review.service.ts" "b/\353\217\204\354\226\217/week8/src/modules/reviews/services/review.service.ts"
new file mode 100644
index 0000000..13b2281
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/reviews/services/review.service.ts"
@@ -0,0 +1,50 @@
+import { ReviewCreateRequest, bodyToReview, responseFromReview, responseFromUserReviews } from '../dtos/review.dto.js'
+import { addReview, getReviewById, getUserReviews } from '../repositories/review.repository.js'
+import { getStoreById } from '../../stores/repositories/store.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const listUserReviews = async (userId: number, cursor: number) => {
+ const reviews = await getUserReviews(userId, cursor)
+ return responseFromUserReviews(reviews)
+}
+
+export const createReview = async (storeId: number, data: ReviewCreateRequest) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(
+ ErrorCode.STORE_NOT_FOUND.message,
+ ErrorCode.STORE_NOT_FOUND.status,
+ ErrorCode.STORE_NOT_FOUND.code,
+ )
+ }
+
+ if (data.score < 1 || data.score > 5) {
+ throw new BaseError(
+ ErrorCode.INVALID_SCORE.message,
+ ErrorCode.INVALID_SCORE.status,
+ ErrorCode.INVALID_SCORE.code,
+ )
+ }
+
+ const reviewData = bodyToReview(data)
+ const reviewId = await addReview({ ...reviewData, storeId })
+
+ const review = await getReviewById(reviewId)
+ if (!review) {
+ throw new BaseError(
+ ErrorCode.REVIEW_CREATE_FAILED.message,
+ ErrorCode.REVIEW_CREATE_FAILED.status,
+ ErrorCode.REVIEW_CREATE_FAILED.code,
+ )
+ }
+
+ return responseFromReview(review as {
+ id: number
+ member_id: number
+ store_id: number
+ content: string
+ score: number
+ created_at: Date
+ })
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/stores/controllers/store.controller.ts" "b/\353\217\204\354\226\217/week8/src/modules/stores/controllers/store.controller.ts"
new file mode 100644
index 0000000..a20c3f9
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/stores/controllers/store.controller.ts"
@@ -0,0 +1,106 @@
+import { Body, Controller, Get, Path, Post, Query, Route, Tags, Response as TsoaResponse, SuccessResponse } from 'tsoa'
+import { StoreCreateRequest, StoreCreateResponse, ReviewListResponse } from '../dtos/store.dto.js'
+import { createStore, listStoreReviews } from '../services/store.service.js'
+import { createReview } from '../../reviews/services/review.service.js'
+import { createMission, listStoreMissions } from '../../missions/services/mission.service.js'
+import { ReviewCreateRequest, ReviewCreateResponse } from '../../reviews/dtos/review.dto.js'
+import { MissionCreateRequest, MissionCreateResponse, StoreMissionListResponse } from '../../missions/dtos/mission.dto.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('stores')
+@Tags('Store')
+export class StoreController extends Controller {
+ /**
+ * ๊ฐ๊ฒ ์์ฑ API
+ * @summary ์๋ก์ด ๊ฐ๊ฒ๋ฅผ ๋ฑ๋กํฉ๋๋ค.
+ */
+ @Post()
+ @SuccessResponse(201, '๊ฐ๊ฒ ์์ฑ ์ฑ๊ณต')
+ @TsoaResponse>(400, '์๋ชป๋ ์์ฒญ (COMMON400)')
+ public async handleCreateStore(
+ @Body() body: StoreCreateRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await createStore(body)
+ return successResponse(result)
+ }
+
+ /**
+ * ๊ฐ๊ฒ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์ ์ปค์ ๊ธฐ๋ฐ์ผ๋ก ์กฐํํฉ๋๋ค.
+ */
+ @Get('{storeId}/reviews')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleListStoreReviews(
+ /**
+ * ๋ฆฌ๋ทฐ๋ฅผ ์กฐํํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ (์ด์ ์๋ต์ cursor)
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const result = await listStoreReviews(storeId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ๋ฆฌ๋ทฐ ์์ฑ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ๋ฅผ ์์ฑํฉ๋๋ค.
+ */
+ @Post('{storeId}/reviews')
+ @SuccessResponse(201, '๋ฆฌ๋ทฐ ์์ฑ ์ฑ๊ณต')
+ @TsoaResponse>(400, '๋ณ์ ๋ฒ์ ์ค๋ฅ (REVIEW4001)')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleCreateReview(
+ /**
+ * ๋ฆฌ๋ทฐ๋ฅผ ์์ฑํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ @Body() body: ReviewCreateRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await createReview(storeId, body)
+ return successResponse(result)
+ }
+
+ /**
+ * ๊ฐ๊ฒ ๋ฏธ์
๋ชฉ๋ก ์กฐํ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ๋ฑ๋ก๋ ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+ */
+ @Get('{storeId}/missions')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleListStoreMissions(
+ /**
+ * ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const result = await listStoreMissions(storeId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ๋ฏธ์
์์ฑ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ์๋ก์ด ๋ฏธ์
์ ๋ฑ๋กํฉ๋๋ค.
+ */
+ @Post('{storeId}/missions')
+ @SuccessResponse(201, '๋ฏธ์
์์ฑ ์ฑ๊ณต')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleCreateMission(
+ /**
+ * ๋ฏธ์
์ ๋ฑ๋กํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ @Body() body: MissionCreateRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await createMission(storeId, body)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/stores/dtos/store.dto.ts" "b/\353\217\204\354\226\217/week8/src/modules/stores/dtos/store.dto.ts"
new file mode 100644
index 0000000..aa19060
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/stores/dtos/store.dto.ts"
@@ -0,0 +1,108 @@
+// ๊ฐ๊ฒ ์ถ๊ฐ ์์ฒญ ์ธํฐํ์ด์ค
+export interface StoreCreateRequest {
+ /**
+ * ์ง์ญ ID
+ * @example 1
+ */
+ regionId: number
+ /**
+ * ์์ ์นดํ
๊ณ ๋ฆฌ ID
+ * @example 2
+ */
+ foodCategoryId: number
+ /** ๊ฐ๊ฒ ์ด๋ฆ */
+ name: string
+ /** ๊ฐ๊ฒ ์ค๋ช
*/
+ description?: string
+ /**
+ * ๊ฐ๊ฒ ์ฃผ์
+ * @example "์์ธ์ ๊ฐ๋จ๊ตฌ ํ
ํค๋๋ก 123"
+ */
+ address: string
+ /**
+ * ์๋
+ * @example 37.5665
+ */
+ lat?: number
+ /**
+ * ๊ฒฝ๋
+ * @example 126.978
+ */
+ lng?: number
+}
+
+// ๊ฐ๊ฒ ์์ฑ ์๋ต ์ธํฐํ์ด์ค
+export interface StoreCreateResponse {
+ /** ์์ฑ๋ ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๊ฐ๊ฒ ์ด๋ฆ */
+ name: string
+ /** ๊ฐ๊ฒ ์ฃผ์ */
+ address: string
+ /** ์ง์ญ ID */
+ regionId: number
+}
+
+// ๋ฆฌ๋ทฐ ์์ดํ
์ธํฐํ์ด์ค
+export interface ReviewItem {
+ /** ๋ฆฌ๋ทฐ ID */
+ reviewId: number
+ /** ์์ฑ์ ํ์ ID */
+ memberId: number
+ /** ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๋ฆฌ๋ทฐ ๋ด์ฉ */
+ content: string
+ /** ๋ณ์ (1~5) */
+ score: number
+ /** ์์ฑ์ผ์ */
+ createdAt: Date
+}
+
+// ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์๋ต ์ธํฐํ์ด์ค (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export interface ReviewListResponse {
+ data: ReviewItem[]
+ pagination: {
+ /** ๋ค์ ํ์ด์ง ์ปค์ (๋ง์ง๋ง ํญ๋ชฉ์ ID, ์์ผ๋ฉด null) */
+ cursor: number | null
+ }
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ
+export const bodyToStore = (body: StoreCreateRequest) => {
+ return {
+ regionId: body.regionId,
+ foodCategoryId: body.foodCategoryId,
+ name: body.name,
+ description: body.description ?? null,
+ address: body.address,
+ lat: body.lat ?? null,
+ lng: body.lng ?? null,
+ }
+}
+
+// ๋ฆฌ๋ทฐ ๋ชฉ๋ก โ ์๋ต ํํ๋ก ๋ณํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const responseFromReviews = (reviews: any[]) => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews,
+ pagination: {
+ cursor: last ? last.id : null,
+ },
+ }
+}
+
+// DB ์กฐํ ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ
+export const responseFromStore = (store: {
+ id: number
+ name: string
+ address: string
+ region_id: number
+}) => {
+ return {
+ storeId: store.id,
+ name: store.name,
+ address: store.address,
+ regionId: store.region_id,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/modules/stores/repositories/store.repository.ts" "b/\353\217\204\354\226\217/week8/src/modules/stores/repositories/store.repository.ts"
new file mode 100644
index 0000000..9a9ac1e
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/stores/repositories/store.repository.ts"
@@ -0,0 +1,28 @@
+import { prisma } from '../../../db.config.js'
+
+// ๊ฐ๊ฒ ์ถ๊ฐ
+export const addStore = async (data: { name: string }): Promise => {
+ const created = await prisma.store.create({ data })
+ return created.id
+}
+
+// ๊ฐ๊ฒ ์กฐํ
+export const getStoreById = async (storeId: number) =>
+ prisma.store.findFirst({ where: { id: storeId } })
+
+// ๊ฐ๊ฒ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getAllStoreReviews = async (storeId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ select: {
+ id: true,
+ content: true,
+ store: true,
+ user: true,
+ },
+ where: {
+ storeId,
+ id: { gt: cursor },
+ },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week8/src/modules/stores/services/store.service.ts" "b/\353\217\204\354\226\217/week8/src/modules/stores/services/store.service.ts"
new file mode 100644
index 0000000..91f2c48
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/modules/stores/services/store.service.ts"
@@ -0,0 +1,30 @@
+import { StoreCreateRequest, bodyToStore, responseFromStore, responseFromReviews } from '../dtos/store.dto.js'
+import { addStore, getStoreById, getAllStoreReviews } from '../repositories/store.repository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const listStoreReviews = async (storeId: number, cursor: number) => {
+ const reviews = await getAllStoreReviews(storeId, cursor)
+ return responseFromReviews(reviews)
+}
+
+export const createStore = async (data: StoreCreateRequest) => {
+ const storeData = bodyToStore(data)
+ const storeId = await addStore(storeData)
+
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(
+ ErrorCode.STORE_CREATE_FAILED.message,
+ ErrorCode.STORE_CREATE_FAILED.status,
+ ErrorCode.STORE_CREATE_FAILED.code,
+ )
+ }
+
+ return responseFromStore(store as {
+ id: number
+ name: string
+ address: string
+ region_id: number
+ })
+}
diff --git "a/\353\217\204\354\226\217/week8/src/utils/errorCode.ts" "b/\353\217\204\354\226\217/week8/src/utils/errorCode.ts"
new file mode 100644
index 0000000..1a271dd
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/utils/errorCode.ts"
@@ -0,0 +1,26 @@
+export const ErrorCode = {
+ // ๊ณตํต
+ INTERNAL_ERROR: { status: 500, code: 'COMMON500', message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.' },
+ INVALID_INPUT: { status: 400, code: 'COMMON400', message: '์๋ชป๋ ์
๋ ฅ๊ฐ์
๋๋ค.' },
+
+ // ํ์
+ USER_NOT_FOUND: { status: 404, code: 'USER4001', message: '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.' },
+ DUPLICATE_EMAIL: { status: 409, code: 'USER4002', message: '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.' },
+ MEMBER_REQUIRED_FIELD: { status: 400, code: 'USER4003', message: 'name๊ณผ nickname์ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค.' },
+
+ // ๊ฐ๊ฒ
+ STORE_NOT_FOUND: { status: 404, code: 'STORE4001', message: '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.' },
+ STORE_CREATE_FAILED: { status: 500, code: 'STORE5001', message: '๊ฐ๊ฒ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+
+ // ๋ฆฌ๋ทฐ
+ INVALID_SCORE: { status: 400, code: 'REVIEW4001', message: '๋ณ์ ์ 1~5 ์ฌ์ด์ฌ์ผ ํฉ๋๋ค.' },
+ REVIEW_CREATE_FAILED: { status: 500, code: 'REVIEW5001', message: '๋ฆฌ๋ทฐ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+
+ // ๋ฏธ์
+ MISSION_NOT_FOUND: { status: 404, code: 'MISSION4001', message: '์กด์ฌํ์ง ์๋ ๋ฏธ์
์
๋๋ค.' },
+ MISSION_ALREADY_CHALLENGING: { status: 409, code: 'MISSION4002', message: '์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.' },
+ ONGOING_MISSION_NOT_FOUND: { status: 404, code: 'MISSION4003', message: '์งํ ์ค์ธ ๋ฏธ์
์ด ์์ต๋๋ค.' },
+ MISSION_STATUS_REQUIRED: { status: 400, code: 'MISSION4004', message: 'status๋ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค. (CHALLENGING ๋๋ COMPLETE)' },
+ MISSION_CREATE_FAILED: { status: 500, code: 'MISSION5001', message: '๋ฏธ์
์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+ MISSION_CHALLENGE_FAILED: { status: 500, code: 'MISSION5002', message: '๋ฏธ์
๋์ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+} as const
diff --git "a/\353\217\204\354\226\217/week8/src/utils/errors.ts" "b/\353\217\204\354\226\217/week8/src/utils/errors.ts"
new file mode 100644
index 0000000..c237d36
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/utils/errors.ts"
@@ -0,0 +1,11 @@
+export class BaseError extends Error {
+ status: number
+ code: string
+
+ constructor(message: string, statusCode = 500, code = 'INTERNAL_ERROR') {
+ super(message)
+ this.name = 'BaseError'
+ this.status = statusCode
+ this.code = code
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/src/utils/response.ts" "b/\353\217\204\354\226\217/week8/src/utils/response.ts"
new file mode 100644
index 0000000..c3d3b92
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/src/utils/response.ts"
@@ -0,0 +1,25 @@
+// TSOA Swagger ๋ฌธ์ํ์ ์ฌ์ฉํ ํ์ค ์๋ต ํ์
+export interface ApiResponse {
+ isSuccess: boolean
+ code: string
+ message: string
+ result: T | null
+}
+
+// ์ฑ๊ณต ์๋ต ์์ฑ ํฌํผ
+export const successResponse = (result: T): ApiResponse => ({
+ isSuccess: true,
+ code: 'COMMON200',
+ message: '์ฑ๊ณต์
๋๋ค.',
+ result,
+})
+
+// ๊ธฐ์กด BaseResponse (ํ์ ํธํ ์ ์ง)
+export const BaseResponse = (result: T, message = '์ฑ๊ณต์
๋๋ค.', code = 'COMMON200') => {
+ return {
+ isSuccess: true,
+ code,
+ message,
+ result,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/todolist.json" "b/\353\217\204\354\226\217/week8/todolist.json"
new file mode 100644
index 0000000..830b572
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/todolist.json"
@@ -0,0 +1,353 @@
+{
+ "chapter": "Chapter 5. API ๋ฐ ํ๋ก์ ํธ ์ค์ ๊ธฐ์ด",
+ "branch": "feature/chapter-05",
+ "week4_reference": "../week4",
+ "keywords": [
+ {
+ "term": "ํ๊ฒฝ ๋ณ์ (Environment Variables)",
+ "summary": "DB ๋น๋ฐ๋ฒํธยทAPI Key ๋ฑ ๋ฏผ๊ฐํ ๊ฐ์ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ณ .env ํ์ผ๋ก ๊ด๋ฆฌ. dotenv ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ก๋ํ๋ฉฐ .gitignore์ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํจ.",
+ "example": "process.env.DB_PASSWORD"
+ },
+ {
+ "term": "CORS (Cross-Origin Resource Sharing)",
+ "summary": "๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ Origin(๋๋ฉ์ธยทํฌํธ)์ ์๋ฒ์ ์์ฒญํ ๋ ๋ฐ์ํ๋ ๋ณด์ ์ ์ฑ
. Express์์๋ cors ๋ฏธ๋ค์จ์ด๋ก ํ์ฉ ์ฒ๋ฆฌ.",
+ "example": "app.use(cors())"
+ },
+ {
+ "term": "DB Connection Pool",
+ "summary": "๋งค ์์ฒญ๋ง๋ค DB ์ปค๋ฅ์
์ ์๋ก ์์ฑยทํด์ ํ์ง ์๊ณ , ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋ ์ปค๋ฅ์
ํ์์ ๋น๋ ค ์ฐ๋ ๋ฐฉ์. mysql2์ createPool()๋ก ๊ตฌํ. finally ๋ธ๋ก์์ conn.release()๋ก ๋ฐํํด์ผ ํจ.",
+ "example": "const pool = mysql.createPool({ connectionLimit: 10 })"
+ },
+ {
+ "term": "๋น๋๊ธฐ (async / await)",
+ "summary": "DB ์ฟผ๋ฆฌยท์ธ๋ถ API ํธ์ถ์ฒ๋ผ ์๋ต ๋๊ธฐ๊ฐ ํ์ํ ์์
์ Promise ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌ. async ํจ์ ์์์ await๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ฉฐ, ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์ฝ๊ธฐ ์ฝ๊ฒ ์์ฑ ๊ฐ๋ฅ.",
+ "example": "const [rows] = await pool.query('SELECT ...')"
+ },
+ {
+ "term": "try / catch / finally",
+ "summary": "๋น๋๊ธฐ ์์
์์ ๋ฐ์ํ ์ ์๋ ์๋ฌ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฒ๋ฆฌ. try: ์ ์ ๋ก์ง, catch: ์๋ฌ ์ฒ๋ฆฌ, finally: ์ปค๋ฅ์
๋ฐํ(conn.release()) ๋ฑ ํญ์ ์คํ๋์ด์ผ ํ๋ ์ ๋ฆฌ ์ฝ๋.",
+ "example": "try { ... } catch(err) { throw new Error(...) } finally { conn.release() }"
+ },
+ {
+ "term": "Interface (์ธํฐํ์ด์ค)",
+ "summary": "TypeScript์์ ๊ฐ์ฒด์ ํํ(์์ฑยทํ์
)๋ฅผ ์ ์ํ๋ ์ค๊ณ๋. DTOยทRepository ๋ฐํ ํ์
๋ฑ์ ํ์ฉ. ?๋ฅผ ๋ถ์ด๋ฉด ์ ํ์ ํ๋กํผํฐ.",
+ "example": "export interface StoreCreateRequest { regionId: number; name: string; address: string; }"
+ },
+ {
+ "term": "Type Assertion (as ํค์๋)",
+ "summary": "TypeScript ์ปดํ์ผ๋ฌ์๊ฒ '์ด ๊ฐ์ ์ด ํ์
์ด์ผ'๋ผ๊ณ ๊ฐ์ ๋ก ์๋ ค์ฃผ๋ ๋ฌธ๋ฒ. req.body๋ ๊ธฐ๋ณธ์ ์ผ๋ก any์ด๋ฏ๋ก, as ๋ก ์ ์ํ ์ธํฐํ์ด์ค ํ์
์ผ๋ก ๋ณํํด ์ฌ์ฉ.",
+ "example": "req.body as StoreCreateRequest"
+ }
+ ],
+ "project_structure": {
+ "root": "week5/",
+ "note": "week4์ in-memory DB โ MySQL ์ค์ DB ์ฐ๊ฒฐ๋ก ์ ํ. ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค ๊ตฌ์กฐ ์ฑํ.",
+ "files": [
+ "src/index.ts - Express ์ฑ ์ง์
์ , ๋ฏธ๋ค์จ์ดยท๋ผ์ฐํฐ ๋ฑ๋ก",
+ "src/db.config.ts - MySQL Connection Pool ์ค์ ",
+ "src/modules/stores/controllers/store.controller.ts",
+ "src/modules/stores/services/store.service.ts",
+ "src/modules/stores/repositories/store.repository.ts",
+ "src/modules/stores/dtos/store.dto.ts",
+ "src/modules/reviews/controllers/review.controller.ts",
+ "src/modules/reviews/services/review.service.ts",
+ "src/modules/reviews/repositories/review.repository.ts",
+ "src/modules/reviews/dtos/review.dto.ts",
+ "src/modules/missions/controllers/mission.controller.ts",
+ "src/modules/missions/services/mission.service.ts",
+ "src/modules/missions/repositories/mission.repository.ts",
+ "src/modules/missions/dtos/mission.dto.ts",
+ ".env - DB ์ ์ ์ ๋ณดยทPORT (gitignore ํ์)",
+ ".gitignore",
+ "package.json",
+ "tsconfig.json",
+ "schema.sql - week4 schema.sql ์ฌ์ฌ์ฉ (ํ
์ด๋ธ ์ด๋ฏธ ์ ์๋จ)"
+ ]
+ },
+ "todos": [
+ {
+ "id": 1,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "GitHub ์ด์ ์์ฑ ๋ฐ ๋ธ๋์น ๋ถ๊ธฐ",
+ "status": "todo",
+ "details": [
+ "GitHub ์ ์ฅ์ Issues ํญ์์ ๋ผ๋ฒจ ์ ๋ฆฌ: bug, docs, feature, refactor",
+ "์ด์ ์ ๋ชฉ: '[feat] Chapter 5 - API ๊ตฌํ (๊ฐ๊ฒ ์ถ๊ฐ / ๋ฆฌ๋ทฐ / ๋ฏธ์
)'",
+ "Assignee: ๋ณธ์ธ, Label: feature ๋ก ์ด์ ์์ฑ",
+ "์ด์์์ 'Create a branch' ํด๋ฆญ โ feature/chapter-05 ๋ธ๋์น ์์ฑ",
+ "๋ก์ปฌ์์: git fetch origin && git checkout feature/chapter-05"
+ ]
+ },
+ {
+ "id": 2,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "Postman ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ ํ์ธ",
+ "status": "todo",
+ "details": [
+ "Postman ์ค์น (https://www.postman.com/downloads/)",
+ "Params / Authorization / Headers / Body ํญ ์ญํ ์ดํด",
+ "Body > raw > JSON ์ ํ ๋ฐฉ๋ฒ ์์ง",
+ "๋์ค์ API ํ
์คํธ ์ ์คํฌ๋ฆฐ์ท ์ ์ฅํ ์ค๋น"
+ ]
+ },
+ {
+ "id": 3,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "week5 ํด๋ ์ด๊ธฐํ ๋ฐ ์์กด์ฑ ์ค์น",
+ "status": "todo",
+ "details": [
+ "cd week5 && npm init -y",
+ "npm install express cors dotenv http-status-codes mysql2",
+ "npm install -D typescript @types/node @types/express @types/cors @types/dotenv nodemon tsx",
+ "npx tsc --init ํ tsconfig.json ์์ (rootDir: ./src, outDir: ./dist, module: NodeNext, strict: true ๋ฑ)",
+ "week4/tsconfig.json์ ์ฐธ๊ณ ํด module/moduleResolution ์ค์ ์ผ์น์ํค๊ธฐ",
+ "package.json scripts ์ถ๊ฐ: start / dev (nodemon --exec tsx src/index.ts)"
+ ]
+ },
+ {
+ "id": 4,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": ".env ํ์ผ ๋ฐ .gitignore ์์ฑ",
+ "status": "todo",
+ "details": [
+ ".gitignore์ node_modules/ / .env / .env.* ์ถ๊ฐ",
+ ".env์ PORT=3000, DB_HOST=localhost, DB_PORT=3306, DB_USER=root, DB_PASSWORD=๋น๋ฐ๋ฒํธ, DB_NAME=umc_mission ์์ฑ",
+ "DB_NAME์ week4/schema.sql ๊ธฐ์ค umc_mission ์ฌ์ฉ (์ด๋ฏธ ํ
์ด๋ธ ์์)"
+ ]
+ },
+ {
+ "id": 5,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/db.config.ts ์์ฑ - MySQL Connection Pool",
+ "status": "todo",
+ "details": [
+ "mysql2/promise์ createPool ์ฌ์ฉ",
+ "ํ๊ฒฝ ๋ณ์(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME)๋ก ์ค์ ",
+ "connectionLimit: 10, waitForConnections: true",
+ "week4 schema.sql์ DB(umc_mission)์ ์ฐ๊ฒฐ"
+ ],
+ "reference_week4": "week4/src/db/index.ts ๊ตฌ์กฐ ์ฐธ๊ณ (๋จ, in-memoryโMySQL๋ก ๋ณ๊ฒฝ)"
+ },
+ {
+ "id": 6,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/index.ts ์์ฑ - Express ์ฑ ์ง์
์ ",
+ "status": "todo",
+ "details": [
+ "dotenv.config() ์ต์๋จ ํธ์ถ",
+ "cors(), express.json(), express.urlencoded() ๋ฏธ๋ค์จ์ด ๋ฑ๋ก",
+ "๊ฐ ๋ชจ๋ ๋ผ์ฐํฐ ๋ฑ๋ก: /api/v1/stores, /api/v1/reviews, /api/v1/missions",
+ "์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ๋ฏธ๋ค์จ์ด ๋ฑ๋ก (์๋์ด ๋ฏธ์
: JSON ์๋ฌ ์๋ต)",
+ "app.listen(process.env.PORT || 3000)"
+ ],
+ "reference_week4": "week4/src/index.ts ๊ตฌ์กฐ ๊ทธ๋๋ก ํ์ฉ, corsยทdotenv ์ถ๊ฐ"
+ },
+ {
+ "id": 7,
+ "phase": "DB ์ค๋น",
+ "title": "MySQL์ week5์ฉ ํ
์ด๋ธ ํ์ธ ๋ฐ ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
",
+ "status": "todo",
+ "details": [
+ "week4/schema.sql๋ก ํ
์ด๋ธ ์์ฑ (์ด๋ฏธ ๋์ด์์ผ๋ฉด ์๋ต)",
+ "food_category ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO food_category(name) VALUES ('ํ์'),('์ค์'),('์ผ์'),('์์'),('์นํจ'),('๋ถ์'),('๊ณ ๊ธฐ/๊ตฌ์ด'),('๋์๋ฝ'),('์์'),('ํจ์คํธํธ๋'),('๋ค์ ํธ'),('์์์ํธ๋')",
+ "region ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO region(name) VALUES ('์์ธ'),('๊ฒฝ๊ธฐ'),('๋ถ์ฐ')...",
+ "member ๋๋ฏธ๋ฐ์ดํฐ 1๊ฑด ์ฝ์
(API ํ
์คํธ์ฉ ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ ]
+ },
+ {
+ "id": 8,
+ "phase": "API ๊ตฌํ - 1-1",
+ "title": "[ํ์] ํน์ ์ง์ญ์ ๊ฐ๊ฒ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores",
+ "request_body": {
+ "regionId": "number",
+ "foodCategoryId": "number",
+ "name": "string",
+ "description": "string (์ ํ)",
+ "address": "string",
+ "lat": "number (์ ํ)",
+ "lng": "number (์ ํ)"
+ },
+ "details": [
+ "store.dto.ts - StoreCreateRequest ์ธํฐํ์ด์ค ์ ์",
+ "store.dto.ts - bodyToStore() ๋ณํ ํจ์ ์์ฑ",
+ "store.repository.ts - addStore(): INSERT INTO store(...) VALUES(?)",
+ "store.service.ts - createStore(data): addStore ํธ์ถ ํ ๊ฒฐ๊ณผ ๋ฐํ",
+ "store.controller.ts - handleCreateStore: bodyToStore(req.body as ...) โ service ํธ์ถ โ 201 ์๋ต",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก: app.post('/api/v1/stores', handleCreateStore)"
+ ],
+ "reference_week4": "week4/src/controllers/store.controller.ts ํจํด ์ฐธ๊ณ "
+ },
+ {
+ "id": 9,
+ "phase": "API ๊ตฌํ - 1-2",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/stores/:storeId/reviews",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)",
+ "content": "string",
+ "score": "number (1.0~5.0)"
+ },
+ "details": [
+ "review.dto.ts - ReviewCreateRequest ์ธํฐํ์ด์ค (content, score, memberId)",
+ "review.dto.ts - bodyToReview() ๋ณํ ํจ์",
+ "review.repository.ts - addReview(): INSERT INTO review(...)",
+ "store.repository.ts (๋๋ review.repository.ts) - findStoreById(): SELECT * FROM store WHERE id=?",
+ "review.service.ts - createReview(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addReview ํธ์ถ",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.')",
+ "review.controller.ts - handleCreateReview: storeId = parseInt(req.params.storeId) โ service ํธ์ถ",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋ฆฌ๋ทฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ ๊ฐ๊ฒ๊ฐ ์กด์ฌํ๋์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/services/store.service.ts์ createReview ํจํด, week4 ์คํค๋ง์ review ํ
์ด๋ธ"
+ },
+ {
+ "id": 10,
+ "phase": "API ๊ตฌํ - 1-3",
+ "title": "๊ฐ๊ฒ์ ๋ฏธ์
์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores/:storeId/missions",
+ "request_body": {
+ "title": "string",
+ "reward": "number",
+ "spec": "string (์ ํ)",
+ "deadLine": "string (YYYY-MM-DD, ์ ํ)"
+ },
+ "details": [
+ "mission.dto.ts - MissionCreateRequest ์ธํฐํ์ด์ค",
+ "mission.dto.ts - bodyToMission() ๋ณํ ํจ์ (deadLine โ Date ๋ณํ)",
+ "mission.repository.ts - addMission(): INSERT INTO mission(store_id, title, reward, spec, dead_line)",
+ "mission.service.ts - createMission(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addMission",
+ "mission.controller.ts - handleCreateMission",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4/src/repositories/mission.repository.ts ํจํด, week4 schema.sql์ mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 11,
+ "phase": "API ๊ตฌํ - 1-4",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฏธ์
์ ๋์ ์ค์ธ ๋ฏธ์
์ ์ถ๊ฐ(๋ฏธ์
๋์ ํ๊ธฐ) API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/missions/:missionId/challenge",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ },
+ "details": [
+ "mission.dto.ts - MissionChallengeRequest ์ธํฐํ์ด์ค ({ memberId: number })",
+ "mission.repository.ts - findMemberMission(): SELECT * FROM member_mission WHERE member_id=? AND mission_id=?",
+ "mission.repository.ts - addMemberMission(): INSERT INTO member_mission(member_id, mission_id, status) VALUES(?,?,'CHALLENGING')",
+ "mission.service.ts - challengeMission(missionId, data): ์ค๋ณต ๋์ ๊ฒ์ฆ โ addMemberMission",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.')",
+ "mission.controller.ts - handleChallengeMission: missionId = parseInt(req.params.missionId) โ service",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋์ ํ๋ ค๋ ๋ฏธ์
์ด ์ด๋ฏธ ๋์ ์ค์ด์ง๋ ์์์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/repositories/mission.repository.ts, week4 schema.sql์ member_mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 12,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
2๋ฒ] Controller โ Service โ Repository โ DB ์์ฒญ ํ๋ฆ ์ ๋ฆฌ",
+ "status": "todo",
+ "details": [
+ "์: POST /api/v1/stores/:storeId/reviews ์์ฒญ ํ๋ฆ์ ์์๋๋ก ์์ฑ",
+ "1. ์ฌ์ฉ์๊ฐ POST /api/v1/stores/1/reviews ์์ฒญ ์ ์ก",
+ "2. index.ts์ ๋ผ์ฐํฐ๊ฐ handleCreateReview ์ปจํธ๋กค๋ฌ ํธ์ถ",
+ "3. Controller: req.body๋ฅผ ReviewCreateRequest ํ์
์ผ๋ก ๋ณํ(bodyToReview), storeId ํ์ฑ",
+ "4. Service: ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ(findStoreById) โ ์์ผ๋ฉด Error throw",
+ "5. Repository: INSERT INTO review ์ฟผ๋ฆฌ ์คํ โ insertId ๋ฐํ",
+ "6. Service: ๊ฒฐ๊ณผ๋ฅผ DTO๋ก ๋ณํํด Controller์ ๋ฐํ",
+ "7. Controller: 201 JSON ์๋ต ์ ์ก",
+ "์ํฌ๋ถ์ ์์ฝ ์ ๋ฆฌ ์น์
์ ์ด ๋ด์ฉ ํฌํจ"
+ ]
+ },
+ {
+ "id": 13,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
3๋ฒ] ํ์๊ฐ์
API์ bcrypt ๋น๋ฐ๋ฒํธ ํด์ฑ ์ถ๊ฐ",
+ "status": "todo",
+ "details": [
+ "npm install bcryptjs && npm install -D @types/bcryptjs (week4์ ์ด๋ฏธ ์ค์น๋จ)",
+ "member.dto.ts - MemberSignUpRequest ์ธํฐํ์ด์ค์ password ํ๋ ์ถ๊ฐ",
+ "member.repository.ts - addMember(): INSERT INTO member(..., password) VALUES(...)",
+ "member.service.ts - signUp(): const hashedPw = await bcrypt.hash(data.password, 10) โ addMember์ ์ ๋ฌ",
+ "member.controller.ts - handleSignUp ์์ฑ",
+ "POST /api/v1/members/signup ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4์์ bcryptjs ์ด๋ฏธ ์ฌ์ฉ ์ค - week4/package.json ์ฐธ๊ณ "
+ },
+ {
+ "id": 14,
+ "phase": "์๋์ด ๋ฏธ์
",
+ "title": "[์๋์ด] ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ - JSON ํํ ์๋ฌ ์๋ต",
+ "status": "todo",
+ "details": [
+ "src/middleware/error.middleware.ts ์์ฑ",
+ "ErrorRequestHandler ํ์
์ฌ์ฉ: (err, req, res, next) => void",
+ "res.status(err.status || 500).json({ success: false, message: err.message || '์๋ฒ ์๋ฌ' })",
+ "index.ts ๋งจ ๋ง์ง๋ง์ app.use(errorMiddleware) ๋ฑ๋ก",
+ "Controller์์ try-catch ์ ๊ฑฐํ๊ฑฐ๋ next(err) ์ฌ์ฉ์ผ๋ก ๋ณ๊ฒฝ",
+ "๊ธฐ์กด HTML ์๋ฌ ์๋ต โ JSON ์๋ฌ ์๋ต์ผ๋ก ๊ฐ์ "
+ ],
+ "reference_week4": "week4/src/middleware/error.middleware.ts ๊ทธ๋๋ก ํ์ฉ ๊ฐ๋ฅ"
+ },
+ {
+ "id": 15,
+ "phase": "ํ
์คํธ",
+ "title": "Postman / curl๋ก ๊ฐ API ํธ์ถ ๋ฐ ์คํฌ๋ฆฐ์ท ์ ์ฅ",
+ "status": "todo",
+ "details": [
+ "npm run dev ๋ก ์๋ฒ ์คํ",
+ "API 1-1: POST /api/v1/stores - ๊ฐ๊ฒ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: POST /api/v1/stores/:storeId/reviews - ๋ฆฌ๋ทฐ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: ์กด์ฌํ์ง ์๋ storeId๋ก ์์ฒญ โ ์๋ฌ ์๋ต ์คํฌ๋ฆฐ์ท",
+ "API 1-3: POST /api/v1/stores/:storeId/missions - ๋ฏธ์
์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: POST /api/v1/missions/:missionId/challenge - ๋์ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: ๋์ผ ๋ฏธ์
์ฌ๋์ โ '์ด๋ฏธ ๋์ ์ค' ์๋ฌ ์คํฌ๋ฆฐ์ท",
+ "DB์์ SELECT๋ก ๋ฐ์ดํฐ ์ฝ์
ํ์ธ ์คํฌ๋ฆฐ์ท"
+ ]
+ },
+ {
+ "id": 16,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "feature/chapter-05 ๋ธ๋์น์ push ๋ฐ PR ์์ฑ",
+ "status": "todo",
+ "details": [
+ "git add . && git commit -m 'feat: 5์ฃผ์ฐจ ๋ฏธ์
- API ๊ตฌํ (๊ฐ๊ฒ/๋ฆฌ๋ทฐ/๋ฏธ์
)'",
+ "git push origin feature/chapter-05",
+ "GitHub์์ PR ์์ฑ (main ๋ธ๋์น์ mergeํ์ง ๋ง ๊ฒ!)",
+ "์ํฌ๋ถ์ ๋ฏธ์
๊ธฐ๋ก๋์ GitHub ๋งํฌ ์ ์ถ",
+ "GitHub ์ด์ Close"
+ ]
+ },
+ {
+ "id": 17,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "ํต์ฌ ํค์๋ ๋ฐ ์์ฝ ์ ๋ฆฌ ์์ฑ",
+ "status": "todo",
+ "details": [
+ "์ด ํ์ผ ์๋จ์ keywords ์น์
์ ์ฐธ๊ณ ํด ์ํฌ๋ถ์ ๊ธฐ์
",
+ "์์ฝ ์ ๋ฆฌ: ControllerโServiceโRepositoryโDB ํ๋ฆ ์ค๋ช
",
+ "์ํด๋ฆฌ ์คํฌ๋ผ ์ง๋ฌธ ๋ต๋ณ: DTO ์์ด ์ฌ์ฉํ ๋์ ๋ฌธ์ ์ / Service Layer ํ์์ฑ"
+ ]
+ }
+ ],
+ "required_apis_summary": {
+ "must_implement": ["1-2 (๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ)", "1-4 (๋ฏธ์
๋์ ํ๊ธฐ)"],
+ "minimum_count": "ํ์ 2๊ฐ ํฌํจ ์ด 3๊ฐ ์ด์",
+ "senior_mission": "4๊ฐ ์ ๋ถ + JSON ์๋ฌ ์๋ต ๊ฐ์ "
+ },
+ "key_differences_from_week4": {
+ "database": "in-memory(week4) โ MySQL Connection Pool(week5)",
+ "modules": "flat ๊ตฌ์กฐ(week4) โ ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค(week5: src/modules/{๋๋ฉ์ธ}/)",
+ "env": ".env ํ์ผ ์ถ๊ฐ (dotenv ์ฌ์ฉ)",
+ "cors": "cors ๋ฏธ๋ค์จ์ด ์ถ๊ฐ",
+ "error_response": "HTML ์๋ฌ(๊ธฐ๋ณธ) โ JSON ์๋ฌ(์๋์ด ๋ฏธ์
๊ฐ์ )"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/tsconfig.json" "b/\353\217\204\354\226\217/week8/tsconfig.json"
new file mode 100644
index 0000000..f43e7d2
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/tsconfig.json"
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "./dist",
+ "target": "ESNext",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "lib": ["ESNext"],
+ "types": ["node"],
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "sourceMap": true,
+ "experimentalDecorators": true
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git "a/\353\217\204\354\226\217/week8/tsoa.json" "b/\353\217\204\354\226\217/week8/tsoa.json"
new file mode 100644
index 0000000..ca6c134
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/tsoa.json"
@@ -0,0 +1,13 @@
+{
+ "entryFile": "src/index.ts",
+ "noImplicitAdditionalProperties": "throw-on-extras",
+ "controllerPathGlobs": ["src/**/*.controller.ts"],
+ "spec": {
+ "outputDirectory": "dist",
+ "specVersion": 3,
+ "basePath": "/api/v1"
+ },
+ "routes": {
+ "routesDir": "src/generated"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week8/week7-commands.md" "b/\353\217\204\354\226\217/week8/week7-commands.md"
new file mode 100644
index 0000000..37beced
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/week7-commands.md"
@@ -0,0 +1,116 @@
+# Week 7 ์คํ ๋ช
๋ น์ด ์์ ์ ๋ฆฌ
+
+> ์๋ ์์๋๋ก ์คํํ์ธ์. ๊ฐ ๋จ๊ณ ์๋ฃ ํ ๋ค์ ๋จ๊ณ๋ก ์งํํฉ๋๋ค.
+
+---
+
+## 1๋จ๊ณ. ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ ์ด๋
+
+```bash
+cd
+```
+
+> week6 ์ฝ๋๊ฐ ์๋ ํ๋ก์ ํธ ๋ฃจํธ ํด๋๋ก ์ด๋ํฉ๋๋ค.
+
+---
+
+## 2๋จ๊ณ. ํ์ ๋ฏธ๋ค์จ์ด ํจํค์ง ์ค์น
+
+```bash
+npm install morgan cookie-parser
+```
+
+---
+
+## 3๋จ๊ณ. ์ค์น ํ์ธ
+
+```bash
+cat package.json
+```
+
+> `dependencies`์ `morgan`๊ณผ `cookie-parser`๊ฐ ์ถ๊ฐ๋์๋์ง ํ์ธํฉ๋๋ค.
+
+---
+
+## 4๋จ๊ณ. Git ์ด๊ธฐํ ๋ฐ ์๊ฒฉ ์ ์ฅ์ ์ฐ๊ฒฐ
+
+> GitHub์์ ์ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ๋จผ์ ๋ง๋ ํ ์คํํฉ๋๋ค.
+
+```bash
+git init
+git remote add origin https://github.com//.git
+```
+
+---
+
+## 5๋จ๊ณ. ์์
๋ธ๋์น ์์ฑ ๋ฐ ์ด๋
+
+```bash
+git checkout -b feature/chapter-07
+```
+
+> **์ฃผ์:** `main` ๋ธ๋์น์๋ ์ ๋ pushํ์ง ์์ต๋๋ค. (Week 11 CI/CD ์ ์ฉ)
+
+---
+
+## 6๋จ๊ณ. ์ฝ๋ ์์ฑ ํ ์คํ
์ด์ง
+
+> `index.js`, `BaseResponse`, `BaseError`, `errorCode.js` ๋ฑ ์์ /์์ฑ ํ ์คํ
+
+```bash
+git add .
+```
+
+---
+
+## 7๋จ๊ณ. ์ปค๋ฐ
+
+```bash
+git commit -m "feat: add middleware, standard response, and error handling"
+```
+
+---
+
+## 8๋จ๊ณ. feature ๋ธ๋์น์ push
+
+```bash
+git push origin feature/chapter-07
+```
+
+---
+
+## (์ ํ) Prisma ๊ด๋ จ ๋ช
๋ น์ด
+
+### ์คํค๋ง ๋ณ๊ฒฝ ํ ๋ง์ด๊ทธ๋ ์ด์
์คํ
+
+```bash
+npx prisma migrate dev --name
+```
+
+### DB ์ํ์ ์คํค๋ง ๋๊ธฐํ ํ์ธ
+
+```bash
+npx prisma db pull
+```
+
+### Prisma Studio (GUI๋ก DB ํ์ธ)
+
+```bash
+npx prisma studio
+```
+
+---
+
+## ์ ์ฒด ์์ ์์ฝ
+
+| ์์ | ๋ช
๋ น์ด | ์ค๋ช
|
+|---|---|---|
+| 1 | `cd ` | ํ๋ก์ ํธ ํด๋ ์ด๋ |
+| 2 | `npm install morgan cookie-parser` | ๋ฏธ๋ค์จ์ด ์ค์น |
+| 3 | `cat package.json` | ์ค์น ํ์ธ |
+| 4 | `git init` | Git ์ด๊ธฐํ |
+| 5 | `git remote add origin ` | ์๊ฒฉ ์ ์ฅ์ ์ฐ๊ฒฐ |
+| 6 | `git checkout -b feature/chapter-07` | ์์
๋ธ๋์น ์์ฑ |
+| 7 | `git add .` | ๋ณ๊ฒฝ ํ์ผ ์คํ
์ด์ง |
+| 8 | `git commit -m "..."` | ์ปค๋ฐ |
+| 9 | `git push origin feature/chapter-07` | ๋ธ๋์น์ push |
diff --git "a/\353\217\204\354\226\217/week8/week7-workbook.md" "b/\353\217\204\354\226\217/week8/week7-workbook.md"
new file mode 100644
index 0000000..c235612
--- /dev/null
+++ "b/\353\217\204\354\226\217/week8/week7-workbook.md"
@@ -0,0 +1,282 @@
+# Week 7 ์ํฌ๋ถ - ๋ฏธ๋ค์จ์ด, ํ์ค ์๋ต/์๋ฌ ์ฒ๋ฆฌ, Git ์ ๋ต
+
+> **ํ๋ก์ ํธ ์ฐธ์กฐ:** week6_directory_contents ๊ธฐ๋ฐ์ผ๋ก week7 ๋ฆฌํฉํ ๋ง
+
+---
+
+## Step 1. ํ์ ๋ฏธ๋ค์จ์ด ์ค์น ๋ฐ ์ค์
+
+### ์ค์น ํจํค์ง
+
+```bash
+npm install morgan cookie-parser
+```
+
+### `index.js` ์ ์ฉ ์์
+
+```js
+const express = require('express');
+const morgan = require('morgan');
+const cookieParser = require('cookie-parser');
+
+const app = express();
+
+// ์์ฒญ ๋ก๊น
๋ฏธ๋ค์จ์ด
+app.use(morgan('dev'));
+
+// ์ฟ ํค ํ์ฑ ๋ฏธ๋ค์จ์ด
+app.use(cookieParser());
+
+// JSON ๋ฐ URL-encoded body ํ์ฑ
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+```
+
+### ๋ฏธ๋ค์จ์ด ์ญํ ์ค๋ช
+
+| ๋ฏธ๋ค์จ์ด | ์ญํ |
+|---|---|
+| `morgan('dev')` | HTTP ์์ฒญ/์๋ต ๋ก๊ทธ๋ฅผ ํฐ๋ฏธ๋์ ์ถ๋ ฅ (๊ฐ๋ฐ ํ๊ฒฝ์ฉ) |
+| `cookieParser()` | `req.cookies` ๊ฐ์ฒด๋ก ์ฟ ํค ๊ฐ ํ์ฑ |
+| `express.json()` | `Content-Type: application/json` ์์ฒญ ๋ฐ๋ ํ์ฑ |
+| `express.urlencoded()` | HTML form ๋ฐ์ดํฐ ํ์ฑ |
+
+---
+
+## Step 2. ํ์ค API ์ฑ๊ณต ์๋ต ํ์ ํต์ผ
+
+### ์๋ต ํ์ ์ ์
+
+๋ชจ๋ API์ ์ฑ๊ณต ์๋ต์ ์๋ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฆ
๋๋ค:
+
+```json
+{
+ "isSuccess": true,
+ "code": "COMMON200",
+ "message": "์ฑ๊ณต์
๋๋ค.",
+ "result": { }
+}
+```
+
+| ํ๋ | ํ์
| ์ค๋ช
|
+|---|---|---|
+| `isSuccess` | boolean | ์์ฒญ ์ฑ๊ณต ์ฌ๋ถ |
+| `code` | string | ์๋ต ์ฝ๋ (์: `COMMON200`, `USER404`) |
+| `message` | string | ์ฌ๋์ด ์ฝ์ ์ ์๋ ๋ฉ์์ง |
+| `result` | object \| array | ์ค์ ๋ฐ์ดํฐ |
+
+### BaseResponse ์ ํธ๋ฆฌํฐ ๊ตฌํ ์์
+
+```js
+// src/utils/response.js
+
+const BaseResponse = (result, message = '์ฑ๊ณต์
๋๋ค.', code = 'COMMON200') => {
+ return {
+ isSuccess: true,
+ code,
+ message,
+ result,
+ };
+};
+
+module.exports = { BaseResponse };
+```
+
+### ์ปจํธ๋กค๋ฌ์์ ์ฌ์ฉ
+
+```js
+const { BaseResponse } = require('../utils/response');
+
+// Before (๊ธฐ์กด ๋ฐฉ์)
+res.status(200).json(user);
+
+// After (ํ์ค ์๋ต ์ ์ฉ)
+res.status(200).json(BaseResponse(user));
+```
+
+---
+
+## Step 3. ์ค์ ์ง์ค์ ์ปค์คํ
์๋ฌ ์ฒ๋ฆฌ
+
+### BaseError ํด๋์ค ์ ์
+
+```js
+// src/utils/errors.js
+
+class BaseError extends Error {
+ constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
+ super(message);
+ this.name = 'BaseError';
+ this.status = statusCode;
+ this.code = code;
+ }
+}
+
+module.exports = { BaseError };
+```
+
+### ์๋ฌ ์ฝ๋ ์์ ์์
+
+```js
+// src/utils/errorCode.js
+
+const ErrorCode = {
+ USER_NOT_FOUND: { status: 404, code: 'USER4001', message: '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.' },
+ DUPLICATE_EMAIL: { status: 409, code: 'USER4002', message: '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.' },
+ INTERNAL_ERROR: { status: 500, code: 'COMMON500', message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.' },
+};
+
+module.exports = { ErrorCode };
+```
+
+### ๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ฌ ๋ฏธ๋ค์จ์ด (`index.js` ํ๋จ์ ์ถ๊ฐ)
+
+```js
+// ๊ธ๋ก๋ฒ ์๋ฌ ํธ๋ค๋ฌ - ๋ฐ๋์ ๋ค๋ฅธ app.use() ์๋์ ์์นํด์ผ ํจ
+app.use((err, req, res, next) => {
+ if (err instanceof BaseError) {
+ return res.status(err.status).json({
+ isSuccess: false,
+ code: err.code,
+ message: err.message,
+ result: null,
+ });
+ }
+
+ // Prisma unique constraint ์๋ฌ ์ฒ๋ฆฌ
+ if (err.code === 'P2002') {
+ return res.status(409).json({
+ isSuccess: false,
+ code: 'USER4002',
+ message: '์ด๋ฏธ ์กด์ฌํ๋ ๋ฐ์ดํฐ์
๋๋ค.',
+ result: null,
+ });
+ }
+
+ console.error(err);
+ return res.status(500).json({
+ isSuccess: false,
+ code: 'COMMON500',
+ message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.',
+ result: null,
+ });
+});
+```
+
+### ์ปจํธ๋กค๋ฌ์์ ์๋ฌ ๋์ง๊ธฐ
+
+```js
+const { BaseError } = require('../utils/errors');
+const { ErrorCode } = require('../utils/errorCode');
+
+// Before (๊ธฐ์กด ๋ฐฉ์)
+res.status(404).send('User Not Found');
+
+// After (next()๋ก ์๋ฌ ์์)
+const user = await prisma.user.findUnique({ where: { id } });
+if (!user) {
+ return next(new BaseError(
+ ErrorCode.USER_NOT_FOUND.message,
+ ErrorCode.USER_NOT_FOUND.status,
+ ErrorCode.USER_NOT_FOUND.code
+ ));
+}
+```
+
+---
+
+## Step 4. GitHub ๋ธ๋์น ์ ๋ต
+
+### ๋ธ๋์น ๊ท์น
+
+| ๋ธ๋์น | ์ฉ๋ |
+|---|---|
+| `main` | Week 11 CI/CD ํ์ดํ๋ผ์ธ ์ ์ฉ โ **์ง์ push ๊ธ์ง** |
+| `feature/chapter-07` | Week 7 ์์
๋ธ๋์น |
+
+### ์ฃผ์์ฌํญ
+
+> `main` ๋ธ๋์น๋ Week 11 CI/CD ์ค์ ์ ์ํด ๋ณดํธ๋์ด ์์ต๋๋ค.
+> ๋ชจ๋ ์ฝ๋๋ ๋ฐ๋์ `feature/chapter-07` ๋ธ๋์น์ push ํ์ธ์.
+
+---
+
+## Step 5. Prisma ORM vs Raw SQL (mysql2) ๋น๊ต ๋ถ์
+
+### ๋น๊ตํ
+
+| ํญ๋ชฉ | Prisma ORM | Raw SQL (mysql2) |
+|---|---|---|
+| **๋ฌธ๋ฒ** | TypeScript ๊ธฐ๋ฐ ํ์
์์ API | ์ง์ SQL ๋ฌธ์์ด ์์ฑ |
+| **๊ฐ๋
์ฑ** | ๋์ (์ง๊ด์ ๋ฉ์๋ ์ฒด์ด๋) | ๋ฎ์ (SQL ์๋ จ๋ ํ์) |
+| **ํ์
์์ ์ฑ** | ์๋ ํ์
์ถ๋ก ์ง์ | ์์ (์ง์ ์บ์คํ
ํ์) |
+| **์ฑ๋ฅ ํ๋** | ์ ํ์ | ์์ ํ SQL ์ ์ด ๊ฐ๋ฅ |
+| **๋ง์ด๊ทธ๋ ์ด์
** | `prisma migrate dev` ์๋ํ | ์๋ ๊ด๋ฆฌ |
+| **ํ์ต ๋น์ฉ** | Prisma ๋ฌธ๋ฒ ํ์ต ํ์ | SQL ์ง์๋ง ์์ผ๋ฉด ๋จ |
+| **๋ณต์ก ์ฟผ๋ฆฌ** | ํ๊ณ ์์ (rawQuery ํผ์ฉ) | ๋ฌด์ ํ |
+
+---
+
+### `prisma migrate dev` ์ฅ๋จ์ ๋ถ์
+
+#### ์ฅ์
+
+- **์๋ ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ ์์ฑ:** `schema.prisma` ๋ณ๊ฒฝ ์ฌํญ์ ๊ฐ์งํด SQL ํ์ผ ์๋ ์์ฑ
+- **ํ์คํ ๋ฆฌ ๊ด๋ฆฌ:** `prisma/migrations/` ํด๋์ ๋ณ๊ฒฝ ์ด๋ ฅ ๋์ ๋ณด๊ด
+- **๊ฐ๋ฐ ํธ์์ฑ:** ์คํค๋ง โ DB ๋๊ธฐํ๋ฅผ ๋ช
๋ น์ด ํ ์ค๋ก ์ฒ๋ฆฌ
+- **Seed ์ฐ๋:** `prisma db seed`์ ์ฐ๊ณํ์ฌ ์ด๊ธฐ ๋ฐ์ดํฐ ์ฝ์
๊ฐ๋ฅ
+
+#### ๋จ์
+
+- **ํ ํ์
์ ์ถฉ๋ ์ํ:** ์ฌ๋ฌ ์ฌ๋์ด `schema.prisma`๋ฅผ ๋์์ ์์ ํ๋ฉด migration ์ถฉ๋ ๋ฐ์
+- **ํ๋ก๋์
๋ถ์ ํฉ:** ์ด์ ํ๊ฒฝ์์๋ `prisma migrate deploy` ์ฌ์ฉ ๊ถ์ฅ (`dev`๋ ๊ฐ๋ฐ ์ ์ฉ)
+- **์๋ํ์ ๋ถํฌ๋ช
์ฑ:** ์์ฑ๋ SQL์ ๊ฒํ ํ์ง ์์ผ๋ฉด ์๋์น ์์ ๋ฐ์ดํฐ ์์ค ์ํ
+
+---
+
+### ํ์
์ Migration ์ถฉ๋ ๋ฐฉ์ง ์ ๋ต
+
+1. **์คํค๋ง ๋ด๋น์ ๋จ์ผํ:** `schema.prisma` ์์ ์ ํ ๋ช
์ด ๋ด๋นํ๊ฑฐ๋ PR ๋ฆฌ๋ทฐ๋ฅผ ํ์๋ก ์ค์
+2. **๋ธ๋์น ์ ๋ต ์ค์:** ๊ธฐ๋ฅ ๋ธ๋์น์์ ์คํค๋ง ์์ ํ PR ๋จธ์ง ์์ ์งํค๊ธฐ
+3. **migration ํ์ผ ์ปค๋ฐ ํ์:** `prisma/migrations/` ํด๋๋ฅผ `.gitignore`์ ์ถ๊ฐํ์ง ๋ง๊ณ ํญ์ ์ปค๋ฐ
+4. **๋จธ์ง ์ `prisma migrate dev` ์ฌ์คํ:** ๋จธ์ง ํ ๋ก์ปฌ์์ ๋ฐ๋์ ์ฌ์คํํ์ฌ ๋๊ธฐํ ํ์ธ
+5. **ํ ๋ด DB ์ํ ๊ณต์ :** ๋ง์ด๊ทธ๋ ์ด์
์คํ ์ฌ๋ถ๋ฅผ ํ ์ฑ๋(์ฌ๋ ๋ฑ)์ ๊ณต์
+
+---
+
+### ๊ฐ์ธ ์ ํธ๋: Prisma vs mysql2
+
+**์ ํ: Prisma ORM**
+
+**์ด์ :**
+
+- TypeScript ํ๊ฒฝ์์ ํ์
์๋ ์์ฑ๊ณผ ์ปดํ์ผ ํ์ ์๋ฌ ๊ฒ์ถ์ด ๊ฐ๋ฅํด ๋ฐํ์ ๋ฒ๊ทธ ๊ฐ์
+- ๋ณต์กํ JOIN๋ณด๋ค ๋จ์ CRUD ์์ฃผ์ธ ํ์ฌ ํ๋ก์ ํธ ๊ท๋ชจ์ ์ถฉ๋ถ
+- ๋ง์ด๊ทธ๋ ์ด์
์๋ํ๋ก ํ ์ ์ฒด์ DB ์ํ๋ฅผ ์ผ๊ด๋๊ฒ ์ ์ง ๊ฐ๋ฅ
+- `prisma studio`๋ฅผ ํตํ GUI ๋ฐ์ดํฐ ํ์ธ์ด ๊ฐ๋ฐ ์์ฐ์ฑ์ ๋์
+
+**mysql2๊ฐ ๋ ์ ํฉํ ๊ฒฝ์ฐ:**
+
+- ๋ณต์กํ ์ง๊ณ ์ฟผ๋ฆฌ๋ ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋ง์ ์๋น์ค
+- ๊ทน๋์ ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ ๋๊ท๋ชจ ํธ๋ํฝ ํ๊ฒฝ
+
+---
+
+## ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ ์ ์
+
+```
+project/
+โโโ index.js
+โโโ prisma/
+โ โโโ schema.prisma
+โ โโโ migrations/
+โโโ src/
+โ โโโ controllers/
+โ โโโ services/
+โ โโโ repositories/
+โ โโโ utils/
+โ โโโ response.js โ BaseResponse
+โ โโโ errors.js โ BaseError
+โ โโโ errorCode.js โ ์๋ฌ ์ฝ๋ ์์
+โโโ package.json
+```
diff --git "a/\353\217\204\354\226\217/week9/.gitignore" "b/\353\217\204\354\226\217/week9/.gitignore"
new file mode 100644
index 0000000..7206753
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/.gitignore"
@@ -0,0 +1,22 @@
+# dependency directories
+node_modules/
+
+# build output
+dist/
+
+# dotenv environment variable files
+.env
+.env.local
+.env.development
+.env.production
+.env.*
+.claude*
+
+# macOS
+.DS_Store
+
+# logs
+*.log
+npm-debug.log*
+
+/src/generated/prisma
diff --git "a/\353\217\204\354\226\217/week9/README.md" "b/\353\217\204\354\226\217/week9/README.md"
new file mode 100644
index 0000000..bb278f1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/README.md"
@@ -0,0 +1 @@
+# umc-node-study
\ No newline at end of file
diff --git "a/\353\217\204\354\226\217/week9/package-lock.json" "b/\353\217\204\354\226\217/week9/package-lock.json"
new file mode 100644
index 0000000..fd10bef
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/package-lock.json"
@@ -0,0 +1,5127 @@
+{
+ "name": "week9",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0",
+ "jsonwebtoken": "^9.0.3",
+ "morgan": "^1.10.1",
+ "passport": "^0.7.0",
+ "passport-google-oauth20": "^2.0.0",
+ "passport-jwt": "^4.0.1",
+ "swagger-ui-express": "^5.0.1",
+ "tsoa": "^6.6.0"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/morgan": "^1.9.10",
+ "@types/node": "^25.6.0",
+ "@types/passport": "^1.0.17",
+ "@types/passport-google-oauth20": "^2.0.17",
+ "@types/passport-jwt": "^4.0.1",
+ "@types/swagger-ui-express": "^4.1.8",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0",
+ "typescript": "^6.0.3"
+ }
+ },
+ "node_modules/@electric-sql/pglite": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz",
+ "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@electric-sql/pglite-socket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz",
+ "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "pglite-server": "dist/scripts/server.js"
+ },
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@electric-sql/pglite-tools": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz",
+ "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.4.1"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hapi/accept": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-6.0.3.tgz",
+ "integrity": "sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/ammo": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-6.0.1.tgz",
+ "integrity": "sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/b64": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-6.0.1.tgz",
+ "integrity": "sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/boom": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz",
+ "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/bounce": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-3.0.2.tgz",
+ "integrity": "sha512-d0XmlTi3H9HFDHhQLjg4F4auL1EY3Wqj7j7/hGDhFFe6xAbnm3qiGrXeT93zZnPH8gH+SKAFYiRzu26xkXcH3g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/bourne": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
+ "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/call": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/call/-/call-9.0.1.tgz",
+ "integrity": "sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/catbox": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-12.1.1.tgz",
+ "integrity": "sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/podium": "^5.0.0",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/catbox-memory": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-6.0.2.tgz",
+ "integrity": "sha512-H1l4ugoFW/ZRkqeFrIo8p1rWN0PA4MDTfu4JmcoNDvnY975o29mqoZblqFTotxNHlEkMPpIiIBJTV+Mbi+aF0g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/content": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/content/-/content-6.0.2.tgz",
+ "integrity": "sha512-OKyCOTjNR1hftwSjk9ueyAQTw8AwapvzBrPIWMGn39vhR5PmqLdYFmLc35bsSBye7gSMnlkXfc679bUdMIcRyQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.0"
+ }
+ },
+ "node_modules/@hapi/cryptiles": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-6.0.3.tgz",
+ "integrity": "sha512-r6VKalpbMHz4ci3gFjFysBmhwCg70RpYZy6OkjEpdXzAYnYFX5XsW7n4YMJvuIYpnMwLxGUjK/cBhA7X3JDvXw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@hapi/file": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@hapi/file/-/file-3.0.0.tgz",
+ "integrity": "sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/hapi": {
+ "version": "21.4.9",
+ "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-21.4.9.tgz",
+ "integrity": "sha512-YnecZOVx2AD08VvPl0ZaFS0MjEHqg+InGRmBRli731ct+VwI++dpu3BIYA1Z4SMr6HUAnpyvbQ1aq5woe3fBWg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/accept": "^6.0.3",
+ "@hapi/ammo": "^6.0.1",
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bounce": "^3.0.2",
+ "@hapi/call": "^9.0.1",
+ "@hapi/catbox": "^12.1.1",
+ "@hapi/catbox-memory": "^6.0.2",
+ "@hapi/heavy": "^8.0.1",
+ "@hapi/hoek": "^11.0.7",
+ "@hapi/mimos": "^7.0.1",
+ "@hapi/podium": "^5.0.2",
+ "@hapi/shot": "^6.0.2",
+ "@hapi/somever": "^4.1.1",
+ "@hapi/statehood": "^8.2.1",
+ "@hapi/subtext": "^8.1.3",
+ "@hapi/teamwork": "^6.0.1",
+ "@hapi/topo": "^6.0.2",
+ "@hapi/validate": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=14.15.0"
+ }
+ },
+ "node_modules/@hapi/heavy": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-8.0.1.tgz",
+ "integrity": "sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/hoek": {
+ "version": "11.0.7",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz",
+ "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/iron": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-7.0.1.tgz",
+ "integrity": "sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/b64": "^6.0.1",
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/cryptiles": "^6.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/mimos": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-7.0.1.tgz",
+ "integrity": "sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "mime-db": "^1.52.0"
+ }
+ },
+ "node_modules/@hapi/nigel": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-5.0.1.tgz",
+ "integrity": "sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/vise": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@hapi/pez": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-6.1.1.tgz",
+ "integrity": "sha512-yg2OS1tC0S1sHXvhUtWsfRn6lrKl9jKtRhZ+EI0woOW/gqX5vM2PZ1459ypCvCYDRLJ9nIyueeEH5MJV1ZDqIg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/b64": "^6.0.1",
+ "@hapi/boom": "^10.0.1",
+ "@hapi/content": "^6.0.1",
+ "@hapi/hoek": "^11.0.7",
+ "@hapi/nigel": "^5.0.1"
+ }
+ },
+ "node_modules/@hapi/podium": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-5.0.2.tgz",
+ "integrity": "sha512-T7gf2JYHQQfEfewTQFbsaXoZxSvuXO/QBIGljucUQ/lmPnTTNAepoIKOakWNVWvo2fMEDjycu77r8k6dhreqHA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/teamwork": "^6.0.0",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/shot": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-6.0.2.tgz",
+ "integrity": "sha512-WKK1ShfJTrL1oXC0skoIZQYzvLsyMDEF8lfcWuQBjpjCN29qivr9U36ld1z0nt6edvzv28etNMOqUF4klnHryw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/somever": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-4.1.1.tgz",
+ "integrity": "sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/bounce": "^3.0.1",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/statehood": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-8.2.1.tgz",
+ "integrity": "sha512-xf72TG/QINW26jUu+uL5H+crE1o8GplIgfPWwPZhnAGJzetIVAQEQYvzq+C0aEVHg5/lMMtQ+L9UryuSa5Yjkg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bounce": "^3.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/cryptiles": "^6.0.1",
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/iron": "^7.0.1",
+ "@hapi/validate": "^2.0.1"
+ }
+ },
+ "node_modules/@hapi/subtext": {
+ "version": "8.1.3",
+ "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-8.1.3.tgz",
+ "integrity": "sha512-WTpEZQjBP3UJ3gGunNl3w5Ao1EOJsuu2vttZ2KEcG+csSLxc0dI6VIkl2md2jDlHiQ2ARAoqdSUScy05A/NHtA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/content": "^6.0.2",
+ "@hapi/file": "^3.0.0",
+ "@hapi/hoek": "^11.0.7",
+ "@hapi/pez": "^6.1.1",
+ "@hapi/wreck": "^18.1.1"
+ }
+ },
+ "node_modules/@hapi/teamwork": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-6.0.1.tgz",
+ "integrity": "sha512-52OXRslUfYwXAOG8k58f2h2ngXYQGP0x5RPOo+eWA/FtyLgHjGMrE3+e9LSXP/0q2YfHAK5wj9aA9DTy1K+kyQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@hapi/topo": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz",
+ "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/validate": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/validate/-/validate-2.0.1.tgz",
+ "integrity": "sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2",
+ "@hapi/topo": "^6.0.1"
+ }
+ },
+ "node_modules/@hapi/vise": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-5.0.1.tgz",
+ "integrity": "sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/wreck": {
+ "version": "18.1.2",
+ "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.2.tgz",
+ "integrity": "sha512-3dMnV2pfhQiyEqu8DL3VBmxkdLiRDiiUDuG79Dp+UK1gL9ZxAfDOUhB6k3D5MLqcgJJ1IARyGFhwoc1NITr/pg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/bourne": "^3.0.0",
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.11",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
+ "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@prisma/adapter-mariadb": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.8.0.tgz",
+ "integrity": "sha512-mWsgcfbUjxB3qSzRlLs8E03vsKrqXzYK2zpx3e8u6wIgeHJM/sE46cuOGcYvHiZGmeQLCd3xL6YSSGM9QOLI6w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/driver-adapter-utils": "7.8.0",
+ "mariadb": "3.4.5"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz",
+ "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/client-runtime-utils": "7.8.0"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/client-runtime-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz",
+ "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/config": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz",
+ "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.3.4",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.20.0",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz",
+ "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/dev": {
+ "version": "0.24.3",
+ "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz",
+ "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "@electric-sql/pglite": "0.4.1",
+ "@electric-sql/pglite-socket": "0.1.1",
+ "@electric-sql/pglite-tools": "0.3.1",
+ "@hono/node-server": "1.19.11",
+ "@prisma/get-platform": "7.2.0",
+ "@prisma/query-plan-executor": "7.2.0",
+ "@prisma/streams-local": "0.1.2",
+ "foreground-child": "3.3.1",
+ "get-port-please": "3.2.0",
+ "hono": "^4.12.8",
+ "http-status-codes": "2.3.0",
+ "pathe": "2.0.3",
+ "proper-lockfile": "4.1.2",
+ "remeda": "2.33.4",
+ "std-env": "3.10.0",
+ "valibot": "1.2.0",
+ "zeptomatch": "2.1.0"
+ }
+ },
+ "node_modules/@prisma/driver-adapter-utils": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz",
+ "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz",
+ "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/fetch-engine": "7.8.0",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz",
+ "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz",
+ "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0",
+ "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
+ "@prisma/get-platform": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
+ "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.8.0"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
+ "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.2.0"
+ }
+ },
+ "node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+ "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/query-plan-executor": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
+ "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/streams-local": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz",
+ "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ajv": "^8.12.0",
+ "better-result": "^2.7.0",
+ "env-paths": "^3.0.0",
+ "proper-lockfile": "^4.1.2"
+ },
+ "engines": {
+ "bun": ">=1.3.6",
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@prisma/studio-core": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz",
+ "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@radix-ui/react-toggle": "1.1.10",
+ "chart.js": "4.5.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0",
+ "pnpm": "8"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toggle": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
+ "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@scarf/scarf": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsoa/cli": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@tsoa/cli/-/cli-6.6.0.tgz",
+ "integrity": "sha512-thSW0EiqjkF7HspcPIVIy0ZX65VqbWALHbxwl8Sk83j2kakOMq+fJvfo8FcBAWlMki+JDH7CO5iaAaSLHbeqtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tsoa/runtime": "^6.6.0",
+ "@types/multer": "^1.4.12",
+ "fs-extra": "^11.2.0",
+ "glob": "^10.3.10",
+ "handlebars": "^4.7.8",
+ "merge-anything": "^5.1.7",
+ "minimatch": "^9.0.1",
+ "ts-deepmerge": "^7.0.2",
+ "typescript": "^5.7.2",
+ "validator": "^13.12.0",
+ "yaml": "^2.6.1",
+ "yargs": "^17.7.1"
+ },
+ "bin": {
+ "tsoa": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "yarn": ">=1.9.4"
+ }
+ },
+ "node_modules/@tsoa/cli/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/@tsoa/cli/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@tsoa/cli/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@tsoa/cli/node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/@tsoa/runtime": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@tsoa/runtime/-/runtime-6.6.0.tgz",
+ "integrity": "sha512-+rF2gdL8CX+jQ82/IBc+MRJFNAvWPoBBl77HHJv3ESVMqbKhlhlo97JHmKyFbLcX6XOJN8zl8gfQpAEJN4SOMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@hapi/hapi": "^21.3.12",
+ "@types/koa": "^2.15.0",
+ "@types/multer": "^1.4.12",
+ "express": "^4.21.2",
+ "reflect-metadata": "^0.2.2",
+ "validator": "^13.12.0"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "yarn": ">=1.9.4"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/body-parser": {
+ "version": "1.20.5",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+ "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.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.15.1",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/debug/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/@tsoa/runtime/node_modules/express": {
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.5",
+ "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.15.1",
+ "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/@tsoa/runtime/node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "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.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/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/@tsoa/runtime/node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/@tsoa/runtime/node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@tsoa/runtime/node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/@tsoa/runtime/node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/@tsoa/runtime/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/@types/accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/content-disposition": {
+ "version": "0.5.9",
+ "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.9.tgz",
+ "integrity": "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==",
+ "license": "MIT"
+ },
+ "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",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/cookies": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.2.tgz",
+ "integrity": "sha512-1AvkDdZM2dbyFybL4fxpuNCaWyv//0AwsuUk2DWeXyM1/5ZKm6W3z6mQi24RZ4l2ucY+bkSHzbDVpySqPGuV8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/express": "*",
+ "@types/keygrip": "*",
+ "@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": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/http-assert": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz",
+ "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==",
+ "license": "MIT"
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "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/keygrip": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz",
+ "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/koa": {
+ "version": "2.15.2",
+ "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.2.tgz",
+ "integrity": "sha512-CB+iyjjh1uS5N6/CKwXvw0qA7USMS2WVc4Tjf660yCjhdvqzNr8gdFcIawB41zGGptOQ+d1fnpaQWIIUXYxR3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/accepts": "*",
+ "@types/content-disposition": "*",
+ "@types/cookies": "*",
+ "@types/http-assert": "*",
+ "@types/http-errors": "*",
+ "@types/keygrip": "*",
+ "@types/koa-compose": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/koa-compose": {
+ "version": "3.2.9",
+ "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.9.tgz",
+ "integrity": "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/koa": "*"
+ }
+ },
+ "node_modules/@types/morgan": {
+ "version": "1.9.10",
+ "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz",
+ "integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "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/multer": {
+ "version": "1.4.13",
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
+ "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
+ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.19.0"
+ }
+ },
+ "node_modules/@types/oauth": {
+ "version": "0.9.6",
+ "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz",
+ "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/passport": {
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz",
+ "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/passport-google-oauth20": {
+ "version": "2.0.17",
+ "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.17.tgz",
+ "integrity": "sha512-MHNOd2l7gOTCn3iS+wInPQMiukliAUvMpODO3VlXxOiwNEMSyzV7UNvAdqxSN872o8OXx1SqPDVT6tLW74AtqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/passport": "*",
+ "@types/passport-oauth2": "*"
+ }
+ },
+ "node_modules/@types/passport-jwt": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz",
+ "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/jsonwebtoken": "*",
+ "@types/passport-strategy": "*"
+ }
+ },
+ "node_modules/@types/passport-oauth2": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
+ "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/oauth": "*",
+ "@types/passport": "*"
+ }
+ },
+ "node_modules/@types/passport-strategy": {
+ "version": "0.2.38",
+ "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz",
+ "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/passport": "*"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/swagger-ui-express": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
+ "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "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/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/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/base64url": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
+ "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/better-result": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.0.tgz",
+ "integrity": "sha512-NHwGDGVbRlWDOce3CwcfGIrcNR9zY37ut3SVwQVfv57DZdVhxjhA4mfaHN1n8QwWnRAR4iErpW1X/eaiaUaFYg==",
+ "devOptional": 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": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "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-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/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/c12": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
+ "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^5.0.0",
+ "confbox": "^0.2.4",
+ "defu": "^6.1.6",
+ "dotenv": "^17.3.1",
+ "exsolve": "^1.0.8",
+ "giget": "^3.2.0",
+ "jiti": "^2.6.1",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^2.1.0",
+ "pkg-types": "^2.3.0",
+ "rc9": "^3.0.1"
+ },
+ "peerDependencies": {
+ "magicast": "*"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": 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": {
+ "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/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "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/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==",
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/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/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
+ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/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": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+ "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/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
+ "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/effect": {
+ "version": "3.20.0",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz",
+ "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "license": "MIT"
+ },
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
+ "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "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/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": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "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": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/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": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "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/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/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==",
+ "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-port-please": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
+ "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "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-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/giget": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
+ "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "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/glob/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/glob/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/grammex": {
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
+ "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/graphmatch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
+ "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.9",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
+ "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "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.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.15",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz",
+ "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/http-status-codes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "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-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",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
+ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "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": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "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/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/mariadb": {
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz",
+ "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==",
+ "license": "LGPL-2.1-or-later",
+ "dependencies": {
+ "@types/geojson": "^7946.0.16",
+ "@types/node": "^24.0.13",
+ "denque": "^2.1.0",
+ "iconv-lite": "^0.6.3",
+ "lru-cache": "^10.4.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/mariadb/node_modules/@types/node": {
+ "version": "24.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
+ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/mariadb/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/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": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-anything": {
+ "version": "5.1.7",
+ "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz",
+ "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/morgan": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
+ "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
+ "license": "MIT",
+ "dependencies": {
+ "basic-auth": "~2.0.1",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/morgan/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/morgan/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/morgan/node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/mysql2": {
+ "version": "3.15.3",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
+ "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.0",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "license": "MIT"
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "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/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/nodemon/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/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/oauth": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz",
+ "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==",
+ "license": "MIT"
+ },
+ "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/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "license": "MIT",
+ "engines": {
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
+ "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/passport": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
+ "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "passport-strategy": "1.x.x",
+ "pause": "0.0.1",
+ "utils-merge": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jaredhanson"
+ }
+ },
+ "node_modules/passport-google-oauth20": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz",
+ "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "passport-oauth2": "1.x.x"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/passport-jwt": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz",
+ "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "jsonwebtoken": "^9.0.0",
+ "passport-strategy": "^1.0.0"
+ }
+ },
+ "node_modules/passport-oauth2": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
+ "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==",
+ "license": "MIT",
+ "dependencies": {
+ "base64url": "3.x.x",
+ "oauth": "0.10.x",
+ "passport-strategy": "1.x.x",
+ "uid2": "0.0.x",
+ "utils-merge": "1.x.x"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jaredhanson"
+ }
+ },
+ "node_modules/passport-strategy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+ "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
+ "engines": {
+ "node": ">= 0.4.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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/pause": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
+ },
+ "node_modules/perfect-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
+ "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.4",
+ "exsolve": "^1.0.8",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/postgres": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
+ "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
+ "devOptional": true,
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/porsager"
+ }
+ },
+ "node_modules/prisma": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz",
+ "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "7.8.0",
+ "@prisma/dev": "0.24.3",
+ "@prisma/engines": "7.8.0",
+ "@prisma/studio-core": "0.27.3",
+ "mysql2": "3.15.3",
+ "postgres": "3.4.7"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "better-sqlite3": ">=9.0.0",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "better-sqlite3": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "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/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "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": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/raw-body/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/rc9": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
+ "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.6",
+ "destr": "^2.0.5"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.5"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/reflect-metadata": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
+ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/remeda": {
+ "version": "2.33.4",
+ "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
+ "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/remeda"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "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/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/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
+ "devOptional": true
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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==",
+ "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==",
+ "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",
+ "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.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "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/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "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/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/swagger-ui-dist": {
+ "version": "5.32.6",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz",
+ "integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@scarf/scarf": "=1.4.0"
+ }
+ },
+ "node_modules/swagger-ui-express": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
+ "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
+ "license": "MIT",
+ "dependencies": {
+ "swagger-ui-dist": ">=5.0.0"
+ },
+ "engines": {
+ "node": ">= v0.10.32"
+ },
+ "peerDependencies": {
+ "express": ">=4.0.0 || >=5.0.0-beta"
+ }
+ },
+ "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/ts-deepmerge": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz",
+ "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14.13.1"
+ }
+ },
+ "node_modules/tsoa": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/tsoa/-/tsoa-6.6.0.tgz",
+ "integrity": "sha512-7FudRojmbEpbSQ3t1pyG5EjV3scF7/X75giQt1q+tnuGjjJppB8BOEmIdCK/G8S5Dqnmpwz5Q3vxluKozpIW9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@tsoa/cli": "^6.6.0",
+ "@tsoa/runtime": "^6.6.0"
+ },
+ "bin": {
+ "tsoa": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "yarn": ">=1.9.4"
+ }
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/uid2": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
+ "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==",
+ "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/undici-types": {
+ "version": "7.19.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
+ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "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/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/valibot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.15.35",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
+ "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
+ "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/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "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/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "license": "ISC"
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "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==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/zeptomatch": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
+ "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "grammex": "^3.1.11",
+ "graphmatch": "^1.1.0"
+ }
+ }
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/package.json" "b/\353\217\204\354\226\217/week9/package.json"
new file mode 100644
index 0000000..1d1fb29
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/package.json"
@@ -0,0 +1,41 @@
+{
+ "scripts": {
+ "build": "tsoa spec-and-routes && tsc",
+ "start": "tsoa spec-and-routes && tsx src/index.ts",
+ "dev": "tsoa spec-and-routes && nodemon --ext ts,prisma --ignore src/generated --exec \"npx prisma generate --config prismaConfig.ts && tsx src/index.ts\""
+ },
+ "dependencies": {
+ "@prisma/adapter-mariadb": "^7.8.0",
+ "@prisma/client": "^7.8.0",
+ "bcryptjs": "^3.0.3",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "http-status-codes": "^2.3.0",
+ "jsonwebtoken": "^9.0.3",
+ "morgan": "^1.10.1",
+ "passport": "^0.7.0",
+ "passport-google-oauth20": "^2.0.0",
+ "passport-jwt": "^4.0.1",
+ "swagger-ui-express": "^5.0.1",
+ "tsoa": "^6.6.0"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.10",
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.6",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/morgan": "^1.9.10",
+ "@types/node": "^25.6.0",
+ "@types/passport": "^1.0.17",
+ "@types/passport-google-oauth20": "^2.0.17",
+ "@types/passport-jwt": "^4.0.1",
+ "@types/swagger-ui-express": "^4.1.8",
+ "nodemon": "^3.1.14",
+ "prisma": "^7.8.0",
+ "tsx": "^4.21.0",
+ "typescript": "^6.0.3"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/prisma/migrations/20260428071132_init_database/migration.sql" "b/\353\217\204\354\226\217/week9/prisma/migrations/20260428071132_init_database/migration.sql"
new file mode 100644
index 0000000..31b12a1
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/prisma/migrations/20260428071132_init_database/migration.sql"
@@ -0,0 +1,39 @@
+-- CreateTable
+CREATE TABLE `user` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `email` VARCHAR(255) NOT NULL,
+ `name` VARCHAR(100) NOT NULL,
+ `gender` VARCHAR(15) NOT NULL,
+ `birth` DATE NOT NULL,
+ `address` VARCHAR(255) NOT NULL,
+ `detail_address` VARCHAR(255) NULL,
+ `phone_number` VARCHAR(15) NOT NULL,
+
+ UNIQUE INDEX `email`(`email`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `food_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_favor_category` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `user_id` INTEGER NOT NULL,
+ `food_category_id` INTEGER NOT NULL,
+
+ INDEX `f_category_id`(`food_category_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_favor_category` ADD CONSTRAINT `user_favor_category_food_category_id_fkey` FOREIGN KEY (`food_category_id`) REFERENCES `food_category`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week9/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql" "b/\353\217\204\354\226\217/week9/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
new file mode 100644
index 0000000..99445d4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/prisma/migrations/20260428071239_add_store_and_review_tables/migration.sql"
@@ -0,0 +1,25 @@
+-- CreateTable
+CREATE TABLE `store` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(100) NOT NULL,
+
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `user_store_review` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `user_id` INTEGER NOT NULL,
+ `content` TEXT NOT NULL,
+
+ INDEX `store_id`(`store_id`),
+ INDEX `user_id`(`user_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `user_store_review` ADD CONSTRAINT `user_store_review_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week9/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql" "b/\353\217\204\354\226\217/week9/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
new file mode 100644
index 0000000..a6dd8b0
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/prisma/migrations/20260428072732_npx_prisma_migrate_dev/migration.sql"
@@ -0,0 +1,33 @@
+-- CreateTable
+CREATE TABLE `mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `store_id` INTEGER NOT NULL,
+ `title` VARCHAR(200) NOT NULL,
+ `reward` INTEGER NOT NULL,
+ `spec` TEXT NULL,
+ `dead_line` DATETIME(3) NULL,
+
+ INDEX `store_id`(`store_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `member_mission` (
+ `id` INTEGER NOT NULL AUTO_INCREMENT,
+ `member_id` INTEGER NOT NULL,
+ `mission_id` INTEGER NOT NULL,
+ `status` VARCHAR(15) NOT NULL,
+
+ INDEX `member_id`(`member_id`),
+ INDEX `mission_id`(`mission_id`),
+ PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- AddForeignKey
+ALTER TABLE `mission` ADD CONSTRAINT `mission_store_id_fkey` FOREIGN KEY (`store_id`) REFERENCES `store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_member_id_fkey` FOREIGN KEY (`member_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `member_mission` ADD CONSTRAINT `member_mission_mission_id_fkey` FOREIGN KEY (`mission_id`) REFERENCES `mission`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git "a/\353\217\204\354\226\217/week9/prisma/migrations/20260525000000_add_auth_fields/migration.sql" "b/\353\217\204\354\226\217/week9/prisma/migrations/20260525000000_add_auth_fields/migration.sql"
new file mode 100644
index 0000000..66aed4b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/prisma/migrations/20260525000000_add_auth_fields/migration.sql"
@@ -0,0 +1,8 @@
+-- AlterTable: User ํ
์ด๋ธ์ ์ธ์ฆ ๊ด๋ จ ํ๋ ์ถ๊ฐ ๋ฐ ์ ํ ํ๋ optional ์ฒ๋ฆฌ
+ALTER TABLE `user`
+ ADD COLUMN `nickname` VARCHAR(50) NULL AFTER `name`,
+ ADD COLUMN `password` VARCHAR(255) NULL AFTER `nickname`,
+ MODIFY COLUMN `gender` VARCHAR(15) NULL,
+ MODIFY COLUMN `birth` DATE NULL,
+ MODIFY COLUMN `address` VARCHAR(255) NULL,
+ MODIFY COLUMN `phone_number` VARCHAR(15) NULL;
diff --git "a/\353\217\204\354\226\217/week9/prisma/migrations/20260527001000_member_mission_status_enum/migration.sql" "b/\353\217\204\354\226\217/week9/prisma/migrations/20260527001000_member_mission_status_enum/migration.sql"
new file mode 100644
index 0000000..a68a3d8
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/prisma/migrations/20260527001000_member_mission_status_enum/migration.sql"
@@ -0,0 +1,10 @@
+UPDATE `member_mission`
+SET `status` = 'CHALLENGING'
+WHERE `status` IN ('์งํ์ค', 'CHALLENGING');
+
+UPDATE `member_mission`
+SET `status` = 'COMPLETE'
+WHERE `status` IN ('์๋ฃ', 'COMPLETE');
+
+ALTER TABLE `member_mission`
+ MODIFY COLUMN `status` ENUM('CHALLENGING', 'COMPLETE') NOT NULL DEFAULT 'CHALLENGING';
diff --git "a/\353\217\204\354\226\217/week9/prisma/migrations/migration_lock.toml" "b/\353\217\204\354\226\217/week9/prisma/migrations/migration_lock.toml"
new file mode 100644
index 0000000..592fc0b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/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 (e.g., Git)
+provider = "mysql"
diff --git "a/\353\217\204\354\226\217/week9/prisma/schema.prisma" "b/\353\217\204\354\226\217/week9/prisma/schema.prisma"
new file mode 100644
index 0000000..82f453a
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/prisma/schema.prisma"
@@ -0,0 +1,106 @@
+generator client {
+ provider = "prisma-client"
+ output = "../src/generated/prisma"
+}
+
+datasource db {
+ provider = "mysql"
+}
+
+enum MissionStatus {
+ CHALLENGING
+ COMPLETE
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique(map: "email") @db.VarChar(255)
+ name String @db.VarChar(100)
+ nickname String? @db.VarChar(50)
+ password String? @db.VarChar(255)
+ gender String? @db.VarChar(15)
+ birth DateTime? @db.Date
+ address String? @db.VarChar(255)
+ detailAddress String? @map("detail_address") @db.VarChar(255)
+ phoneNumber String? @map("phone_number") @db.VarChar(15)
+
+ userFavorCategories UserFavorCategory[]
+ reviews UserStoreReview[]
+ memberMissions MemberMission[]
+
+ @@map("user")
+}
+
+model FoodCategory {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ userFavorCategories UserFavorCategory[]
+
+ @@map("food_category")
+}
+
+model UserFavorCategory {
+ id Int @id @default(autoincrement())
+ userId Int @map("user_id")
+ foodCategoryId Int @map("food_category_id")
+ user User @relation(fields: [userId], references: [id])
+ foodCategory FoodCategory @relation(fields: [foodCategoryId], references: [id])
+
+ @@index([foodCategoryId], map: "f_category_id")
+ @@index([userId], map: "user_id")
+ @@map("user_favor_category")
+}
+
+model Store {
+ id Int @id @default(autoincrement())
+ name String @db.VarChar(100)
+
+ reviews UserStoreReview[]
+ missions Mission[]
+
+ @@map("store")
+}
+
+model UserStoreReview {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ userId Int @map("user_id")
+ content String @db.Text
+
+ store Store @relation(fields: [storeId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+
+ @@index([storeId], map: "store_id")
+ @@index([userId], map: "user_id")
+ @@map("user_store_review")
+}
+
+model Mission {
+ id Int @id @default(autoincrement())
+ storeId Int @map("store_id")
+ title String @db.VarChar(200)
+ reward Int
+ spec String? @db.Text
+ deadLine DateTime? @map("dead_line")
+
+ store Store @relation(fields: [storeId], references: [id])
+ memberMissions MemberMission[]
+
+ @@index([storeId], map: "store_id")
+ @@map("mission")
+}
+
+model MemberMission {
+ id Int @id @default(autoincrement())
+ memberId Int @map("member_id")
+ missionId Int @map("mission_id")
+ status MissionStatus @default(CHALLENGING)
+
+ user User @relation(fields: [memberId], references: [id])
+ mission Mission @relation(fields: [missionId], references: [id])
+
+ @@index([memberId], map: "member_id")
+ @@index([missionId], map: "mission_id")
+ @@map("member_mission")
+}
diff --git "a/\353\217\204\354\226\217/week9/prismaConfig.ts" "b/\353\217\204\354\226\217/week9/prismaConfig.ts"
new file mode 100644
index 0000000..5170cc4
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/prismaConfig.ts"
@@ -0,0 +1,12 @@
+///
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+const { DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME } = process.env;
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ datasource: {
+ url: `mysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT ?? 3306}/${DB_NAME}`,
+ },
+});
diff --git "a/\353\217\204\354\226\217/week9/reset_db.sql" "b/\353\217\204\354\226\217/week9/reset_db.sql"
new file mode 100644
index 0000000..454f20b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/reset_db.sql"
@@ -0,0 +1,215 @@
+-- ============================================================
+-- DB ์ด๊ธฐํ ๋ฐ ์ฌ์์ฑ ์คํฌ๋ฆฝํธ
+-- ============================================================
+
+CREATE DATABASE IF NOT EXISTS umc_mission DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE umc_mission;
+
+-- FK ์ฒดํฌ ๋นํ์ฑํ ํ ์ ์ฒด DROP
+SET FOREIGN_KEY_CHECKS = 0;
+
+DROP TABLE IF EXISTS review_image;
+DROP TABLE IF EXISTS review;
+DROP TABLE IF EXISTS member_mission;
+DROP TABLE IF EXISTS mission;
+DROP TABLE IF EXISTS store_hours;
+DROP TABLE IF EXISTS store_image;
+DROP TABLE IF EXISTS store;
+DROP TABLE IF EXISTS member_prefer;
+DROP TABLE IF EXISTS member_agree;
+DROP TABLE IF EXISTS member;
+DROP TABLE IF EXISTS terms;
+DROP TABLE IF EXISTS food_category;
+DROP TABLE IF EXISTS region;
+
+SET FOREIGN_KEY_CHECKS = 1;
+
+-- ============================================================
+-- ํ
์ด๋ธ ์ฌ์์ฑ
+-- ============================================================
+
+CREATE TABLE region (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์ง์ญ๋ช
',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE food_category (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(50) NOT NULL COMMENT '์นดํ
๊ณ ๋ฆฌ๋ช
(ํ์, ์ค์, ์ผ์ ๋ฑ)',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE terms (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ title VARCHAR(100) NOT NULL COMMENT '์ฝ๊ด ์ ๋ชฉ',
+ content TEXT NOT NULL COMMENT '์ฝ๊ด ๋ด์ฉ',
+ optional BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'TRUE: ์ ํ ๋์, FALSE: ํ์ ๋์',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE member (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ social_type VARCHAR(20) NULL,
+ social_id VARCHAR(100) NULL,
+ email VARCHAR(100) NULL,
+ password VARCHAR(255) NULL,
+ name VARCHAR(50) NOT NULL,
+ nickname VARCHAR(50) NOT NULL,
+ profile_image_url VARCHAR(500) NULL,
+ phone_num VARCHAR(20) NULL,
+ phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
+ birth DATE NULL,
+ gender ENUM('MALE', 'FEMALE', 'OTHER') NULL,
+ address VARCHAR(200) NULL,
+ spec_address VARCHAR(200) NULL,
+ point INT NOT NULL DEFAULT 0,
+ status ENUM('ACTIVE', 'INACTIVE', 'BANNED') NOT NULL DEFAULT 'ACTIVE',
+ inactive_date DATETIME NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_email (email),
+ UNIQUE KEY uq_member_social (social_type, social_id)
+);
+
+CREATE TABLE member_agree (
+ member_id BIGINT NOT NULL,
+ terms_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, terms_id),
+ CONSTRAINT fk_member_agree_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_agree_terms FOREIGN KEY (terms_id) REFERENCES terms (id)
+);
+
+CREATE TABLE member_prefer (
+ member_id BIGINT NOT NULL,
+ food_id BIGINT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (member_id, food_id),
+ CONSTRAINT fk_member_prefer_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_prefer_food FOREIGN KEY (food_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ region_id BIGINT NOT NULL,
+ food_category_id BIGINT NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ description TEXT NULL,
+ lat DECIMAL(10,7) NULL,
+ lng DECIMAL(10,7) NULL,
+ address VARCHAR(200) NOT NULL,
+ status ENUM('OPEN', 'CLOSED', 'PENDING') NOT NULL DEFAULT 'OPEN',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_region FOREIGN KEY (region_id) REFERENCES region (id),
+ CONSTRAINT fk_store_category FOREIGN KEY (food_category_id) REFERENCES food_category (id)
+);
+
+CREATE TABLE store_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_store_image_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE store_hours (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ day_of_week VARCHAR(3) NOT NULL,
+ open_time TIME NOT NULL,
+ close_time TIME NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_store_hours_day (store_id, day_of_week),
+ CONSTRAINT fk_store_hours_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ store_id BIGINT NOT NULL,
+ title VARCHAR(200) NOT NULL,
+ reward INT NOT NULL DEFAULT 0,
+ spec VARCHAR(500) NULL,
+ dead_line DATE NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_mission_store FOREIGN KEY (store_id) REFERENCES store (id)
+);
+
+CREATE TABLE member_mission (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ mission_id BIGINT NOT NULL,
+ status ENUM('CHALLENGING', 'COMPLETE') NOT NULL DEFAULT 'CHALLENGING',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ UNIQUE KEY uq_member_mission (member_id, mission_id),
+ CONSTRAINT fk_member_mission_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_member_mission_mission FOREIGN KEY (mission_id) REFERENCES mission (id)
+);
+
+CREATE TABLE review (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ member_id BIGINT NOT NULL,
+ store_id BIGINT NOT NULL,
+ member_mission_id BIGINT NULL,
+ content TEXT NOT NULL,
+ score DECIMAL(2,1) NOT NULL,
+ owner_reply VARCHAR(500) NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_member FOREIGN KEY (member_id) REFERENCES member (id),
+ CONSTRAINT fk_review_store FOREIGN KEY (store_id) REFERENCES store (id),
+ CONSTRAINT fk_review_member_mission FOREIGN KEY (member_mission_id) REFERENCES member_mission (id),
+ CONSTRAINT chk_review_score CHECK (score BETWEEN 1.0 AND 5.0)
+);
+
+CREATE TABLE review_image (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ review_id BIGINT NOT NULL,
+ image_url VARCHAR(500) NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id),
+ CONSTRAINT fk_review_image_review FOREIGN KEY (review_id) REFERENCES review (id)
+);
+
+-- ============================================================
+-- ์๋ ๋ฐ์ดํฐ (API ํ
์คํธ์ฉ ์ด๊ธฐ ๋ฐ์ดํฐ)
+-- ============================================================
+
+INSERT INTO region (name) VALUES
+ ('์์ธ'),
+ ('๊ฒฝ๊ธฐ'),
+ ('์ธ์ฒ'),
+ ('๋ถ์ฐ'),
+ ('๋๊ตฌ');
+
+INSERT INTO food_category (name) VALUES
+ ('ํ์'),
+ ('์ค์'),
+ ('์ผ์'),
+ ('์์'),
+ ('๋ถ์'),
+ ('์นดํ/๋์ ํธ'),
+ ('์นํจ'),
+ ('ํผ์'),
+ ('ํจ์คํธํธ๋');
diff --git "a/\353\217\204\354\226\217/week9/src/authConfig.ts" "b/\353\217\204\354\226\217/week9/src/authConfig.ts"
new file mode 100644
index 0000000..73ab2bc
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/authConfig.ts"
@@ -0,0 +1,86 @@
+import dotenv from 'dotenv'
+import { Strategy as GoogleStrategy, Profile } from 'passport-google-oauth20'
+import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
+import jwt from 'jsonwebtoken'
+import { prisma } from './dbConfig.js'
+
+dotenv.config()
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// JWT ํ ํฐ ์์ฑ ํฌํผ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+/** Access Token ๋ฐ๊ธ (์๋ช
1์๊ฐ) */
+export const generateAccessToken = (user: { id: number; email: string }) =>
+ jwt.sign({ id: user.id, email: user.email }, process.env.JWT_SECRET!, { expiresIn: '1h' })
+
+/** Refresh Token ๋ฐ๊ธ (์๋ช
14์ผ, ์ต์ ์ ๋ณด๋ง ๋ด์) */
+export const generateRefreshToken = (user: { id: number }) =>
+ jwt.sign({ id: user.id }, process.env.JWT_SECRET!, { expiresIn: '14d' })
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// Google ๋ก๊ทธ์ธ ์ ๋ต
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+/**
+ * Google ํ๋กํ๋ก ์ฌ์ฉ์๋ฅผ ์กฐํํ๊ฑฐ๋ ์ ๊ท ์์ฑํ๋ค.
+ * Google ๋ก๊ทธ์ธ ์์๋ email, name๋ง ํ์์ด๋ฉฐ
+ * ๋๋จธ์ง ํ๋๋ ๋์ค์ PATCH /members/me ๋ก ์ฑ์ธ ์ ์๋ค.
+ */
+const googleVerify = async (profile: Profile) => {
+ const email = profile.emails?.[0]?.value
+ if (!email) throw new Error('Google ํ๋กํ์ ์ด๋ฉ์ผ์ด ์์ต๋๋ค.')
+
+ let user = await prisma.user.findFirst({ where: { email } })
+
+ if (!user) {
+ user = await prisma.user.create({
+ data: {
+ email,
+ name: profile.displayName ?? '์ด๋ฆ ๋ฏธ์ค์ ',
+ },
+ })
+ }
+
+ return { id: user.id, email: user.email, name: user.name }
+}
+
+export const googleStrategy = new GoogleStrategy(
+ {
+ clientID: process.env.PASSPORT_GOOGLE_CLIENT_ID!,
+ clientSecret: process.env.PASSPORT_GOOGLE_CLIENT_SECRET!,
+ callbackURL: '/oauth2/callback/google',
+ scope: ['email', 'profile'],
+ },
+ async (_accessToken, _refreshToken, profile, cb) => {
+ try {
+ const user = await googleVerify(profile)
+ const tokens = {
+ accessToken: generateAccessToken(user),
+ refreshToken: generateRefreshToken(user),
+ }
+ return cb(null, tokens)
+ } catch (err) {
+ return cb(err as Error)
+ }
+ },
+)
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// JWT ๊ฒ์ฆ ์ ๋ต (๋ณดํธ๋ ๋ผ์ฐํธ์์ Bearer ํ ํฐ ๊ฒ์ฆ)
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+export const jwtStrategy = new JwtStrategy(
+ {
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+ secretOrKey: process.env.JWT_SECRET!,
+ },
+ async (payload: { id: number }, done) => {
+ try {
+ const user = await prisma.user.findFirst({ where: { id: payload.id } })
+ return user ? done(null, user) : done(null, false)
+ } catch (err) {
+ return done(err, false)
+ }
+ },
+)
diff --git "a/\353\217\204\354\226\217/week9/src/authentication.ts" "b/\353\217\204\354\226\217/week9/src/authentication.ts"
new file mode 100644
index 0000000..060f767
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/authentication.ts"
@@ -0,0 +1,45 @@
+import { Request } from 'express'
+import jwt from 'jsonwebtoken'
+import { prisma } from './dbConfig.js'
+
+/**
+ * TSOA ๋ณด์ ๋ฏธ๋ค์จ์ด - @Security("bearerAuth") ๋ฐ์ฝ๋ ์ดํฐ์ ์ฐ๋
+ * ์ด ํจ์์ ๋ฐํ๊ฐ์ด req.user ์ ์ฃผ์
๋๋ค.
+ */
+export async function expressAuthentication(
+ request: Request,
+ securityName: string,
+ _scopes?: string[],
+): Promise {
+ if (securityName !== 'bearerAuth') {
+ throw Object.assign(new Error('์ง์ํ์ง ์๋ ์ธ์ฆ ๋ฐฉ์์
๋๋ค.'), { status: 401 })
+ }
+
+ const authHeader = request.headers.authorization
+ if (!authHeader?.startsWith('Bearer ')) {
+ throw Object.assign(new Error('์ธ์ฆ ํ ํฐ์ด ํ์ํฉ๋๋ค.'), { status: 401 })
+ }
+
+ const token = authHeader.split(' ')[1]
+ const jwtSecret = process.env.JWT_SECRET
+
+ if (!token) {
+ throw Object.assign(new Error('์ธ์ฆ ํ ํฐ์ด ํ์ํฉ๋๋ค.'), { status: 401 })
+ }
+
+ if (!jwtSecret) {
+ throw Object.assign(new Error('JWT_SECRET์ด ์ค์ ๋์ง ์์์ต๋๋ค.'), { status: 500 })
+ }
+
+ try {
+ const decoded = jwt.verify(token, jwtSecret) as unknown as { id: number }
+ const user = await prisma.user.findFirst({ where: { id: decoded.id } })
+ if (!user) {
+ throw Object.assign(new Error('์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.'), { status: 401 })
+ }
+ return user
+ } catch (err: any) {
+ if (err.status) throw err
+ throw Object.assign(new Error('์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค.'), { status: 401 })
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/dbConfig.ts" "b/\353\217\204\354\226\217/week9/src/dbConfig.ts"
new file mode 100644
index 0000000..7b88907
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/dbConfig.ts"
@@ -0,0 +1,17 @@
+import "dotenv/config";
+import { PrismaClient } from "./generated/prisma/client.js";
+import { PrismaMariaDb } from "@prisma/adapter-mariadb";
+
+const adapter = new PrismaMariaDb({
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASSWORD,
+ database: process.env.DB_NAME,
+ port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 3306,
+ connectionLimit: 10,
+});
+
+export const prisma = new PrismaClient({
+ adapter,
+ log: ["query", "info", "error", "warn"],
+});
diff --git "a/\353\217\204\354\226\217/week9/src/generated/routes.ts" "b/\353\217\204\354\226\217/week9/src/generated/routes.ts"
new file mode 100644
index 0000000..b5c3a34
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/generated/routes.ts"
@@ -0,0 +1,766 @@
+/* tslint:disable */
+/* eslint-disable */
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import type { TsoaRoute } from '@tsoa/runtime';
+import { fetchMiddlewares, ExpressTemplateService } from '@tsoa/runtime';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { StoreController } from './../modules/stores/controllers/storeController';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { MissionController } from './../modules/missions/controllers/missionController';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { UserController } from './../modules/members/controllers/userController';
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+import { MemberController } from './../modules/members/controllers/memberController';
+import { expressAuthentication } from './../authentication';
+// @ts-ignore - no great way to install types from subpackage
+import type { Request as ExRequest, Response as ExResponse, RequestHandler, Router } from 'express';
+
+const expressAuthenticationRecasted = expressAuthentication as (req: ExRequest, securityName: string, scopes?: string[], res?: ExResponse) => Promise;
+
+
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+const models: TsoaRoute.Models = {
+ "StoreCreateResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "storeId": {"dataType":"double","required":true},
+ "name": {"dataType":"string","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_StoreCreateResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"StoreCreateResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "StoreCreateRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "name": {"dataType":"string","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewItem": {
+ "dataType": "refObject",
+ "properties": {
+ "reviewId": {"dataType":"double","required":true},
+ "memberId": {"dataType":"double","required":true},
+ "storeId": {"dataType":"double","required":true},
+ "content": {"dataType":"string","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"ReviewItem"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_ReviewListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"ReviewListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewCreateResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "reviewId": {"dataType":"double","required":true},
+ "memberId": {"dataType":"double","required":true},
+ "storeId": {"dataType":"double","required":true},
+ "content": {"dataType":"string","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_ReviewCreateResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"ReviewCreateResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ReviewCreateRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "content": {"dataType":"string","required":true},
+ "score": {"dataType":"double"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionCreateResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "missionId": {"dataType":"double","required":true},
+ "storeId": {"dataType":"double","required":true},
+ "title": {"dataType":"string","required":true},
+ "reward": {"dataType":"double","required":true},
+ "spec": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "deadLine": {"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "StoreMissionListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"MissionCreateResponse"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_StoreMissionListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"StoreMissionListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_MissionCreateResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"MissionCreateResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionCreateRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "title": {"dataType":"string","required":true},
+ "reward": {"dataType":"double","required":true},
+ "spec": {"dataType":"string"},
+ "deadLine": {"dataType":"string"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionStatus": {
+ "dataType": "refEnum",
+ "enums": ["CHALLENGING","COMPLETE"],
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionChallengeResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "memberMissionId": {"dataType":"double","required":true},
+ "memberId": {"dataType":"double","required":true},
+ "missionId": {"dataType":"double","required":true},
+ "status": {"ref":"MissionStatus","required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_MissionChallengeResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"MissionChallengeResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MissionChallengeRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "status": {"ref":"MissionStatus"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "UserReviewListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"ReviewCreateResponse"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_UserReviewListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"UserReviewListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "OngoingMissionListResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "data": {"dataType":"array","array":{"dataType":"refObject","ref":"MissionChallengeResponse"},"required":true},
+ "pagination": {"dataType":"nestedObjectLiteral","nestedProperties":{"cursor":{"dataType":"union","subSchemas":[{"dataType":"double"},{"dataType":"enum","enums":[null]}],"required":true}},"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_OngoingMissionListResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"OngoingMissionListResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MemberSignUpResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "memberId": {"dataType":"double","required":true},
+ "name": {"dataType":"string","required":true},
+ "nickname": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "email": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "phoneNumber": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_MemberSignUpResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"MemberSignUpResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "MemberSignUpRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "name": {"dataType":"string","required":true},
+ "nickname": {"dataType":"string"},
+ "email": {"dataType":"string"},
+ "password": {"dataType":"string"},
+ "phoneNumber": {"dataType":"string"},
+ "birth": {"dataType":"string"},
+ "gender": {"dataType":"string"},
+ "address": {"dataType":"string"},
+ "detailAddress": {"dataType":"string"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "UserUpdateResponse": {
+ "dataType": "refObject",
+ "properties": {
+ "memberId": {"dataType":"double","required":true},
+ "name": {"dataType":"string","required":true},
+ "nickname": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "email": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "phoneNumber": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "gender": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "birth": {"dataType":"union","subSchemas":[{"dataType":"datetime"},{"dataType":"enum","enums":[null]}],"required":true},
+ "address": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ "detailAddress": {"dataType":"union","subSchemas":[{"dataType":"string"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "ApiResponse_UserUpdateResponse_": {
+ "dataType": "refObject",
+ "properties": {
+ "isSuccess": {"dataType":"boolean","required":true},
+ "code": {"dataType":"string","required":true},
+ "message": {"dataType":"string","required":true},
+ "result": {"dataType":"union","subSchemas":[{"ref":"UserUpdateResponse"},{"dataType":"enum","enums":[null]}],"required":true},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ "UserUpdateRequest": {
+ "dataType": "refObject",
+ "properties": {
+ "nickname": {"dataType":"string"},
+ "phoneNumber": {"dataType":"string"},
+ "birth": {"dataType":"string"},
+ "gender": {"dataType":"string"},
+ "address": {"dataType":"string"},
+ "detailAddress": {"dataType":"string"},
+ },
+ "additionalProperties": false,
+ },
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+};
+const templateService = new ExpressTemplateService(models, {"noImplicitAdditionalProperties":"throw-on-extras","bodyCoercion":true});
+
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+
+
+
+export function RegisterRoutes(app: Router) {
+
+ // ###########################################################################################################
+ // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look
+ // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa
+ // ###########################################################################################################
+
+
+
+ const argsStoreController_handleCreateStore: Record = {
+ body: {"in":"body","name":"body","required":true,"ref":"StoreCreateRequest"},
+ };
+ app.post('/stores',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleCreateStore)),
+
+ async function StoreController_handleCreateStore(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleCreateStore, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCreateStore',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleListStoreReviews: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/stores/:storeId/reviews',
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleListStoreReviews)),
+
+ async function StoreController_handleListStoreReviews(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleListStoreReviews, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListStoreReviews',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleCreateReview: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ req: {"in":"request","name":"req","required":true,"dataType":"object"},
+ body: {"in":"body","name":"body","required":true,"ref":"ReviewCreateRequest"},
+ };
+ app.post('/stores/:storeId/reviews',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleCreateReview)),
+
+ async function StoreController_handleCreateReview(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleCreateReview, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCreateReview',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleListStoreMissions: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/stores/:storeId/missions',
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleListStoreMissions)),
+
+ async function StoreController_handleListStoreMissions(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleListStoreMissions, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListStoreMissions',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsStoreController_handleCreateMission: Record = {
+ storeId: {"in":"path","name":"storeId","required":true,"dataType":"double"},
+ body: {"in":"body","name":"body","required":true,"ref":"MissionCreateRequest"},
+ };
+ app.post('/stores/:storeId/missions',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(StoreController)),
+ ...(fetchMiddlewares(StoreController.prototype.handleCreateMission)),
+
+ async function StoreController_handleCreateMission(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsStoreController_handleCreateMission, request, response });
+
+ const controller = new StoreController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCreateMission',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsMissionController_handleChallengeMission: Record = {
+ missionId: {"in":"path","name":"missionId","required":true,"dataType":"double"},
+ req: {"in":"request","name":"req","required":true,"dataType":"object"},
+ body: {"in":"body","name":"body","required":true,"ref":"MissionChallengeRequest"},
+ };
+ app.post('/missions/:missionId/challenge',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(MissionController)),
+ ...(fetchMiddlewares(MissionController.prototype.handleChallengeMission)),
+
+ async function MissionController_handleChallengeMission(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsMissionController_handleChallengeMission, request, response });
+
+ const controller = new MissionController();
+
+ await templateService.apiHandler({
+ methodName: 'handleChallengeMission',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsUserController_handleListUserReviews: Record = {
+ req: {"in":"request","name":"req","required":true,"dataType":"object"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/users/me/reviews',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(UserController)),
+ ...(fetchMiddlewares(UserController.prototype.handleListUserReviews)),
+
+ async function UserController_handleListUserReviews(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleListUserReviews, request, response });
+
+ const controller = new UserController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListUserReviews',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsUserController_handleListOngoingMissions: Record = {
+ req: {"in":"request","name":"req","required":true,"dataType":"object"},
+ cursor: {"in":"query","name":"cursor","dataType":"double"},
+ };
+ app.get('/users/me/missions',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(UserController)),
+ ...(fetchMiddlewares(UserController.prototype.handleListOngoingMissions)),
+
+ async function UserController_handleListOngoingMissions(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleListOngoingMissions, request, response });
+
+ const controller = new UserController();
+
+ await templateService.apiHandler({
+ methodName: 'handleListOngoingMissions',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsUserController_handleCompleteMission: Record = {
+ req: {"in":"request","name":"req","required":true,"dataType":"object"},
+ missionId: {"in":"path","name":"missionId","required":true,"dataType":"double"},
+ };
+ app.patch('/users/me/missions/:missionId',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(UserController)),
+ ...(fetchMiddlewares(UserController.prototype.handleCompleteMission)),
+
+ async function UserController_handleCompleteMission(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsUserController_handleCompleteMission, request, response });
+
+ const controller = new UserController();
+
+ await templateService.apiHandler({
+ methodName: 'handleCompleteMission',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsMemberController_handleSignUp: Record = {
+ body: {"in":"body","name":"body","required":true,"ref":"MemberSignUpRequest"},
+ };
+ app.post('/members/signup',
+ ...(fetchMiddlewares(MemberController)),
+ ...(fetchMiddlewares(MemberController.prototype.handleSignUp)),
+
+ async function MemberController_handleSignUp(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsMemberController_handleSignUp, request, response });
+
+ const controller = new MemberController();
+
+ await templateService.apiHandler({
+ methodName: 'handleSignUp',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: 201,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ const argsMemberController_handleUpdateMe: Record = {
+ req: {"in":"request","name":"req","required":true,"dataType":"object"},
+ body: {"in":"body","name":"body","required":true,"ref":"UserUpdateRequest"},
+ };
+ app.patch('/members/me',
+ authenticateMiddleware([{"bearerAuth":[]}]),
+ ...(fetchMiddlewares(MemberController)),
+ ...(fetchMiddlewares(MemberController.prototype.handleUpdateMe)),
+
+ async function MemberController_handleUpdateMe(request: ExRequest, response: ExResponse, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ let validatedArgs: any[] = [];
+ try {
+ validatedArgs = templateService.getValidatedArgs({ args: argsMemberController_handleUpdateMe, request, response });
+
+ const controller = new MemberController();
+
+ await templateService.apiHandler({
+ methodName: 'handleUpdateMe',
+ controller,
+ response,
+ next,
+ validatedArgs,
+ successStatus: undefined,
+ });
+ } catch (err) {
+ return next(err);
+ }
+ });
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ function authenticateMiddleware(security: TsoaRoute.Security[] = []) {
+ return async function runAuthenticationMiddleware(request: any, response: any, next: any) {
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ // keep track of failed auth attempts so we can hand back the most
+ // recent one. This behavior was previously existing so preserving it
+ // here
+ const failedAttempts: any[] = [];
+ const pushAndRethrow = (error: any) => {
+ failedAttempts.push(error);
+ throw error;
+ };
+
+ const secMethodOrPromises: Promise[] = [];
+ for (const secMethod of security) {
+ if (Object.keys(secMethod).length > 1) {
+ const secMethodAndPromises: Promise[] = [];
+
+ for (const name in secMethod) {
+ secMethodAndPromises.push(
+ expressAuthenticationRecasted(request, name, secMethod[name], response)
+ .catch(pushAndRethrow)
+ );
+ }
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ secMethodOrPromises.push(Promise.all(secMethodAndPromises)
+ .then(users => { return users[0]; }));
+ } else {
+ for (const name in secMethod) {
+ secMethodOrPromises.push(
+ expressAuthenticationRecasted(request, name, secMethod[name], response)
+ .catch(pushAndRethrow)
+ );
+ }
+ }
+ }
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+
+ try {
+ request['user'] = await Promise.any(secMethodOrPromises);
+
+ // Response was sent in middleware, abort
+ if (response.writableEnded) {
+ return;
+ }
+
+ next();
+ }
+ catch(err) {
+ // Show most recent error as response
+ const error = failedAttempts.pop();
+ error.status = error.status || 401;
+
+ // Response was sent in middleware, abort
+ if (response.writableEnded) {
+ return;
+ }
+ next(error);
+ }
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+ }
+ }
+
+ // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
+}
+
+// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
diff --git "a/\353\217\204\354\226\217/week9/src/index.ts" "b/\353\217\204\354\226\217/week9/src/index.ts"
new file mode 100644
index 0000000..e77038f
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/index.ts"
@@ -0,0 +1,75 @@
+import dotenv from 'dotenv'
+import express, { Express } from 'express'
+import cors from 'cors'
+import morgan from 'morgan'
+import cookieParser from 'cookie-parser'
+import swaggerUi from 'swagger-ui-express'
+import passport from 'passport'
+import path from 'path'
+import fs from 'fs'
+import { RegisterRoutes } from './generated/routes.js'
+import { errorMiddleware } from './middleware/errorMiddleware.js'
+import { googleStrategy, jwtStrategy } from './authConfig.js'
+
+// 1. ํ๊ฒฝ ๋ณ์ ์ค์ (๊ฐ์ฅ ๋จผ์ ํธ์ถ)
+dotenv.config()
+
+const app: Express = express()
+const port = process.env.PORT ?? 3000
+
+// 2. Passport ์ ๋ต ๋ฑ๋ก
+passport.use(googleStrategy)
+passport.use(jwtStrategy)
+
+// 3. ๋ฏธ๋ค์จ์ด ์ค์
+app.use(morgan('dev'))
+app.use(cookieParser())
+app.use(cors())
+app.use(express.json())
+app.use(express.urlencoded({ extended: false }))
+app.use(passport.initialize())
+
+// 4. OAuth ๋ผ์ฐํธ (TSOA ์ปจํธ๋กค๋ฌ ๋ฐ์์ ์ง์ ๋ฑ๋ก)
+// Google ๋ก๊ทธ์ธ ์์ โ Google ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ
+app.get('/oauth2/login/google', passport.authenticate('google', { session: false }))
+
+// Google ๋ก๊ทธ์ธ ์ฝ๋ฐฑ โ Access/Refresh ํ ํฐ ๋ฐ๊ธ
+app.get(
+ '/oauth2/callback/google',
+ passport.authenticate('google', { session: false, failureRedirect: '/login-failed' }),
+ (req, res) => {
+ // googleStrategy์ cb(null, tokens) ๊ฒฐ๊ณผ๊ฐ req.user ์ ๋ด๊น
+ res.status(200).json({
+ isSuccess: true,
+ code: 'COMMON200',
+ message: 'Google ๋ก๊ทธ์ธ ์ฑ๊ณต',
+ result: req.user,
+ })
+ },
+)
+
+app.get('/login-failed', (_req, res) => {
+ res.status(401).json({ isSuccess: false, code: 'AUTH4001', message: 'Google ๋ก๊ทธ์ธ์ ์คํจํ์ต๋๋ค.' })
+})
+
+// 5. TSOA๊ฐ ์์ฑํ ๋ผ์ฐํธ ๋ฑ๋ก
+const router = express.Router()
+RegisterRoutes(router)
+app.use('/api/v1', router)
+
+// 6. Swagger UI ์ฐ๊ฒฐ (dist/swagger.json ์ด ์กด์ฌํ ๋๋ง)
+const swaggerPath = path.resolve('dist/swagger.json')
+if (fs.existsSync(swaggerPath)) {
+ const swaggerFile = JSON.parse(fs.readFileSync(swaggerPath, 'utf8'))
+ app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
+}
+
+// 7. ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ (๋ฐ๋์ ๋ผ์ฐํฐ ๋ฑ๋ก ์ดํ์ ์์น)
+app.use(errorMiddleware)
+
+// 8. ์๋ฒ ์์
+app.listen(port, () => {
+ console.log(`[server]: Server is running at http://localhost:${port}`)
+ console.log(`[docs]: Swagger UI at http://localhost:${port}/docs`)
+ console.log(`[oauth]: Google Login at http://localhost:${port}/oauth2/login/google`)
+})
diff --git "a/\353\217\204\354\226\217/week9/src/middleware/errorMiddleware.ts" "b/\353\217\204\354\226\217/week9/src/middleware/errorMiddleware.ts"
new file mode 100644
index 0000000..15a7a82
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/middleware/errorMiddleware.ts"
@@ -0,0 +1,47 @@
+import { Request, Response, NextFunction } from 'express'
+import { BaseError } from '../utils/errors.js'
+import { ErrorCode } from '../utils/errorCode.js'
+
+export const errorMiddleware = (
+ err: unknown,
+ _req: Request,
+ res: Response,
+ _next: NextFunction,
+): void => {
+ const sendErrorResponse = (error: (typeof ErrorCode)[keyof typeof ErrorCode]) => {
+ res.status(error.status).json({
+ isSuccess: false,
+ code: error.code,
+ message: error.message,
+ result: null,
+ })
+ }
+
+ if (err instanceof BaseError) {
+ res.status(err.status).json({
+ isSuccess: false,
+ code: err.code,
+ message: err.message,
+ result: null,
+ })
+ return
+ }
+
+ const prismaCode = typeof err === 'object' && err !== null ? (err as { code?: string }).code : undefined
+
+ if (prismaCode === 'P2002') {
+ const target = (err as { meta?: { target?: string[] | string } }).meta?.target
+ const fields = Array.isArray(target) ? target : [target]
+ const error = fields.includes('email') ? ErrorCode.DUPLICATE_EMAIL : ErrorCode.INVALID_INPUT
+ sendErrorResponse(error)
+ return
+ }
+
+ if (prismaCode === 'P2025') {
+ sendErrorResponse(ErrorCode.USER_NOT_FOUND)
+ return
+ }
+
+ console.error(err)
+ sendErrorResponse(ErrorCode.INTERNAL_ERROR)
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/members/controllers/memberController.ts" "b/\353\217\204\354\226\217/week9/src/modules/members/controllers/memberController.ts"
new file mode 100644
index 0000000..4c78425
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/members/controllers/memberController.ts"
@@ -0,0 +1,43 @@
+import { Body, Controller, Patch, Post, Request, Route, Security, Tags, Response as TsoaResponse, SuccessResponse } from 'tsoa'
+import { Request as ExRequest } from 'express'
+import { MemberSignUpRequest, MemberSignUpResponse, UserUpdateRequest, UserUpdateResponse } from '../dtos/memberDto.js'
+import { signUp, updateUserInfo } from '../services/memberService.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('members')
+@Tags('Member')
+export class MemberController extends Controller {
+ /**
+ * ํ์๊ฐ์
API
+ * @summary ์๋ก์ด ํ์์ ๋ฑ๋กํฉ๋๋ค.
+ */
+ @Post('signup')
+ @SuccessResponse(201, 'ํ์๊ฐ์
์ฑ๊ณต')
+ @TsoaResponse>(409, '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ (USER4002)')
+ @TsoaResponse>(400, 'name ๋๋ฝ (USER4003)')
+ public async handleSignUp(
+ @Body() body: MemberSignUpRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await signUp(body)
+ return successResponse(result)
+ }
+
+ /**
+ * ๋ด ์ ๋ณด ์์ API (๋ฏธ์
2)
+ * @summary JWT๋ก ์ธ์ฆ๋ ์ฌ์ฉ์ ๋ณธ์ธ์ ์ ๋ณด๋ฅผ ์์ ํฉ๋๋ค.
+ * Google ๋ก๊ทธ์ธ์ผ๋ก ๊ฐ์
ํ ์ ํ๋ฒํธยท์์ผ ๋ฑ ๋น ํ๋๋ฅผ ์ฑ์ธ ๋ ์ฌ์ฉํฉ๋๋ค.
+ */
+ @Patch('me')
+ @Security('bearerAuth')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(404, '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ (USER4001)')
+ public async handleUpdateMe(
+ @Request() req: ExRequest,
+ @Body() body: UserUpdateRequest,
+ ): Promise> {
+ const userId = (req.user as any).id
+ const result = await updateUserInfo(userId, body)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/members/controllers/userController.ts" "b/\353\217\204\354\226\217/week9/src/modules/members/controllers/userController.ts"
new file mode 100644
index 0000000..7f6025d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/members/controllers/userController.ts"
@@ -0,0 +1,72 @@
+import { Controller, Get, Patch, Path, Query, Request, Route, Security, Tags, Response as TsoaResponse } from 'tsoa'
+import { Request as ExRequest } from 'express'
+import { listUserReviews } from '../../reviews/services/reviewService.js'
+import { listOngoingMissions, finishMission } from '../../missions/services/missionService.js'
+import { UserReviewListResponse } from '../../reviews/dtos/reviewDto.js'
+import { OngoingMissionListResponse, MissionChallengeResponse } from '../../missions/dtos/missionDto.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('users')
+@Tags('User')
+export class UserController extends Controller {
+ /**
+ * ๋ด ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ API
+ * @summary ๋ก๊ทธ์ธํ ์ฌ์ฉ์ ๋ณธ์ธ์ด ์์ฑํ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+ */
+ @Get('me/reviews')
+ @Security('bearerAuth')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(404, '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ (USER4001)')
+ public async handleListUserReviews(
+ @Request() req: ExRequest,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const userId = (req.user as any).id
+ const result = await listUserReviews(userId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก ์กฐํ API
+ * @summary ๋ก๊ทธ์ธํ ์ฌ์ฉ์ ๋ณธ์ธ์ด ํ์ฌ ์งํ ์ค์ธ ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+ */
+ @Get('me/missions')
+ @Security('bearerAuth')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(404, '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ (USER4001)')
+ public async handleListOngoingMissions(
+ @Request() req: ExRequest,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const userId = (req.user as any).id
+ const result = await listOngoingMissions(userId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ๋ฏธ์
์๋ฃ ์ฒ๋ฆฌ API
+ * @summary ๋ก๊ทธ์ธํ ์ฌ์ฉ์ ๋ณธ์ธ์ ์งํ ์ค์ธ ๋ฏธ์
์ ์๋ฃ ์ํ๋ก ๋ณ๊ฒฝํฉ๋๋ค.
+ */
+ @Patch('me/missions/{missionId}')
+ @Security('bearerAuth')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(404, '์งํ ์ค์ธ ๋ฏธ์
์์ (MISSION4003)')
+ @TsoaResponse>(404, '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ (USER4001)')
+ public async handleCompleteMission(
+ @Request() req: ExRequest,
+ /**
+ * ์๋ฃํ ๋ฏธ์
ID
+ */
+ @Path() missionId: number,
+ ): Promise> {
+ const userId = (req.user as any).id
+ const result = await finishMission(userId, missionId)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/members/dtos/memberDto.ts" "b/\353\217\204\354\226\217/week9/src/modules/members/dtos/memberDto.ts"
new file mode 100644
index 0000000..6459825
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/members/dtos/memberDto.ts"
@@ -0,0 +1,156 @@
+// ํ์๊ฐ์
์์ฒญ ์ธํฐํ์ด์ค
+export interface MemberSignUpRequest {
+ /** ํ์ ์ด๋ฆ */
+ name: string
+ /** ๋๋ค์ */
+ nickname?: string
+ /**
+ * ์ด๋ฉ์ผ
+ * @example "test@example.com"
+ */
+ email?: string
+ /**
+ * ๋น๋ฐ๋ฒํธ
+ * @example "qwer1234!"
+ */
+ password?: string
+ /**
+ * ์ ํ๋ฒํธ
+ * @example "010-1234-5678"
+ */
+ phoneNumber?: string
+ /**
+ * ์๋
์์ผ (YYYY-MM-DD)
+ * @example "2000-01-01"
+ */
+ birth?: string
+ /**
+ * ์ฑ๋ณ
+ * @example "FEMALE"
+ */
+ gender?: string
+ /** ์ฃผ์ */
+ address?: string
+ /** ์์ธ ์ฃผ์ */
+ detailAddress?: string
+}
+
+// ํ์๊ฐ์
์๋ต ์ธํฐํ์ด์ค
+export interface MemberSignUpResponse {
+ /** ์์ฑ๋ ํ์ ID */
+ memberId: number
+ /** ํ์ ์ด๋ฆ */
+ name: string
+ /** ๋๋ค์ */
+ nickname: string | null
+ /** ์ด๋ฉ์ผ */
+ email: string | null
+ /** ์ ํ๋ฒํธ */
+ phoneNumber: string | null
+}
+
+// ์ฌ์ฉ์ ์ ๋ณด ์์ ์์ฒญ ์ธํฐํ์ด์ค (๋ฏธ์
2)
+export interface UserUpdateRequest {
+ /** ๋๋ค์ */
+ nickname?: string
+ /**
+ * ์ ํ๋ฒํธ
+ * @example "010-1234-5678"
+ */
+ phoneNumber?: string
+ /**
+ * ์๋
์์ผ (YYYY-MM-DD)
+ * @example "2000-01-01"
+ */
+ birth?: string
+ /**
+ * ์ฑ๋ณ
+ * @example "FEMALE"
+ */
+ gender?: string
+ /** ์ฃผ์ */
+ address?: string
+ /** ์์ธ ์ฃผ์ */
+ detailAddress?: string
+}
+
+// ์ฌ์ฉ์ ์ ๋ณด ์์ ์๋ต ์ธํฐํ์ด์ค
+export interface UserUpdateResponse {
+ memberId: number
+ name: string
+ nickname: string | null
+ email: string | null
+ phoneNumber: string | null
+ gender: string | null
+ birth: Date | null
+ address: string | null
+ detailAddress: string | null
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ (ํ์๊ฐ์
)
+export const bodyToMember = (body: MemberSignUpRequest) => {
+ return {
+ name: body.name,
+ nickname: body.nickname ?? null,
+ email: body.email ?? null,
+ phoneNumber: body.phoneNumber ?? null,
+ birth: body.birth ? new Date(body.birth) : null,
+ gender: body.gender ?? null,
+ address: body.address ?? null,
+ detailAddress: body.detailAddress ?? null,
+ }
+}
+
+// req.body โ ๋ด๋ถ ๋ฐ์ดํฐ๋ก ๋ณํ (์ฌ์ฉ์ ์ ๋ณด ์์ )
+export const bodyToUserUpdate = (body: UserUpdateRequest) => {
+ const data: Record = {}
+ if (body.nickname !== undefined) data.nickname = body.nickname
+ if (body.phoneNumber !== undefined) data.phoneNumber = body.phoneNumber
+ if (body.birth !== undefined) data.birth = new Date(body.birth)
+ if (body.gender !== undefined) data.gender = body.gender
+ if (body.address !== undefined) data.address = body.address
+ if (body.detailAddress !== undefined) data.detailAddress = body.detailAddress
+ return data
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (ํ์๊ฐ์
)
+export const responseFromMember = (member: {
+ id: number
+ name: string
+ nickname: string | null
+ email: string | null
+ phoneNumber: string | null
+}) => {
+ return {
+ memberId: member.id,
+ name: member.name,
+ nickname: member.nickname,
+ email: member.email,
+ phoneNumber: member.phoneNumber,
+ }
+}
+
+// DB ๊ฒฐ๊ณผ โ ์๋ต ํํ๋ก ๋ณํ (์ฌ์ฉ์ ์ ๋ณด ์์ )
+export const responseFromUserUpdate = (member: {
+ id: number
+ name: string
+ nickname: string | null
+ email: string | null
+ phoneNumber: string | null
+ gender: string | null
+ birth: Date | null
+ address: string | null
+ detailAddress: string | null
+}): UserUpdateResponse => {
+ return {
+ memberId: member.id,
+ name: member.name,
+ nickname: member.nickname,
+ email: member.email,
+ phoneNumber: member.phoneNumber,
+ gender: member.gender,
+ birth: member.birth,
+ address: member.address,
+ detailAddress: member.detailAddress,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/members/repositories/memberRepository.ts" "b/\353\217\204\354\226\217/week9/src/modules/members/repositories/memberRepository.ts"
new file mode 100644
index 0000000..36de960
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/members/repositories/memberRepository.ts"
@@ -0,0 +1,30 @@
+import { prisma } from '../../../dbConfig.js'
+
+// ์ ์ ์์ฑ (์ด๋ฉ์ผ ์ค๋ณต ์ null ๋ฐํ)
+export const addUser = async (data: any) => {
+ const exists = await prisma.user.findFirst({ where: { email: data.email } })
+ if (exists) return null
+
+ const created = await prisma.user.create({ data })
+ return created.id
+}
+
+// ์ ์ ์กฐํ (์์ผ๋ฉด ์์ธ throw)
+export const getUser = async (userId: number) =>
+ prisma.user.findFirstOrThrow({ where: { id: userId } })
+
+// ์ ์ ์ ๋ณด ์์ (๋ฏธ์
2: PATCH /members/me)
+export const updateUser = async (userId: number, data: Record) =>
+ prisma.user.update({ where: { id: userId }, data })
+
+// ์ ํธ ์์ ์นดํ
๊ณ ๋ฆฌ ๋ฑ๋ก
+export const setPreference = async (userId: number, foodCategoryId: number) =>
+ prisma.userFavorCategory.create({ data: { userId, foodCategoryId } })
+
+// ์ ํธ ์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํ (JOIN ํฌํจ)
+export const getUserPreferencesByUserId = async (userId: number) =>
+ prisma.userFavorCategory.findMany({
+ where: { userId },
+ include: { foodCategory: true },
+ orderBy: { foodCategoryId: 'asc' },
+ })
diff --git "a/\353\217\204\354\226\217/week9/src/modules/members/services/memberService.ts" "b/\353\217\204\354\226\217/week9/src/modules/members/services/memberService.ts"
new file mode 100644
index 0000000..d5a794c
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/members/services/memberService.ts"
@@ -0,0 +1,39 @@
+import bcrypt from 'bcryptjs'
+import {
+ MemberSignUpRequest,
+ UserUpdateRequest,
+ bodyToMember,
+ bodyToUserUpdate,
+ responseFromMember,
+ responseFromUserUpdate,
+} from '../dtos/memberDto.js'
+import { addUser, getUser, updateUser } from '../repositories/memberRepository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+// ํ์๊ฐ์
+export const signUp = async (data: MemberSignUpRequest) => {
+ // TSOA required ๊ฒ์ฆ ์ดํ์๋ ๋น ๋ฌธ์์ด ์
๋ ฅ์ ์๋น์ค์์ ํ ๋ฒ ๋ ๋ฐฉ์ดํฉ๋๋ค.
+ if (!data.name) {
+ throw new BaseError(ErrorCode.MEMBER_REQUIRED_FIELD)
+ }
+
+ const hashedPassword = data.password ? await bcrypt.hash(data.password, 10) : null
+
+ const memberData = bodyToMember(data)
+ const memberId = await addUser({ ...memberData, ...(hashedPassword ? { password: hashedPassword } : {}) })
+
+ if (memberId === null) {
+ throw new BaseError(ErrorCode.DUPLICATE_EMAIL)
+ }
+
+ const member = await getUser(memberId)
+ return responseFromMember(member)
+}
+
+// ์ฌ์ฉ์ ์ ๋ณด ์์ (๋ฏธ์
2: Google ๋ก๊ทธ์ธ์ผ๋ก ๊ฐ์
๋ ์ฌ์ฉ์๊ฐ ๋น ํ๋ ์ฑ์ฐ๊ธฐ)
+export const updateUserInfo = async (userId: number, data: UserUpdateRequest) => {
+ const updateData = bodyToUserUpdate(data)
+ const updated = await updateUser(userId, updateData)
+ return responseFromUserUpdate(updated)
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/missions/controllers/missionController.ts" "b/\353\217\204\354\226\217/week9/src/modules/missions/controllers/missionController.ts"
new file mode 100644
index 0000000..a150aea
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/missions/controllers/missionController.ts"
@@ -0,0 +1,33 @@
+import { Body, Controller, Path, Post, Request, Route, Security, Tags, Response as TsoaResponse, SuccessResponse } from 'tsoa'
+import { Request as ExRequest } from 'express'
+import { MissionChallengeRequest, MissionChallengeResponse } from '../dtos/missionDto.js'
+import { challengeMission } from '../services/missionService.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('missions')
+@Tags('Mission')
+export class MissionController extends Controller {
+ /**
+ * ๋ฏธ์
๋์ API
+ * @summary ํน์ ๋ฏธ์
์ ๋์ (์ฐธ์ฌ)ํฉ๋๋ค. (๋ก๊ทธ์ธ ํ์)
+ */
+ @Post('{missionId}/challenge')
+ @Security('bearerAuth')
+ @SuccessResponse(201, '๋ฏธ์
๋์ ์ฑ๊ณต')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(409, '์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
(MISSION4002)')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๋ฏธ์
(MISSION4001)')
+ public async handleChallengeMission(
+ /**
+ * ๋์ ํ ๋ฏธ์
ID
+ */
+ @Path() missionId: number,
+ @Request() req: ExRequest,
+ @Body() body: MissionChallengeRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const userId = (req.user as any).id
+ const result = await challengeMission(missionId, userId, body)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/missions/dtos/missionDto.ts" "b/\353\217\204\354\226\217/week9/src/modules/missions/dtos/missionDto.ts"
new file mode 100644
index 0000000..cfadf21
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/missions/dtos/missionDto.ts"
@@ -0,0 +1,111 @@
+export enum MissionStatus {
+ CHALLENGING = 'CHALLENGING',
+ COMPLETE = 'COMPLETE',
+}
+
+export interface MissionCreateRequest {
+ /** ๋ฏธ์
์ ๋ชฉ */
+ title: string
+ /**
+ * ํฌ์ธํธ ๋ณด์
+ * @example 500
+ */
+ reward: number
+ /** ๋ฏธ์
์์ธ ์ค๋ช
*/
+ spec?: string
+ /**
+ * ๋ง๊ฐ์ผ (YYYY-MM-DD)
+ * @example "2026-12-31"
+ */
+ deadLine?: string
+}
+
+export interface MissionChallengeRequest {
+ /**
+ * ๋ฏธ์
์ํ
+ * @example "CHALLENGING"
+ */
+ status?: MissionStatus
+}
+
+export interface MissionCreateResponse {
+ /** ์์ฑ๋ ๋ฏธ์
ID */
+ missionId: number
+ /** ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๋ฏธ์
์ ๋ชฉ */
+ title: string
+ /** ํฌ์ธํธ ๋ณด์ */
+ reward: number
+ /** ๋ฏธ์
์ค๋ช
*/
+ spec: string | null
+ /** ๋ง๊ฐ์ผ */
+ deadLine: Date | null
+}
+
+export interface MissionChallengeResponse {
+ /** ํ์-๋ฏธ์
๋งคํ ID */
+ memberMissionId: number
+ /** ํ์ ID */
+ memberId: number
+ /** ๋ฏธ์
ID */
+ missionId: number
+ /** ๋ฏธ์
์ํ */
+ status: MissionStatus
+}
+
+export interface OngoingMissionListResponse {
+ data: MissionChallengeResponse[]
+ pagination: {
+ /** ๋ค์ ํ์ด์ง ์ปค์ */
+ cursor: number | null
+ }
+}
+
+export interface StoreMissionListResponse {
+ data: MissionCreateResponse[]
+ pagination: {
+ cursor: number | null
+ }
+}
+
+export const bodyToMission = (body: MissionCreateRequest) => {
+ return {
+ title: body.title,
+ reward: body.reward,
+ spec: body.spec ?? null,
+ deadLine: body.deadLine ? new Date(body.deadLine) : null,
+ }
+}
+
+export const responseFromMission = (mission: {
+ id: number
+ storeId: number
+ title: string
+ reward: number
+ spec: string | null
+ deadLine: Date | null
+}): MissionCreateResponse => {
+ return {
+ missionId: mission.id,
+ storeId: mission.storeId,
+ title: mission.title,
+ reward: mission.reward,
+ spec: mission.spec,
+ deadLine: mission.deadLine,
+ }
+}
+
+export const responseFromMemberMission = (mm: {
+ id: number
+ memberId: number
+ missionId: number
+ status: string
+}): MissionChallengeResponse => {
+ return {
+ memberMissionId: mm.id,
+ memberId: mm.memberId,
+ missionId: mm.missionId,
+ status: mm.status as MissionStatus,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/missions/repositories/missionRepository.ts" "b/\353\217\204\354\226\217/week9/src/modules/missions/repositories/missionRepository.ts"
new file mode 100644
index 0000000..6263db8
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/missions/repositories/missionRepository.ts"
@@ -0,0 +1,67 @@
+import { prisma } from '../../../dbConfig.js'
+import { MissionStatus } from '../dtos/missionDto.js'
+
+export const addMission = async (data: {
+ storeId: number
+ title: string
+ reward: number
+ spec: string | null
+ deadLine: Date | null
+}): Promise => {
+ const created = await prisma.mission.create({
+ data: {
+ storeId: data.storeId,
+ title: data.title,
+ reward: data.reward,
+ spec: data.spec,
+ deadLine: data.deadLine,
+ },
+ })
+ return created.id
+}
+
+export const getMissionById = async (missionId: number) =>
+ prisma.mission.findFirst({ where: { id: missionId } })
+
+export const findMemberMission = async (memberId: number, missionId: number) =>
+ prisma.memberMission.findFirst({
+ where: { memberId, missionId },
+ })
+
+export const addMemberMission = async (
+ memberId: number,
+ missionId: number,
+ status: MissionStatus,
+): Promise => {
+ const created = await prisma.memberMission.create({
+ data: { memberId, missionId, status },
+ })
+ return created.id
+}
+
+export const getMemberMissionById = async (memberMissionId: number) =>
+ prisma.memberMission.findFirst({ where: { id: memberMissionId } })
+
+export const getMemberMissionByMemberAndMission = async (memberId: number, missionId: number) =>
+ prisma.memberMission.findFirst({ where: { memberId, missionId } })
+
+export const getStoreMissions = async (storeId: number, cursor: number) =>
+ prisma.mission.findMany({
+ where: { storeId, id: { gt: cursor } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+export const getOngoingMissions = async (memberId: number, cursor: number) =>
+ prisma.memberMission.findMany({
+ where: { memberId, status: MissionStatus.CHALLENGING, id: { gt: cursor } },
+ include: { mission: { include: { store: true } } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
+
+export const completeMission = async (memberId: number, missionId: number) =>
+ prisma.memberMission.updateMany({
+ where: { memberId, missionId, status: MissionStatus.CHALLENGING },
+ data: { status: MissionStatus.COMPLETE },
+ })
diff --git "a/\353\217\204\354\226\217/week9/src/modules/missions/services/missionService.ts" "b/\353\217\204\354\226\217/week9/src/modules/missions/services/missionService.ts"
new file mode 100644
index 0000000..6f353f8
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/missions/services/missionService.ts"
@@ -0,0 +1,118 @@
+import {
+ MissionChallengeRequest,
+ MissionCreateRequest,
+ MissionStatus,
+ bodyToMission,
+ responseFromMemberMission,
+ responseFromMission,
+} from '../dtos/missionDto.js'
+import {
+ addMemberMission,
+ addMission,
+ completeMission,
+ findMemberMission,
+ getMemberMissionById,
+ getMemberMissionByMemberAndMission,
+ getMissionById,
+ getOngoingMissions,
+ getStoreMissions,
+} from '../repositories/missionRepository.js'
+import { getUser } from '../../members/repositories/memberRepository.js'
+import { getStoreById } from '../../stores/repositories/storeRepository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const createMission = async (storeId: number, data: MissionCreateRequest) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(ErrorCode.STORE_NOT_FOUND)
+ }
+
+ const missionData = bodyToMission(data)
+ const missionId = await addMission({ ...missionData, storeId })
+ const mission = await getMissionById(missionId)
+
+ if (!mission) {
+ throw new BaseError(ErrorCode.MISSION_CREATE_FAILED)
+ }
+
+ return responseFromMission(mission)
+}
+
+export const listStoreMissions = async (storeId: number, cursor: number) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(ErrorCode.STORE_NOT_FOUND)
+ }
+
+ const missions = await getStoreMissions(storeId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions.map(responseFromMission),
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+export const listOngoingMissions = async (userId: number, cursor: number) => {
+ const user = await getUser(userId)
+ if (!user) {
+ throw new BaseError(ErrorCode.USER_NOT_FOUND)
+ }
+
+ const missions = await getOngoingMissions(userId, cursor)
+ const last = missions[missions.length - 1]
+ return {
+ data: missions.map(responseFromMemberMission),
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+export const finishMission = async (userId: number, missionId: number) => {
+ const user = await getUser(userId)
+ if (!user) {
+ throw new BaseError(ErrorCode.USER_NOT_FOUND)
+ }
+
+ const result = await completeMission(userId, missionId)
+ if (result.count === 0) {
+ throw new BaseError(ErrorCode.ONGOING_MISSION_NOT_FOUND)
+ }
+
+ const memberMission = await getMemberMissionByMemberAndMission(userId, missionId)
+ if (!memberMission) {
+ throw new BaseError(ErrorCode.ONGOING_MISSION_NOT_FOUND)
+ }
+
+ return responseFromMemberMission(memberMission)
+}
+
+export const challengeMission = async (missionId: number, userId: number, data: MissionChallengeRequest) => {
+ const mission = await getMissionById(missionId)
+ if (!mission) {
+ throw new BaseError(ErrorCode.MISSION_NOT_FOUND)
+ }
+
+ const user = await getUser(userId)
+ if (!user) {
+ throw new BaseError(ErrorCode.USER_NOT_FOUND)
+ }
+
+ const status = data.status ?? MissionStatus.CHALLENGING
+ if (!Object.values(MissionStatus).includes(status)) {
+ throw new BaseError(ErrorCode.MISSION_STATUS_REQUIRED)
+ }
+
+ const existing = await findMemberMission(userId, missionId)
+ if (existing) {
+ throw new BaseError(ErrorCode.MISSION_ALREADY_CHALLENGING)
+ }
+
+ const memberMissionId = await addMemberMission(userId, missionId, status)
+ const memberMission = await getMemberMissionById(memberMissionId)
+
+ if (!memberMission) {
+ throw new BaseError(ErrorCode.MISSION_CHALLENGE_FAILED)
+ }
+
+ return responseFromMemberMission(memberMission)
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/reviews/controllers/reviewController.ts" "b/\353\217\204\354\226\217/week9/src/modules/reviews/controllers/reviewController.ts"
new file mode 100644
index 0000000..77d8ccf
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/reviews/controllers/reviewController.ts"
@@ -0,0 +1,7 @@
+// review ๊ด๋ จ ์๋ํฌ์ธํธ๋ StoreController(๊ฐ๊ฒ ๋ฆฌ๋ทฐ ์์ฑ/์กฐํ)์
+// UserController(์ฌ์ฉ์ ๋ฆฌ๋ทฐ ๋ชฉ๋ก)๋ก ๋ถ์ฐ ๊ด๋ฆฌํฉ๋๋ค.
+// - POST /api/v1/stores/:storeId/reviews โ StoreController
+// - GET /api/v1/stores/:storeId/reviews โ StoreController
+// - GET /api/v1/users/:userId/reviews โ UserController
+export {}
+
diff --git "a/\353\217\204\354\226\217/week9/src/modules/reviews/dtos/reviewDto.ts" "b/\353\217\204\354\226\217/week9/src/modules/reviews/dtos/reviewDto.ts"
new file mode 100644
index 0000000..468ef97
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/reviews/dtos/reviewDto.ts"
@@ -0,0 +1,65 @@
+export interface ReviewCreateRequest {
+ /**
+ * ๋ฆฌ๋ทฐ ๋ด์ฉ
+ * @example "์์์ด ์ ๋ง ๋ง์์์ด์."
+ */
+ content: string
+ /**
+ * ๋ณ์ (1~5). ํ์ฌ DB์๋ ์ ์ฅํ์ง ์๊ณ ์
๋ ฅ ๊ฒ์ฆ์๋ง ์ฌ์ฉํฉ๋๋ค.
+ * @example 4.5
+ */
+ score?: number
+}
+
+export interface ReviewCreateResponse {
+ /** ์์ฑ๋ ๋ฆฌ๋ทฐ ID */
+ reviewId: number
+ /** ์์ฑ์ ํ์ ID */
+ memberId: number
+ /** ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๋ฆฌ๋ทฐ ๋ด์ฉ */
+ content: string
+}
+
+export interface UserReviewListResponse {
+ data: ReviewCreateResponse[]
+ pagination: {
+ /** ๋ค์ ํ์ด์ง ์ปค์ */
+ cursor: number | null
+ }
+}
+
+export const bodyToReview = (body: ReviewCreateRequest, memberId: number) => {
+ return {
+ memberId,
+ content: body.content,
+ }
+}
+
+export const responseFromUserReviews = (reviews: Array<{
+ id: number
+ userId: number
+ storeId: number
+ content: string
+}>): UserReviewListResponse => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews.map(responseFromReview),
+ pagination: { cursor: last ? last.id : null },
+ }
+}
+
+export const responseFromReview = (review: {
+ id: number
+ userId: number
+ storeId: number
+ content: string
+}): ReviewCreateResponse => {
+ return {
+ reviewId: review.id,
+ memberId: review.userId,
+ storeId: review.storeId,
+ content: review.content,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/reviews/repositories/reviewRepository.ts" "b/\353\217\204\354\226\217/week9/src/modules/reviews/repositories/reviewRepository.ts"
new file mode 100644
index 0000000..12d6df5
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/reviews/repositories/reviewRepository.ts"
@@ -0,0 +1,29 @@
+import { prisma } from '../../../dbConfig.js'
+
+// ๋ฆฌ๋ทฐ ์ถ๊ฐ
+export const addReview = async (data: {
+ memberId: number
+ storeId: number
+ content: string
+}): Promise => {
+ const created = await prisma.userStoreReview.create({
+ data: {
+ userId: data.memberId,
+ storeId: data.storeId,
+ content: data.content,
+ },
+ })
+ return created.id
+}
+
+// ๋ฆฌ๋ทฐ ์กฐํ
+export const getReviewById = async (reviewId: number) =>
+ prisma.userStoreReview.findFirst({ where: { id: reviewId } })
+
+// ๋ด๊ฐ ์์ฑํ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getUserReviews = async (userId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ where: { userId, id: { gt: cursor } },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week9/src/modules/reviews/services/reviewService.ts" "b/\353\217\204\354\226\217/week9/src/modules/reviews/services/reviewService.ts"
new file mode 100644
index 0000000..362bee3
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/reviews/services/reviewService.ts"
@@ -0,0 +1,43 @@
+import { ReviewCreateRequest, bodyToReview, responseFromReview, responseFromUserReviews } from '../dtos/reviewDto.js'
+import { addReview, getReviewById, getUserReviews } from '../repositories/reviewRepository.js'
+import { getStoreById } from '../../stores/repositories/storeRepository.js'
+import { getUser } from '../../members/repositories/memberRepository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const listUserReviews = async (userId: number, cursor: number) => {
+ const user = await getUser(userId)
+ if (!user) {
+ throw new BaseError(ErrorCode.USER_NOT_FOUND)
+ }
+
+ const reviews = await getUserReviews(userId, cursor)
+ return responseFromUserReviews(reviews)
+}
+
+// userId๋ JWT์์ ์ถ์ถํ ๊ฐ (body.memberId ์ ๊ฑฐ)
+export const createReview = async (storeId: number, userId: number, data: ReviewCreateRequest) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(ErrorCode.STORE_NOT_FOUND)
+ }
+
+ const user = await getUser(userId)
+ if (!user) {
+ throw new BaseError(ErrorCode.USER_NOT_FOUND)
+ }
+
+ if (data.score !== undefined && (data.score < 1 || data.score > 5)) {
+ throw new BaseError(ErrorCode.INVALID_SCORE)
+ }
+
+ const reviewData = bodyToReview(data, userId)
+ const reviewId = await addReview({ ...reviewData, storeId })
+
+ const review = await getReviewById(reviewId)
+ if (!review) {
+ throw new BaseError(ErrorCode.REVIEW_CREATE_FAILED)
+ }
+
+ return responseFromReview(review)
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/stores/controllers/storeController.ts" "b/\353\217\204\354\226\217/week9/src/modules/stores/controllers/storeController.ts"
new file mode 100644
index 0000000..e2c9b04
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/stores/controllers/storeController.ts"
@@ -0,0 +1,115 @@
+import { Body, Controller, Get, Path, Post, Query, Request, Route, Security, Tags, Response as TsoaResponse, SuccessResponse } from 'tsoa'
+import { Request as ExRequest } from 'express'
+import { StoreCreateRequest, StoreCreateResponse, ReviewListResponse } from '../dtos/storeDto.js'
+import { createStore, listStoreReviews } from '../services/storeService.js'
+import { createReview } from '../../reviews/services/reviewService.js'
+import { createMission, listStoreMissions } from '../../missions/services/missionService.js'
+import { ReviewCreateRequest, ReviewCreateResponse } from '../../reviews/dtos/reviewDto.js'
+import { MissionCreateRequest, MissionCreateResponse, StoreMissionListResponse } from '../../missions/dtos/missionDto.js'
+import { ApiResponse, successResponse } from '../../../utils/response.js'
+
+@Route('stores')
+@Tags('Store')
+export class StoreController extends Controller {
+ /**
+ * ๊ฐ๊ฒ ์์ฑ API
+ * @summary ์๋ก์ด ๊ฐ๊ฒ๋ฅผ ๋ฑ๋กํฉ๋๋ค.
+ */
+ @Post()
+ @Security('bearerAuth')
+ @SuccessResponse(201, '๊ฐ๊ฒ ์์ฑ ์ฑ๊ณต')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(400, '์๋ชป๋ ์์ฒญ (COMMON400)')
+ public async handleCreateStore(
+ @Body() body: StoreCreateRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await createStore(body)
+ return successResponse(result)
+ }
+
+ /**
+ * ๊ฐ๊ฒ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์ ์ปค์ ๊ธฐ๋ฐ์ผ๋ก ์กฐํํฉ๋๋ค.
+ */
+ @Get('{storeId}/reviews')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleListStoreReviews(
+ /**
+ * ๋ฆฌ๋ทฐ๋ฅผ ์กฐํํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ (์ด์ ์๋ต์ cursor)
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const result = await listStoreReviews(storeId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ๋ฆฌ๋ทฐ ์์ฑ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ๋ฅผ ์์ฑํฉ๋๋ค. (๋ก๊ทธ์ธ ํ์)
+ */
+ @Post('{storeId}/reviews')
+ @Security('bearerAuth')
+ @SuccessResponse(201, '๋ฆฌ๋ทฐ ์์ฑ ์ฑ๊ณต')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(400, '๋ณ์ ๋ฒ์ ์ค๋ฅ (REVIEW4001)')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleCreateReview(
+ /**
+ * ๋ฆฌ๋ทฐ๋ฅผ ์์ฑํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ @Request() req: ExRequest,
+ @Body() body: ReviewCreateRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const userId = (req.user as any).id
+ const result = await createReview(storeId, userId, body)
+ return successResponse(result)
+ }
+
+ /**
+ * ๊ฐ๊ฒ ๋ฏธ์
๋ชฉ๋ก ์กฐํ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ๋ฑ๋ก๋ ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
+ */
+ @Get('{storeId}/missions')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleListStoreMissions(
+ /**
+ * ๋ฏธ์
๋ชฉ๋ก์ ์กฐํํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ /**
+ * ์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
๊ฐ
+ */
+ @Query() cursor?: number,
+ ): Promise> {
+ const result = await listStoreMissions(storeId, cursor ?? 0)
+ return successResponse(result)
+ }
+
+ /**
+ * ๋ฏธ์
์์ฑ API
+ * @summary ํน์ ๊ฐ๊ฒ์ ์๋ก์ด ๋ฏธ์
์ ๋ฑ๋กํฉ๋๋ค. (๋ก๊ทธ์ธ ํ์)
+ */
+ @Post('{storeId}/missions')
+ @Security('bearerAuth')
+ @SuccessResponse(201, '๋ฏธ์
์์ฑ ์ฑ๊ณต')
+ @TsoaResponse>(401, '์ธ์ฆ ํ ํฐ ์์ ๋๋ ๋ง๋ฃ (AUTH4001)')
+ @TsoaResponse>(404, '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ (STORE4001)')
+ public async handleCreateMission(
+ /**
+ * ๋ฏธ์
์ ๋ฑ๋กํ ๊ฐ๊ฒ ID
+ */
+ @Path() storeId: number,
+ @Body() body: MissionCreateRequest,
+ ): Promise> {
+ this.setStatus(201)
+ const result = await createMission(storeId, body)
+ return successResponse(result)
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/stores/dtos/storeDto.ts" "b/\353\217\204\354\226\217/week9/src/modules/stores/dtos/storeDto.ts"
new file mode 100644
index 0000000..0a5e8a7
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/stores/dtos/storeDto.ts"
@@ -0,0 +1,66 @@
+export interface StoreCreateRequest {
+ /** ๊ฐ๊ฒ ์ด๋ฆ */
+ name: string
+}
+
+export interface StoreCreateResponse {
+ /** ์์ฑ๋ ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๊ฐ๊ฒ ์ด๋ฆ */
+ name: string
+}
+
+export interface ReviewItem {
+ /** ๋ฆฌ๋ทฐ ID */
+ reviewId: number
+ /** ์์ฑ์ ํ์ ID */
+ memberId: number
+ /** ๊ฐ๊ฒ ID */
+ storeId: number
+ /** ๋ฆฌ๋ทฐ ๋ด์ฉ */
+ content: string
+}
+
+export interface ReviewListResponse {
+ data: ReviewItem[]
+ pagination: {
+ /** ๋ค์ ํ์ด์ง ์ปค์ */
+ cursor: number | null
+ }
+}
+
+export const bodyToStore = (body: StoreCreateRequest) => {
+ return {
+ name: body.name,
+ }
+}
+
+export const responseFromReviews = (reviews: Array<{
+ id: number
+ userId: number
+ storeId: number
+ content: string
+}>): ReviewListResponse => {
+ const last = reviews[reviews.length - 1]
+ return {
+ data: reviews.map((review) => ({
+ reviewId: review.id,
+ memberId: review.userId,
+ storeId: review.storeId,
+ content: review.content,
+ })),
+ pagination: {
+ cursor: last ? last.id : null,
+ },
+ }
+}
+
+export const responseFromStore = (store: {
+ id: number
+ name: string
+}): StoreCreateResponse => {
+ return {
+ storeId: store.id,
+ name: store.name,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/modules/stores/repositories/storeRepository.ts" "b/\353\217\204\354\226\217/week9/src/modules/stores/repositories/storeRepository.ts"
new file mode 100644
index 0000000..2ff46f7
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/stores/repositories/storeRepository.ts"
@@ -0,0 +1,28 @@
+import { prisma } from '../../../dbConfig.js'
+
+// ๊ฐ๊ฒ ์ถ๊ฐ
+export const addStore = async (data: { name: string }): Promise => {
+ const created = await prisma.store.create({ data })
+ return created.id
+}
+
+// ๊ฐ๊ฒ ์กฐํ
+export const getStoreById = async (storeId: number) =>
+ prisma.store.findFirst({ where: { id: storeId } })
+
+// ๊ฐ๊ฒ ๋ฆฌ๋ทฐ ๋ชฉ๋ก ์กฐํ (์ปค์ ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์
)
+export const getAllStoreReviews = async (storeId: number, cursor: number) =>
+ prisma.userStoreReview.findMany({
+ select: {
+ id: true,
+ userId: true,
+ storeId: true,
+ content: true,
+ },
+ where: {
+ storeId,
+ id: { gt: cursor },
+ },
+ orderBy: { id: 'asc' },
+ take: 5,
+ })
diff --git "a/\353\217\204\354\226\217/week9/src/modules/stores/services/storeService.ts" "b/\353\217\204\354\226\217/week9/src/modules/stores/services/storeService.ts"
new file mode 100644
index 0000000..e12fd45
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/modules/stores/services/storeService.ts"
@@ -0,0 +1,26 @@
+import { StoreCreateRequest, bodyToStore, responseFromStore, responseFromReviews } from '../dtos/storeDto.js'
+import { addStore, getStoreById, getAllStoreReviews } from '../repositories/storeRepository.js'
+import { BaseError } from '../../../utils/errors.js'
+import { ErrorCode } from '../../../utils/errorCode.js'
+
+export const listStoreReviews = async (storeId: number, cursor: number) => {
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(ErrorCode.STORE_NOT_FOUND)
+ }
+
+ const reviews = await getAllStoreReviews(storeId, cursor)
+ return responseFromReviews(reviews)
+}
+
+export const createStore = async (data: StoreCreateRequest) => {
+ const storeData = bodyToStore(data)
+ const storeId = await addStore(storeData)
+
+ const store = await getStoreById(storeId)
+ if (!store) {
+ throw new BaseError(ErrorCode.STORE_CREATE_FAILED)
+ }
+
+ return responseFromStore(store)
+}
diff --git "a/\353\217\204\354\226\217/week9/src/utils/errorCode.ts" "b/\353\217\204\354\226\217/week9/src/utils/errorCode.ts"
new file mode 100644
index 0000000..767265d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/utils/errorCode.ts"
@@ -0,0 +1,33 @@
+export const ErrorCode = {
+ // Common
+ INTERNAL_ERROR: { status: 500, code: 'COMMON500', message: '์๋ฒ ๋ด๋ถ ์ค๋ฅ์
๋๋ค.' },
+ INVALID_INPUT: { status: 400, code: 'COMMON400', message: '์๋ชป๋ ์
๋ ฅ๊ฐ์
๋๋ค.' },
+
+ // Auth
+ UNAUTHORIZED: { status: 401, code: 'AUTH4001', message: '์ธ์ฆ ํ ํฐ์ด ํ์ํฉ๋๋ค.' },
+ INVALID_TOKEN: { status: 401, code: 'AUTH4002', message: '์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค.' },
+ FORBIDDEN: { status: 403, code: 'AUTH4003', message: '์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค.' },
+
+ // Member
+ USER_NOT_FOUND: { status: 404, code: 'USER4001', message: '์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.' },
+ DUPLICATE_EMAIL: { status: 409, code: 'USER4002', message: '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.' },
+ MEMBER_REQUIRED_FIELD: { status: 400, code: 'USER4003', message: 'ํ์ ์
๋ ฅ๊ฐ์ด ๋๋ฝ๋์์ต๋๋ค.' },
+
+ // Store
+ STORE_NOT_FOUND: { status: 404, code: 'STORE4001', message: '์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.' },
+ STORE_CREATE_FAILED: { status: 500, code: 'STORE5001', message: '๊ฐ๊ฒ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+
+ // Review
+ INVALID_SCORE: { status: 400, code: 'REVIEW4001', message: '๋ณ์ ์ 1~5 ์ฌ์ด์ฌ์ผ ํฉ๋๋ค.' },
+ REVIEW_CREATE_FAILED: { status: 500, code: 'REVIEW5001', message: '๋ฆฌ๋ทฐ ์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+
+ // Mission
+ MISSION_NOT_FOUND: { status: 404, code: 'MISSION4001', message: '์กด์ฌํ์ง ์๋ ๋ฏธ์
์
๋๋ค.' },
+ MISSION_ALREADY_CHALLENGING: { status: 409, code: 'MISSION4002', message: '์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.' },
+ ONGOING_MISSION_NOT_FOUND: { status: 404, code: 'MISSION4003', message: '์งํ ์ค์ธ ๋ฏธ์
์ด ์์ต๋๋ค.' },
+ MISSION_STATUS_REQUIRED: { status: 400, code: 'MISSION4004', message: 'status๋ ํ์ ์
๋ ฅ๊ฐ์
๋๋ค.' },
+ MISSION_CREATE_FAILED: { status: 500, code: 'MISSION5001', message: '๋ฏธ์
์์ฑ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+ MISSION_CHALLENGE_FAILED: { status: 500, code: 'MISSION5002', message: '๋ฏธ์
๋์ ํ ์กฐํ์ ์คํจํ์ต๋๋ค.' },
+} as const
+
+export type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode]
diff --git "a/\353\217\204\354\226\217/week9/src/utils/errors.ts" "b/\353\217\204\354\226\217/week9/src/utils/errors.ts"
new file mode 100644
index 0000000..643ea5b
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/utils/errors.ts"
@@ -0,0 +1,13 @@
+import { ErrorCode, ErrorCodeValue } from './errorCode.js'
+
+export class BaseError extends Error {
+ status: number
+ code: string
+
+ constructor(errorCode: ErrorCodeValue = ErrorCode.INTERNAL_ERROR) {
+ super(errorCode.message)
+ this.name = 'BaseError'
+ this.status = errorCode.status
+ this.code = errorCode.code
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/src/utils/response.ts" "b/\353\217\204\354\226\217/week9/src/utils/response.ts"
new file mode 100644
index 0000000..beae590
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/src/utils/response.ts"
@@ -0,0 +1,25 @@
+export interface ApiResponse {
+ isSuccess: boolean
+ code: string
+ message: string
+ result: T | null
+}
+
+export const successResponse = (result: T): ApiResponse => ({
+ isSuccess: true,
+ code: 'COMMON200',
+ message: '์ฑ๊ณต์
๋๋ค.',
+ result,
+})
+
+/**
+ * @deprecated successResponse๋ฅผ ์ฌ์ฉํ์ธ์.
+ */
+export const BaseResponse = (result: T, message = '์ฑ๊ณต์
๋๋ค.', code = 'COMMON200') => {
+ return {
+ isSuccess: true,
+ code,
+ message,
+ result,
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/todolist.json" "b/\353\217\204\354\226\217/week9/todolist.json"
new file mode 100644
index 0000000..830b572
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/todolist.json"
@@ -0,0 +1,353 @@
+{
+ "chapter": "Chapter 5. API ๋ฐ ํ๋ก์ ํธ ์ค์ ๊ธฐ์ด",
+ "branch": "feature/chapter-05",
+ "week4_reference": "../week4",
+ "keywords": [
+ {
+ "term": "ํ๊ฒฝ ๋ณ์ (Environment Variables)",
+ "summary": "DB ๋น๋ฐ๋ฒํธยทAPI Key ๋ฑ ๋ฏผ๊ฐํ ๊ฐ์ ์ฝ๋์ ํ๋์ฝ๋ฉํ์ง ์๊ณ .env ํ์ผ๋ก ๊ด๋ฆฌ. dotenv ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ก๋ํ๋ฉฐ .gitignore์ ๋ฐ๋์ ์ถ๊ฐํด์ผ ํจ.",
+ "example": "process.env.DB_PASSWORD"
+ },
+ {
+ "term": "CORS (Cross-Origin Resource Sharing)",
+ "summary": "๋ธ๋ผ์ฐ์ ๊ฐ ๋ค๋ฅธ Origin(๋๋ฉ์ธยทํฌํธ)์ ์๋ฒ์ ์์ฒญํ ๋ ๋ฐ์ํ๋ ๋ณด์ ์ ์ฑ
. Express์์๋ cors ๋ฏธ๋ค์จ์ด๋ก ํ์ฉ ์ฒ๋ฆฌ.",
+ "example": "app.use(cors())"
+ },
+ {
+ "term": "DB Connection Pool",
+ "summary": "๋งค ์์ฒญ๋ง๋ค DB ์ปค๋ฅ์
์ ์๋ก ์์ฑยทํด์ ํ์ง ์๊ณ , ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋ ์ปค๋ฅ์
ํ์์ ๋น๋ ค ์ฐ๋ ๋ฐฉ์. mysql2์ createPool()๋ก ๊ตฌํ. finally ๋ธ๋ก์์ conn.release()๋ก ๋ฐํํด์ผ ํจ.",
+ "example": "const pool = mysql.createPool({ connectionLimit: 10 })"
+ },
+ {
+ "term": "๋น๋๊ธฐ (async / await)",
+ "summary": "DB ์ฟผ๋ฆฌยท์ธ๋ถ API ํธ์ถ์ฒ๋ผ ์๋ต ๋๊ธฐ๊ฐ ํ์ํ ์์
์ Promise ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌ. async ํจ์ ์์์ await๋ก ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ฉฐ, ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์ฝ๊ธฐ ์ฝ๊ฒ ์์ฑ ๊ฐ๋ฅ.",
+ "example": "const [rows] = await pool.query('SELECT ...')"
+ },
+ {
+ "term": "try / catch / finally",
+ "summary": "๋น๋๊ธฐ ์์
์์ ๋ฐ์ํ ์ ์๋ ์๋ฌ๋ฅผ ๊ตฌ์กฐ์ ์ผ๋ก ์ฒ๋ฆฌ. try: ์ ์ ๋ก์ง, catch: ์๋ฌ ์ฒ๋ฆฌ, finally: ์ปค๋ฅ์
๋ฐํ(conn.release()) ๋ฑ ํญ์ ์คํ๋์ด์ผ ํ๋ ์ ๋ฆฌ ์ฝ๋.",
+ "example": "try { ... } catch(err) { throw new Error(...) } finally { conn.release() }"
+ },
+ {
+ "term": "Interface (์ธํฐํ์ด์ค)",
+ "summary": "TypeScript์์ ๊ฐ์ฒด์ ํํ(์์ฑยทํ์
)๋ฅผ ์ ์ํ๋ ์ค๊ณ๋. DTOยทRepository ๋ฐํ ํ์
๋ฑ์ ํ์ฉ. ?๋ฅผ ๋ถ์ด๋ฉด ์ ํ์ ํ๋กํผํฐ.",
+ "example": "export interface StoreCreateRequest { regionId: number; name: string; address: string; }"
+ },
+ {
+ "term": "Type Assertion (as ํค์๋)",
+ "summary": "TypeScript ์ปดํ์ผ๋ฌ์๊ฒ '์ด ๊ฐ์ ์ด ํ์
์ด์ผ'๋ผ๊ณ ๊ฐ์ ๋ก ์๋ ค์ฃผ๋ ๋ฌธ๋ฒ. req.body๋ ๊ธฐ๋ณธ์ ์ผ๋ก any์ด๋ฏ๋ก, as ๋ก ์ ์ํ ์ธํฐํ์ด์ค ํ์
์ผ๋ก ๋ณํํด ์ฌ์ฉ.",
+ "example": "req.body as StoreCreateRequest"
+ }
+ ],
+ "project_structure": {
+ "root": "week5/",
+ "note": "week4์ in-memory DB โ MySQL ์ค์ DB ์ฐ๊ฒฐ๋ก ์ ํ. ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค ๊ตฌ์กฐ ์ฑํ.",
+ "files": [
+ "src/index.ts - Express ์ฑ ์ง์
์ , ๋ฏธ๋ค์จ์ดยท๋ผ์ฐํฐ ๋ฑ๋ก",
+ "src/db.config.ts - MySQL Connection Pool ์ค์ ",
+ "src/modules/stores/controllers/store.controller.ts",
+ "src/modules/stores/services/store.service.ts",
+ "src/modules/stores/repositories/store.repository.ts",
+ "src/modules/stores/dtos/store.dto.ts",
+ "src/modules/reviews/controllers/review.controller.ts",
+ "src/modules/reviews/services/review.service.ts",
+ "src/modules/reviews/repositories/review.repository.ts",
+ "src/modules/reviews/dtos/review.dto.ts",
+ "src/modules/missions/controllers/mission.controller.ts",
+ "src/modules/missions/services/mission.service.ts",
+ "src/modules/missions/repositories/mission.repository.ts",
+ "src/modules/missions/dtos/mission.dto.ts",
+ ".env - DB ์ ์ ์ ๋ณดยทPORT (gitignore ํ์)",
+ ".gitignore",
+ "package.json",
+ "tsconfig.json",
+ "schema.sql - week4 schema.sql ์ฌ์ฌ์ฉ (ํ
์ด๋ธ ์ด๋ฏธ ์ ์๋จ)"
+ ]
+ },
+ "todos": [
+ {
+ "id": 1,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "GitHub ์ด์ ์์ฑ ๋ฐ ๋ธ๋์น ๋ถ๊ธฐ",
+ "status": "todo",
+ "details": [
+ "GitHub ์ ์ฅ์ Issues ํญ์์ ๋ผ๋ฒจ ์ ๋ฆฌ: bug, docs, feature, refactor",
+ "์ด์ ์ ๋ชฉ: '[feat] Chapter 5 - API ๊ตฌํ (๊ฐ๊ฒ ์ถ๊ฐ / ๋ฆฌ๋ทฐ / ๋ฏธ์
)'",
+ "Assignee: ๋ณธ์ธ, Label: feature ๋ก ์ด์ ์์ฑ",
+ "์ด์์์ 'Create a branch' ํด๋ฆญ โ feature/chapter-05 ๋ธ๋์น ์์ฑ",
+ "๋ก์ปฌ์์: git fetch origin && git checkout feature/chapter-05"
+ ]
+ },
+ {
+ "id": 2,
+ "phase": "์ฌ์ ์ค๋น",
+ "title": "Postman ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ ํ์ธ",
+ "status": "todo",
+ "details": [
+ "Postman ์ค์น (https://www.postman.com/downloads/)",
+ "Params / Authorization / Headers / Body ํญ ์ญํ ์ดํด",
+ "Body > raw > JSON ์ ํ ๋ฐฉ๋ฒ ์์ง",
+ "๋์ค์ API ํ
์คํธ ์ ์คํฌ๋ฆฐ์ท ์ ์ฅํ ์ค๋น"
+ ]
+ },
+ {
+ "id": 3,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "week5 ํด๋ ์ด๊ธฐํ ๋ฐ ์์กด์ฑ ์ค์น",
+ "status": "todo",
+ "details": [
+ "cd week5 && npm init -y",
+ "npm install express cors dotenv http-status-codes mysql2",
+ "npm install -D typescript @types/node @types/express @types/cors @types/dotenv nodemon tsx",
+ "npx tsc --init ํ tsconfig.json ์์ (rootDir: ./src, outDir: ./dist, module: NodeNext, strict: true ๋ฑ)",
+ "week4/tsconfig.json์ ์ฐธ๊ณ ํด module/moduleResolution ์ค์ ์ผ์น์ํค๊ธฐ",
+ "package.json scripts ์ถ๊ฐ: start / dev (nodemon --exec tsx src/index.ts)"
+ ]
+ },
+ {
+ "id": 4,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": ".env ํ์ผ ๋ฐ .gitignore ์์ฑ",
+ "status": "todo",
+ "details": [
+ ".gitignore์ node_modules/ / .env / .env.* ์ถ๊ฐ",
+ ".env์ PORT=3000, DB_HOST=localhost, DB_PORT=3306, DB_USER=root, DB_PASSWORD=๋น๋ฐ๋ฒํธ, DB_NAME=umc_mission ์์ฑ",
+ "DB_NAME์ week4/schema.sql ๊ธฐ์ค umc_mission ์ฌ์ฉ (์ด๋ฏธ ํ
์ด๋ธ ์์)"
+ ]
+ },
+ {
+ "id": 5,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/db.config.ts ์์ฑ - MySQL Connection Pool",
+ "status": "todo",
+ "details": [
+ "mysql2/promise์ createPool ์ฌ์ฉ",
+ "ํ๊ฒฝ ๋ณ์(DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME)๋ก ์ค์ ",
+ "connectionLimit: 10, waitForConnections: true",
+ "week4 schema.sql์ DB(umc_mission)์ ์ฐ๊ฒฐ"
+ ],
+ "reference_week4": "week4/src/db/index.ts ๊ตฌ์กฐ ์ฐธ๊ณ (๋จ, in-memoryโMySQL๋ก ๋ณ๊ฒฝ)"
+ },
+ {
+ "id": 6,
+ "phase": "ํ๋ก์ ํธ ์ธํ
",
+ "title": "src/index.ts ์์ฑ - Express ์ฑ ์ง์
์ ",
+ "status": "todo",
+ "details": [
+ "dotenv.config() ์ต์๋จ ํธ์ถ",
+ "cors(), express.json(), express.urlencoded() ๋ฏธ๋ค์จ์ด ๋ฑ๋ก",
+ "๊ฐ ๋ชจ๋ ๋ผ์ฐํฐ ๋ฑ๋ก: /api/v1/stores, /api/v1/reviews, /api/v1/missions",
+ "์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ๋ฏธ๋ค์จ์ด ๋ฑ๋ก (์๋์ด ๋ฏธ์
: JSON ์๋ฌ ์๋ต)",
+ "app.listen(process.env.PORT || 3000)"
+ ],
+ "reference_week4": "week4/src/index.ts ๊ตฌ์กฐ ๊ทธ๋๋ก ํ์ฉ, corsยทdotenv ์ถ๊ฐ"
+ },
+ {
+ "id": 7,
+ "phase": "DB ์ค๋น",
+ "title": "MySQL์ week5์ฉ ํ
์ด๋ธ ํ์ธ ๋ฐ ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
",
+ "status": "todo",
+ "details": [
+ "week4/schema.sql๋ก ํ
์ด๋ธ ์์ฑ (์ด๋ฏธ ๋์ด์์ผ๋ฉด ์๋ต)",
+ "food_category ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO food_category(name) VALUES ('ํ์'),('์ค์'),('์ผ์'),('์์'),('์นํจ'),('๋ถ์'),('๊ณ ๊ธฐ/๊ตฌ์ด'),('๋์๋ฝ'),('์์'),('ํจ์คํธํธ๋'),('๋ค์ ํธ'),('์์์ํธ๋')",
+ "region ๋๋ฏธ๋ฐ์ดํฐ ์ฝ์
: INSERT INTO region(name) VALUES ('์์ธ'),('๊ฒฝ๊ธฐ'),('๋ถ์ฐ')...",
+ "member ๋๋ฏธ๋ฐ์ดํฐ 1๊ฑด ์ฝ์
(API ํ
์คํธ์ฉ ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ ]
+ },
+ {
+ "id": 8,
+ "phase": "API ๊ตฌํ - 1-1",
+ "title": "[ํ์] ํน์ ์ง์ญ์ ๊ฐ๊ฒ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores",
+ "request_body": {
+ "regionId": "number",
+ "foodCategoryId": "number",
+ "name": "string",
+ "description": "string (์ ํ)",
+ "address": "string",
+ "lat": "number (์ ํ)",
+ "lng": "number (์ ํ)"
+ },
+ "details": [
+ "store.dto.ts - StoreCreateRequest ์ธํฐํ์ด์ค ์ ์",
+ "store.dto.ts - bodyToStore() ๋ณํ ํจ์ ์์ฑ",
+ "store.repository.ts - addStore(): INSERT INTO store(...) VALUES(?)",
+ "store.service.ts - createStore(data): addStore ํธ์ถ ํ ๊ฒฐ๊ณผ ๋ฐํ",
+ "store.controller.ts - handleCreateStore: bodyToStore(req.body as ...) โ service ํธ์ถ โ 201 ์๋ต",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก: app.post('/api/v1/stores', handleCreateStore)"
+ ],
+ "reference_week4": "week4/src/controllers/store.controller.ts ํจํด ์ฐธ๊ณ "
+ },
+ {
+ "id": 9,
+ "phase": "API ๊ตฌํ - 1-2",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/stores/:storeId/reviews",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)",
+ "content": "string",
+ "score": "number (1.0~5.0)"
+ },
+ "details": [
+ "review.dto.ts - ReviewCreateRequest ์ธํฐํ์ด์ค (content, score, memberId)",
+ "review.dto.ts - bodyToReview() ๋ณํ ํจ์",
+ "review.repository.ts - addReview(): INSERT INTO review(...)",
+ "store.repository.ts (๋๋ review.repository.ts) - findStoreById(): SELECT * FROM store WHERE id=?",
+ "review.service.ts - createReview(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addReview ํธ์ถ",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์กด์ฌํ์ง ์๋ ๊ฐ๊ฒ์
๋๋ค.')",
+ "review.controller.ts - handleCreateReview: storeId = parseInt(req.params.storeId) โ service ํธ์ถ",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋ฆฌ๋ทฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ ๊ฐ๊ฒ๊ฐ ์กด์ฌํ๋์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/services/store.service.ts์ createReview ํจํด, week4 ์คํค๋ง์ review ํ
์ด๋ธ"
+ },
+ {
+ "id": 10,
+ "phase": "API ๊ตฌํ - 1-3",
+ "title": "๊ฐ๊ฒ์ ๋ฏธ์
์ถ๊ฐํ๊ธฐ API",
+ "status": "todo",
+ "priority": "optional",
+ "endpoint": "POST /api/v1/stores/:storeId/missions",
+ "request_body": {
+ "title": "string",
+ "reward": "number",
+ "spec": "string (์ ํ)",
+ "deadLine": "string (YYYY-MM-DD, ์ ํ)"
+ },
+ "details": [
+ "mission.dto.ts - MissionCreateRequest ์ธํฐํ์ด์ค",
+ "mission.dto.ts - bodyToMission() ๋ณํ ํจ์ (deadLine โ Date ๋ณํ)",
+ "mission.repository.ts - addMission(): INSERT INTO mission(store_id, title, reward, spec, dead_line)",
+ "mission.service.ts - createMission(storeId, data): ๊ฐ๊ฒ ์กด์ฌ ๊ฒ์ฆ โ addMission",
+ "mission.controller.ts - handleCreateMission",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4/src/repositories/mission.repository.ts ํจํด, week4 schema.sql์ mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 11,
+ "phase": "API ๊ตฌํ - 1-4",
+ "title": "[ํ์โ
] ๊ฐ๊ฒ์ ๋ฏธ์
์ ๋์ ์ค์ธ ๋ฏธ์
์ ์ถ๊ฐ(๋ฏธ์
๋์ ํ๊ธฐ) API",
+ "status": "todo",
+ "priority": "required",
+ "endpoint": "POST /api/v1/missions/:missionId/challenge",
+ "request_body": {
+ "memberId": "number (ํน์ ์ฌ์ฉ์๋ก ๊ฐ์ , DB ์ฒซ ๋ฒ์งธ ์ฌ์ฉ์)"
+ },
+ "details": [
+ "mission.dto.ts - MissionChallengeRequest ์ธํฐํ์ด์ค ({ memberId: number })",
+ "mission.repository.ts - findMemberMission(): SELECT * FROM member_mission WHERE member_id=? AND mission_id=?",
+ "mission.repository.ts - addMemberMission(): INSERT INTO member_mission(member_id, mission_id, status) VALUES(?,?,'CHALLENGING')",
+ "mission.service.ts - challengeMission(missionId, data): ์ค๋ณต ๋์ ๊ฒ์ฆ โ addMemberMission",
+ " ๊ฒ์ฆ ์คํจ ์ throw new Error('์ด๋ฏธ ๋์ ์ค์ธ ๋ฏธ์
์
๋๋ค.')",
+ "mission.controller.ts - handleChallengeMission: missionId = parseInt(req.params.missionId) โ service",
+ "index.ts์ ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "validation": "๋์ ํ๋ ค๋ ๋ฏธ์
์ด ์ด๋ฏธ ๋์ ์ค์ด์ง๋ ์์์ง ๊ฒ์ฆ ํ์",
+ "reference_week4": "week4/src/repositories/mission.repository.ts, week4 schema.sql์ member_mission ํ
์ด๋ธ"
+ },
+ {
+ "id": 12,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
2๋ฒ] Controller โ Service โ Repository โ DB ์์ฒญ ํ๋ฆ ์ ๋ฆฌ",
+ "status": "todo",
+ "details": [
+ "์: POST /api/v1/stores/:storeId/reviews ์์ฒญ ํ๋ฆ์ ์์๋๋ก ์์ฑ",
+ "1. ์ฌ์ฉ์๊ฐ POST /api/v1/stores/1/reviews ์์ฒญ ์ ์ก",
+ "2. index.ts์ ๋ผ์ฐํฐ๊ฐ handleCreateReview ์ปจํธ๋กค๋ฌ ํธ์ถ",
+ "3. Controller: req.body๋ฅผ ReviewCreateRequest ํ์
์ผ๋ก ๋ณํ(bodyToReview), storeId ํ์ฑ",
+ "4. Service: ๊ฐ๊ฒ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฆ(findStoreById) โ ์์ผ๋ฉด Error throw",
+ "5. Repository: INSERT INTO review ์ฟผ๋ฆฌ ์คํ โ insertId ๋ฐํ",
+ "6. Service: ๊ฒฐ๊ณผ๋ฅผ DTO๋ก ๋ณํํด Controller์ ๋ฐํ",
+ "7. Controller: 201 JSON ์๋ต ์ ์ก",
+ "์ํฌ๋ถ์ ์์ฝ ์ ๋ฆฌ ์น์
์ ์ด ๋ด์ฉ ํฌํจ"
+ ]
+ },
+ {
+ "id": 13,
+ "phase": "์ถ๊ฐ ๋ฏธ์
",
+ "title": "[๊ณตํต ๋ฏธ์
3๋ฒ] ํ์๊ฐ์
API์ bcrypt ๋น๋ฐ๋ฒํธ ํด์ฑ ์ถ๊ฐ",
+ "status": "todo",
+ "details": [
+ "npm install bcryptjs && npm install -D @types/bcryptjs (week4์ ์ด๋ฏธ ์ค์น๋จ)",
+ "member.dto.ts - MemberSignUpRequest ์ธํฐํ์ด์ค์ password ํ๋ ์ถ๊ฐ",
+ "member.repository.ts - addMember(): INSERT INTO member(..., password) VALUES(...)",
+ "member.service.ts - signUp(): const hashedPw = await bcrypt.hash(data.password, 10) โ addMember์ ์ ๋ฌ",
+ "member.controller.ts - handleSignUp ์์ฑ",
+ "POST /api/v1/members/signup ๋ผ์ฐํฐ ๋ฑ๋ก"
+ ],
+ "reference_week4": "week4์์ bcryptjs ์ด๋ฏธ ์ฌ์ฉ ์ค - week4/package.json ์ฐธ๊ณ "
+ },
+ {
+ "id": 14,
+ "phase": "์๋์ด ๋ฏธ์
",
+ "title": "[์๋์ด] ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ - JSON ํํ ์๋ฌ ์๋ต",
+ "status": "todo",
+ "details": [
+ "src/middleware/error.middleware.ts ์์ฑ",
+ "ErrorRequestHandler ํ์
์ฌ์ฉ: (err, req, res, next) => void",
+ "res.status(err.status || 500).json({ success: false, message: err.message || '์๋ฒ ์๋ฌ' })",
+ "index.ts ๋งจ ๋ง์ง๋ง์ app.use(errorMiddleware) ๋ฑ๋ก",
+ "Controller์์ try-catch ์ ๊ฑฐํ๊ฑฐ๋ next(err) ์ฌ์ฉ์ผ๋ก ๋ณ๊ฒฝ",
+ "๊ธฐ์กด HTML ์๋ฌ ์๋ต โ JSON ์๋ฌ ์๋ต์ผ๋ก ๊ฐ์ "
+ ],
+ "reference_week4": "week4/src/middleware/error.middleware.ts ๊ทธ๋๋ก ํ์ฉ ๊ฐ๋ฅ"
+ },
+ {
+ "id": 15,
+ "phase": "ํ
์คํธ",
+ "title": "Postman / curl๋ก ๊ฐ API ํธ์ถ ๋ฐ ์คํฌ๋ฆฐ์ท ์ ์ฅ",
+ "status": "todo",
+ "details": [
+ "npm run dev ๋ก ์๋ฒ ์คํ",
+ "API 1-1: POST /api/v1/stores - ๊ฐ๊ฒ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: POST /api/v1/stores/:storeId/reviews - ๋ฆฌ๋ทฐ ์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-2: ์กด์ฌํ์ง ์๋ storeId๋ก ์์ฒญ โ ์๋ฌ ์๋ต ์คํฌ๋ฆฐ์ท",
+ "API 1-3: POST /api/v1/stores/:storeId/missions - ๋ฏธ์
์ถ๊ฐ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: POST /api/v1/missions/:missionId/challenge - ๋์ ์ฑ๊ณต ์คํฌ๋ฆฐ์ท",
+ "API 1-4: ๋์ผ ๋ฏธ์
์ฌ๋์ โ '์ด๋ฏธ ๋์ ์ค' ์๋ฌ ์คํฌ๋ฆฐ์ท",
+ "DB์์ SELECT๋ก ๋ฐ์ดํฐ ์ฝ์
ํ์ธ ์คํฌ๋ฆฐ์ท"
+ ]
+ },
+ {
+ "id": 16,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "feature/chapter-05 ๋ธ๋์น์ push ๋ฐ PR ์์ฑ",
+ "status": "todo",
+ "details": [
+ "git add . && git commit -m 'feat: 5์ฃผ์ฐจ ๋ฏธ์
- API ๊ตฌํ (๊ฐ๊ฒ/๋ฆฌ๋ทฐ/๋ฏธ์
)'",
+ "git push origin feature/chapter-05",
+ "GitHub์์ PR ์์ฑ (main ๋ธ๋์น์ mergeํ์ง ๋ง ๊ฒ!)",
+ "์ํฌ๋ถ์ ๋ฏธ์
๊ธฐ๋ก๋์ GitHub ๋งํฌ ์ ์ถ",
+ "GitHub ์ด์ Close"
+ ]
+ },
+ {
+ "id": 17,
+ "phase": "๋ง๋ฌด๋ฆฌ",
+ "title": "ํต์ฌ ํค์๋ ๋ฐ ์์ฝ ์ ๋ฆฌ ์์ฑ",
+ "status": "todo",
+ "details": [
+ "์ด ํ์ผ ์๋จ์ keywords ์น์
์ ์ฐธ๊ณ ํด ์ํฌ๋ถ์ ๊ธฐ์
",
+ "์์ฝ ์ ๋ฆฌ: ControllerโServiceโRepositoryโDB ํ๋ฆ ์ค๋ช
",
+ "์ํด๋ฆฌ ์คํฌ๋ผ ์ง๋ฌธ ๋ต๋ณ: DTO ์์ด ์ฌ์ฉํ ๋์ ๋ฌธ์ ์ / Service Layer ํ์์ฑ"
+ ]
+ }
+ ],
+ "required_apis_summary": {
+ "must_implement": ["1-2 (๊ฐ๊ฒ์ ๋ฆฌ๋ทฐ ์ถ๊ฐํ๊ธฐ)", "1-4 (๋ฏธ์
๋์ ํ๊ธฐ)"],
+ "minimum_count": "ํ์ 2๊ฐ ํฌํจ ์ด 3๊ฐ ์ด์",
+ "senior_mission": "4๊ฐ ์ ๋ถ + JSON ์๋ฌ ์๋ต ๊ฐ์ "
+ },
+ "key_differences_from_week4": {
+ "database": "in-memory(week4) โ MySQL Connection Pool(week5)",
+ "modules": "flat ๊ตฌ์กฐ(week4) โ ๋ชจ๋ํ ๋ชจ๋
ธ๋ฆฌ์ค(week5: src/modules/{๋๋ฉ์ธ}/)",
+ "env": ".env ํ์ผ ์ถ๊ฐ (dotenv ์ฌ์ฉ)",
+ "cors": "cors ๋ฏธ๋ค์จ์ด ์ถ๊ฐ",
+ "error_response": "HTML ์๋ฌ(๊ธฐ๋ณธ) โ JSON ์๋ฌ(์๋์ด ๋ฏธ์
๊ฐ์ )"
+ }
+}
diff --git "a/\353\217\204\354\226\217/week9/tsconfig.json" "b/\353\217\204\354\226\217/week9/tsconfig.json"
new file mode 100644
index 0000000..f43e7d2
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/tsconfig.json"
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "./dist",
+ "target": "ESNext",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "lib": ["ESNext"],
+ "types": ["node"],
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "sourceMap": true,
+ "experimentalDecorators": true
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git "a/\353\217\204\354\226\217/week9/tsoa.json" "b/\353\217\204\354\226\217/week9/tsoa.json"
new file mode 100644
index 0000000..a42227d
--- /dev/null
+++ "b/\353\217\204\354\226\217/week9/tsoa.json"
@@ -0,0 +1,21 @@
+{
+ "entryFile": "src/index.ts",
+ "noImplicitAdditionalProperties": "throw-on-extras",
+ "controllerPathGlobs": ["src/**/*Controller.ts"],
+ "spec": {
+ "outputDirectory": "dist",
+ "specVersion": 3,
+ "basePath": "/api/v1",
+ "securityDefinitions": {
+ "bearerAuth": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "JWT"
+ }
+ }
+ },
+ "routes": {
+ "routesDir": "src/generated",
+ "authenticationModule": "src/authentication.ts"
+ }
+}