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 ์ง€์ •ํ•ด์ฃผ์„ธ์š”! -แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2025-09-18 แ„‹แ…ฉแ„’แ…ฎ 1 53 51 - -### 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" + } +}