diff --git a/.env b/.env
new file mode 100644
index 0000000..5bb745c
--- /dev/null
+++ b/.env
@@ -0,0 +1,6 @@
+DB_HOST=172.16.8.151
+DB_PORT=3306
+DB_USER=itam_admin
+DB_PASS=itam1234
+DB_NAME=itam
+PORT=3001
\ No newline at end of file
diff --git a/QR_system.md b/QR_system.md
new file mode 100644
index 0000000..6dc308b
--- /dev/null
+++ b/QR_system.md
@@ -0,0 +1,22 @@
+ 목적
+ - 정기적인 실물자산 점검을 실시하여 시스템 내 자산정보의 정확성을 확보하고, 실제 자산의 위치 및 상태를 체계적으로 파악·관리할 수 있는 관리체계를 구축
+ - QR 스캔 시스템을 통해 자산별 관리 이력 및 관리 책임자 정보를 즉시 확인할 수 있으며, 자산의 이동·변경 이력 추적과 안정적인 운영 관리를 추구
+
+ 구조 구성안
+ A. 실제 위치 정보를 가진 마스터 테이블 구축
+ - 현재 DB의 위치 정보는 건물 및 호수 정보(예: 기술개발센터 / 서버실)와 이미지 파일 내 픽셀 좌표 정보로 관리되고 있으며, 실제 서버가 설치된 랙(Rack) 및 물리적 위치 정보를 관리하는 항목은 존재하지 않음
+ - 이미지 좌표 데이터와 실제 자산 위치 데이터를 연결하는 별도 마스터 테이블을 생성하여, 좌표 정보와 물리적 위치 정보 간 관계 정의 필요
+
+ B. 기존 테이블 개편
+ - 픽셀 좌표 정보는 마스터 테이블에서 통합관리하고, 기존 테이블은 마스터 코드를상속받는 구조로 변경하여 유지 보수성을 확보
+
+ QR코드 정보
+ - 자산 QR : 시스템에 등록된 자산 고유의 자산번호
+ - 위치 QR : 물리적 위치 테이블에 저장된 마스터 코드
+
+ 현장실사 시나리오
+ ① 담당자가 서버 렉 전면에 부착된 위치 QR을 스캔
+ ② 위치 QR에 저장된 주소로 접속하여 세션에 현재 위치를 저장
+ ③ 자산에 부착된 자산 QR을 스캔하여 주소에 접속하게 되면 정보를 매칭하여 API로 전송
+ ④ 결합된 정보를 받아 기존 위치를 확인 혹은 업데이트
+ ⑤ 시스템에서 관리자가 확인하여 승인하게 되면 시스템에도 업데이트 완료
\ No newline at end of file
diff --git a/asset_pc (2026.06.15).xlsx b/asset_pc (2026.06.15).xlsx
deleted file mode 100644
index e443941..0000000
Binary files a/asset_pc (2026.06.15).xlsx and /dev/null differ
diff --git a/index.html b/index.html
index 50e1f65..dbeaba1 100644
--- a/index.html
+++ b/index.html
@@ -1,61 +1,63 @@
-
-
-
-
-
-
- 한맥가족 자산관리시스템
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ 한맥가족 자산관리시스템
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/label/DevExpress.Data.v14.1.dll b/label/DevExpress.Data.v14.1.dll
new file mode 100644
index 0000000..4b5f490
Binary files /dev/null and b/label/DevExpress.Data.v14.1.dll differ
diff --git a/label/DevExpress.Printing.v14.1.Core.dll b/label/DevExpress.Printing.v14.1.Core.dll
new file mode 100644
index 0000000..791bf60
Binary files /dev/null and b/label/DevExpress.Printing.v14.1.Core.dll differ
diff --git a/label/DevExpress.Utils.v14.1.dll b/label/DevExpress.Utils.v14.1.dll
new file mode 100644
index 0000000..c207258
Binary files /dev/null and b/label/DevExpress.Utils.v14.1.dll differ
diff --git a/label/DevExpress.XtraEditors.v14.1.dll b/label/DevExpress.XtraEditors.v14.1.dll
new file mode 100644
index 0000000..2d04260
Binary files /dev/null and b/label/DevExpress.XtraEditors.v14.1.dll differ
diff --git a/label/DevExpress.XtraGrid.v14.1.dll b/label/DevExpress.XtraGrid.v14.1.dll
new file mode 100644
index 0000000..1ae745a
Binary files /dev/null and b/label/DevExpress.XtraGrid.v14.1.dll differ
diff --git a/label/DevExpress.XtraLayout.v14.1.dll b/label/DevExpress.XtraLayout.v14.1.dll
new file mode 100644
index 0000000..483959f
Binary files /dev/null and b/label/DevExpress.XtraLayout.v14.1.dll differ
diff --git a/label/DevExpress.XtraPrinting.v14.1.dll b/label/DevExpress.XtraPrinting.v14.1.dll
new file mode 100644
index 0000000..97faa6b
Binary files /dev/null and b/label/DevExpress.XtraPrinting.v14.1.dll differ
diff --git a/label/LabelPrinter.exe b/label/LabelPrinter.exe
new file mode 100644
index 0000000..e9d7604
Binary files /dev/null and b/label/LabelPrinter.exe differ
diff --git a/label/Newtonsoft.Json.dll b/label/Newtonsoft.Json.dll
new file mode 100644
index 0000000..0aeee4f
Binary files /dev/null and b/label/Newtonsoft.Json.dll differ
diff --git a/label/WebQuery.dll b/label/WebQuery.dll
new file mode 100644
index 0000000..971ea4e
Binary files /dev/null and b/label/WebQuery.dll differ
diff --git a/label/config.ini b/label/config.ini
new file mode 100644
index 0000000..5d9b313
--- /dev/null
+++ b/label/config.ini
@@ -0,0 +1,4 @@
+[PRINT]
+FONT=8
+LEFT=143
+TOP=40
\ No newline at end of file
diff --git a/label/de/DevExpress.Data.v14.1.resources.dll b/label/de/DevExpress.Data.v14.1.resources.dll
new file mode 100644
index 0000000..7d244ff
Binary files /dev/null and b/label/de/DevExpress.Data.v14.1.resources.dll differ
diff --git a/label/de/DevExpress.Printing.v14.1.Core.resources.dll b/label/de/DevExpress.Printing.v14.1.Core.resources.dll
new file mode 100644
index 0000000..cd2768a
Binary files /dev/null and b/label/de/DevExpress.Printing.v14.1.Core.resources.dll differ
diff --git a/label/de/DevExpress.Utils.v14.1.resources.dll b/label/de/DevExpress.Utils.v14.1.resources.dll
new file mode 100644
index 0000000..70ef0a0
Binary files /dev/null and b/label/de/DevExpress.Utils.v14.1.resources.dll differ
diff --git a/label/de/DevExpress.XtraEditors.v14.1.resources.dll b/label/de/DevExpress.XtraEditors.v14.1.resources.dll
new file mode 100644
index 0000000..22cd884
Binary files /dev/null and b/label/de/DevExpress.XtraEditors.v14.1.resources.dll differ
diff --git a/label/de/DevExpress.XtraGrid.v14.1.resources.dll b/label/de/DevExpress.XtraGrid.v14.1.resources.dll
new file mode 100644
index 0000000..0a9d695
Binary files /dev/null and b/label/de/DevExpress.XtraGrid.v14.1.resources.dll differ
diff --git a/label/de/DevExpress.XtraLayout.v14.1.resources.dll b/label/de/DevExpress.XtraLayout.v14.1.resources.dll
new file mode 100644
index 0000000..ff430cf
Binary files /dev/null and b/label/de/DevExpress.XtraLayout.v14.1.resources.dll differ
diff --git a/label/de/DevExpress.XtraPrinting.v14.1.resources.dll b/label/de/DevExpress.XtraPrinting.v14.1.resources.dll
new file mode 100644
index 0000000..758aa61
Binary files /dev/null and b/label/de/DevExpress.XtraPrinting.v14.1.resources.dll differ
diff --git a/label/es/DevExpress.Data.v14.1.resources.dll b/label/es/DevExpress.Data.v14.1.resources.dll
new file mode 100644
index 0000000..8a89749
Binary files /dev/null and b/label/es/DevExpress.Data.v14.1.resources.dll differ
diff --git a/label/es/DevExpress.Printing.v14.1.Core.resources.dll b/label/es/DevExpress.Printing.v14.1.Core.resources.dll
new file mode 100644
index 0000000..e8004b1
Binary files /dev/null and b/label/es/DevExpress.Printing.v14.1.Core.resources.dll differ
diff --git a/label/es/DevExpress.Utils.v14.1.resources.dll b/label/es/DevExpress.Utils.v14.1.resources.dll
new file mode 100644
index 0000000..20d219d
Binary files /dev/null and b/label/es/DevExpress.Utils.v14.1.resources.dll differ
diff --git a/label/es/DevExpress.XtraEditors.v14.1.resources.dll b/label/es/DevExpress.XtraEditors.v14.1.resources.dll
new file mode 100644
index 0000000..39f4ceb
Binary files /dev/null and b/label/es/DevExpress.XtraEditors.v14.1.resources.dll differ
diff --git a/label/es/DevExpress.XtraGrid.v14.1.resources.dll b/label/es/DevExpress.XtraGrid.v14.1.resources.dll
new file mode 100644
index 0000000..69ead14
Binary files /dev/null and b/label/es/DevExpress.XtraGrid.v14.1.resources.dll differ
diff --git a/label/es/DevExpress.XtraLayout.v14.1.resources.dll b/label/es/DevExpress.XtraLayout.v14.1.resources.dll
new file mode 100644
index 0000000..c505d61
Binary files /dev/null and b/label/es/DevExpress.XtraLayout.v14.1.resources.dll differ
diff --git a/label/es/DevExpress.XtraPrinting.v14.1.resources.dll b/label/es/DevExpress.XtraPrinting.v14.1.resources.dll
new file mode 100644
index 0000000..a6e5780
Binary files /dev/null and b/label/es/DevExpress.XtraPrinting.v14.1.resources.dll differ
diff --git a/label/ja/DevExpress.Data.v14.1.resources.dll b/label/ja/DevExpress.Data.v14.1.resources.dll
new file mode 100644
index 0000000..cf7eca3
Binary files /dev/null and b/label/ja/DevExpress.Data.v14.1.resources.dll differ
diff --git a/label/ja/DevExpress.Printing.v14.1.Core.resources.dll b/label/ja/DevExpress.Printing.v14.1.Core.resources.dll
new file mode 100644
index 0000000..4fbb4bc
Binary files /dev/null and b/label/ja/DevExpress.Printing.v14.1.Core.resources.dll differ
diff --git a/label/ja/DevExpress.Utils.v14.1.resources.dll b/label/ja/DevExpress.Utils.v14.1.resources.dll
new file mode 100644
index 0000000..1db3fb6
Binary files /dev/null and b/label/ja/DevExpress.Utils.v14.1.resources.dll differ
diff --git a/label/ja/DevExpress.XtraEditors.v14.1.resources.dll b/label/ja/DevExpress.XtraEditors.v14.1.resources.dll
new file mode 100644
index 0000000..857678b
Binary files /dev/null and b/label/ja/DevExpress.XtraEditors.v14.1.resources.dll differ
diff --git a/label/ja/DevExpress.XtraGrid.v14.1.resources.dll b/label/ja/DevExpress.XtraGrid.v14.1.resources.dll
new file mode 100644
index 0000000..b1b60b0
Binary files /dev/null and b/label/ja/DevExpress.XtraGrid.v14.1.resources.dll differ
diff --git a/label/ja/DevExpress.XtraLayout.v14.1.resources.dll b/label/ja/DevExpress.XtraLayout.v14.1.resources.dll
new file mode 100644
index 0000000..0033732
Binary files /dev/null and b/label/ja/DevExpress.XtraLayout.v14.1.resources.dll differ
diff --git a/label/ja/DevExpress.XtraPrinting.v14.1.resources.dll b/label/ja/DevExpress.XtraPrinting.v14.1.resources.dll
new file mode 100644
index 0000000..3bf94c9
Binary files /dev/null and b/label/ja/DevExpress.XtraPrinting.v14.1.resources.dll differ
diff --git a/label/ru/DevExpress.Data.v14.1.resources.dll b/label/ru/DevExpress.Data.v14.1.resources.dll
new file mode 100644
index 0000000..8f57abe
Binary files /dev/null and b/label/ru/DevExpress.Data.v14.1.resources.dll differ
diff --git a/label/ru/DevExpress.Printing.v14.1.Core.resources.dll b/label/ru/DevExpress.Printing.v14.1.Core.resources.dll
new file mode 100644
index 0000000..172e382
Binary files /dev/null and b/label/ru/DevExpress.Printing.v14.1.Core.resources.dll differ
diff --git a/label/ru/DevExpress.Utils.v14.1.resources.dll b/label/ru/DevExpress.Utils.v14.1.resources.dll
new file mode 100644
index 0000000..82ff924
Binary files /dev/null and b/label/ru/DevExpress.Utils.v14.1.resources.dll differ
diff --git a/label/ru/DevExpress.XtraEditors.v14.1.resources.dll b/label/ru/DevExpress.XtraEditors.v14.1.resources.dll
new file mode 100644
index 0000000..6f9543c
Binary files /dev/null and b/label/ru/DevExpress.XtraEditors.v14.1.resources.dll differ
diff --git a/label/ru/DevExpress.XtraGrid.v14.1.resources.dll b/label/ru/DevExpress.XtraGrid.v14.1.resources.dll
new file mode 100644
index 0000000..c01f2f2
Binary files /dev/null and b/label/ru/DevExpress.XtraGrid.v14.1.resources.dll differ
diff --git a/label/ru/DevExpress.XtraLayout.v14.1.resources.dll b/label/ru/DevExpress.XtraLayout.v14.1.resources.dll
new file mode 100644
index 0000000..531986b
Binary files /dev/null and b/label/ru/DevExpress.XtraLayout.v14.1.resources.dll differ
diff --git a/label/ru/DevExpress.XtraPrinting.v14.1.resources.dll b/label/ru/DevExpress.XtraPrinting.v14.1.resources.dll
new file mode 100644
index 0000000..124cd9b
Binary files /dev/null and b/label/ru/DevExpress.XtraPrinting.v14.1.resources.dll differ
diff --git a/label/tmp/file_1.txt b/label/tmp/file_1.txt
new file mode 100644
index 0000000..0df722c
--- /dev/null
+++ b/label/tmp/file_1.txt
@@ -0,0 +1,7 @@
+자산번호 : 210312
+자산명 : 가을-PC(i5-12400F)
+공급사 : (주)가을디에스
+자산위치 : 지반부
+관리부서 : 전산
+사용자 : 박노석
+취득일자 : 2024-08-05
diff --git a/label/tmp/file_1.txt - 바로 가기.lnk b/label/tmp/file_1.txt - 바로 가기.lnk
new file mode 100644
index 0000000..f30ea46
Binary files /dev/null and b/label/tmp/file_1.txt - 바로 가기.lnk differ
diff --git a/map_config.json b/map_config.json
index d280885..7f7e13c 100644
--- a/map_config.json
+++ b/map_config.json
@@ -1,726 +1,742 @@
-{
- "img/location_photo/IDC/서관205.png": [
- {
- "x": "50.78",
- "y": "1.53",
- "w": "46.10",
- "h": "6.27",
- "asset_id": null
- },
- {
- "x": "50.78",
- "y": "10.35",
- "w": "46.10",
- "h": "6.27",
- "asset_id": null
- },
- {
- "x": "50.78",
- "y": "19.06",
- "w": "46.10",
- "h": "6.50",
- "asset_id": null
- },
- {
- "x": "50.78",
- "y": "27.89",
- "w": "46.10",
- "h": "6.50",
- "asset_id": "server_1779761946023_14"
- },
- {
- "x": "50.78",
- "y": "36.71",
- "w": "46.10",
- "h": "6.50",
- "asset_id": "server_1779761946023_18"
- },
- {
- "x": "50.78",
- "y": "45.64",
- "w": "46.10",
- "h": "6.32",
- "asset_id": "server_1779761946023_23"
- },
- {
- "x": "50.78",
- "y": "54.25",
- "w": "46.10",
- "h": "6.54",
- "asset_id": "server_1779761946023_24"
- },
- {
- "x": "50.78",
- "y": "63.29",
- "w": "46.10",
- "h": "6.50",
- "asset_id": "server_1779761946023_1"
- },
- {
- "x": "50.78",
- "y": "72.00",
- "w": "46.10",
- "h": "6.32",
- "asset_id": "server_1779761946023_21"
- },
- {
- "x": "50.78",
- "y": "81.92",
- "w": "18.40",
- "h": "15.58",
- "asset_id": "server_1779761946023_17"
- },
- {
- "x": "78.62",
- "y": "81.92",
- "w": "18.31",
- "h": "15.58",
- "asset_id": "server_1779761946023_20"
- }
- ],
- "img/location_photo/IDC/서관202.png": [
- {
- "x": "56.35",
- "y": "64.02",
- "w": "40.87",
- "h": "6.24",
- "asset_id": "server_1779761946023_9"
- },
- {
- "x": "56.35",
- "y": "71.57",
- "w": "40.87",
- "h": "6.24",
- "asset_id": "server_1779761946023_10"
- },
- {
- "x": "56.35",
- "y": "79.17",
- "w": "40.87",
- "h": "6.24",
- "asset_id": "server_1779761946023_26"
- },
- {
- "x": "56.35",
- "y": "86.66",
- "w": "40.87",
- "h": "6.24",
- "asset_id": "server_1779761946023_8"
- },
- {
- "x": "56.35",
- "y": "32.01",
- "w": "40.87",
- "h": "6.24",
- "asset_id": null
- }
- ],
- "img/location_photo/IDC/서관203.png": [
- {
- "x": "56.07",
- "y": "2.54",
- "w": "41.11",
- "h": "6.52",
- "asset_id": null
- },
- {
- "x": "56.07",
- "y": "10.12",
- "w": "41.11",
- "h": "6.52",
- "asset_id": null
- },
- {
- "x": "56.07",
- "y": "17.80",
- "w": "41.11",
- "h": "6.52",
- "asset_id": null
- },
- {
- "x": "56.07",
- "y": "63.51",
- "w": "41.11",
- "h": "6.52",
- "asset_id": null
- },
- {
- "x": "56.07",
- "y": "71.19",
- "w": "41.11",
- "h": "6.52",
- "asset_id": null
- },
- {
- "x": "56.07",
- "y": "87.70",
- "w": "41.11",
- "h": "6.52",
- "asset_id": "server_1779761946023_25"
- }
- ],
- "img/location_photo/IDC/서관204.png": [
- {
- "x": "48.87",
- "y": "2.73",
- "w": "47.80",
- "h": "6.27",
- "asset_id": null
- },
- {
- "x": "48.87",
- "y": "10.38",
- "w": "47.80",
- "h": "6.27",
- "asset_id": "server_1779761946023_3"
- },
- {
- "x": "48.87",
- "y": "17.93",
- "w": "47.80",
- "h": "6.50",
- "asset_id": "server_1779761946023_6"
- },
- {
- "x": "48.87",
- "y": "25.49",
- "w": "47.80",
- "h": "6.50",
- "asset_id": null
- },
- {
- "x": "48.87",
- "y": "33.17",
- "w": "47.80",
- "h": "6.50",
- "asset_id": "server_1779761946023_5"
- },
- {
- "x": "48.87",
- "y": "40.59",
- "w": "47.80",
- "h": "6.50",
- "asset_id": "server_1779761946023_4"
- },
- {
- "x": "48.87",
- "y": "48.40",
- "w": "47.80",
- "h": "6.50",
- "asset_id": null
- },
- {
- "x": "48.87",
- "y": "55.95",
- "w": "47.80",
- "h": "6.50",
- "asset_id": "server_1779761946023_19"
- },
- {
- "x": "48.87",
- "y": "63.63",
- "w": "47.80",
- "h": "6.50",
- "asset_id": "server_1779761946023_2"
- },
- {
- "x": "48.87",
- "y": "71.06",
- "w": "47.80",
- "h": "6.50",
- "asset_id": "server_1779761946023_0"
- },
- {
- "x": "48.87",
- "y": "78.74",
- "w": "47.80",
- "h": "6.50",
- "asset_id": "server_1779761946023_7"
- },
- {
- "x": "48.87",
- "y": "86.68",
- "w": "18.99",
- "h": "12.62",
- "asset_id": "server_1779761946023_29"
- }
- ],
- "img/location_photo/IDC/동관53.png": [
- {
- "x": "61.62",
- "y": "3.08",
- "w": "35.96",
- "h": "7.90",
- "asset_id": "server_1779761946023_13"
- },
- {
- "x": "61.62",
- "y": "12.68",
- "w": "35.96",
- "h": "7.90",
- "asset_id": "server_1779761946023_15"
- },
- {
- "x": "61.62",
- "y": "21.75",
- "w": "35.96",
- "h": "7.90",
- "asset_id": "server_1779761946023_22"
- }
- ],
- "img/location_photo/IDC/동관54.png": [
- {
- "x": "54.71",
- "y": "2.57",
- "w": "42.42",
- "h": "6.50",
- "asset_id": null
- },
- {
- "x": "54.71",
- "y": "10.38",
- "w": "42.42",
- "h": "6.50",
- "asset_id": null
- },
- {
- "x": "54.71",
- "y": "27.15",
- "w": "42.42",
- "h": "6.62",
- "asset_id": "server_1779761946023_12"
- },
- {
- "x": "54.71",
- "y": "43.54",
- "w": "42.42",
- "h": "6.50",
- "asset_id": "server_1779761946023_11"
- },
- {
- "x": "54.71",
- "y": "54.93",
- "w": "42.42",
- "h": "6.50",
- "asset_id": null
- },
- {
- "x": "54.71",
- "y": "70.16",
- "w": "42.42",
- "h": "6.50",
- "asset_id": "server_1779761946023_27"
- },
- {
- "x": "54.71",
- "y": "79.51",
- "w": "42.42",
- "h": "6.50",
- "asset_id": "server_1779761946023_28"
- }
- ],
- "img/location_photo/한맥빌딩/MDF실/MDF_1.png": [
- {
- "x": "49.33",
- "y": "14.99",
- "w": "7.35",
- "h": "11.22",
- "asset_id": "cdp0e0c"
- },
- {
- "x": "59.23",
- "y": "14.99",
- "w": "7.35",
- "h": "11.22",
- "asset_id": "emys9gb"
- },
- {
- "x": "69.22",
- "y": "14.99",
- "w": "7.35",
- "h": "11.22",
- "asset_id": "vmbv3pj"
- },
- {
- "x": "79.12",
- "y": "14.99",
- "w": "7.35",
- "h": "11.22",
- "asset_id": "4fysk40"
- },
- {
- "x": "88.97",
- "y": "14.99",
- "w": "7.35",
- "h": "11.22",
- "asset_id": "x6jaehn"
- },
- {
- "x": "48.57",
- "y": "34.11",
- "w": "7.52",
- "h": "11.44",
- "asset_id": "t87p0l0"
- },
- {
- "x": "56.80",
- "y": "34.11",
- "w": "7.52",
- "h": "11.44",
- "asset_id": "ywosxiv"
- },
- {
- "x": "64.94",
- "y": "34.11",
- "w": "7.52",
- "h": "11.44",
- "asset_id": null
- },
- {
- "x": "72.89",
- "y": "34.11",
- "w": "7.56",
- "h": "11.44",
- "asset_id": null
- },
- {
- "x": "81.22",
- "y": "34.06",
- "w": "7.52",
- "h": "11.44",
- "asset_id": null
- },
- {
- "x": "89.36",
- "y": "34.06",
- "w": "7.52",
- "h": "11.44",
- "asset_id": "tormk2l"
- },
- {
- "x": "48.57",
- "y": "53.06",
- "w": "9.06",
- "h": "20.99",
- "asset_id": null
- },
- {
- "x": "58.48",
- "y": "53.06",
- "w": "9.06",
- "h": "20.99",
- "asset_id": "server_1779761946023_30"
- },
- {
- "x": "68.55",
- "y": "53.06",
- "w": "9.06",
- "h": "20.99",
- "asset_id": "server_1779761946023_31"
- },
- {
- "x": "78.54",
- "y": "53.06",
- "w": "9.01",
- "h": "20.99",
- "asset_id": "server_1779761946023_32"
- },
- {
- "x": "89.36",
- "y": "53.22",
- "w": "7.45",
- "h": "10.11",
- "asset_id": "TEMP-03g59cx"
- },
- {
- "x": "89.36",
- "y": "64.92",
- "w": "7.45",
- "h": "9.81",
- "asset_id": "TEMP-06l8zjx"
- },
- {
- "x": "48.57",
- "y": "77.41",
- "w": "9.18",
- "h": "21.45",
- "asset_id": "server_1779761946023_34"
- },
- {
- "x": "58.56",
- "y": "77.41",
- "w": "9.23",
- "h": "21.45",
- "asset_id": "server_1779761946023_35"
- },
- {
- "x": "68.63",
- "y": "77.41",
- "w": "9.06",
- "h": "21.45",
- "asset_id": "server_1779761946023_36"
- },
- {
- "x": "78.71",
- "y": "77.41",
- "w": "8.98",
- "h": "21.45",
- "asset_id": "server_1779761946023_37"
- }
- ],
- "img/location_photo/한맥빌딩/MDF실/MDF_2.png": [
- {
- "x": "56.59",
- "y": "44.53",
- "w": "40.65",
- "h": "6.90",
- "asset_id": "1vbkbzr"
- },
- {
- "x": "56.59",
- "y": "54.80",
- "w": "40.65",
- "h": "6.90",
- "asset_id": "0ru63ay"
- },
- {
- "x": "56.59",
- "y": "65.94",
- "w": "40.65",
- "h": "6.90",
- "asset_id": "server_1779761946023_40"
- }
- ],
- "img/location_photo/한맥빌딩/MDF실/MDF_3.png": [
- {
- "x": "56.71",
- "y": "13.20",
- "w": "40.58",
- "h": "6.90",
- "asset_id": null
- },
- {
- "x": "56.71",
- "y": "23.57",
- "w": "40.58",
- "h": "6.90",
- "asset_id": "8aeog58"
- },
- {
- "x": "56.71",
- "y": "34.57",
- "w": "40.58",
- "h": "6.90",
- "asset_id": "ywosxiv"
- },
- {
- "x": "56.71",
- "y": "44.69",
- "w": "40.58",
- "h": "6.90",
- "asset_id": "1vbkbzr"
- },
- {
- "x": "56.71",
- "y": "54.80",
- "w": "40.58",
- "h": "6.90",
- "asset_id": "0ru63ay"
- },
- {
- "x": "56.71",
- "y": "65.81",
- "w": "40.58",
- "h": "6.90",
- "asset_id": "server_1779761946023_40"
- },
- {
- "x": "56.71",
- "y": "76.05",
- "w": "40.58",
- "h": "6.90",
- "asset_id": null
- }
- ],
- "img/location_photo/한맥빌딩/MDF실/MDF_4.png": [
- {
- "x": "52.36",
- "y": "64.02",
- "w": "44.60",
- "h": "6.73",
- "asset_id": "5tbpuy4"
- }
- ],
- "img/location_photo/기술개발센터/서버실/서버실_1.png": [
- {
- "x": "69.45",
- "y": "3.30",
- "w": "8.58",
- "h": "11.45",
- "asset_id": "server_1779761946023_41"
- },
- {
- "x": "79.05",
- "y": "3.30",
- "w": "12.02",
- "h": "11.45",
- "asset_id": "server_1779761946023_42"
- },
- {
- "x": "90.16",
- "y": "26.04",
- "w": "8.43",
- "h": "21.11",
- "asset_id": "server_1779761946023_43"
- },
- {
- "x": "53.04",
- "y": "52.91",
- "w": "8.43",
- "h": "21.11",
- "asset_id": "server_1779761946023_44"
- },
- {
- "x": "62.36",
- "y": "52.91",
- "w": "8.43",
- "h": "21.11",
- "asset_id": "server_1779761946023_45"
- },
- {
- "x": "71.65",
- "y": "52.91",
- "w": "8.43",
- "h": "21.11",
- "asset_id": "server_1779761946023_46"
- },
- {
- "x": "80.87",
- "y": "52.91",
- "w": "8.43",
- "h": "21.11",
- "asset_id": "server_1779761946023_47"
- },
- {
- "x": "90.08",
- "y": "52.91",
- "w": "8.43",
- "h": "21.11",
- "asset_id": "server_1779761946023_48"
- },
- {
- "x": "43.78",
- "y": "78.00",
- "w": "8.50",
- "h": "21.23",
- "asset_id": "19kai41"
- },
- {
- "x": "53.15",
- "y": "78.00",
- "w": "8.43",
- "h": "21.23",
- "asset_id": "server_1779761946023_50"
- },
- {
- "x": "62.36",
- "y": "78.00",
- "w": "8.43",
- "h": "21.23",
- "asset_id": "server_1779761946023_51"
- },
- {
- "x": "71.36",
- "y": "78.00",
- "w": "8.43",
- "h": "21.23",
- "asset_id": "server_1779761946023_52"
- },
- {
- "x": "80.53",
- "y": "78.00",
- "w": "8.43",
- "h": "21.23",
- "asset_id": "srlmyar"
- },
- {
- "x": "89.77",
- "y": "78.00",
- "w": "8.50",
- "h": "21.23",
- "asset_id": "server_1779761946023_54"
- }
- ],
- "img/location_photo/기술개발센터/서버실/서버실_2.png": [
- {
- "x": "49.60",
- "y": "1.93",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_55"
- },
- {
- "x": "49.60",
- "y": "12.04",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_56"
- },
- {
- "x": "49.60",
- "y": "21.39",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_57"
- },
- {
- "x": "49.60",
- "y": "30.73",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_58"
- },
- {
- "x": "49.60",
- "y": "39.82",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_59"
- },
- {
- "x": "49.60",
- "y": "50.13",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_60"
- },
- {
- "x": "49.60",
- "y": "59.28",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_53"
- },
- {
- "x": "49.60",
- "y": "68.63",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_62"
- },
- {
- "x": "49.60",
- "y": "77.84",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_63"
- },
- {
- "x": "49.60",
- "y": "86.93",
- "w": "47.19",
- "h": "6.75",
- "asset_id": "server_1779761946023_64"
- }
- ]
+{
+ "img/location_photo/IDC/서관205.png": [
+ {
+ "x": "50.78",
+ "y": "1.53",
+ "w": "46.10",
+ "h": "6.27",
+ "asset_id": null
+ },
+ {
+ "x": "50.78",
+ "y": "10.35",
+ "w": "46.10",
+ "h": "6.27",
+ "asset_id": null
+ },
+ {
+ "x": "50.78",
+ "y": "19.06",
+ "w": "46.10",
+ "h": "6.50",
+ "asset_id": null
+ },
+ {
+ "x": "50.78",
+ "y": "27.89",
+ "w": "46.10",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_14"
+ },
+ {
+ "x": "50.78",
+ "y": "36.71",
+ "w": "46.10",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_18"
+ },
+ {
+ "x": "50.78",
+ "y": "45.64",
+ "w": "46.10",
+ "h": "6.32",
+ "asset_id": "server_1779761946023_23"
+ },
+ {
+ "x": "50.78",
+ "y": "54.25",
+ "w": "46.10",
+ "h": "6.54",
+ "asset_id": "server_1779761946023_24"
+ },
+ {
+ "x": "50.78",
+ "y": "63.29",
+ "w": "46.10",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_1"
+ },
+ {
+ "x": "50.78",
+ "y": "72.00",
+ "w": "46.10",
+ "h": "6.32",
+ "asset_id": "server_1779761946023_21"
+ },
+ {
+ "x": "50.78",
+ "y": "81.92",
+ "w": "18.40",
+ "h": "15.58",
+ "asset_id": "server_1779761946023_17"
+ },
+ {
+ "x": "78.62",
+ "y": "81.92",
+ "w": "18.31",
+ "h": "15.58",
+ "asset_id": "server_1779761946023_20"
+ }
+ ],
+ "img/location_photo/IDC/서관202.png": [
+ {
+ "x": "56.35",
+ "y": "64.02",
+ "w": "40.87",
+ "h": "6.24",
+ "asset_id": "server_1779761946023_9"
+ },
+ {
+ "x": "56.35",
+ "y": "71.57",
+ "w": "40.87",
+ "h": "6.24",
+ "asset_id": "server_1779761946023_10"
+ },
+ {
+ "x": "56.35",
+ "y": "79.17",
+ "w": "40.87",
+ "h": "6.24",
+ "asset_id": "server_1779761946023_26"
+ },
+ {
+ "x": "56.35",
+ "y": "86.66",
+ "w": "40.87",
+ "h": "6.24",
+ "asset_id": "server_1779761946023_8"
+ },
+ {
+ "x": "56.35",
+ "y": "32.01",
+ "w": "40.87",
+ "h": "6.24",
+ "asset_id": "9pvkqyi"
+ }
+ ],
+ "img/location_photo/IDC/서관203.png": [
+ {
+ "x": "56.07",
+ "y": "2.54",
+ "w": "41.11",
+ "h": "6.52",
+ "asset_id": null
+ },
+ {
+ "x": "56.07",
+ "y": "10.12",
+ "w": "41.11",
+ "h": "6.52",
+ "asset_id": null
+ },
+ {
+ "x": "56.07",
+ "y": "17.80",
+ "w": "41.11",
+ "h": "6.52",
+ "asset_id": null
+ },
+ {
+ "x": "56.07",
+ "y": "63.51",
+ "w": "41.11",
+ "h": "6.52",
+ "asset_id": null
+ },
+ {
+ "x": "56.07",
+ "y": "71.19",
+ "w": "41.11",
+ "h": "6.52",
+ "asset_id": null
+ },
+ {
+ "x": "56.07",
+ "y": "87.70",
+ "w": "41.11",
+ "h": "6.52",
+ "asset_id": "server_1779761946023_25"
+ }
+ ],
+ "img/location_photo/IDC/서관204.png": [
+ {
+ "x": "48.87",
+ "y": "2.73",
+ "w": "47.80",
+ "h": "6.27",
+ "asset_id": null
+ },
+ {
+ "x": "48.87",
+ "y": "10.38",
+ "w": "47.80",
+ "h": "6.27",
+ "asset_id": "server_1779761946023_3"
+ },
+ {
+ "x": "48.87",
+ "y": "17.93",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_6"
+ },
+ {
+ "x": "48.87",
+ "y": "25.49",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": null
+ },
+ {
+ "x": "48.87",
+ "y": "33.17",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_5"
+ },
+ {
+ "x": "48.87",
+ "y": "40.59",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_4"
+ },
+ {
+ "x": "48.87",
+ "y": "48.40",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": null
+ },
+ {
+ "x": "48.87",
+ "y": "55.95",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_19"
+ },
+ {
+ "x": "48.87",
+ "y": "63.63",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_2"
+ },
+ {
+ "x": "48.87",
+ "y": "71.06",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_0"
+ },
+ {
+ "x": "48.87",
+ "y": "78.74",
+ "w": "47.80",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_7"
+ },
+ {
+ "x": "48.87",
+ "y": "86.68",
+ "w": "18.99",
+ "h": "12.62",
+ "asset_id": "server_1779761946023_29"
+ }
+ ],
+ "img/location_photo/IDC/동관53.png": [
+ {
+ "x": "61.62",
+ "y": "3.08",
+ "w": "35.96",
+ "h": "7.90",
+ "asset_id": "server_1779761946023_13"
+ },
+ {
+ "x": "61.62",
+ "y": "12.68",
+ "w": "35.96",
+ "h": "7.90",
+ "asset_id": "server_1779761946023_15"
+ },
+ {
+ "x": "61.62",
+ "y": "21.75",
+ "w": "35.96",
+ "h": "7.90",
+ "asset_id": "server_1779761946023_22"
+ }
+ ],
+ "img/location_photo/IDC/동관54.png": [
+ {
+ "x": "54.71",
+ "y": "2.57",
+ "w": "42.42",
+ "h": "6.50",
+ "asset_id": null
+ },
+ {
+ "x": "54.71",
+ "y": "10.38",
+ "w": "42.42",
+ "h": "6.50",
+ "asset_id": null
+ },
+ {
+ "x": "54.71",
+ "y": "27.15",
+ "w": "42.42",
+ "h": "6.62",
+ "asset_id": "server_1779761946023_12"
+ },
+ {
+ "x": "54.71",
+ "y": "43.54",
+ "w": "42.42",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_11"
+ },
+ {
+ "x": "54.71",
+ "y": "54.93",
+ "w": "42.42",
+ "h": "6.50",
+ "asset_id": null
+ },
+ {
+ "x": "54.71",
+ "y": "70.16",
+ "w": "42.42",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_27"
+ },
+ {
+ "x": "54.71",
+ "y": "79.51",
+ "w": "42.42",
+ "h": "6.50",
+ "asset_id": "server_1779761946023_28"
+ }
+ ],
+ "img/location_photo/한맥빌딩/MDF실/MDF_1.png": [
+ {
+ "x": "49.33",
+ "y": "14.99",
+ "w": "7.35",
+ "h": "11.22",
+ "asset_id": "cdp0e0c"
+ },
+ {
+ "x": "59.23",
+ "y": "14.99",
+ "w": "7.35",
+ "h": "11.22",
+ "asset_id": "emys9gb"
+ },
+ {
+ "x": "69.22",
+ "y": "14.99",
+ "w": "7.35",
+ "h": "11.22",
+ "asset_id": "vmbv3pj"
+ },
+ {
+ "x": "79.12",
+ "y": "14.99",
+ "w": "7.35",
+ "h": "11.22",
+ "asset_id": "4fysk40"
+ },
+ {
+ "x": "88.97",
+ "y": "14.99",
+ "w": "7.35",
+ "h": "11.22",
+ "asset_id": "x6jaehn"
+ },
+ {
+ "x": "48.57",
+ "y": "34.11",
+ "w": "7.52",
+ "h": "11.44",
+ "asset_id": "t87p0l0"
+ },
+ {
+ "x": "56.80",
+ "y": "34.11",
+ "w": "7.52",
+ "h": "11.44",
+ "asset_id": "ywosxiv"
+ },
+ {
+ "x": "64.94",
+ "y": "34.11",
+ "w": "7.52",
+ "h": "11.44",
+ "asset_id": null
+ },
+ {
+ "x": "72.89",
+ "y": "34.11",
+ "w": "7.56",
+ "h": "11.44",
+ "asset_id": null
+ },
+ {
+ "x": "81.22",
+ "y": "34.06",
+ "w": "7.52",
+ "h": "11.44",
+ "asset_id": null
+ },
+ {
+ "x": "89.36",
+ "y": "34.06",
+ "w": "7.52",
+ "h": "11.44",
+ "asset_id": "tormk2l"
+ },
+ {
+ "x": "48.57",
+ "y": "53.06",
+ "w": "9.06",
+ "h": "20.99",
+ "asset_id": null
+ },
+ {
+ "x": "58.48",
+ "y": "53.06",
+ "w": "9.06",
+ "h": "20.99",
+ "asset_id": "server_1779761946023_30"
+ },
+ {
+ "x": "68.55",
+ "y": "53.06",
+ "w": "9.06",
+ "h": "20.99",
+ "asset_id": "server_1779761946023_31"
+ },
+ {
+ "x": "78.54",
+ "y": "53.06",
+ "w": "9.01",
+ "h": "20.99",
+ "asset_id": "server_1779761946023_32"
+ },
+ {
+ "x": "89.36",
+ "y": "53.22",
+ "w": "7.45",
+ "h": "10.11",
+ "asset_id": "TEMP-03g59cx"
+ },
+ {
+ "x": "89.36",
+ "y": "64.92",
+ "w": "7.45",
+ "h": "9.81",
+ "asset_id": "TEMP-06l8zjx"
+ },
+ {
+ "x": "48.57",
+ "y": "77.41",
+ "w": "9.18",
+ "h": "21.45",
+ "asset_id": "server_1779761946023_34"
+ },
+ {
+ "x": "58.56",
+ "y": "77.41",
+ "w": "9.23",
+ "h": "21.45",
+ "asset_id": "server_1779761946023_35"
+ },
+ {
+ "x": "68.63",
+ "y": "77.41",
+ "w": "9.06",
+ "h": "21.45",
+ "asset_id": "server_1779761946023_36"
+ },
+ {
+ "x": "78.71",
+ "y": "77.41",
+ "w": "8.98",
+ "h": "21.45",
+ "asset_id": "server_1779761946023_37"
+ }
+ ],
+ "img/location_photo/한맥빌딩/MDF실/MDF_2.png": [
+ {
+ "x": "56.59",
+ "y": "44.53",
+ "w": "40.65",
+ "h": "6.90",
+ "asset_id": "1vbkbzr"
+ },
+ {
+ "x": "56.59",
+ "y": "54.80",
+ "w": "40.65",
+ "h": "6.90",
+ "asset_id": "0ru63ay"
+ },
+ {
+ "x": "56.59",
+ "y": "65.94",
+ "w": "40.65",
+ "h": "6.90",
+ "asset_id": "server_1779761946023_40"
+ }
+ ],
+ "img/location_photo/한맥빌딩/MDF실/MDF_3.png": [
+ {
+ "x": "56.71",
+ "y": "13.20",
+ "w": "40.58",
+ "h": "6.90",
+ "asset_id": null
+ },
+ {
+ "x": "56.71",
+ "y": "23.57",
+ "w": "40.58",
+ "h": "6.90",
+ "asset_id": "8aeog58"
+ },
+ {
+ "x": "56.71",
+ "y": "34.57",
+ "w": "40.58",
+ "h": "6.90",
+ "asset_id": "ywosxiv"
+ },
+ {
+ "x": "56.71",
+ "y": "44.69",
+ "w": "40.58",
+ "h": "6.90",
+ "asset_id": "1vbkbzr"
+ },
+ {
+ "x": "56.71",
+ "y": "54.80",
+ "w": "40.58",
+ "h": "6.90",
+ "asset_id": "0ru63ay"
+ },
+ {
+ "x": "56.71",
+ "y": "65.81",
+ "w": "40.58",
+ "h": "6.90",
+ "asset_id": "server_1779761946023_40"
+ },
+ {
+ "x": "56.71",
+ "y": "76.05",
+ "w": "40.58",
+ "h": "6.90",
+ "asset_id": null
+ }
+ ],
+ "img/location_photo/한맥빌딩/MDF실/MDF_4.png": [
+ {
+ "x": "52.36",
+ "y": "64.02",
+ "w": "44.60",
+ "h": "6.73",
+ "asset_id": "5tbpuy4"
+ }
+ ],
+ "img/location_photo/기술개발센터/서버실/서버실_1.png": [
+ {
+ "x": "69.45",
+ "y": "3.30",
+ "w": "8.58",
+ "h": "11.45",
+ "asset_id": "server_1779761946023_41"
+ },
+ {
+ "x": "79.05",
+ "y": "3.30",
+ "w": "12.02",
+ "h": "11.45",
+ "asset_id": "server_1779761946023_42"
+ },
+ {
+ "x": "90.16",
+ "y": "26.04",
+ "w": "8.43",
+ "h": "21.11",
+ "asset_id": "server_1779761946023_43"
+ },
+ {
+ "x": "53.04",
+ "y": "52.91",
+ "w": "8.43",
+ "h": "21.11",
+ "asset_id": "server_1779761946023_44"
+ },
+ {
+ "x": "62.36",
+ "y": "52.91",
+ "w": "8.43",
+ "h": "21.11",
+ "asset_id": "server_1779761946023_45"
+ },
+ {
+ "x": "71.65",
+ "y": "52.91",
+ "w": "8.43",
+ "h": "21.11",
+ "asset_id": "server_1779761946023_46"
+ },
+ {
+ "x": "80.87",
+ "y": "52.91",
+ "w": "8.43",
+ "h": "21.11",
+ "asset_id": "server_1779761946023_47"
+ },
+ {
+ "x": "90.08",
+ "y": "52.91",
+ "w": "8.43",
+ "h": "21.11",
+ "asset_id": "server_1779761946023_48"
+ },
+ {
+ "x": "43.78",
+ "y": "78.00",
+ "w": "8.50",
+ "h": "21.23",
+ "asset_id": "19kai41"
+ },
+ {
+ "x": "53.15",
+ "y": "78.00",
+ "w": "8.43",
+ "h": "21.23",
+ "asset_id": "server_1779761946023_50"
+ },
+ {
+ "x": "62.36",
+ "y": "78.00",
+ "w": "8.43",
+ "h": "21.23",
+ "asset_id": "server_1779761946023_51"
+ },
+ {
+ "x": "71.36",
+ "y": "78.00",
+ "w": "8.43",
+ "h": "21.23",
+ "asset_id": "server_1779761946023_52"
+ },
+ {
+ "x": "80.53",
+ "y": "78.00",
+ "w": "8.43",
+ "h": "21.23",
+ "asset_id": "srlmyar"
+ },
+ {
+ "x": "89.77",
+ "y": "78.00",
+ "w": "8.50",
+ "h": "21.23",
+ "asset_id": "server_1779761946023_54"
+ }
+ ],
+ "img/location_photo/기술개발센터/서버실/서버실_2.png": [
+ {
+ "x": "49.60",
+ "y": "1.93",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_55"
+ },
+ {
+ "x": "49.60",
+ "y": "12.04",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_56"
+ },
+ {
+ "x": "49.60",
+ "y": "21.39",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_57"
+ },
+ {
+ "x": "49.60",
+ "y": "30.73",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_58"
+ },
+ {
+ "x": "49.60",
+ "y": "39.82",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_59"
+ },
+ {
+ "x": "49.60",
+ "y": "50.13",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_60"
+ },
+ {
+ "x": "49.60",
+ "y": "59.28",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_53"
+ },
+ {
+ "x": "49.60",
+ "y": "68.63",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_62"
+ },
+ {
+ "x": "49.60",
+ "y": "77.84",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_63"
+ },
+ {
+ "x": "49.60",
+ "y": "86.93",
+ "w": "47.19",
+ "h": "6.75",
+ "asset_id": "server_1779761946023_64"
+ }
+ ],
+ "img/location_photo/TDD_TEST_MAP.png": [
+ {
+ "x": "30.50",
+ "y": "40.25",
+ "w": "10.00",
+ "h": "12.00",
+ "asset_id": null
+ },
+ {
+ "x": "50.00",
+ "y": "60.00",
+ "w": "5.00",
+ "h": "5.00",
+ "asset_id": null
+ }
+ ]
}
\ No newline at end of file
diff --git a/map_editor.html b/map_editor.html
index c8447da..636121a 100644
--- a/map_editor.html
+++ b/map_editor.html
@@ -1,42 +1,44 @@
-
-
-
-
-
- ITAM Map Coordinate Editor v3.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ ITAM Map Coordinate Editor v3.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile.html b/mobile.html
new file mode 100644
index 0000000..8050aa3
--- /dev/null
+++ b/mobile.html
@@ -0,0 +1,299 @@
+
+
+
+
+
+ ITAM 모바일 실사 점검
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
현재 점검 위치 (Location)
+
+ 위치 QR 코드를 먼저 스캔하세요.
+ 해제
+
+
+
+
+
+
+
+
+
+
+
카메라가 안 되나요? 수동 코드로 입력
+
+
+ 입력 확인
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 14de853..b7b5dae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,2089 +1,2374 @@
-{
- "name": "hm-itam",
- "version": "0.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "hm-itam",
- "version": "0.0.0",
- "dependencies": {
- "cors": "^2.8.6",
- "dotenv": "^17.4.2",
- "express": "^5.2.1",
- "iconv-lite": "^0.7.2",
- "lucide": "^0.364.0",
- "mysql2": "^3.22.1",
- "xlsx": "^0.18.5"
- },
- "devDependencies": {
- "typescript": "^5.2.2",
- "vite": "^5.2.0"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
- "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
- "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
- "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
- "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
- "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
- "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
- "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
- "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
- "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
- "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
- "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
- "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
- "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
- "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
- "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
- "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
- "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
- "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
- "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
- "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
- "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
- "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
- "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
- "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
- "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "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==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "undici-types": "~7.19.0"
- }
- },
- "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/adler-32": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
- "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.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==",
- "license": "MIT",
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "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/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/cfb": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
- "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
- "license": "Apache-2.0",
- "dependencies": {
- "adler-32": "~1.3.0",
- "crc-32": "~1.2.0"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/codepage": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
- "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8"
- }
- },
- "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/crc-32": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
- "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
- "license": "Apache-2.0",
- "bin": {
- "crc32": "bin/crc32.njs"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "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/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/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/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.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
- }
- },
- "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/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/frac": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
- "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8"
- }
- },
- "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==",
- "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/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-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/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-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==",
- "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/lucide": {
- "version": "0.364.0",
- "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.364.0.tgz",
- "integrity": "sha512-fUicNBP/uinzxvHUch75z2swiNwRDanakwAB3lgKx2vv6nFeJNjteDkwmmbUrlWsVZqZvO9CDQZQepoB3YDbnw==",
- "license": "ISC"
- },
- "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/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.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/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/nanoid": {
- "version": "3.3.11",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "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/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/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/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/postcss": {
- "version": "8.5.9",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
- "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.11",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "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/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/rollup": {
- "version": "4.60.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
- "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.8"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.60.1",
- "@rollup/rollup-android-arm64": "4.60.1",
- "@rollup/rollup-darwin-arm64": "4.60.1",
- "@rollup/rollup-darwin-x64": "4.60.1",
- "@rollup/rollup-freebsd-arm64": "4.60.1",
- "@rollup/rollup-freebsd-x64": "4.60.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
- "@rollup/rollup-linux-arm64-gnu": "4.60.1",
- "@rollup/rollup-linux-arm64-musl": "4.60.1",
- "@rollup/rollup-linux-loong64-gnu": "4.60.1",
- "@rollup/rollup-linux-loong64-musl": "4.60.1",
- "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
- "@rollup/rollup-linux-ppc64-musl": "4.60.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
- "@rollup/rollup-linux-riscv64-musl": "4.60.1",
- "@rollup/rollup-linux-s390x-gnu": "4.60.1",
- "@rollup/rollup-linux-x64-gnu": "4.60.1",
- "@rollup/rollup-linux-x64-musl": "4.60.1",
- "@rollup/rollup-openbsd-x64": "4.60.1",
- "@rollup/rollup-openharmony-arm64": "4.60.1",
- "@rollup/rollup-win32-arm64-msvc": "4.60.1",
- "@rollup/rollup-win32-ia32-msvc": "4.60.1",
- "@rollup/rollup-win32-x64-gnu": "4.60.1",
- "@rollup/rollup-win32-x64-msvc": "4.60.1",
- "fsevents": "~2.3.2"
- }
- },
- "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/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.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/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "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/ssf": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
- "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
- "license": "Apache-2.0",
- "dependencies": {
- "frac": "~1.1.2"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "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/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/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/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",
- "peer": true
- },
- "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/vite": {
- "version": "5.4.21",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
- "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- }
- }
- },
- "node_modules/wmf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
- "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/word": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
- "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
- "license": "Apache-2.0",
- "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"
- },
- "node_modules/xlsx": {
- "version": "0.18.5",
- "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
- "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "adler-32": "~1.3.0",
- "cfb": "~1.2.1",
- "codepage": "~1.15.0",
- "crc-32": "~1.2.1",
- "ssf": "~0.11.2",
- "wmf": "~1.0.1",
- "word": "~0.3.0"
- },
- "bin": {
- "xlsx": "bin/xlsx.njs"
- },
- "engines": {
- "node": ">=0.8"
- }
- }
- }
-}
+{
+ "name": "hm-itam",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "hm-itam",
+ "version": "0.0.0",
+ "dependencies": {
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "iconv-lite": "^0.7.2",
+ "lucide": "^0.364.0",
+ "mysql2": "^3.22.1",
+ "qrcode": "^1.5.4",
+ "xlsx": "^0.18.5"
+ },
+ "devDependencies": {
+ "@types/qrcode": "^1.5.6",
+ "typescript": "^5.2.2",
+ "vite": "^5.2.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
+ "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
+ "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
+ "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
+ "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
+ "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
+ "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
+ "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
+ "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
+ "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
+ "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
+ "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
+ "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
+ "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
+ "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
+ "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
+ "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
+ "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
+ "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
+ "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
+ "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
+ "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
+ "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
+ "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.19.0"
+ }
+ },
+ "node_modules/@types/qrcode": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
+ "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
+ "dev": true,
+ "dependencies": {
+ "@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/adler-32": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
+ "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "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/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/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/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cfb": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
+ "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "crc-32": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/codepage": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
+ "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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==",
+ "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=="
+ },
+ "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/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "engines": {
+ "node": ">=0.10.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/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
+ },
+ "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/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=="
+ },
+ "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.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "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/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/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/frac": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
+ "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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==",
+ "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==",
+ "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-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/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-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/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-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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/lucide": {
+ "version": "0.364.0",
+ "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.364.0.tgz",
+ "integrity": "sha512-fUicNBP/uinzxvHUch75z2swiNwRDanakwAB3lgKx2vv6nFeJNjteDkwmmbUrlWsVZqZvO9CDQZQepoB3YDbnw==",
+ "license": "ISC"
+ },
+ "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/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.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/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/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "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/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/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/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "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/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.9",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
+ "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "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/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "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/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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+ },
+ "node_modules/rollup": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
+ "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.1",
+ "@rollup/rollup-android-arm64": "4.60.1",
+ "@rollup/rollup-darwin-arm64": "4.60.1",
+ "@rollup/rollup-darwin-x64": "4.60.1",
+ "@rollup/rollup-freebsd-arm64": "4.60.1",
+ "@rollup/rollup-freebsd-x64": "4.60.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.1",
+ "@rollup/rollup-linux-arm64-musl": "4.60.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.1",
+ "@rollup/rollup-linux-loong64-musl": "4.60.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-musl": "4.60.1",
+ "@rollup/rollup-openbsd-x64": "4.60.1",
+ "@rollup/rollup-openharmony-arm64": "4.60.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.1",
+ "@rollup/rollup-win32-x64-gnu": "4.60.1",
+ "@rollup/rollup-win32-x64-msvc": "4.60.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "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/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/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+ },
+ "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/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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/ssf": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
+ "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "frac": "~1.1.2"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "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/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==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/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/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/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/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
+ },
+ "node_modules/wmf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
+ "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/word": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
+ "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "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/xlsx": {
+ "version": "0.18.5",
+ "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
+ "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "cfb": "~1.2.1",
+ "codepage": "~1.15.0",
+ "crc-32": "~1.2.1",
+ "ssf": "~0.11.2",
+ "wmf": "~1.0.1",
+ "word": "~0.3.0"
+ },
+ "bin": {
+ "xlsx": "bin/xlsx.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 43e92fd..a71cabe 100644
--- a/package.json
+++ b/package.json
@@ -1,26 +1,28 @@
-{
- "name": "hm-itam",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc && vite build",
- "preview": "vite preview",
- "server": "node server.js",
- "db-init": "node db_init.js"
- },
- "devDependencies": {
- "typescript": "^5.2.2",
- "vite": "^5.2.0"
- },
- "dependencies": {
- "cors": "^2.8.6",
- "dotenv": "^17.4.2",
- "express": "^5.2.1",
- "iconv-lite": "^0.7.2",
- "lucide": "^0.364.0",
- "mysql2": "^3.22.1",
- "xlsx": "^0.18.5"
- }
-}
+{
+ "name": "hm-itam",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "server": "node server.js",
+ "db-init": "node db_init.js"
+ },
+ "devDependencies": {
+ "@types/qrcode": "^1.5.6",
+ "typescript": "^5.2.2",
+ "vite": "^5.2.0"
+ },
+ "dependencies": {
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "iconv-lite": "^0.7.2",
+ "lucide": "^0.364.0",
+ "mysql2": "^3.22.1",
+ "qrcode": "^1.5.4",
+ "xlsx": "^0.18.5"
+ }
+}
diff --git a/public/qrcode.min.js b/public/qrcode.min.js
new file mode 100644
index 0000000..fb20af6
--- /dev/null
+++ b/public/qrcode.min.js
@@ -0,0 +1 @@
+var QRCode=function(t){"use strict";function R(){return void 0!==a}var a,O=[0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706],Q=function(t){if(!t)throw new Error('"version" cannot be null or undefined');if(t<1||40>>=1;return e};function e(t,e){return t(e={exports:{}},e.exports),e.exports}var g=e(function(t,n){n.L={bit:1},n.M={bit:0},n.Q={bit:3},n.H={bit:2},n.isValid=function(t){return t&&void 0!==t.bit&&0<=t.bit&&t.bit<4},n.from=function(t,e){if(n.isValid(t))return t;try{var r=t;if("string"!=typeof r)throw new Error("Param is not a string");switch(r.toLowerCase()){case"l":case"low":return n.L;case"m":case"medium":return n.M;case"q":case"quartile":return n.Q;case"h":case"high":return n.H;default:throw new Error("Unknown EC Level: "+r)}return}catch(t){return e}}});function T(){this.buffer=[],this.length=0}g.L,g.M,g.Q,g.H,g.isValid,T.prototype={get:function(t){var e=Math.floor(t/8);return 1==(this.buffer[e]>>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}};var W=T;function r(t){if(!t||t<1)throw new Error("BitMatrix size must be defined and greater than 0");this.size=t,this.data=new Uint8Array(t*t),this.reservedBit=new Uint8Array(t*t)}r.prototype.set=function(t,e,r,n){t=t*this.size+e;this.data[t]=r,n&&(this.reservedBit[t]=!0)},r.prototype.get=function(t,e){return this.data[t*this.size+e]},r.prototype.xor=function(t,e,r){this.data[t*this.size+e]^=r},r.prototype.isReserved=function(t,e){return this.reservedBit[t*this.size+e]};for(var G=r,V=e(function(t,i){var a=Q;i.getRowColCoords=function(t){if(1===t)return[];for(var e=Math.floor(t/7)+2,t=a(t),r=145===t?26:2*Math.ceil((t-13)/(2*e-2)),n=[t-7],o=1;o>6|192),e.push(63&a|128)):a<55296||57344<=a&&a<65536?(e.push(a>>12|224),e.push(a>>6&63|128),e.push(63&a|128)):65536<=a&&a<=1114111?(e.push(a>>18|240),e.push(a>>12&63|128),e.push(a>>6&63|128),e.push(63&a|128)):e.push(239,191,189)}return new Uint8Array(e).buffer}(t)),this.data=new Uint8Array(t)}N.getBitsLength=function(t){return 8*t},N.prototype.getLength=function(){return this.data.length},N.prototype.getBitsLength=function(){return N.getBitsLength(this.data.length)},N.prototype.write=function(t){for(var e=0,r=this.data.length;e>>8&255)+(255&r),13)}};var H=B,J=e(function(t){var d={single_source_shortest_paths:function(t,e,r){var n={},o={};o[e]=0;var a,i,u,s,h,f,c,g=d.PriorityQueue.make();for(g.push(e,0);!g.empty();)for(u in i=(a=g.pop()).value,s=a.cost,h=t[i]||{})h.hasOwnProperty(u)&&(f=s+h[u],c=o[u],(void 0===o[u]||f>i&1),i<6?t.set(i,8,n,!0):i<8?t.set(i+1,8,n,!0):t.set(o-15+i,8,n,!0),i<8?t.set(8,o-i-1,n,!0):i<9?t.set(8,15-i-1+1,n,!0):t.set(8,15-i-1,n,!0);t.set(o-8,8,1,!0)}function K(t,e,r,n){var o;if(Array.isArray(t))o=Z.fromArray(t);else{if("string"!=typeof t)throw new Error("Invalid data");var a=e;a||(i=Z.rawSplit(t),a=X.getBestVersionForData(i,r)),o=Z.fromString(t,a||40)}var i=X.getBestVersionForData(o,r);if(!i)throw new Error("The amount of data is too big to be stored in a QR Code");if(e){if(e>C&1),!0),N.set(_,B,z,!0)}for(var P=i,K=t,R=P.size,T=-1,L=R-1,b=7,U=0,x=R-1;0>>b&1)),P.set(L,x-F,k),-1==--b&&(U++,b=7));if((L+=T)<0||R<=L){L-=T,T=-T;break}}return isNaN(n)&&(n=q.getBestMask(i,nt.bind(null,i,r))),q.applyMask(n,i),nt(i,r,n),{modules:i,version:e,errorCorrectionLevel:r,maskPattern:n,segments:o}}Z.fromArray,Z.fromString,Z.rawSplit;function ot(t,e){if(void 0===t||""===t)throw new Error("No input text");var r,n,o=g.M;if(void 0!==e&&(o=g.from(e.errorCorrectionLevel,g.M),r=X.from(e.version),n=q.from(e.maskPattern),e.toSJISFunc)){if("function"!=typeof(e=e.toSJISFunc))throw new Error('"toSJISFunc" is not a valid function.');a=e}return K(t,r,o,n)}var C=e(function(t,d){function o(t){if("string"!=typeof(t="number"==typeof t?t.toString():t))throw new Error("Color should be defined as hex string");var e=t.slice().replace("#","").split("");if(e.length<3||5===e.length||8>24&255,g:t>>16&255,b:t>>8&255,a:255&t,hex:"#"+e.slice(0,6).join("")}}d.getOptions=function(t){(t=t||{}).color||(t.color={});var e=void 0===t.margin||null===t.margin||t.margin<0?4:t.margin,r=t.width&&21<=t.width?t.width:void 0,n=t.scale||4;return{width:r,scale:r?4:n,margin:e,color:{dark:o(t.color.dark||"#000000ff"),light:o(t.color.light||"#ffffffff")},type:t.type,rendererOpts:t.rendererOpts||{}}},d.getScale=function(t,e){return e.width&&e.width>=t+2*e.margin?e.width/(t+2*e.margin):e.scale},d.getImageWidth=function(t,e){var r=d.getScale(t,e);return Math.floor((t+2*e.margin)*r)},d.qrToImageData=function(t,e,r){for(var n=e.modules.size,o=e.modules.data,a=d.getScale(n,r),i=Math.floor((n+2*r.margin)*a),u=r.margin*a,s=[r.color.light,r.color.dark],h=0;h ':"",t=" ',o=''+i+t+" \n","function"==typeof n&&n(null,o),o;var n,o,a,i}),y={create:w,toCanvas:m,toDataURL:v,toString:E};return t.create=w,t.default=y,t.toCanvas=m,t.toDataURL=v,t.toString=E,Object.defineProperty(t,"__esModule",{value:!0}),t}({});
\ No newline at end of file
diff --git a/scratch/db_migrate.cjs b/scratch/db_migrate.cjs
new file mode 100644
index 0000000..6958e49
--- /dev/null
+++ b/scratch/db_migrate.cjs
@@ -0,0 +1,189 @@
+const mysql = require('mysql2/promise');
+const fs = require('fs');
+require('dotenv').config();
+
+function getCleanMapKey(path) {
+ let clean = path.replace('img/location_photo/', '').replace('.png', '');
+ clean = clean.replace('서관', 'W').replace('동관', 'E');
+ clean = clean.replace('한맥빌딩/MDF실/MDF_', 'HAN-MDF-');
+ clean = clean.replace('기술개발센터/서버실/서버실_', 'DEV-SVR-');
+ clean = clean.replace(/\//g, '-');
+ return clean;
+}
+
+function getLocationName(path) {
+ if (path.includes('IDC')) return 'IDC';
+ if (path.includes('한맥빌딩')) return '한맥빌딩';
+ if (path.includes('기술개발센터')) return '기술개발센터';
+ return '기타';
+}
+
+function getLocationDetail(path, idx) {
+ let clean = path.replace('img/location_photo/', '').replace('.png', '');
+ let parts = clean.split('/');
+ let lastPart = parts[parts.length - 1]; // e.g. "서관205", "MDF_1", "서버실_1"
+ return `${lastPart} 구역 자리 #${idx + 1}`;
+}
+
+async function main() {
+ console.log('🏁 Starting DB migration...');
+
+ const pool = mysql.createPool({
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASS,
+ database: process.env.DB_NAME,
+ port: process.env.DB_PORT
+ });
+
+ const connection = await pool.getConnection();
+
+ try {
+ // 1. Create physical_locations table
+ console.log('⏳ Creating physical_locations table...');
+ await connection.query(`
+ CREATE TABLE IF NOT EXISTS physical_locations (
+ location_code VARCHAR(50) NOT NULL COMMENT '위치 식별 코드 (예: LOC-IDC-W205-001)',
+ location_name VARCHAR(100) NOT NULL COMMENT '물리 위치 대분류 (예: IDC 서관)',
+ location_detail VARCHAR(100) NOT NULL COMMENT '상세 위치/랙 번호 (예: 205호 1번 랙)',
+ map_image VARCHAR(150) NOT NULL COMMENT '해당 도면 파일 경로 (예: img/location_photo/IDC/서관205.png)',
+ map_x DECIMAL(5,2) NOT NULL COMMENT '도면 내 X 백분율 좌표',
+ map_y DECIMAL(5,2) NOT NULL COMMENT '도면 내 Y 백분율 좌표',
+ map_w DECIMAL(5,2) NOT NULL DEFAULT 4.00 COMMENT '도면 내 박스 너비(%)',
+ map_h DECIMAL(5,2) NOT NULL DEFAULT 4.00 COMMENT '도면 내 박스 높이(%)',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (location_code)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+ `);
+ console.log('✅ physical_locations table ready.');
+
+ // 2. Create asset_audit_pending table
+ console.log('⏳ Creating asset_audit_pending table...');
+ await connection.query(`
+ CREATE TABLE IF NOT EXISTS asset_audit_pending (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ asset_code VARCHAR(50) NOT NULL COMMENT '스캔된 자산 고유번호 (예: server_1779761946023_14)',
+ physical_location_code VARCHAR(50) NOT NULL COMMENT '스캔된 위치 마스터 코드 (예: LOC-IDC-W205-001)',
+ status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '상태: PENDING(대기), APPROVED(승인), REJECTED(반려)',
+ scanned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ processed_at TIMESTAMP NULL COMMENT '승인/반려 처리 일시',
+ processed_by VARCHAR(50) NULL COMMENT '처리한 관리자',
+ CONSTRAINT fk_audit_physical FOREIGN KEY (physical_location_code) REFERENCES physical_locations(location_code)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+ `);
+ console.log('✅ asset_audit_pending table ready.');
+
+ // 3. Add physical_location_code to asset_location
+ console.log('⏳ Checking physical_location_code column in asset_location...');
+ const [cols] = await connection.query('DESCRIBE asset_location');
+ const hasCol = cols.some(c => c.Field === 'physical_location_code');
+ if (!hasCol) {
+ await connection.query(`
+ ALTER TABLE asset_location
+ ADD COLUMN physical_location_code VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'physical_locations의 location_code FK'
+ `);
+ console.log('✅ physical_location_code column added with utf8mb4_unicode_ci collation.');
+ } else {
+ console.log('ℹ️ physical_location_code column already exists. Enforcing collation...');
+ await connection.query(`
+ ALTER TABLE asset_location
+ MODIFY COLUMN physical_location_code VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'physical_locations의 location_code FK'
+ `);
+ console.log('✅ physical_location_code column collation enforced.');
+ }
+
+ // Add constraint if not exists
+ console.log('⏳ Checking foreign key constraint fk_asset_loc_physical...');
+ const [constraints] = await connection.query(`
+ SELECT CONSTRAINT_NAME
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
+ WHERE TABLE_NAME = 'asset_location'
+ AND CONSTRAINT_NAME = 'fk_asset_loc_physical'
+ AND TABLE_SCHEMA = DATABASE()
+ `);
+
+ if (constraints.length === 0) {
+ console.log('⏳ Adding foreign key constraint...');
+ await connection.query(`
+ ALTER TABLE asset_location
+ ADD CONSTRAINT fk_asset_loc_physical
+ FOREIGN KEY (physical_location_code) REFERENCES physical_locations(location_code)
+ `);
+ console.log('✅ Foreign key constraint added.');
+ } else {
+ console.log('ℹ️ Foreign key constraint already exists.');
+ }
+
+ // 4. Load map_config.json and migrate
+ console.log('⏳ Migrating map_config.json data to physical_locations...');
+ if (fs.existsSync('map_config.json')) {
+ const mapConfig = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}');
+ let insertCount = 0;
+ let syncCount = 0;
+
+ for (const [mapPath, boxes] of Object.entries(mapConfig)) {
+ const cleanKey = getCleanMapKey(mapPath);
+ const locName = getLocationName(mapPath);
+
+ for (let i = 0; i < boxes.length; i++) {
+ const box = boxes[i];
+ const padIdx = String(i + 1).padStart(3, '0');
+ const locCode = `LOC-${cleanKey}-${padIdx}`;
+ const locDetail = getLocationDetail(mapPath, i);
+
+ const bx = parseFloat(box.x);
+ const by = parseFloat(box.y);
+ const bw = parseFloat(box.w || 4.00);
+ const bh = parseFloat(box.h || 4.00);
+
+ // Insert into physical_locations (ignore if duplicate)
+ await connection.query(`
+ INSERT INTO physical_locations
+ (location_code, location_name, location_detail, map_image, map_x, map_y, map_w, map_h)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ ON DUPLICATE KEY UPDATE
+ location_name = VALUES(location_name),
+ location_detail = VALUES(location_detail),
+ map_image = VALUES(map_image),
+ map_x = VALUES(map_x),
+ map_y = VALUES(map_y),
+ map_w = VALUES(map_w),
+ map_h = VALUES(map_h)
+ `, [locCode, locName, locDetail, mapPath, bx, by, bw, bh]);
+
+ insertCount++;
+
+ // Sync database asset if box.asset_id exists
+ if (box.asset_id) {
+ const [rows] = await connection.query(
+ 'SELECT id FROM asset_location WHERE asset_id = ? AND is_active = 1',
+ [box.asset_id]
+ );
+ if (rows.length > 0) {
+ await connection.query(
+ 'UPDATE asset_location SET physical_location_code = ? WHERE asset_id = ? AND is_active = 1',
+ [locCode, box.asset_id]
+ );
+ syncCount++;
+ }
+ }
+ }
+ }
+ console.log(`✅ Migrated ${insertCount} physical locations and synced ${syncCount} existing assets.`);
+ } else {
+ console.log('⚠️ map_config.json not found, skipping initial migration.');
+ }
+
+ console.log('🎉 DB Migration successfully completed!');
+ } catch (err) {
+ console.error('❌ Migration failed:', err);
+ throw err;
+ } finally {
+ connection.release();
+ await pool.end();
+ }
+}
+
+main().catch(err => {
+ process.exit(1);
+});
diff --git a/scratch/test_audit.cjs b/scratch/test_audit.cjs
new file mode 100644
index 0000000..a7a9d7b
--- /dev/null
+++ b/scratch/test_audit.cjs
@@ -0,0 +1,231 @@
+const assert = require('assert');
+const http = require('http');
+const mysql = require('mysql2/promise');
+require('dotenv').config();
+
+const BASE_URL = 'http://localhost:3001';
+
+function request(method, path, body = null) {
+ return new Promise((resolve, reject) => {
+ const url = `${BASE_URL}${path}`;
+ const options = {
+ method: method,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ };
+ const req = http.request(url, options, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ try {
+ const parsed = JSON.parse(data);
+ resolve({ status: res.statusCode, body: parsed });
+ } catch (e) {
+ resolve({ status: res.statusCode, body: data });
+ }
+ });
+ });
+ req.on('error', (err) => reject(err));
+ if (body) {
+ req.write(JSON.stringify(body));
+ }
+ req.end();
+ });
+}
+
+async function runTests() {
+ console.log('🧪 Starting Audit TDD Tests...');
+ const pool = mysql.createPool({
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASS,
+ database: process.env.DB_NAME,
+ port: process.env.DB_PORT
+ });
+
+ const connection = await pool.getConnection();
+
+ try {
+ // Clean up any test records
+ console.log('🧹 Cleaning up test records...');
+ await connection.query("DELETE FROM asset_audit_pending WHERE asset_code LIKE 'TEST-ASSET-%'");
+
+ // Check if test assets exist in asset_core & asset_location
+ // We will use an existing asset or insert a dummy test asset
+ const [testAssets] = await connection.query("SELECT id FROM asset_core WHERE asset_code = 'TEST-ASSET-001'");
+ let testAssetId;
+ if (testAssets.length === 0) {
+ console.log('⏳ Inserting dummy test asset...');
+ testAssetId = 'test_asset_uuid_123456';
+ await connection.query(`
+ INSERT INTO asset_core (id, asset_code, category, asset_type, asset_purpose)
+ VALUES (?, 'TEST-ASSET-001', 'server', 'Server', 'TDD Test Server')
+ `, [testAssetId]);
+ await connection.query(`
+ INSERT INTO asset_location (asset_id, location, location_detail, location_photo, loc_x, loc_y, is_active)
+ VALUES (?, 'Initial Location', 'Initial Detail', 'initial.png', '10.00', '10.00', 1)
+ `, [testAssetId]);
+ } else {
+ testAssetId = testAssets[0].id;
+ }
+
+ // 1. Test GET /api/physical-locations
+ console.log('👉 Test 1: GET /api/physical-locations');
+ const res1 = await request('GET', '/api/physical-locations');
+ assert.strictEqual(res1.status, 200, 'GET /api/physical-locations should return 200');
+ assert(Array.isArray(res1.body), 'Response should be an array of physical locations');
+ assert(res1.body.length > 0, 'Should return at least one physical location');
+ console.log(`✅ Test 1 Passed: Found ${res1.body.length} physical locations.`);
+
+ const sampleLocation = res1.body[0].location_code;
+
+ // 2. Test POST /api/audit/scan
+ console.log(`👉 Test 2: POST /api/audit/scan (Location: ${sampleLocation}, Asset: TEST-ASSET-001)`);
+ const res2 = await request('POST', '/api/audit/scan', {
+ asset_code: 'TEST-ASSET-001',
+ physical_location_code: sampleLocation
+ });
+ assert.strictEqual(res2.status, 200, 'POST /api/audit/scan should return 200');
+ assert.strictEqual(res2.body.success, true, 'Response success should be true');
+ assert(res2.body.pending_id, 'Response should contain pending_id');
+ console.log(`✅ Test 2 Passed: Pending scan registered with ID: ${res2.body.pending_id}`);
+
+ const pendingId = res2.body.pending_id;
+
+ // 3. Test GET /api/audit/pending
+ console.log('👉 Test 3: GET /api/audit/pending');
+ const res3 = await request('GET', '/api/audit/pending');
+ assert.strictEqual(res3.status, 200, 'GET /api/audit/pending should return 200');
+ assert(Array.isArray(res3.body), 'Response should be an array');
+ const pendingItem = res3.body.find(item => item.id === pendingId);
+ assert(pendingItem, 'Pending list should contain the newly registered scan');
+ assert.strictEqual(pendingItem.asset_code, 'TEST-ASSET-001', 'Asset code should match');
+ assert.strictEqual(pendingItem.physical_location_code, sampleLocation, 'Location code should match');
+ assert.strictEqual(pendingItem.status, 'PENDING', 'Status should be PENDING');
+ console.log('✅ Test 3 Passed: Newly registered scan found in pending list with correct details.');
+
+ // 4. Test POST /api/audit/approve
+ console.log(`👉 Test 4: POST /api/audit/approve (Pending ID: ${pendingId})`);
+ const res4 = await request('POST', '/api/audit/approve', {
+ pending_ids: [pendingId],
+ processed_by: 'TDD-TESTER'
+ });
+ assert.strictEqual(res4.status, 200, 'POST /api/audit/approve should return 200');
+ assert.strictEqual(res4.body.success, true, 'Response success should be true');
+ console.log('✅ Test 4 Passed: Audit approved.');
+
+ // Verify database updates
+ console.log('🔍 Verifying updates in database...');
+ const [pendingCheck] = await connection.query(
+ 'SELECT status, processed_by FROM asset_audit_pending WHERE id = ?',
+ [pendingId]
+ );
+ assert.strictEqual(pendingCheck[0].status, 'APPROVED', 'Pending record status should be APPROVED');
+ assert.strictEqual(pendingCheck[0].processed_by, 'TDD-TESTER', 'Processed by should match');
+
+ const [locationCheck] = await connection.query(
+ 'SELECT physical_location_code, location_photo, loc_x, loc_y FROM asset_location WHERE asset_id = ? AND is_active = 1',
+ [testAssetId]
+ );
+ const [physLoc] = await connection.query(
+ 'SELECT map_image, map_x, map_y FROM physical_locations WHERE location_code = ?',
+ [sampleLocation]
+ );
+ assert.strictEqual(locationCheck[0].physical_location_code, sampleLocation, 'Asset location code should be updated');
+ assert.strictEqual(locationCheck[0].location_photo, physLoc[0].map_image, 'Asset map_image should be updated');
+ assert.strictEqual(parseFloat(locationCheck[0].loc_x).toFixed(2), parseFloat(physLoc[0].map_x).toFixed(2), 'Asset map_x should be updated');
+ assert.strictEqual(parseFloat(locationCheck[0].loc_y).toFixed(2), parseFloat(physLoc[0].map_y).toFixed(2), 'Asset map_y should be updated');
+ console.log('✅ Database verification passed: Asset location and map coordinates updated successfully!');
+
+ // 5. Test GET /api/maps (Before modification)
+ console.log('👉 Test 5: GET /api/maps');
+ const res5 = await request('GET', '/api/maps');
+ assert.strictEqual(res5.status, 200, 'GET /api/maps should return 200');
+ assert(typeof res5.body === 'object' && res5.body !== null, 'Response should be a map config object');
+ console.log('✅ Test 5 Passed: GET /api/maps returned valid object.');
+
+ // 6. Test POST /api/maps/save
+ console.log('👉 Test 6: POST /api/maps/save');
+ const testMapPath = 'img/location_photo/TDD_TEST_MAP.png';
+ const testBoxes = [
+ {
+ x: '30.50',
+ y: '40.25',
+ w: '10.00',
+ h: '12.00',
+ asset_id: testAssetId
+ },
+ {
+ x: '50.00',
+ y: '60.00',
+ w: '5.00',
+ h: '5.00',
+ asset_id: null
+ }
+ ];
+
+ const res6 = await request('POST', '/api/maps/save', {
+ path: testMapPath,
+ boxes: testBoxes
+ });
+ assert.strictEqual(res6.status, 200, 'POST /api/maps/save should return 200');
+ assert.strictEqual(res6.body.success, true, 'Save should be successful');
+ console.log('✅ Test 6 Passed: Map coordinate save triggered successfully.');
+
+ // Verify DB update directly for physical_locations
+ console.log('🔍 Verifying physical_locations update in database...');
+ const [physLocCheck] = await connection.query(
+ 'SELECT location_code, map_x, map_y, map_w, map_h FROM physical_locations WHERE map_image = ? ORDER BY location_code',
+ [testMapPath]
+ );
+ assert.strictEqual(physLocCheck.length, 2, 'Should create 2 physical locations for the test map');
+
+ // First location has asset_id mapped
+ assert.strictEqual(parseFloat(physLocCheck[0].map_x).toFixed(2), '30.50', 'First location X coord match');
+ assert.strictEqual(parseFloat(physLocCheck[0].map_y).toFixed(2), '40.25', 'First location Y coord match');
+ assert.strictEqual(parseFloat(physLocCheck[0].map_w).toFixed(2), '10.00', 'First location W size match');
+ assert.strictEqual(parseFloat(physLocCheck[0].map_h).toFixed(2), '12.00', 'First location H size match');
+
+ // Asset location coordinates sync check
+ console.log('🔍 Verifying asset_location coordination sync in database...');
+ const [assetLocSyncCheck] = await connection.query(
+ 'SELECT loc_x, loc_y, physical_location_code FROM asset_location WHERE asset_id = ? AND is_active = 1',
+ [testAssetId]
+ );
+ assert(assetLocSyncCheck.length > 0, 'Asset location should be active');
+ assert.strictEqual(parseFloat(assetLocSyncCheck[0].loc_x).toFixed(2), '30.50', 'Asset location X should sync');
+ assert.strictEqual(parseFloat(assetLocSyncCheck[0].loc_y).toFixed(2), '40.25', 'Asset location Y should sync');
+ assert.strictEqual(assetLocSyncCheck[0].physical_location_code, physLocCheck[0].location_code, 'Physical location code should match');
+ console.log('✅ DB Verification for save: physical_locations and asset_location coordinates synced.');
+
+ // 7. Test GET /api/maps (After modification)
+ console.log('👉 Test 7: GET /api/maps (After saving)');
+ const res7 = await request('GET', '/api/maps');
+ assert.strictEqual(res7.status, 200, 'GET /api/maps should return 200');
+ assert(res7.body[testMapPath], 'Returned config should contain the newly saved test map');
+ const savedBoxes = res7.body[testMapPath];
+ assert.strictEqual(savedBoxes.length, 2, 'Saved boxes count match');
+ assert.strictEqual(savedBoxes[0].asset_id, testAssetId, 'First box asset_id match');
+ assert.strictEqual(savedBoxes[0].x, '30.50', 'First box X match');
+ assert.strictEqual(savedBoxes[1].asset_id, null, 'Second box asset_id is null');
+ console.log('✅ Test 7 Passed: GET /api/maps returned updated configuration.');
+
+ // Clean up
+ console.log('🧹 Cleaning up test assets...');
+ await connection.query("DELETE FROM asset_audit_pending WHERE asset_code = 'TEST-ASSET-001'");
+ await connection.query("DELETE FROM asset_location WHERE asset_id = ?", [testAssetId]);
+ await connection.query("DELETE FROM asset_core WHERE id = ?", [testAssetId]);
+ await connection.query("DELETE FROM physical_locations WHERE map_image = ?", [testMapPath]);
+
+ console.log('🎉 All TDD tests passed successfully!');
+ } catch (err) {
+ console.error('❌ TDD Test Suite Failed:', err.message);
+ throw err;
+ } finally {
+ connection.release();
+ await pool.end();
+ }
+}
+
+runTests().catch(() => process.exit(1));
diff --git a/server.js b/server.js
index 938d2c2..5b57665 100644
--- a/server.js
+++ b/server.js
@@ -1,797 +1,1105 @@
-import express from 'express';
-import mysql from 'mysql2/promise';
-import cors from 'cors';
-import dotenv from 'dotenv';
-import fs from 'fs';
-
-dotenv.config();
-
-const dbConfig = {
- host: process.env.DB_HOST,
- user: process.env.DB_USER,
- password: process.env.DB_PASS,
- database: process.env.DB_NAME,
- port: parseInt(process.env.DB_PORT || '3306')
-};
-
-const getDbConnectionSummary = () => ({
- host: dbConfig.host || '(missing)',
- port: dbConfig.port,
- user: dbConfig.user || '(missing)',
- database: dbConfig.database || '(missing)'
-});
-
-const app = express();
-app.use(cors());
-app.use(express.json({ limit: '50mb' }));
-app.use('/uploads', express.static('uploads')); // 업로드 파일 정적 서빙
-
-// uploads 폴더가 없으면 생성
-if (!fs.existsSync('uploads')) {
- fs.mkdirSync('uploads');
-}
-
-// MySQL Pool Configuration
-const pool = mysql.createPool({
- host: dbConfig.host,
- user: dbConfig.user,
- password: dbConfig.password,
- database: dbConfig.database,
- port: dbConfig.port,
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0
-});
-
-// Database startup check (ensure job_spec_standards table exists)
-(async () => {
- let connection;
- try {
- connection = await pool.getConnection();
- await connection.query(`
- CREATE TABLE IF NOT EXISTS job_spec_standards (
- id INT AUTO_INCREMENT PRIMARY KEY,
- job_name VARCHAR(100) UNIQUE NOT NULL,
- cpu_standard VARCHAR(255),
- ram_standard VARCHAR(100),
- gpu_standard VARCHAR(100),
- min_score INT DEFAULT 0,
- remarks TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `);
- console.log('✅ job_spec_standards table verification completed.');
- } catch (err) {
- console.error('❌ Failed to verify/create job_spec_standards table:', {
- db: getDbConnectionSummary(),
- code: err.code,
- errno: err.errno,
- syscall: err.syscall,
- address: err.address,
- port: err.port,
- message: err.message
- });
- } finally {
- if (connection) connection.release();
- }
-})();
-
-// Error Handler
-const handleError = (res, err, label) => {
- console.error(`❌ [${label}] Error:`, {
- db: getDbConnectionSummary(),
- code: err.code,
- errno: err.errno,
- syscall: err.syscall,
- address: err.address,
- port: err.port,
- message: err.message
- });
- res.status(500).json({ error: err.message });
-};
-
-// --- Global Constants ---
-const CATEGORY_TABLE_MAP = {
- pc: 'asset_core',
- server: 'asset_core',
- storage: 'asset_core',
- network: 'asset_core',
- equipment: 'asset_core',
- officeSupplies: 'asset_core',
- survey: 'asset_core',
- vip: 'asset_core',
- pcParts: 'asset_core',
- swInternal: 'asset_software_perpetual',
- swExternal: 'asset_software_subscription',
- swUsers: 'asset_software_assignment',
- users: 'system_users',
- logs: 'asset_history'
-};
-
-const ASSET_TABLES = [
- 'asset_core'
-];
-
-// --- API Endpoints ---
-
-// 1. Generic Batch Save (Dynamic Table Detection)
-app.post('/api/:table/batch', async (req, res) => {
- const { table } = req.params;
- const dbTable = CATEGORY_TABLE_MAP[table] || table;
- const data = req.body;
- if (!Array.isArray(data)) return res.status(400).json({ error: 'Data must be an array' });
-
- let connection;
- try {
- connection = await pool.getConnection();
- await connection.beginTransaction();
-
- const [columns] = await connection.query(`DESCRIBE ${dbTable}`);
- const validFields = columns.map(c => c.Field);
-
- await connection.query(`DELETE FROM ${dbTable}`);
-
- if (data.length > 0) {
- const placeholders = validFields.map(() => '?').join(', ');
- const sql = `INSERT INTO ${dbTable} (${validFields.join(', ')}) VALUES (${placeholders})`;
-
- for (const item of data) {
- const values = validFields.map(field => {
- const val = item[field];
- return val === undefined ? null : val;
- });
- await connection.query(sql, values);
- }
- }
-
- await connection.commit();
- res.json({ success: true, count: data.length });
- } catch (err) {
- if (connection) await connection.rollback();
- handleError(res, err, 'BATCH SAVE');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 2. Get All Assets (Integrated Master Data from Normalized V3 Schema)
-app.get('/api/assets/master', async (req, res) => {
- let connection;
- try {
- connection = await pool.getConnection();
-
- const masterData = {
- pc: [], server: [], storage: [], network: [],
- equipment: [], officeSupplies: [], survey: [], vip: [], pcParts: [],
- swInternal: [], swExternal: [], swUsers: [], users: [], logs: [], partsMaster: []
- };
-
- // Load from V3 Normalized Schema
- const [rows] = await connection.query(`
- SELECT
- c.*,
- s.hw_status, s.model_name, s.mainboard, s.os, s.cpu, s.ram, s.gpu,
- s.monitoring, s.price, s.monitor_inch, s.serial_num,
- l.location, l.location_detail, l.location_photo, l.loc_x, l.loc_y,
- (
- SELECT JSON_ARRAYAGG(JSON_OBJECT('type', net_type, 'name', net_name, 'val1', net_value1, 'val2', net_value2))
- FROM asset_remote WHERE asset_id = c.id AND is_active = 1
- ) as remotes,
- (
- SELECT JSON_ARRAYAGG(JSON_OBJECT('type', disk_type, 'capacity', capacity, 'unit', unit, 'slot', slot_no))
- FROM asset_volume WHERE asset_id = c.id
- ) as volumes
- FROM asset_core c
- LEFT JOIN asset_spec s ON c.id = s.asset_id
- LEFT JOIN asset_location l ON l.id = (
- SELECT id FROM asset_location
- WHERE asset_id = c.id AND is_active = 1
- ORDER BY created_at DESC LIMIT 1
- )
- `);
-
- const catMap = {
- 'PC': 'pc', '서버': 'server', '저장매체': 'storage', '네트워크': 'network',
- '업무지원장비': 'equipment', '시설자산': 'officeSupplies', '공간정보장비': 'survey',
- '내빈/외빈': 'vip', 'PC부품': 'pcParts'
- };
-
- rows.forEach(row => {
- const key = catMap[row.category] || 'pc';
- masterData[key].push(row);
- });
-
- const [swInternal] = await connection.query('SELECT * FROM asset_software_perpetual');
- const [swExternal] = await connection.query('SELECT * FROM asset_software_subscription');
- const [swUsers] = await connection.query('SELECT * FROM asset_software_assignment');
- const [users] = await connection.query('SELECT * FROM system_users');
- const [logs] = await connection.query('SELECT * FROM asset_history ORDER BY created_at DESC');
- const [partsMaster] = await connection.query('SELECT * FROM hardware_components_master ORDER BY category, component_name');
- const [jobSpecs] = await connection.query('SELECT * FROM job_spec_standards ORDER BY job_name');
-
- masterData.swInternal = swInternal;
- masterData.swExternal = swExternal;
- masterData.swUsers = swUsers;
- masterData.users = users;
- masterData.logs = logs;
- masterData.partsMaster = partsMaster;
- masterData.jobSpecs = jobSpecs;
-
- res.json(masterData);
- } catch (err) {
- handleError(res, err, 'MASTER DATA');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 3. Asset Save (Surgical Split to Normalized V3 Tables)
-app.post('/api/asset/:category/save', async (req, res) => {
- const asset = req.body;
- let connection;
- try {
- connection = await pool.getConnection();
- await connection.beginTransaction();
-
- // 3.0 History Tracking & Auto Field Update
- const [oldCoreRows] = await connection.query('SELECT * FROM asset_core WHERE id = ?', [asset.id]);
- const [oldSpecRows] = await connection.query('SELECT * FROM asset_spec WHERE asset_id = ?', [asset.id]);
- const oldCore = oldCoreRows[0] || {};
- const oldSpec = oldSpecRows[0] || {};
-
- const historyLogs = [];
- const logDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
- const logUser = '관리자';
-
- // 3.0.1 Core 변동 감지 (Dept, User)
- const oldDept = oldCore.current_dept || '';
- const newDept = asset.current_dept || '';
- if (newDept !== '' && oldDept !== newDept) {
- asset.previous_dept = oldDept;
- historyLogs.push({
- event_type: 'DEPT_CHANGE',
- old_dept: oldDept || null,
- new_dept: newDept,
- details: `[조직 변동] ${oldDept || '(없음)'} -> ${newDept}`
- });
- }
-
- const oldUser = oldCore.user_current || '';
- const newUser = asset.user_current || '';
- if (newUser !== '' && oldUser !== newUser) {
- asset.previous_user = oldUser;
- historyLogs.push({
- event_type: 'USER_CHANGE',
- old_user: oldUser || null,
- new_user: newUser,
- details: `[사용자 변동] ${oldUser || '(없음)'} -> ${newUser}`
- });
- }
-
- // 3.0.2 Spec 변동 감지 (CPU, RAM, GPU, OS, Mainboard 등)
- const specFieldsToTrack = [
- { key: 'cpu', label: 'CPU' },
- { key: 'ram', label: 'RAM' },
- { key: 'gpu', label: 'GPU' },
- { key: 'os', label: 'OS' },
- { key: 'mainboard', label: '메인보드' }
- ];
-
- specFieldsToTrack.forEach(field => {
- const oldVal = String(oldSpec[field.key] || '').trim();
- const newVal = String(asset[field.key] || '').trim();
- if (newVal !== '' && oldVal !== newVal) {
- historyLogs.push({
- event_type: 'SPEC_CHANGE',
- details: `[사양 변경] ${field.label}: ${oldVal || '(없음)'} -> ${newVal}`
- });
- }
- });
-
- // 3.0.3 상태 변경 감지
- const oldStatus = oldSpec.hw_status || '';
- const newStatus = asset.hw_status || '';
- if (newStatus !== '' && oldStatus !== newStatus) {
- historyLogs.push({
- event_type: 'STATUS_CHANGE',
- details: `[상태 변경] ${oldStatus || '(없음)'} -> ${newStatus}`
- });
- }
-
- // 로그 일괄 삽입
- for (const log of historyLogs) {
- await connection.query(
- `INSERT INTO asset_history (asset_id, event_type, old_dept, new_dept, old_user, new_user, details, log_date, log_user)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [asset.id, log.event_type, log.old_dept || null, log.new_dept || null, log.old_user || null, log.new_user || null, log.details, logDate, logUser]
- );
- }
-
- // 3.1 asset_core
- const coreFields = ['id', 'asset_code', 'category', 'asset_type', 'current_role', 'asset_purpose', 'service_type', 'purchase_corp', 'purchase_date', 'purchase_amount', 'purchase_vendor', 'approval_document', 'memo', 'manager_primary', 'manager_secondary', 'current_dept', 'previous_dept', 'user_current', 'previous_user', 'emp_no', 'user_position'];
- const coreData = {};
- coreFields.forEach(f => { if (asset[f] !== undefined) coreData[f] = asset[f]; });
- const coreKeys = Object.keys(coreData);
-
- console.log(`[DEBUG] Saving Asset ID: ${asset.id}, Code: ${asset.asset_code}`);
- const [existingCore] = await connection.query('SELECT id FROM asset_core WHERE id = ?', [asset.id]);
- console.log(`[DEBUG] Existing Core Check for ${asset.id}: Found ${existingCore.length}`);
-
- if (existingCore.length > 0) {
- // UPDATE
- const updateKeys = coreKeys.filter(k => k !== 'id');
- const coreSql = `UPDATE asset_core SET ${updateKeys.map(k => `${k} = ?`).join(', ')} WHERE id = ?`;
- const [updRes] = await connection.query(coreSql, [...updateKeys.map(k => coreData[k]), asset.id]);
- console.log(`[DEBUG] Core UPDATE result: affectedRows=${updRes.affectedRows}`);
- } else {
- // INSERT
- const coreSql = `INSERT INTO asset_core (${coreKeys.join(', ')}) VALUES (${coreKeys.map(() => '?').join(', ')})`;
- const [insRes] = await connection.query(coreSql, Object.values(coreData));
- console.log(`[DEBUG] Core INSERT result: affectedRows=${insRes.affectedRows}`);
- }
-
- // 3.2 asset_spec
- const specFields = ['hw_status', 'model_name', 'mainboard', 'os', 'cpu', 'ram', 'gpu', 'monitoring', 'price', 'monitor_inch', 'serial_num'];
- const specData = { asset_id: asset.id };
- specFields.forEach(f => { if (asset[f] !== undefined) specData[f] = asset[f]; });
- const specKeys = Object.keys(specData);
- const [specExists] = await connection.query('SELECT id FROM asset_spec WHERE asset_id = ?', [asset.id]);
- if (specExists.length > 0) {
- const updateSql = `UPDATE asset_spec SET ${specKeys.filter(k => k !== 'asset_id').map(k => `${k} = ?`).join(', ')} WHERE asset_id = ?`;
- await connection.query(updateSql, [...specKeys.filter(k => k !== 'asset_id').map(k => specData[k]), asset.id]);
- } else {
- await connection.query(`INSERT INTO asset_spec (${specKeys.join(', ')}) VALUES (${specKeys.map(() => '?').join(', ')})`, Object.values(specData));
- }
-
- // 3.3 asset_volume
- await connection.query('DELETE FROM asset_volume WHERE asset_id = ?', [asset.id]);
- if (asset.volumes) {
- try {
- let vols = typeof asset.volumes === 'string' ? JSON.parse(asset.volumes) : asset.volumes;
- if (Array.isArray(vols)) {
- for (let i = 0; i < vols.length; i++) {
- const v = vols[i];
- if (v.type && v.capacity) {
- await connection.query(
- 'INSERT INTO asset_volume (asset_id, disk_type, capacity, unit, slot_no) VALUES (?, ?, ?, ?, ?)',
- [asset.id, v.type, v.capacity, v.unit || 'GB', v.slot || (i + 1)]
- );
- }
- }
- }
- } catch(e) { console.error('Volume parse error', e); }
- }
-
- // 3.4 asset_location
- if (asset.location || asset.location_detail) {
- const [locActive] = await connection.query('SELECT * FROM asset_location WHERE asset_id = ? AND is_active = 1', [asset.id]);
- const isChanged = locActive.length === 0 || locActive[0].location !== asset.location || locActive[0].location_detail !== asset.location_detail || locActive[0].loc_x !== asset.loc_x || locActive[0].loc_y !== asset.loc_y;
- if (isChanged) {
- await connection.query('UPDATE asset_location SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]);
- await connection.query(`INSERT INTO asset_location (asset_id, location, location_detail, location_photo, loc_x, loc_y, is_active) VALUES (?, ?, ?, ?, ?, ?, 1)`,
- [asset.id, asset.location, asset.location_detail, asset.location_photo, asset.loc_x, asset.loc_y]);
- }
- }
-
- // 3.5 asset_remote (Dynamic Array Logic)
- if (asset.remotes) {
- try {
- let nets = typeof asset.remotes === 'string' ? JSON.parse(asset.remotes) : asset.remotes;
- if (Array.isArray(nets)) {
- await connection.query('UPDATE asset_remote SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]);
- for (const n of nets) {
- if (n.type) {
- await connection.query(
- 'INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)',
- [asset.id, n.type, n.name || '', n.val1 || '', n.val2 || '']
- );
- }
- }
- }
- } catch(e) { console.error('Remote data parse error', e); }
- } else {
- // Fallback for UI that hasn't sent the networks array yet
- if (asset.ip_address || asset.mac_address || asset.remote_tool) {
- const [netActive] = await connection.query('SELECT * FROM asset_remote WHERE asset_id = ? AND is_active = 1', [asset.id]);
- const isChanged = netActive.length === 0 || netActive[0].net_value1 !== asset.ip_address || netActive[0].net_value2 !== asset.mac_address || netActive[0].net_name !== asset.remote_tool;
- if (isChanged) {
- await connection.query('UPDATE asset_remote SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]);
- if (asset.ip_address || asset.mac_address) {
- await connection.query('INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)', [asset.id, 'IP', '기본망', asset.ip_address, asset.mac_address]);
- }
- if (asset.remote_tool || asset.remote_id || asset.remote_pw) {
- await connection.query('INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)', [asset.id, 'REMOTE', asset.remote_tool, asset.remote_id, asset.remote_pw]);
- }
- }
- }
- }
-
- await connection.commit();
- console.log(`💾 [V3 ASSET SAVE] ID: ${asset.id}`);
- res.json({ success: true });
- } catch (err) {
- if (connection) await connection.rollback();
- handleError(res, err, 'ASSET SAVE V3');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 3.6 PC Flow Transaction (Checkout, Return, Move)
-app.post('/api/pc/flow', async (req, res) => {
- const { action, assetId, userName, dept, empNo, position, date, details, manager } = req.body;
- let connection;
- try {
- connection = await pool.getConnection();
- await connection.beginTransaction();
-
- if (action === 'checkout') {
- await connection.query(
- `UPDATE asset_core
- SET user_current = ?, emp_no = ?, current_dept = ?, user_position = ?
- WHERE id = ?`,
- [userName, empNo, dept, position, assetId]
- );
- await connection.query(
- `UPDATE asset_spec SET hw_status = '운영' WHERE asset_id = ?`,
- [assetId]
- );
- } else if (action === 'return') {
- await connection.query(
- `UPDATE asset_core
- SET previous_user = user_current, previous_dept = current_dept,
- user_current = '', emp_no = '', user_position = ''
- WHERE id = ?`,
- [assetId]
- );
- await connection.query(
- `UPDATE asset_spec SET hw_status = '재고' WHERE asset_id = ?`,
- [assetId]
- );
- } else if (action === 'move') {
- await connection.query(
- `UPDATE asset_core
- SET previous_user = user_current, previous_dept = current_dept,
- user_current = ?, emp_no = ?, current_dept = ?, user_position = ?
- WHERE id = ?`,
- [userName, empNo, dept, position, assetId]
- );
- await connection.query(
- `UPDATE asset_spec SET hw_status = '운영' WHERE asset_id = ?`,
- [assetId]
- );
- } else {
- throw new Error('Invalid action type');
- }
-
- // Insert into asset_history
- await connection.query(
- `INSERT INTO asset_history (asset_id, log_date, log_user, details)
- VALUES (?, ?, ?, ?)`,
- [assetId, date || new Date().toISOString().split('T')[0], manager || 'system', details]
- );
-
- await connection.commit();
- console.log(`💾 [PC FLOW TRANSACTION] Action: ${action}, Asset ID: ${assetId}`);
- res.json({ success: true });
- } catch (err) {
- if (connection) await connection.rollback();
- handleError(res, err, 'PC FLOW TRANSACTION');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 4. Asset Delete
-app.delete('/api/asset/:category/:id', async (req, res) => {
- const { category, id } = req.params;
-
- // Define mapping for which base table handles the delete
- const deleteTableMap = {
- pc: 'asset_core',
- server: 'asset_core',
- storage: 'asset_core',
- network: 'asset_core',
- equipment: 'asset_core',
- officeSupplies: 'asset_core',
- survey: 'asset_core',
- vip: 'asset_core',
- pcParts: 'asset_core',
- swInternal: 'asset_software_perpetual',
- swExternal: 'asset_software_subscription',
- swUsers: 'asset_software_assignment',
- users: 'system_users'
- };
-
- const table = deleteTableMap[category];
-
- if (!table) return res.status(400).json({ error: 'Invalid category for deletion' });
-
- try {
- const connection = await pool.getConnection();
- // For asset_core, ON DELETE CASCADE will handle spec, location, remote, volume
- await connection.query(`DELETE FROM ${table} WHERE id = ?`, [id]);
- connection.release();
- console.log(`🗑️ [ASSET DELETE] Category: ${category}, ID: ${id}`);
- res.json({ success: true });
- } catch (err) {
- handleError(res, err, 'ASSET DELETE');
- }
-});
-
-// 5. Generate Next Asset Code
-app.get('/api/generate-asset-code', async (req, res) => {
- const { prefix, purchaseDate } = req.query;
- if (!prefix) return res.status(400).json({ error: 'Prefix is required' });
- try {
- const connection = await pool.getConnection();
- const datePart = purchaseDate ? purchaseDate.toString().replace(/-/g, '').substring(0, 6) : '';
- const searchPattern = datePart ? `${prefix}-${datePart}-%` : `${prefix}-%`;
- let maxNum = 0;
- for (const table of ASSET_TABLES) {
- try {
- const [rows] = await connection.query(`SELECT asset_code FROM ${table} WHERE asset_code LIKE ?`, [searchPattern]);
- rows.forEach(row => {
- const parts = row.asset_code.split('-');
- const num = parseInt(parts[parts.length - 1]);
- if (!isNaN(num) && num > maxNum) maxNum = num;
- });
- } catch (err) {}
- }
- const nextNum = maxNum + 1;
- const nextCode = datePart ? `${prefix}-${datePart}-${String(nextNum).padStart(4, '0')}` : `${prefix}-${String(nextNum).padStart(4, '0')}`;
- connection.release();
- res.json({ nextCode });
- } catch (err) { handleError(res, err, 'GENERATE CODE'); }
-});
-
-// 6. Map Config API
-app.get('/api/maps', (req, res) => {
- try {
- if (!fs.existsSync('map_config.json')) return res.json({});
- const data = fs.readFileSync('map_config.json', 'utf8');
- res.json(JSON.parse(data || '{}'));
- } catch (err) { handleError(res, err, 'GET MAPS'); }
-});
-
-// 6.5. Get Hardware Components Master List
-app.get('/api/hardware-components', async (req, res) => {
- try {
- const [rows] = await pool.query('SELECT * FROM hardware_components_master ORDER BY category, component_name');
- res.json(rows);
- } catch (err) {
- handleError(res, err, 'GET HARDWARE COMPONENTS');
- }
-});
-
-// 6.6. Save Hardware Component (Add or Update)
-app.post('/api/hardware-components/save', async (req, res) => {
- const { id, category, component_name, score_tier, deduction } = req.body;
- let connection;
- try {
- connection = await pool.getConnection();
- if (id) {
- await connection.query(
- 'UPDATE hardware_components_master SET category = ?, component_name = ?, score_tier = ?, deduction = ? WHERE id = ?',
- [category, component_name, score_tier, deduction, id]
- );
- } else {
- await connection.query(
- 'INSERT INTO hardware_components_master (category, component_name, score_tier, deduction) VALUES (?, ?, ?, ?)',
- [category, component_name, score_tier, deduction]
- );
- }
- res.json({ success: true });
- } catch (err) {
- handleError(res, err, 'SAVE HARDWARE COMPONENT');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 6.7. Delete Hardware Component
-app.delete('/api/hardware-components/:id', async (req, res) => {
- const { id } = req.params;
- let connection;
- try {
- connection = await pool.getConnection();
- await connection.query('DELETE FROM hardware_components_master WHERE id = ?', [id]);
- res.json({ success: true });
- } catch (err) {
- handleError(res, err, 'DELETE HARDWARE COMPONENT');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 6.7.1. Get Job Spec Standards
-app.get('/api/job-specs', async (req, res) => {
- try {
- const [rows] = await pool.query('SELECT * FROM job_spec_standards ORDER BY job_name');
- res.json(rows);
- } catch (err) {
- handleError(res, err, 'GET JOB SPECS');
- }
-});
-
-// 6.7.2. Save Job Spec Standard (Add or Update)
-app.post('/api/job-specs/save', async (req, res) => {
- const { id, job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks } = req.body;
- let connection;
- try {
- connection = await pool.getConnection();
- if (id) {
- await connection.query(
- 'UPDATE job_spec_standards SET job_name = ?, cpu_standard = ?, ram_standard = ?, gpu_standard = ?, min_score = ?, remarks = ? WHERE id = ?',
- [job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks, id]
- );
- } else {
- await connection.query(
- 'INSERT INTO job_spec_standards (job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks) VALUES (?, ?, ?, ?, ?, ?)',
- [job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks]
- );
- }
- res.json({ success: true });
- } catch (err) {
- handleError(res, err, 'SAVE JOB SPEC');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 6.7.3. Delete Job Spec Standard
-app.delete('/api/job-specs/:id', async (req, res) => {
- const { id } = req.params;
- let connection;
- try {
- connection = await pool.getConnection();
- await connection.query('DELETE FROM job_spec_standards WHERE id = ?', [id]);
- res.json({ success: true });
- } catch (err) {
- handleError(res, err, 'DELETE JOB SPEC');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 6.8. Get System Users List
-app.get('/api/system-users', async (req, res) => {
- try {
- const [rows] = await pool.query('SELECT * FROM system_users ORDER BY user_name');
- res.json(rows);
- } catch (err) {
- handleError(res, err, 'GET SYSTEM USERS');
- }
-});
-
-// 6.9. Save System User (Add or Update)
-app.post('/api/system-users/save', async (req, res) => {
- const { id, emp_no, user_name, dept_name, position, status } = req.body;
- let connection;
- try {
- connection = await pool.getConnection();
- if (id) {
- await connection.query(
- 'UPDATE system_users SET emp_no = ?, user_name = ?, dept_name = ?, position = ?, status = ? WHERE id = ?',
- [emp_no, user_name, dept_name, position, status, id]
- );
- } else {
- const newId = 'USER-' + Math.random().toString(36).substring(2, 9).toUpperCase();
- await connection.query(
- 'INSERT INTO system_users (id, emp_no, user_name, dept_name, position, status) VALUES (?, ?, ?, ?, ?, ?)',
- [newId, emp_no, user_name, dept_name, position, status]
- );
- }
- res.json({ success: true });
- } catch (err) {
- handleError(res, err, 'SAVE SYSTEM USER');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 6.10. Delete System User
-app.delete('/api/system-users/:id', async (req, res) => {
- const { id } = req.params;
- let connection;
- try {
- connection = await pool.getConnection();
- await connection.query('DELETE FROM system_users WHERE id = ?', [id]);
- res.json({ success: true });
- } catch (err) {
- handleError(res, err, 'DELETE SYSTEM USER');
- } finally {
- if (connection) connection.release();
- }
-});
-
-app.post('/api/maps/save', async (req, res) => {
- let connection;
- try {
- const { path, boxes } = req.body;
- if (!path) return res.status(400).json({ error: 'Path is required' });
-
- // 1. Get old config to track movements
- let oldConfig = {};
- if (fs.existsSync('map_config.json')) {
- oldConfig = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}');
- }
- const oldBoxes = oldConfig[path] || [];
-
- // 2. Save new config to file
- oldConfig[path] = boxes;
- fs.writeFileSync('map_config.json', JSON.stringify(oldConfig, null, 2));
-
- // 3. Sync Database Assets (asset_location table)
- connection = await pool.getConnection();
- for (const box of boxes) {
- if (box.asset_id) {
- console.log(`Syncing asset ${box.asset_id} to new position: [${box.x}, ${box.y}]`);
- await connection.query(
- 'UPDATE asset_location SET loc_x = ?, loc_y = ? WHERE asset_id = ? AND is_active = 1',
- [box.x, box.y, box.asset_id]
- );
- }
- }
-
- res.json({ success: true, message: 'Map and Database synced successfully' });
- } catch (err) {
- handleError(res, err, 'SAVE MAPS SYNC');
- } finally {
- if (connection) connection.release();
- }
-});
-
-// 7. File Upload API (Base64)
-app.post('/api/upload', (req, res) => {
- try {
- const { fileName, fileData } = req.body;
- if (!fileName || !fileData) return res.status(400).json({ error: 'FileName and FileData are required' });
-
- // base64 데이터에서 실제 바이너리 추출
- const base64Data = fileData.replace(/^data:.*;base64,/, "");
- const buffer = Buffer.from(base64Data, 'base64');
-
- // 고유한 파일명 생성 (타임스탬프 결합)
- const timestamp = Date.now();
- const safeFileName = `${timestamp}_${fileName.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
- const filePath = `uploads/${safeFileName}`;
-
- fs.writeFileSync(filePath, buffer);
-
- console.log(`파일 업로드 성공: ${filePath}`);
- res.json({ success: true, filePath: `/${filePath}`, fileName: safeFileName });
- } catch (err) {
- handleError(res, err, 'FILE UPLOAD');
- }
-});
-
-// Health check endpoint for container orchestration
-app.get('/health', async (req, res) => {
- try {
- const connection = await pool.getConnection();
- const result = await connection.query('SELECT 1');
- connection.release();
- res.status(200).json({ status: 'ok', db: 'connected' });
- } catch (err) {
- // Return degraded status if DB unreachable, but still report service as alive
- res.status(200).json({ status: 'degraded', db: 'unreachable', error: err.message });
- }
-});
-
-// Readiness check endpoint (only returns 200 if fully ready)
-app.get('/ready', async (req, res) => {
- try {
- const connection = await pool.getConnection();
- const result = await connection.query('SELECT 1');
- connection.release();
- res.status(200).json({ status: 'ready' });
- } catch (err) {
- res.status(503).json({ status: 'not_ready', error: err.message });
- }
-});
-
-app.listen(3000, '0.0.0.0', () => {
- console.log('📡 ITAM BACKEND SERVER RUNNING ON PORT 3000 (V3 Normalized)');
-});
+import express from 'express';
+import mysql from 'mysql2/promise';
+import cors from 'cors';
+import dotenv from 'dotenv';
+import fs from 'fs';
+
+dotenv.config();
+
+const dbConfig = {
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASS,
+ database: process.env.DB_NAME,
+ port: parseInt(process.env.DB_PORT || '3306')
+};
+
+const getDbConnectionSummary = () => ({
+ host: dbConfig.host || '(missing)',
+ port: dbConfig.port,
+ user: dbConfig.user || '(missing)',
+ database: dbConfig.database || '(missing)'
+});
+
+const app = express();
+app.use(cors());
+app.use(express.json({ limit: '50mb' }));
+app.use('/uploads', express.static('uploads')); // 업로드 파일 정적 서빙
+
+// uploads 폴더가 없으면 생성
+if (!fs.existsSync('uploads')) {
+ fs.mkdirSync('uploads');
+}
+
+// MySQL Pool Configuration
+const pool = mysql.createPool({
+ host: dbConfig.host,
+ user: dbConfig.user,
+ password: dbConfig.password,
+ database: dbConfig.database,
+ port: dbConfig.port,
+ waitForConnections: true,
+ connectionLimit: 10,
+ queueLimit: 0
+});
+
+// Database startup check (ensure job_spec_standards table exists)
+(async () => {
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ await connection.query(`
+ CREATE TABLE IF NOT EXISTS job_spec_standards (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ job_name VARCHAR(100) UNIQUE NOT NULL,
+ cpu_standard VARCHAR(255),
+ ram_standard VARCHAR(100),
+ gpu_standard VARCHAR(100),
+ min_score INT DEFAULT 0,
+ remarks TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+ `);
+ console.log('✅ job_spec_standards table verification completed.');
+ } catch (err) {
+ console.error('❌ Failed to verify/create job_spec_standards table:', {
+ db: getDbConnectionSummary(),
+ code: err.code,
+ errno: err.errno,
+ syscall: err.syscall,
+ address: err.address,
+ port: err.port,
+ message: err.message
+ });
+ } finally {
+ if (connection) connection.release();
+ }
+})();
+
+// Error Handler
+const handleError = (res, err, label) => {
+ console.error(`❌ [${label}] Error:`, {
+ db: getDbConnectionSummary(),
+ code: err.code,
+ errno: err.errno,
+ syscall: err.syscall,
+ address: err.address,
+ port: err.port,
+ message: err.message
+ });
+ res.status(500).json({ error: err.message });
+};
+
+// --- Global Constants ---
+const CATEGORY_TABLE_MAP = {
+ pc: 'asset_core',
+ server: 'asset_core',
+ storage: 'asset_core',
+ network: 'asset_core',
+ equipment: 'asset_core',
+ officeSupplies: 'asset_core',
+ survey: 'asset_core',
+ vip: 'asset_core',
+ pcParts: 'asset_core',
+ swInternal: 'asset_software_perpetual',
+ swExternal: 'asset_software_subscription',
+ swUsers: 'asset_software_assignment',
+ users: 'system_users',
+ logs: 'asset_history'
+};
+
+const ASSET_TABLES = [
+ 'asset_core'
+];
+
+// --- Helper Functions for Maps ---
+function getCleanMapKey(path) {
+ let clean = path.replace('img/location_photo/', '').replace('.png', '');
+ clean = clean.replace('서관', 'W').replace('동관', 'E');
+ clean = clean.replace('한맥빌딩/MDF실/MDF_', 'HAN-MDF-');
+ clean = clean.replace('기술개발센터/서버실/서버실_', 'DEV-SVR-');
+ clean = clean.replace(/\//g, '-');
+ return clean;
+}
+
+function getLocationName(path) {
+ if (path.includes('IDC')) return 'IDC';
+ if (path.includes('한맥빌딩')) return '한맥빌딩';
+ if (path.includes('기술개발센터')) return '기술개발센터';
+ return '기타';
+}
+
+function getLocationDetail(path, idx) {
+ let clean = path.replace('img/location_photo/', '').replace('.png', '');
+ let parts = clean.split('/');
+ let lastPart = parts[parts.length - 1];
+ return `${lastPart} 구역 자리 #${idx + 1}`;
+}
+
+// --- API Endpoints ---
+
+// 1. Generic Batch Save (Dynamic Table Detection)
+app.post('/api/:table/batch', async (req, res) => {
+ const { table } = req.params;
+ const dbTable = CATEGORY_TABLE_MAP[table] || table;
+ const data = req.body;
+ if (!Array.isArray(data)) return res.status(400).json({ error: 'Data must be an array' });
+
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ await connection.beginTransaction();
+
+ const [columns] = await connection.query(`DESCRIBE ${dbTable}`);
+ const validFields = columns.map(c => c.Field);
+
+ await connection.query(`DELETE FROM ${dbTable}`);
+
+ if (data.length > 0) {
+ const placeholders = validFields.map(() => '?').join(', ');
+ const sql = `INSERT INTO ${dbTable} (${validFields.join(', ')}) VALUES (${placeholders})`;
+
+ for (const item of data) {
+ const values = validFields.map(field => {
+ const val = item[field];
+ return val === undefined ? null : val;
+ });
+ await connection.query(sql, values);
+ }
+ }
+
+ await connection.commit();
+ res.json({ success: true, count: data.length });
+ } catch (err) {
+ if (connection) await connection.rollback();
+ handleError(res, err, 'BATCH SAVE');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 2. Get All Assets (Integrated Master Data from Normalized V3 Schema)
+app.get('/api/assets/master', async (req, res) => {
+ let connection;
+ try {
+ connection = await pool.getConnection();
+
+ const masterData = {
+ pc: [], server: [], storage: [], network: [],
+ equipment: [], officeSupplies: [], survey: [], vip: [], pcParts: [],
+ swInternal: [], swExternal: [], swUsers: [], users: [], logs: [], partsMaster: []
+ };
+
+ // Load from V3 Normalized Schema
+ const [rows] = await connection.query(`
+ SELECT
+ c.*,
+ s.hw_status, s.model_name, s.mainboard, s.os, s.cpu, s.ram, s.gpu,
+ s.monitoring, s.price, s.monitor_inch, s.serial_num,
+ l.location, l.location_detail, l.location_photo, l.loc_x, l.loc_y,
+ (
+ SELECT EXISTS(SELECT 1 FROM asset_audit_pending WHERE asset_code = c.asset_code AND status = 'APPROVED')
+ ) AS is_audit_approved,
+ (
+ SELECT JSON_ARRAYAGG(JSON_OBJECT('type', net_type, 'name', net_name, 'val1', net_value1, 'val2', net_value2))
+ FROM asset_remote WHERE asset_id = c.id AND is_active = 1
+ ) as remotes,
+ (
+ SELECT JSON_ARRAYAGG(JSON_OBJECT('type', disk_type, 'capacity', capacity, 'unit', unit, 'slot', slot_no))
+ FROM asset_volume WHERE asset_id = c.id
+ ) as volumes
+ FROM asset_core c
+ LEFT JOIN asset_spec s ON c.id = s.asset_id
+ LEFT JOIN asset_location l ON l.id = (
+ SELECT id FROM asset_location
+ WHERE asset_id = c.id AND is_active = 1
+ ORDER BY created_at DESC LIMIT 1
+ )
+ `);
+
+ const catMap = {
+ 'PC': 'pc', '서버': 'server', '저장매체': 'storage', '네트워크': 'network',
+ '업무지원장비': 'equipment', '시설자산': 'officeSupplies', '공간정보장비': 'survey',
+ '내빈/외빈': 'vip', 'PC부품': 'pcParts'
+ };
+
+ rows.forEach(row => {
+ const key = catMap[row.category] || 'pc';
+ masterData[key].push(row);
+ });
+
+ const [swInternal] = await connection.query('SELECT * FROM asset_software_perpetual');
+ const [swExternal] = await connection.query('SELECT * FROM asset_software_subscription');
+ const [swUsers] = await connection.query('SELECT * FROM asset_software_assignment');
+ const [users] = await connection.query('SELECT * FROM system_users');
+ const [logs] = await connection.query('SELECT * FROM asset_history ORDER BY created_at DESC');
+ const [partsMaster] = await connection.query('SELECT * FROM hardware_components_master ORDER BY category, component_name');
+ const [jobSpecs] = await connection.query('SELECT * FROM job_spec_standards ORDER BY job_name');
+
+ masterData.swInternal = swInternal;
+ masterData.swExternal = swExternal;
+ masterData.swUsers = swUsers;
+ masterData.users = users;
+ masterData.logs = logs;
+ masterData.partsMaster = partsMaster;
+ masterData.jobSpecs = jobSpecs;
+
+ res.json(masterData);
+ } catch (err) {
+ handleError(res, err, 'MASTER DATA');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 3. Asset Save (Surgical Split to Normalized V3 Tables)
+app.post('/api/asset/:category/save', async (req, res) => {
+ const asset = req.body;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ await connection.beginTransaction();
+
+ // 3.0 History Tracking & Auto Field Update
+ const [oldCoreRows] = await connection.query('SELECT * FROM asset_core WHERE id = ?', [asset.id]);
+ const [oldSpecRows] = await connection.query('SELECT * FROM asset_spec WHERE asset_id = ?', [asset.id]);
+ const oldCore = oldCoreRows[0] || {};
+ const oldSpec = oldSpecRows[0] || {};
+
+ const historyLogs = [];
+ const logDate = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
+ const logUser = '관리자';
+
+ // 3.0.1 Core 변동 감지 (Dept, User)
+ const oldDept = oldCore.current_dept || '';
+ const newDept = asset.current_dept || '';
+ if (newDept !== '' && oldDept !== newDept) {
+ asset.previous_dept = oldDept;
+ historyLogs.push({
+ event_type: 'DEPT_CHANGE',
+ old_dept: oldDept || null,
+ new_dept: newDept,
+ details: `[조직 변동] ${oldDept || '(없음)'} -> ${newDept}`
+ });
+ }
+
+ const oldUser = oldCore.user_current || '';
+ const newUser = asset.user_current || '';
+ if (newUser !== '' && oldUser !== newUser) {
+ asset.previous_user = oldUser;
+ historyLogs.push({
+ event_type: 'USER_CHANGE',
+ old_user: oldUser || null,
+ new_user: newUser,
+ details: `[사용자 변동] ${oldUser || '(없음)'} -> ${newUser}`
+ });
+ }
+
+ // 3.0.2 Spec 변동 감지 (CPU, RAM, GPU, OS, Mainboard 등)
+ const specFieldsToTrack = [
+ { key: 'cpu', label: 'CPU' },
+ { key: 'ram', label: 'RAM' },
+ { key: 'gpu', label: 'GPU' },
+ { key: 'os', label: 'OS' },
+ { key: 'mainboard', label: '메인보드' }
+ ];
+
+ specFieldsToTrack.forEach(field => {
+ const oldVal = String(oldSpec[field.key] || '').trim();
+ const newVal = String(asset[field.key] || '').trim();
+ if (newVal !== '' && oldVal !== newVal) {
+ historyLogs.push({
+ event_type: 'SPEC_CHANGE',
+ details: `[사양 변경] ${field.label}: ${oldVal || '(없음)'} -> ${newVal}`
+ });
+ }
+ });
+
+ // 3.0.3 상태 변경 감지
+ const oldStatus = oldSpec.hw_status || '';
+ const newStatus = asset.hw_status || '';
+ if (newStatus !== '' && oldStatus !== newStatus) {
+ historyLogs.push({
+ event_type: 'STATUS_CHANGE',
+ details: `[상태 변경] ${oldStatus || '(없음)'} -> ${newStatus}`
+ });
+ }
+
+ // 로그 일괄 삽입
+ for (const log of historyLogs) {
+ await connection.query(
+ `INSERT INTO asset_history (asset_id, event_type, old_dept, new_dept, old_user, new_user, details, log_date, log_user)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ [asset.id, log.event_type, log.old_dept || null, log.new_dept || null, log.old_user || null, log.new_user || null, log.details, logDate, logUser]
+ );
+ }
+
+ // 3.1 asset_core
+ const coreFields = ['id', 'asset_code', 'category', 'asset_type', 'current_role', 'asset_purpose', 'service_type', 'purchase_corp', 'purchase_date', 'purchase_amount', 'purchase_vendor', 'approval_document', 'memo', 'manager_primary', 'manager_secondary', 'current_dept', 'previous_dept', 'user_current', 'previous_user', 'emp_no', 'user_position'];
+ const coreData = {};
+ coreFields.forEach(f => { if (asset[f] !== undefined) coreData[f] = asset[f]; });
+ const coreKeys = Object.keys(coreData);
+
+ console.log(`[DEBUG] Saving Asset ID: ${asset.id}, Code: ${asset.asset_code}`);
+ const [existingCore] = await connection.query('SELECT id FROM asset_core WHERE id = ?', [asset.id]);
+ console.log(`[DEBUG] Existing Core Check for ${asset.id}: Found ${existingCore.length}`);
+
+ if (existingCore.length > 0) {
+ // UPDATE
+ const updateKeys = coreKeys.filter(k => k !== 'id');
+ const coreSql = `UPDATE asset_core SET ${updateKeys.map(k => `${k} = ?`).join(', ')} WHERE id = ?`;
+ const [updRes] = await connection.query(coreSql, [...updateKeys.map(k => coreData[k]), asset.id]);
+ console.log(`[DEBUG] Core UPDATE result: affectedRows=${updRes.affectedRows}`);
+ } else {
+ // INSERT
+ const coreSql = `INSERT INTO asset_core (${coreKeys.join(', ')}) VALUES (${coreKeys.map(() => '?').join(', ')})`;
+ const [insRes] = await connection.query(coreSql, Object.values(coreData));
+ console.log(`[DEBUG] Core INSERT result: affectedRows=${insRes.affectedRows}`);
+ }
+
+ // 3.2 asset_spec
+ const specFields = ['hw_status', 'model_name', 'mainboard', 'os', 'cpu', 'ram', 'gpu', 'monitoring', 'price', 'monitor_inch', 'serial_num'];
+ const specData = { asset_id: asset.id };
+ specFields.forEach(f => { if (asset[f] !== undefined) specData[f] = asset[f]; });
+ const specKeys = Object.keys(specData);
+ const [specExists] = await connection.query('SELECT id FROM asset_spec WHERE asset_id = ?', [asset.id]);
+ if (specExists.length > 0) {
+ const updateSql = `UPDATE asset_spec SET ${specKeys.filter(k => k !== 'asset_id').map(k => `${k} = ?`).join(', ')} WHERE asset_id = ?`;
+ await connection.query(updateSql, [...specKeys.filter(k => k !== 'asset_id').map(k => specData[k]), asset.id]);
+ } else {
+ await connection.query(`INSERT INTO asset_spec (${specKeys.join(', ')}) VALUES (${specKeys.map(() => '?').join(', ')})`, Object.values(specData));
+ }
+
+ // 3.3 asset_volume
+ await connection.query('DELETE FROM asset_volume WHERE asset_id = ?', [asset.id]);
+ if (asset.volumes) {
+ try {
+ let vols = typeof asset.volumes === 'string' ? JSON.parse(asset.volumes) : asset.volumes;
+ if (Array.isArray(vols)) {
+ for (let i = 0; i < vols.length; i++) {
+ const v = vols[i];
+ if (v.type && v.capacity) {
+ await connection.query(
+ 'INSERT INTO asset_volume (asset_id, disk_type, capacity, unit, slot_no) VALUES (?, ?, ?, ?, ?)',
+ [asset.id, v.type, v.capacity, v.unit || 'GB', v.slot || (i + 1)]
+ );
+ }
+ }
+ }
+ } catch(e) { console.error('Volume parse error', e); }
+ }
+
+ // 3.4 asset_location
+ if (asset.location || asset.location_detail) {
+ const [locActive] = await connection.query('SELECT * FROM asset_location WHERE asset_id = ? AND is_active = 1', [asset.id]);
+ const isChanged = locActive.length === 0 || locActive[0].location !== asset.location || locActive[0].location_detail !== asset.location_detail || locActive[0].loc_x !== asset.loc_x || locActive[0].loc_y !== asset.loc_y;
+ if (isChanged) {
+ await connection.query('UPDATE asset_location SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]);
+ await connection.query(`INSERT INTO asset_location (asset_id, location, location_detail, location_photo, loc_x, loc_y, is_active) VALUES (?, ?, ?, ?, ?, ?, 1)`,
+ [asset.id, asset.location, asset.location_detail, asset.location_photo, asset.loc_x, asset.loc_y]);
+ }
+ }
+
+ // 3.5 asset_remote (Dynamic Array Logic)
+ if (asset.remotes) {
+ try {
+ let nets = typeof asset.remotes === 'string' ? JSON.parse(asset.remotes) : asset.remotes;
+ if (Array.isArray(nets)) {
+ await connection.query('UPDATE asset_remote SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]);
+ for (const n of nets) {
+ if (n.type) {
+ await connection.query(
+ 'INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)',
+ [asset.id, n.type, n.name || '', n.val1 || '', n.val2 || '']
+ );
+ }
+ }
+ }
+ } catch(e) { console.error('Remote data parse error', e); }
+ } else {
+ // Fallback for UI that hasn't sent the networks array yet
+ if (asset.ip_address || asset.mac_address || asset.remote_tool) {
+ const [netActive] = await connection.query('SELECT * FROM asset_remote WHERE asset_id = ? AND is_active = 1', [asset.id]);
+ const isChanged = netActive.length === 0 || netActive[0].net_value1 !== asset.ip_address || netActive[0].net_value2 !== asset.mac_address || netActive[0].net_name !== asset.remote_tool;
+ if (isChanged) {
+ await connection.query('UPDATE asset_remote SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1', [asset.id]);
+ if (asset.ip_address || asset.mac_address) {
+ await connection.query('INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)', [asset.id, 'IP', '기본망', asset.ip_address, asset.mac_address]);
+ }
+ if (asset.remote_tool || asset.remote_id || asset.remote_pw) {
+ await connection.query('INSERT INTO asset_remote (asset_id, net_type, net_name, net_value1, net_value2, is_active) VALUES (?, ?, ?, ?, ?, 1)', [asset.id, 'REMOTE', asset.remote_tool, asset.remote_id, asset.remote_pw]);
+ }
+ }
+ }
+ }
+
+ await connection.commit();
+ console.log(`💾 [V3 ASSET SAVE] ID: ${asset.id}`);
+ res.json({ success: true });
+ } catch (err) {
+ if (connection) await connection.rollback();
+ handleError(res, err, 'ASSET SAVE V3');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 3.6 PC Flow Transaction (Checkout, Return, Move)
+app.post('/api/pc/flow', async (req, res) => {
+ const { action, assetId, userName, dept, empNo, position, date, details, manager } = req.body;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ await connection.beginTransaction();
+
+ if (action === 'checkout') {
+ await connection.query(
+ `UPDATE asset_core
+ SET user_current = ?, emp_no = ?, current_dept = ?, user_position = ?
+ WHERE id = ?`,
+ [userName, empNo, dept, position, assetId]
+ );
+ await connection.query(
+ `UPDATE asset_spec SET hw_status = '운영' WHERE asset_id = ?`,
+ [assetId]
+ );
+ } else if (action === 'return') {
+ await connection.query(
+ `UPDATE asset_core
+ SET previous_user = user_current, previous_dept = current_dept,
+ user_current = '', emp_no = '', user_position = ''
+ WHERE id = ?`,
+ [assetId]
+ );
+ await connection.query(
+ `UPDATE asset_spec SET hw_status = '재고' WHERE asset_id = ?`,
+ [assetId]
+ );
+ } else if (action === 'move') {
+ await connection.query(
+ `UPDATE asset_core
+ SET previous_user = user_current, previous_dept = current_dept,
+ user_current = ?, emp_no = ?, current_dept = ?, user_position = ?
+ WHERE id = ?`,
+ [userName, empNo, dept, position, assetId]
+ );
+ await connection.query(
+ `UPDATE asset_spec SET hw_status = '운영' WHERE asset_id = ?`,
+ [assetId]
+ );
+ } else {
+ throw new Error('Invalid action type');
+ }
+
+ // Insert into asset_history
+ await connection.query(
+ `INSERT INTO asset_history (asset_id, log_date, log_user, details)
+ VALUES (?, ?, ?, ?)`,
+ [assetId, date || new Date().toISOString().split('T')[0], manager || 'system', details]
+ );
+
+ await connection.commit();
+ console.log(`💾 [PC FLOW TRANSACTION] Action: ${action}, Asset ID: ${assetId}`);
+ res.json({ success: true });
+ } catch (err) {
+ if (connection) await connection.rollback();
+ handleError(res, err, 'PC FLOW TRANSACTION');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 4. Asset Delete
+app.delete('/api/asset/:category/:id', async (req, res) => {
+ const { category, id } = req.params;
+
+ // Define mapping for which base table handles the delete
+ const deleteTableMap = {
+ pc: 'asset_core',
+ server: 'asset_core',
+ storage: 'asset_core',
+ network: 'asset_core',
+ equipment: 'asset_core',
+ officeSupplies: 'asset_core',
+ survey: 'asset_core',
+ vip: 'asset_core',
+ pcParts: 'asset_core',
+ swInternal: 'asset_software_perpetual',
+ swExternal: 'asset_software_subscription',
+ swUsers: 'asset_software_assignment',
+ users: 'system_users'
+ };
+
+ const table = deleteTableMap[category];
+
+ if (!table) return res.status(400).json({ error: 'Invalid category for deletion' });
+
+ try {
+ const connection = await pool.getConnection();
+ // For asset_core, ON DELETE CASCADE will handle spec, location, remote, volume
+ await connection.query(`DELETE FROM ${table} WHERE id = ?`, [id]);
+ connection.release();
+ console.log(`🗑️ [ASSET DELETE] Category: ${category}, ID: ${id}`);
+ res.json({ success: true });
+ } catch (err) {
+ handleError(res, err, 'ASSET DELETE');
+ }
+});
+
+// 5. Generate Next Asset Code
+app.get('/api/generate-asset-code', async (req, res) => {
+ const { prefix, purchaseDate } = req.query;
+ if (!prefix) return res.status(400).json({ error: 'Prefix is required' });
+ try {
+ const connection = await pool.getConnection();
+ const datePart = purchaseDate ? purchaseDate.toString().replace(/-/g, '').substring(0, 6) : '';
+ const searchPattern = datePart ? `${prefix}-${datePart}-%` : `${prefix}-%`;
+ let maxNum = 0;
+ for (const table of ASSET_TABLES) {
+ try {
+ const [rows] = await connection.query(`SELECT asset_code FROM ${table} WHERE asset_code LIKE ?`, [searchPattern]);
+ rows.forEach(row => {
+ const parts = row.asset_code.split('-');
+ const num = parseInt(parts[parts.length - 1]);
+ if (!isNaN(num) && num > maxNum) maxNum = num;
+ });
+ } catch (err) {}
+ }
+ const nextNum = maxNum + 1;
+ const nextCode = datePart ? `${prefix}-${datePart}-${String(nextNum).padStart(4, '0')}` : `${prefix}-${String(nextNum).padStart(4, '0')}`;
+ connection.release();
+ res.json({ nextCode });
+ } catch (err) { handleError(res, err, 'GENERATE CODE'); }
+});
+
+// 6. Map Config API (Adopt database-driven locations from origin/QR_setting)
+app.get('/api/maps', async (req, res) => {
+ try {
+ const query = `
+ SELECT
+ pl.location_code,
+ pl.location_name,
+ pl.location_detail,
+ pl.map_image,
+ pl.map_x,
+ pl.map_y,
+ pl.map_w,
+ pl.map_h,
+ al.asset_id
+ FROM physical_locations pl
+ LEFT JOIN asset_location al ON al.physical_location_code = pl.location_code AND al.is_active = 1
+ `;
+ const [rows] = await pool.query(query);
+
+ const mapConfig = {};
+ rows.forEach(row => {
+ const mapPath = row.map_image;
+ if (!mapConfig[mapPath]) {
+ mapConfig[mapPath] = [];
+ }
+ mapConfig[mapPath].push({
+ x: parseFloat(row.map_x).toFixed(2),
+ y: parseFloat(row.map_y).toFixed(2),
+ w: parseFloat(row.map_w).toFixed(2),
+ h: parseFloat(row.map_h).toFixed(2),
+ asset_id: row.asset_id
+ });
+ });
+
+ res.json(mapConfig);
+ } catch (err) {
+ handleError(res, err, 'GET MAPS');
+ }
+});
+
+app.post('/api/maps/save', async (req, res) => {
+ let connection;
+ try {
+ const { path, boxes } = req.body;
+ if (!path) return res.status(400).json({ error: 'Path is required' });
+ if (!Array.isArray(boxes)) return res.status(400).json({ error: 'Boxes must be an array' });
+
+ connection = await pool.getConnection();
+ await connection.beginTransaction();
+
+ const cleanKey = getCleanMapKey(path);
+ const locName = getLocationName(path);
+
+ // 1. Get old location codes for this map
+ const [oldLocs] = await connection.query(
+ 'SELECT location_code FROM physical_locations WHERE map_image = ?',
+ [path]
+ );
+ const oldLocCodes = oldLocs.map(r => r.location_code);
+
+ // 2. Deactivate and clear foreign key references in asset_location to these old location codes
+ if (oldLocCodes.length > 0) {
+ await connection.query(
+ 'UPDATE asset_location SET is_active = 0, deactivated_at = NOW(), physical_location_code = NULL WHERE physical_location_code IN (?)',
+ [oldLocCodes]
+ );
+ }
+
+ // 3. Delete old physical locations for this map
+ await connection.query(
+ 'DELETE FROM physical_locations WHERE map_image = ?',
+ [path]
+ );
+
+ // 4. Insert new physical locations and setup asset_location mappings
+ for (let i = 0; i < boxes.length; i++) {
+ const box = boxes[i];
+ const padIdx = String(i + 1).padStart(3, '0');
+ const locCode = `LOC-${cleanKey}-${padIdx}`;
+ const locDetail = getLocationDetail(path, i);
+
+ // Insert physical location
+ await connection.query(`
+ INSERT INTO physical_locations
+ (location_code, location_name, location_detail, map_image, map_x, map_y, map_w, map_h)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ `, [locCode, locName, locDetail, path, box.x, box.y, box.w, box.h]);
+
+ // If asset_id is mapped, update asset_location
+ if (box.asset_id) {
+ // Deactivate old active locations for this asset
+ await connection.query(
+ 'UPDATE asset_location SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1',
+ [box.asset_id]
+ );
+
+ // Insert new active location mapping
+ const pathPartsForMap = path.split('/');
+ const stdDetailForMap = pathPartsForMap[pathPartsForMap.length - 2] || locDetail;
+ await connection.query(`
+ INSERT INTO asset_location
+ (asset_id, location, location_detail, location_photo, loc_x, loc_y, physical_location_code, is_active)
+ VALUES (?, ?, ?, ?, ?, ?, ?, 1)
+ `, [box.asset_id, locName, stdDetailForMap, path, box.x, box.y, locCode]);
+ }
+ }
+
+ await connection.commit();
+ res.json({ success: true, message: 'Map and Database synced successfully' });
+ } catch (err) {
+ if (connection) await connection.rollback();
+ handleError(res, err, 'SAVE MAPS SYNC');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 6.5. Get Hardware Components Master List
+app.get('/api/hardware-components', async (req, res) => {
+ try {
+ const [rows] = await pool.query('SELECT * FROM hardware_components_master ORDER BY category, component_name');
+ res.json(rows);
+ } catch (err) {
+ handleError(res, err, 'GET HARDWARE COMPONENTS');
+ }
+});
+
+// 6.6. Save Hardware Component (Add or Update)
+app.post('/api/hardware-components/save', async (req, res) => {
+ const { id, category, component_name, score_tier, deduction } = req.body;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ if (id) {
+ await connection.query(
+ 'UPDATE hardware_components_master SET category = ?, component_name = ?, score_tier = ?, deduction = ? WHERE id = ?',
+ [category, component_name, score_tier, deduction, id]
+ );
+ } else {
+ await connection.query(
+ 'INSERT INTO hardware_components_master (category, component_name, score_tier, deduction) VALUES (?, ?, ?, ?)',
+ [category, component_name, score_tier, deduction]
+ );
+ }
+ res.json({ success: true });
+ } catch (err) {
+ handleError(res, err, 'SAVE HARDWARE COMPONENT');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 6.7. Delete Hardware Component
+app.delete('/api/hardware-components/:id', async (req, res) => {
+ const { id } = req.params;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ await connection.query('DELETE FROM hardware_components_master WHERE id = ?', [id]);
+ res.json({ success: true });
+ } catch (err) {
+ handleError(res, err, 'DELETE HARDWARE COMPONENT');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 6.7.1. Get Job Spec Standards
+app.get('/api/job-specs', async (req, res) => {
+ try {
+ const [rows] = await pool.query('SELECT * FROM job_spec_standards ORDER BY job_name');
+ res.json(rows);
+ } catch (err) {
+ handleError(res, err, 'GET JOB SPECS');
+ }
+});
+
+// 6.7.2. Save Job Spec Standard (Add or Update)
+app.post('/api/job-specs/save', async (req, res) => {
+ const { id, job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks } = req.body;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ if (id) {
+ await connection.query(
+ 'UPDATE job_spec_standards SET job_name = ?, cpu_standard = ?, ram_standard = ?, gpu_standard = ?, min_score = ?, remarks = ? WHERE id = ?',
+ [job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks, id]
+ );
+ } else {
+ await connection.query(
+ 'INSERT INTO job_spec_standards (job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks) VALUES (?, ?, ?, ?, ?, ?)',
+ [job_name, cpu_standard, ram_standard, gpu_standard, min_score, remarks]
+ );
+ }
+ res.json({ success: true });
+ } catch (err) {
+ handleError(res, err, 'SAVE JOB SPEC');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 6.7.3. Delete Job Spec Standard
+app.delete('/api/job-specs/:id', async (req, res) => {
+ const { id } = req.params;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ await connection.query('DELETE FROM job_spec_standards WHERE id = ?', [id]);
+ res.json({ success: true });
+ } catch (err) {
+ handleError(res, err, 'DELETE JOB SPEC');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 6.8. Get System Users List
+app.get('/api/system-users', async (req, res) => {
+ try {
+ const [rows] = await pool.query('SELECT * FROM system_users ORDER BY user_name');
+ res.json(rows);
+ } catch (err) {
+ handleError(res, err, 'GET SYSTEM USERS');
+ }
+});
+
+// 6.9. Save System User (Add or Update)
+app.post('/api/system-users/save', async (req, res) => {
+ const { id, emp_no, user_name, dept_name, position, status } = req.body;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ if (id) {
+ await connection.query(
+ 'UPDATE system_users SET emp_no = ?, user_name = ?, dept_name = ?, position = ?, status = ? WHERE id = ?',
+ [emp_no, user_name, dept_name, position, status, id]
+ );
+ } else {
+ const newId = 'USER-' + Math.random().toString(36).substring(2, 9).toUpperCase();
+ await connection.query(
+ 'INSERT INTO system_users (id, emp_no, user_name, dept_name, position, status) VALUES (?, ?, ?, ?, ?, ?)',
+ [newId, emp_no, user_name, dept_name, position, status]
+ );
+ }
+ res.json({ success: true });
+ } catch (err) {
+ handleError(res, err, 'SAVE SYSTEM USER');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 6.10. Delete System User
+app.delete('/api/system-users/:id', async (req, res) => {
+ const { id } = req.params;
+ let connection;
+ try {
+ connection = await pool.getConnection();
+ await connection.query('DELETE FROM system_users WHERE id = ?', [id]);
+ res.json({ success: true });
+ } catch (err) {
+ handleError(res, err, 'DELETE SYSTEM USER');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// ==========================================
+// 8. QR Asset Audit & Scan APIs (From origin/QR_setting)
+// ==========================================
+
+// GET all physical locations
+app.get('/api/physical-locations', async (req, res) => {
+ try {
+ const [rows] = await pool.query('SELECT * FROM physical_locations ORDER BY location_code');
+ res.json(rows);
+ } catch (err) {
+ handleError(res, err, 'GET PHYSICAL LOCATIONS');
+ }
+});
+
+// POST register scan (mobile)
+app.post('/api/audit/scan', async (req, res) => {
+ let connection;
+ try {
+ const { asset_code, physical_location_code } = req.body;
+ if (!asset_code || !physical_location_code) {
+ return res.status(400).json({ error: 'asset_code and physical_location_code are required' });
+ }
+
+ connection = await pool.getConnection();
+
+ // Verify if asset exists
+ const [assets] = await connection.query('SELECT id FROM asset_core WHERE asset_code = ?', [asset_code]);
+ if (assets.length === 0) {
+ return res.status(404).json({ error: `Asset with code ${asset_code} not found` });
+ }
+
+ // Insert pending audit record
+ const [result] = await connection.query(
+ 'INSERT INTO asset_audit_pending (asset_code, physical_location_code, status) VALUES (?, ?, ?)',
+ [asset_code, physical_location_code, 'PENDING']
+ );
+
+ res.json({ success: true, pending_id: result.insertId });
+ } catch (err) {
+ handleError(res, err, 'REGISTER SCAN');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// GET pending audits list (admin)
+app.get('/api/audit/pending', async (req, res) => {
+ try {
+ const [rows] = await pool.query(`
+ SELECT
+ ap.*,
+ c.id AS asset_id,
+ c.asset_purpose,
+ c.asset_type,
+ pl.location_name,
+ pl.location_detail,
+ pl.map_image,
+ l.location AS old_location,
+ l.location_detail AS old_location_detail
+ FROM asset_audit_pending ap
+ JOIN asset_core c ON c.asset_code = ap.asset_code
+ JOIN physical_locations pl ON pl.location_code = ap.physical_location_code
+ LEFT JOIN asset_location l ON l.asset_id = c.id AND l.is_active = 1
+ WHERE ap.status = 'PENDING'
+ ORDER BY ap.scanned_at DESC
+ `);
+ res.json(rows);
+ } catch (err) {
+ handleError(res, err, 'GET PENDING AUDITS');
+ }
+});
+
+// POST approve audits (admin)
+app.post('/api/audit/approve', async (req, res) => {
+ let connection;
+ try {
+ const { pending_ids, processed_by } = req.body;
+ if (!Array.isArray(pending_ids) || pending_ids.length === 0) {
+ return res.status(400).json({ error: 'pending_ids must be a non-empty array' });
+ }
+
+ connection = await pool.getConnection();
+ await connection.beginTransaction();
+
+ let mapConfigChanged = false;
+ let mapConfig = {};
+ if (fs.existsSync('map_config.json')) {
+ mapConfig = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}');
+ }
+
+ for (const pendingId of pending_ids) {
+ // 1. Get pending scan details
+ const [pendings] = await connection.query(
+ 'SELECT asset_code, physical_location_code FROM asset_audit_pending WHERE id = ? AND status = ?',
+ [pendingId, 'PENDING']
+ );
+ if (pendings.length === 0) continue;
+
+ const { asset_code, physical_location_code } = pendings[0];
+
+ // 2. Get asset ID
+ const [assets] = await connection.query('SELECT id FROM asset_core WHERE asset_code = ?', [asset_code]);
+ if (assets.length === 0) continue;
+ const assetId = assets[0].id;
+
+ // 3. Get physical location details
+ const [locations] = await connection.query(
+ 'SELECT location_name, location_detail, map_image, map_x, map_y FROM physical_locations WHERE location_code = ?',
+ [physical_location_code]
+ );
+ if (locations.length === 0) continue;
+ const loc = locations[0];
+
+ // 4. Deactivate old active locations for this asset
+ await connection.query(
+ 'UPDATE asset_location SET is_active = 0, deactivated_at = NOW() WHERE asset_id = ? AND is_active = 1',
+ [assetId]
+ );
+
+ // 5. Insert new active location
+ const pathPartsForApprove = loc.map_image.split('/');
+ const stdDetailForApprove = pathPartsForApprove[pathPartsForApprove.length - 2] || loc.location_detail;
+ await connection.query(`
+ INSERT INTO asset_location
+ (asset_id, location, location_detail, location_photo, loc_x, loc_y, physical_location_code, is_active)
+ VALUES (?, ?, ?, ?, ?, ?, ?, 1)
+ `, [assetId, loc.location_name, stdDetailForApprove, loc.map_image, loc.map_x, loc.map_y, physical_location_code]);
+
+ // 6. Update pending audit status
+ await connection.query(
+ 'UPDATE asset_audit_pending SET status = ?, processed_at = NOW(), processed_by = ? WHERE id = ?',
+ ['APPROVED', processed_by || 'ADMIN', pendingId]
+ );
+
+ // 7. Sync map_config.json
+ // Remove asset from any other map coordinates
+ for (const [mapPath, boxes] of Object.entries(mapConfig)) {
+ let changed = false;
+ const newBoxes = boxes.map(b => {
+ if (b.asset_id === assetId) {
+ changed = true;
+ return { ...b, asset_id: null };
+ }
+ return b;
+ });
+ if (changed) {
+ mapConfig[mapPath] = newBoxes;
+ mapConfigChanged = true;
+ }
+ }
+
+ // Add asset to the new map coordinate box matching map_image, map_x, map_y
+ if (mapConfig[loc.map_image]) {
+ const ax = parseFloat(loc.map_x);
+ const ay = parseFloat(loc.map_y);
+ const boxes = mapConfig[loc.map_image];
+ const matchedBox = boxes.find(b => {
+ const bx = parseFloat(b.x);
+ const by = parseFloat(b.y);
+ return Math.abs(bx - ax) < 0.1 && Math.abs(by - ay) < 0.1;
+ });
+ if (matchedBox) {
+ matchedBox.asset_id = assetId;
+ mapConfigChanged = true;
+ }
+ }
+ }
+
+ if (mapConfigChanged) {
+ fs.writeFileSync('map_config.json', JSON.stringify(mapConfig, null, 2));
+ }
+
+ await connection.commit();
+ res.json({ success: true, message: 'Audits approved successfully' });
+ } catch (err) {
+ if (connection) await connection.rollback();
+ handleError(res, err, 'APPROVE AUDITS');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// POST reject audits (admin)
+app.post('/api/audit/reject', async (req, res) => {
+ let connection;
+ try {
+ const { pending_ids, processed_by } = req.body;
+ if (!Array.isArray(pending_ids) || pending_ids.length === 0) {
+ return res.status(400).json({ error: 'pending_ids must be a non-empty array' });
+ }
+
+ connection = await pool.getConnection();
+ await connection.beginTransaction();
+
+ for (const pendingId of pending_ids) {
+ await connection.query(
+ 'UPDATE asset_audit_pending SET status = ?, processed_at = NOW(), processed_by = ? WHERE id = ? AND status = ?',
+ ['REJECTED', processed_by || 'ADMIN', pendingId, 'PENDING']
+ );
+ }
+
+ await connection.commit();
+ res.json({ success: true, message: 'Audits rejected successfully' });
+ } catch (err) {
+ if (connection) await connection.rollback();
+ handleError(res, err, 'REJECT AUDITS');
+ } finally {
+ if (connection) connection.release();
+ }
+});
+
+// 7. File Upload API (Base64)
+app.post('/api/upload', (req, res) => {
+ try {
+ const { fileName, fileData } = req.body;
+ if (!fileName || !fileData) return res.status(400).json({ error: 'FileName and FileData are required' });
+
+ // base64 데이터에서 실제 바이너리 추출
+ const base64Data = fileData.replace(/^data:.*;base64,/, "");
+ const buffer = Buffer.from(base64Data, 'base64');
+
+ // 고유한 파일명 생성 (타임스탬프 결합)
+ const timestamp = Date.now();
+ const safeFileName = `${timestamp}_${fileName.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
+ const filePath = `uploads/${safeFileName}`;
+
+ fs.writeFileSync(filePath, buffer);
+
+ console.log(`파일 업로드 성공: ${filePath}`);
+ res.json({ success: true, filePath: `/${filePath}`, fileName: safeFileName });
+ } catch (err) {
+ handleError(res, err, 'FILE UPLOAD');
+ }
+});
+
+// Health check endpoint for container orchestration
+app.get('/health', async (req, res) => {
+ try {
+ const connection = await pool.getConnection();
+ await connection.query('SELECT 1');
+ connection.release();
+ res.status(200).json({ status: 'ok', db: 'connected' });
+ } catch (err) {
+ res.status(200).json({ status: 'degraded', db: 'unreachable', error: err.message });
+ }
+});
+
+// Readiness check endpoint (only returns 200 if fully ready)
+app.get('/ready', async (req, res) => {
+ try {
+ const connection = await pool.getConnection();
+ await connection.query('SELECT 1');
+ connection.release();
+ res.status(200).json({ status: 'ready' });
+ } catch (err) {
+ res.status(503).json({ status: 'not_ready', error: err.message });
+ }
+});
+
+app.listen(process.env.PORT || 3000, '0.0.0.0', () => {
+ console.log(`📡 ITAM BACKEND SERVER RUNNING ON PORT ${process.env.PORT || 3000} (V3 Normalized)`);
+});
diff --git a/src/components/Modal/HWModal.ts b/src/components/Modal/HWModal.ts
index f089c5d..945cf18 100644
--- a/src/components/Modal/HWModal.ts
+++ b/src/components/Modal/HWModal.ts
@@ -11,6 +11,7 @@ import {
} from './ModalUtils';
import { CORP_LIST, LOCATION_DATA, CATEGORY_TYPE_MAP, HW_STATUS_LIST, ORG_LIST, IMAGE_LOCATIONS, TYPE_PREFIX_MAP } from './SharedData';
import { BaseModal } from './BaseModal';
+import { QRPrinter } from '../../core/qr_print';
/**
* 하드웨어 자산 상세 모달 (Styled Main Edition)
@@ -30,9 +31,11 @@ class HwAssetModal extends BaseModal {