diff --git a/.gitignore b/.gitignore
index b5fd2b3..3e6fd5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,6 @@ redis_data/
backend/scrape_*.cjs
backend/scrape_*.js
backend/scrape_*.txt
+
+# Backup
+backend-backup/
diff --git a/Dockerfile b/Dockerfile
index fb9c2b8..fd54062 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -24,4 +24,4 @@ COPY backend/ ./
COPY --from=frontend-builder /frontend/dist ./dist
EXPOSE 80
-CMD ["node", "server.js"]
+CMD ["npm", "start"]
diff --git a/backend/lib/date.js b/backend/lib/date.js
deleted file mode 100644
index efb0225..0000000
--- a/backend/lib/date.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * 날짜/시간 유틸리티 (dayjs 기반)
- * 백엔드 전체에서 공통으로 사용
- */
-
-import dayjs from "dayjs";
-import utc from "dayjs/plugin/utc.js";
-import timezone from "dayjs/plugin/timezone.js";
-import customParseFormat from "dayjs/plugin/customParseFormat.js";
-
-// 플러그인 등록
-dayjs.extend(utc);
-dayjs.extend(timezone);
-dayjs.extend(customParseFormat);
-
-// 기본 시간대: KST
-const KST = "Asia/Seoul";
-
-/**
- * UTC 시간을 KST로 변환
- * @param {Date|string} utcDate - UTC 시간
- * @returns {dayjs.Dayjs} KST 시간
- */
-export function toKST(utcDate) {
- return dayjs(utcDate).tz(KST);
-}
-
-/**
- * 날짜를 YYYY-MM-DD 형식으로 포맷
- * @param {Date|string|dayjs.Dayjs} date - 날짜
- * @returns {string} YYYY-MM-DD
- */
-export function formatDate(date) {
- return dayjs(date).format("YYYY-MM-DD");
-}
-
-/**
- * 시간을 HH:mm:ss 형식으로 포맷
- * @param {Date|string|dayjs.Dayjs} date - 시간
- * @returns {string} HH:mm:ss
- */
-export function formatTime(date) {
- return dayjs(date).format("HH:mm:ss");
-}
-
-/**
- * UTC 시간을 KST로 변환 후 날짜/시간 분리
- * @param {Date|string} utcDate - UTC 시간
- * @returns {{date: string, time: string}} KST 날짜/시간
- */
-export function utcToKSTDateTime(utcDate) {
- const kst = toKST(utcDate);
- return {
- date: kst.format("YYYY-MM-DD"),
- time: kst.format("HH:mm:ss"),
- };
-}
-
-/**
- * 현재 KST 시간 반환
- * @returns {dayjs.Dayjs} 현재 KST 시간
- */
-export function nowKST() {
- return dayjs().tz(KST);
-}
-
-/**
- * Nitter 날짜 문자열 파싱 (UTC 반환)
- * 예: "Jan 9, 2026 · 4:00 PM UTC" → Date 객체
- * @param {string} timeStr - Nitter 날짜 문자열
- * @returns {dayjs.Dayjs|null} UTC 시간
- */
-export function parseNitterDateTime(timeStr) {
- if (!timeStr) return null;
- try {
- const cleaned = timeStr.replace(" · ", " ").replace(" UTC", "");
- const date = dayjs.utc(cleaned, "MMM D, YYYY h:mm A");
- if (!date.isValid()) return null;
- return date;
- } catch (e) {
- return null;
- }
-}
-
-export default dayjs;
diff --git a/backend/lib/db.js b/backend/lib/db.js
deleted file mode 100644
index 0749f70..0000000
--- a/backend/lib/db.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import mysql from "mysql2/promise";
-
-// MariaDB 연결 풀 생성
-const pool = mysql.createPool({
- host: process.env.DB_HOST || "mariadb",
- port: parseInt(process.env.DB_PORT) || 3306,
- user: process.env.DB_USER || "fromis9",
- password: process.env.DB_PASSWORD,
- database: process.env.DB_NAME || "fromis9",
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0,
-});
-
-export default pool;
diff --git a/backend/lib/redis.js b/backend/lib/redis.js
deleted file mode 100644
index 195f4c7..0000000
--- a/backend/lib/redis.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import Redis from "ioredis";
-
-// Redis 클라이언트 초기화
-const redis = new Redis({
- host: "fromis9-redis",
- port: 6379,
- retryDelayOnFailover: 100,
- maxRetriesPerRequest: 3,
-});
-
-redis.on("connect", () => {
- console.log("[Redis] 연결 성공");
-});
-
-redis.on("error", (err) => {
- console.error("[Redis] 연결 오류:", err.message);
-});
-
-export default redis;
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 8c7d1fb..32f17f7 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -1,3859 +1,883 @@
{
- "name": "fromis9-backend",
- "version": "1.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "fromis9-backend",
- "version": "1.0.0",
- "dependencies": {
- "@aws-sdk/client-s3": "^3.700.0",
- "bcrypt": "^6.0.0",
- "dayjs": "^1.11.19",
- "express": "^4.18.2",
- "fluent-ffmpeg": "^2.1.3",
- "inko": "^1.1.1",
- "ioredis": "^5.4.0",
- "jsonwebtoken": "^9.0.3",
- "meilisearch": "^0.55.0",
- "multer": "^1.4.5-lts.1",
- "mysql2": "^3.11.0",
- "node-cron": "^4.2.1",
- "rss-parser": "^3.13.0",
- "sharp": "^0.33.5"
- }
- },
- "node_modules/@aws-crypto/crc32": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
- "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@aws-crypto/crc32c": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz",
- "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@aws-crypto/sha1-browser": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz",
- "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/supports-web-crypto": "^5.2.0",
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "@aws-sdk/util-locate-window": "^3.0.0",
- "@smithy/util-utf8": "^2.0.0",
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
- "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
- "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
- "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/sha256-browser": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
- "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-js": "^5.2.0",
- "@aws-crypto/supports-web-crypto": "^5.2.0",
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "@aws-sdk/util-locate-window": "^3.0.0",
- "@smithy/util-utf8": "^2.0.0",
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
- "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
- "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
- "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/sha256-js": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
- "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/util": "^5.2.0",
- "@aws-sdk/types": "^3.222.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/@aws-crypto/supports-web-crypto": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz",
- "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@aws-crypto/util": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
- "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "^3.222.0",
- "@smithy/util-utf8": "^2.0.0",
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
- "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
- "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
- "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^2.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/client-s3": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.958.0.tgz",
- "integrity": "sha512-ol8Sw37AToBWb6PjRuT/Wu40SrrZSA0N4F7U3yTkjUNX0lirfO1VFLZ0hZtZplVJv8GNPITbiczxQ8VjxESXxg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha1-browser": "5.2.0",
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/credential-provider-node": "3.958.0",
- "@aws-sdk/middleware-bucket-endpoint": "3.957.0",
- "@aws-sdk/middleware-expect-continue": "3.957.0",
- "@aws-sdk/middleware-flexible-checksums": "3.957.0",
- "@aws-sdk/middleware-host-header": "3.957.0",
- "@aws-sdk/middleware-location-constraint": "3.957.0",
- "@aws-sdk/middleware-logger": "3.957.0",
- "@aws-sdk/middleware-recursion-detection": "3.957.0",
- "@aws-sdk/middleware-sdk-s3": "3.957.0",
- "@aws-sdk/middleware-ssec": "3.957.0",
- "@aws-sdk/middleware-user-agent": "3.957.0",
- "@aws-sdk/region-config-resolver": "3.957.0",
- "@aws-sdk/signature-v4-multi-region": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@aws-sdk/util-endpoints": "3.957.0",
- "@aws-sdk/util-user-agent-browser": "3.957.0",
- "@aws-sdk/util-user-agent-node": "3.957.0",
- "@smithy/config-resolver": "^4.4.5",
- "@smithy/core": "^3.20.0",
- "@smithy/eventstream-serde-browser": "^4.2.7",
- "@smithy/eventstream-serde-config-resolver": "^4.3.7",
- "@smithy/eventstream-serde-node": "^4.2.7",
- "@smithy/fetch-http-handler": "^5.3.8",
- "@smithy/hash-blob-browser": "^4.2.8",
- "@smithy/hash-node": "^4.2.7",
- "@smithy/hash-stream-node": "^4.2.7",
- "@smithy/invalid-dependency": "^4.2.7",
- "@smithy/md5-js": "^4.2.7",
- "@smithy/middleware-content-length": "^4.2.7",
- "@smithy/middleware-endpoint": "^4.4.1",
- "@smithy/middleware-retry": "^4.4.17",
- "@smithy/middleware-serde": "^4.2.8",
- "@smithy/middleware-stack": "^4.2.7",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/node-http-handler": "^4.4.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "@smithy/url-parser": "^4.2.7",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.16",
- "@smithy/util-defaults-mode-node": "^4.2.19",
- "@smithy/util-endpoints": "^3.2.7",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-retry": "^4.2.7",
- "@smithy/util-stream": "^4.5.8",
- "@smithy/util-utf8": "^4.2.0",
- "@smithy/util-waiter": "^4.2.7",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sso": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.958.0.tgz",
- "integrity": "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/middleware-host-header": "3.957.0",
- "@aws-sdk/middleware-logger": "3.957.0",
- "@aws-sdk/middleware-recursion-detection": "3.957.0",
- "@aws-sdk/middleware-user-agent": "3.957.0",
- "@aws-sdk/region-config-resolver": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@aws-sdk/util-endpoints": "3.957.0",
- "@aws-sdk/util-user-agent-browser": "3.957.0",
- "@aws-sdk/util-user-agent-node": "3.957.0",
- "@smithy/config-resolver": "^4.4.5",
- "@smithy/core": "^3.20.0",
- "@smithy/fetch-http-handler": "^5.3.8",
- "@smithy/hash-node": "^4.2.7",
- "@smithy/invalid-dependency": "^4.2.7",
- "@smithy/middleware-content-length": "^4.2.7",
- "@smithy/middleware-endpoint": "^4.4.1",
- "@smithy/middleware-retry": "^4.4.17",
- "@smithy/middleware-serde": "^4.2.8",
- "@smithy/middleware-stack": "^4.2.7",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/node-http-handler": "^4.4.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "@smithy/url-parser": "^4.2.7",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.16",
- "@smithy/util-defaults-mode-node": "^4.2.19",
- "@smithy/util-endpoints": "^3.2.7",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-retry": "^4.2.7",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/core": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.957.0.tgz",
- "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@aws-sdk/xml-builder": "3.957.0",
- "@smithy/core": "^3.20.0",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/signature-v4": "^5.3.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/crc64-nvme": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.957.0.tgz",
- "integrity": "sha512-qSwSfI+qBU9HDsd6/4fM9faCxYJx2yDuHtj+NVOQ6XYDWQzFab/hUdwuKZ77Pi6goLF1pBZhJ2azaC2w7LbnTA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.957.0.tgz",
- "integrity": "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.957.0.tgz",
- "integrity": "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/fetch-http-handler": "^5.3.8",
- "@smithy/node-http-handler": "^4.4.7",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "@smithy/util-stream": "^4.5.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.958.0.tgz",
- "integrity": "sha512-u7twvZa1/6GWmPBZs6DbjlegCoNzNjBsMS/6fvh5quByYrcJr/uLd8YEr7S3UIq4kR/gSnHqcae7y2nL2bqZdg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/credential-provider-env": "3.957.0",
- "@aws-sdk/credential-provider-http": "3.957.0",
- "@aws-sdk/credential-provider-login": "3.958.0",
- "@aws-sdk/credential-provider-process": "3.957.0",
- "@aws-sdk/credential-provider-sso": "3.958.0",
- "@aws-sdk/credential-provider-web-identity": "3.958.0",
- "@aws-sdk/nested-clients": "3.958.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/credential-provider-imds": "^4.2.7",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-login": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.958.0.tgz",
- "integrity": "sha512-sDwtDnBSszUIbzbOORGh5gmXGl9aK25+BHb4gb1aVlqB+nNL2+IUEJA62+CE55lXSH8qXF90paivjK8tOHTwPA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/nested-clients": "3.958.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.958.0.tgz",
- "integrity": "sha512-vdoZbNG2dt66I7EpN3fKCzi6fp9xjIiwEA/vVVgqO4wXCGw8rKPIdDUus4e13VvTr330uQs2W0UNg/7AgtquEQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/credential-provider-env": "3.957.0",
- "@aws-sdk/credential-provider-http": "3.957.0",
- "@aws-sdk/credential-provider-ini": "3.958.0",
- "@aws-sdk/credential-provider-process": "3.957.0",
- "@aws-sdk/credential-provider-sso": "3.958.0",
- "@aws-sdk/credential-provider-web-identity": "3.958.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/credential-provider-imds": "^4.2.7",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.957.0.tgz",
- "integrity": "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.958.0.tgz",
- "integrity": "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/client-sso": "3.958.0",
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/token-providers": "3.958.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.958.0.tgz",
- "integrity": "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/nested-clients": "3.958.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-bucket-endpoint": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.957.0.tgz",
- "integrity": "sha512-iczcn/QRIBSpvsdAS/rbzmoBpleX1JBjXvCynMbDceVLBIcVrwT1hXECrhtIC2cjh4HaLo9ClAbiOiWuqt+6MA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@aws-sdk/util-arn-parser": "3.957.0",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-config-provider": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-expect-continue": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.957.0.tgz",
- "integrity": "sha512-AlbK3OeVNwZZil0wlClgeI/ISlOt/SPUxBsIns876IFaVu/Pj3DgImnYhpcJuFRek4r4XM51xzIaGQXM6GDHGg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-flexible-checksums": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.957.0.tgz",
- "integrity": "sha512-iJpeVR5V8se1hl2pt+k8bF/e9JO4KWgPCMjg8BtRspNtKIUGy7j6msYvbDixaKZaF2Veg9+HoYcOhwnZumjXSA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/crc32": "5.2.0",
- "@aws-crypto/crc32c": "5.2.0",
- "@aws-crypto/util": "5.2.0",
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/crc64-nvme": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/is-array-buffer": "^4.2.0",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-stream": "^4.5.8",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-host-header": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.957.0.tgz",
- "integrity": "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-location-constraint": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.957.0.tgz",
- "integrity": "sha512-y8/W7TOQpmDJg/fPYlqAhwA4+I15LrS7TwgUEoxogtkD8gfur9wFMRLT8LCyc9o4NMEcAnK50hSb4+wB0qv6tQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-logger": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.957.0.tgz",
- "integrity": "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-recursion-detection": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.957.0.tgz",
- "integrity": "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@aws/lambda-invoke-store": "^0.2.2",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-sdk-s3": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.957.0.tgz",
- "integrity": "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@aws-sdk/util-arn-parser": "3.957.0",
- "@smithy/core": "^3.20.0",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/signature-v4": "^5.3.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "@smithy/util-config-provider": "^4.2.0",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-stream": "^4.5.8",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-ssec": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.957.0.tgz",
- "integrity": "sha512-qwkmrK0lizdjNt5qxl4tHYfASh8DFpHXM1iDVo+qHe+zuslfMqQEGRkzxS8tJq/I+8F0c6v3IKOveKJAfIvfqQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-user-agent": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.957.0.tgz",
- "integrity": "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@aws-sdk/util-endpoints": "3.957.0",
- "@smithy/core": "^3.20.0",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/nested-clients": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.958.0.tgz",
- "integrity": "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/sha256-browser": "5.2.0",
- "@aws-crypto/sha256-js": "5.2.0",
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/middleware-host-header": "3.957.0",
- "@aws-sdk/middleware-logger": "3.957.0",
- "@aws-sdk/middleware-recursion-detection": "3.957.0",
- "@aws-sdk/middleware-user-agent": "3.957.0",
- "@aws-sdk/region-config-resolver": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@aws-sdk/util-endpoints": "3.957.0",
- "@aws-sdk/util-user-agent-browser": "3.957.0",
- "@aws-sdk/util-user-agent-node": "3.957.0",
- "@smithy/config-resolver": "^4.4.5",
- "@smithy/core": "^3.20.0",
- "@smithy/fetch-http-handler": "^5.3.8",
- "@smithy/hash-node": "^4.2.7",
- "@smithy/invalid-dependency": "^4.2.7",
- "@smithy/middleware-content-length": "^4.2.7",
- "@smithy/middleware-endpoint": "^4.4.1",
- "@smithy/middleware-retry": "^4.4.17",
- "@smithy/middleware-serde": "^4.2.8",
- "@smithy/middleware-stack": "^4.2.7",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/node-http-handler": "^4.4.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "@smithy/url-parser": "^4.2.7",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-body-length-node": "^4.2.1",
- "@smithy/util-defaults-mode-browser": "^4.3.16",
- "@smithy/util-defaults-mode-node": "^4.2.19",
- "@smithy/util-endpoints": "^3.2.7",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-retry": "^4.2.7",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/region-config-resolver": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.957.0.tgz",
- "integrity": "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/config-resolver": "^4.4.5",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/signature-v4-multi-region": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.957.0.tgz",
- "integrity": "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/middleware-sdk-s3": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/signature-v4": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/token-providers": {
- "version": "3.958.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.958.0.tgz",
- "integrity": "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/core": "3.957.0",
- "@aws-sdk/nested-clients": "3.958.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/types": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz",
- "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/util-arn-parser": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.957.0.tgz",
- "integrity": "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/util-endpoints": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.957.0.tgz",
- "integrity": "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/types": "^4.11.0",
- "@smithy/url-parser": "^4.2.7",
- "@smithy/util-endpoints": "^3.2.7",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/util-locate-window": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.957.0.tgz",
- "integrity": "sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws-sdk/util-user-agent-browser": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.957.0.tgz",
- "integrity": "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/types": "3.957.0",
- "@smithy/types": "^4.11.0",
- "bowser": "^2.11.0",
- "tslib": "^2.6.2"
- }
- },
- "node_modules/@aws-sdk/util-user-agent-node": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.957.0.tgz",
- "integrity": "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-sdk/middleware-user-agent": "3.957.0",
- "@aws-sdk/types": "3.957.0",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- },
- "peerDependencies": {
- "aws-crt": ">=1.0.0"
- },
- "peerDependenciesMeta": {
- "aws-crt": {
- "optional": true
- }
- }
- },
- "node_modules/@aws-sdk/xml-builder": {
- "version": "3.957.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz",
- "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "fast-xml-parser": "5.2.5",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@aws/lambda-invoke-store": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz",
- "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
- "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
- "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
- "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
- "cpu": [
- "arm"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
- "cpu": [
- "s390x"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
- "cpu": [
- "arm"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.0.5"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
- "cpu": [
- "s390x"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.2.0"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@ioredis/commands": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.0.tgz",
- "integrity": "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==",
- "license": "MIT"
- },
- "node_modules/@smithy/abort-controller": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz",
- "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/chunked-blob-reader": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz",
- "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/chunked-blob-reader-native": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz",
- "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-base64": "^4.3.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/config-resolver": {
- "version": "4.4.5",
- "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz",
- "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-config-provider": "^4.2.0",
- "@smithy/util-endpoints": "^3.2.7",
- "@smithy/util-middleware": "^4.2.7",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/core": {
- "version": "3.20.0",
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.0.tgz",
- "integrity": "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/middleware-serde": "^4.2.8",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-body-length-browser": "^4.2.0",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-stream": "^4.5.8",
- "@smithy/util-utf8": "^4.2.0",
- "@smithy/uuid": "^1.1.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/credential-provider-imds": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz",
- "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "@smithy/url-parser": "^4.2.7",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/eventstream-codec": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.7.tgz",
- "integrity": "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@aws-crypto/crc32": "5.2.0",
- "@smithy/types": "^4.11.0",
- "@smithy/util-hex-encoding": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/eventstream-serde-browser": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.7.tgz",
- "integrity": "sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/eventstream-serde-universal": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/eventstream-serde-config-resolver": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.7.tgz",
- "integrity": "sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/eventstream-serde-node": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.7.tgz",
- "integrity": "sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/eventstream-serde-universal": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/eventstream-serde-universal": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.7.tgz",
- "integrity": "sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/eventstream-codec": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/fetch-http-handler": {
- "version": "5.3.8",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz",
- "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/querystring-builder": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-base64": "^4.3.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/hash-blob-browser": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.8.tgz",
- "integrity": "sha512-07InZontqsM1ggTCPSRgI7d8DirqRrnpL7nIACT4PW0AWrgDiHhjGZzbAE5UtRSiU0NISGUYe7/rri9ZeWyDpw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/chunked-blob-reader": "^5.2.0",
- "@smithy/chunked-blob-reader-native": "^4.2.1",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/hash-node": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz",
- "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "@smithy/util-buffer-from": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/hash-stream-node": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.7.tgz",
- "integrity": "sha512-ZQVoAwNYnFMIbd4DUc517HuwNelJUY6YOzwqrbcAgCnVn+79/OK7UjwA93SPpdTOpKDVkLIzavWm/Ck7SmnDPQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/invalid-dependency": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz",
- "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/is-array-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz",
- "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/md5-js": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.7.tgz",
- "integrity": "sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/middleware-content-length": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz",
- "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/middleware-endpoint": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.1.tgz",
- "integrity": "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.20.0",
- "@smithy/middleware-serde": "^4.2.8",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "@smithy/url-parser": "^4.2.7",
- "@smithy/util-middleware": "^4.2.7",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/middleware-retry": {
- "version": "4.4.17",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.17.tgz",
- "integrity": "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/service-error-classification": "^4.2.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-retry": "^4.2.7",
- "@smithy/uuid": "^1.1.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/middleware-serde": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz",
- "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/middleware-stack": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz",
- "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/node-config-provider": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz",
- "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/property-provider": "^4.2.7",
- "@smithy/shared-ini-file-loader": "^4.4.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/node-http-handler": {
- "version": "4.4.7",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz",
- "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/abort-controller": "^4.2.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/querystring-builder": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/property-provider": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz",
- "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/protocol-http": {
- "version": "5.3.7",
- "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz",
- "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/querystring-builder": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz",
- "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "@smithy/util-uri-escape": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/querystring-parser": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz",
- "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/service-error-classification": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz",
- "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/shared-ini-file-loader": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz",
- "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/signature-v4": {
- "version": "5.3.7",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz",
- "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-hex-encoding": "^4.2.0",
- "@smithy/util-middleware": "^4.2.7",
- "@smithy/util-uri-escape": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/smithy-client": {
- "version": "4.10.2",
- "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.2.tgz",
- "integrity": "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/core": "^3.20.0",
- "@smithy/middleware-endpoint": "^4.4.1",
- "@smithy/middleware-stack": "^4.2.7",
- "@smithy/protocol-http": "^5.3.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-stream": "^4.5.8",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/types": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz",
- "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/url-parser": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz",
- "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/querystring-parser": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-base64": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz",
- "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-body-length-browser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz",
- "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-body-length-node": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz",
- "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-buffer-from": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz",
- "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/is-array-buffer": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-config-provider": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz",
- "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-defaults-mode-browser": {
- "version": "4.3.16",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.16.tgz",
- "integrity": "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/property-provider": "^4.2.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-defaults-mode-node": {
- "version": "4.2.19",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.19.tgz",
- "integrity": "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/config-resolver": "^4.4.5",
- "@smithy/credential-provider-imds": "^4.2.7",
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/property-provider": "^4.2.7",
- "@smithy/smithy-client": "^4.10.2",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-endpoints": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz",
- "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/node-config-provider": "^4.3.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-hex-encoding": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz",
- "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-middleware": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz",
- "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-retry": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz",
- "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/service-error-classification": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-stream": {
- "version": "4.5.8",
- "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz",
- "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/fetch-http-handler": "^5.3.8",
- "@smithy/node-http-handler": "^4.4.7",
- "@smithy/types": "^4.11.0",
- "@smithy/util-base64": "^4.3.0",
- "@smithy/util-buffer-from": "^4.2.0",
- "@smithy/util-hex-encoding": "^4.2.0",
- "@smithy/util-utf8": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-uri-escape": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz",
- "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-utf8": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz",
- "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/util-buffer-from": "^4.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/util-waiter": {
- "version": "4.2.7",
- "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.7.tgz",
- "integrity": "sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/abort-controller": "^4.2.7",
- "@smithy/types": "^4.11.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/uuid": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz",
- "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
- "license": "MIT",
- "dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/append-field": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
- "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
- "license": "MIT"
- },
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "license": "MIT"
- },
- "node_modules/async": {
- "version": "0.2.10",
- "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
- "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
- },
- "node_modules/aws-ssl-profiles": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
- "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
- "license": "MIT",
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "license": "MIT"
- },
- "node_modules/bcrypt": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
- "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "node-addon-api": "^8.3.0",
- "node-gyp-build": "^4.8.4"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/body-parser": {
- "version": "1.20.4",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
- "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
- "license": "MIT",
- "dependencies": {
- "bytes": "~3.1.2",
- "content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "~1.2.0",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
- "on-finished": "~2.4.1",
- "qs": "~6.14.0",
- "raw-body": "~2.5.3",
- "type-is": "~1.6.18",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/bowser": {
- "version": "2.13.1",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz",
- "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==",
- "license": "MIT"
- },
- "node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/browser-stdout": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
- "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
- "license": "ISC"
- },
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "license": "MIT"
- },
- "node_modules/busboy": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
- "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
- "dependencies": {
- "streamsearch": "^1.1.0"
- },
- "engines": {
- "node": ">=10.16.0"
- }
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/cluster-key-slot": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
- "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
- },
- "engines": {
- "node": ">=12.5.0"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
- },
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
- "license": "MIT",
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
- "node_modules/commander": {
- "version": "2.15.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
- "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
- "license": "MIT"
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "license": "MIT"
- },
- "node_modules/concat-stream": {
- "version": "1.6.2",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
- "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
- "engines": [
- "node >= 0.8"
- ],
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^2.2.2",
- "typedarray": "^0.0.6"
- }
- },
- "node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
- "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
- "license": "MIT"
- },
- "node_modules/core-util-is": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
- "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
- "license": "MIT"
- },
- "node_modules/dayjs": {
- "version": "1.11.19",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
- "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
- "license": "MIT"
- },
- "node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/denque": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
- "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/diff": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
- "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
- "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/entities": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
- "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
- "license": "BSD-2-Clause",
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "license": "MIT"
- },
- "node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/express": {
- "version": "4.22.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
- "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
- "license": "MIT",
- "dependencies": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "~1.20.3",
- "content-disposition": "~0.5.4",
- "content-type": "~1.0.4",
- "cookie": "~0.7.1",
- "cookie-signature": "~1.0.6",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.3.1",
- "fresh": "~0.5.2",
- "http-errors": "~2.0.0",
- "merge-descriptors": "1.0.3",
- "methods": "~1.1.2",
- "on-finished": "~2.4.1",
- "parseurl": "~1.3.3",
- "path-to-regexp": "~0.1.12",
- "proxy-addr": "~2.0.7",
- "qs": "~6.14.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "~0.19.0",
- "serve-static": "~1.16.2",
- "setprototypeof": "1.2.0",
- "statuses": "~2.0.1",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- },
- "engines": {
- "node": ">= 0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/fast-xml-parser": {
- "version": "5.2.5",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz",
- "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "strnum": "^2.1.0"
- },
- "bin": {
- "fxparser": "src/cli/cli.js"
- }
- },
- "node_modules/finalhandler": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
- "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "on-finished": "~2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "~2.0.2",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/fluent-ffmpeg": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
- "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==",
- "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
- "license": "MIT",
- "dependencies": {
- "async": "^0.2.9",
- "which": "^1.1.1"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "license": "ISC"
- },
- "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/glob": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
- "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- }
- },
- "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/growl": {
- "version": "1.10.5",
- "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
- "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
- "license": "MIT",
- "engines": {
- "node": ">=4.x"
- }
- },
- "node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/he": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
- "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==",
- "license": "MIT",
- "bin": {
- "he": "bin/he"
- }
- },
- "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.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "license": "ISC",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/inko": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/inko/-/inko-1.1.1.tgz",
- "integrity": "sha512-Lr+HH4xr1eT0OKokYXjr+lRk6ubVEw6iCpOEGsdeokwLsvLXODWZG2XuzvTOlCl5hEVApK8TVWSkWnf55c6RJA==",
- "license": "MIT",
- "dependencies": {
- "mocha": "^5.1.1"
- },
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/ioredis": {
- "version": "5.9.1",
- "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.1.tgz",
- "integrity": "sha512-BXNqFQ66oOsR82g9ajFFsR8ZKrjVvYCLyeML9IvSMAsP56XH2VXBdZjmI11p65nXXJxTEt1hie3J2QeFJVgrtQ==",
- "license": "MIT",
- "dependencies": {
- "@ioredis/commands": "1.5.0",
- "cluster-key-slot": "^1.1.0",
- "debug": "^4.3.4",
- "denque": "^2.1.0",
- "lodash.defaults": "^4.2.0",
- "lodash.isarguments": "^3.1.0",
- "redis-errors": "^1.2.0",
- "redis-parser": "^3.0.0",
- "standard-as-callback": "^2.1.0"
- },
- "engines": {
- "node": ">=12.22.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/ioredis"
- }
- },
- "node_modules/ioredis/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/ioredis/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/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
- "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
- "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/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "license": "ISC"
- },
- "node_modules/jsonwebtoken": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
- "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
- "license": "MIT",
- "dependencies": {
- "jws": "^4.0.1",
- "lodash.includes": "^4.3.0",
- "lodash.isboolean": "^3.0.3",
- "lodash.isinteger": "^4.0.4",
- "lodash.isnumber": "^3.0.3",
- "lodash.isplainobject": "^4.0.6",
- "lodash.isstring": "^4.0.1",
- "lodash.once": "^4.0.0",
- "ms": "^2.1.1",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=12",
- "npm": ">=6"
- }
- },
- "node_modules/jsonwebtoken/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
- "license": "MIT",
- "dependencies": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jws": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
- "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
- "license": "MIT",
- "dependencies": {
- "jwa": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/lodash.defaults": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
- "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
- "license": "MIT"
- },
- "node_modules/lodash.includes": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
- "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
- "license": "MIT"
- },
- "node_modules/lodash.isarguments": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
- "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
- "license": "MIT"
- },
- "node_modules/lodash.isboolean": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
- "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
- "license": "MIT"
- },
- "node_modules/lodash.isinteger": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
- "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
- "license": "MIT"
- },
- "node_modules/lodash.isnumber": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
- "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
- "license": "MIT"
- },
- "node_modules/lodash.isplainobject": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
- "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
- "license": "MIT"
- },
- "node_modules/lodash.isstring": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
- "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
- "license": "MIT"
- },
- "node_modules/lodash.once": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
- "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
- "license": "MIT"
- },
- "node_modules/long": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
- "license": "Apache-2.0"
- },
- "node_modules/lru.min": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz",
- "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==",
- "license": "MIT",
- "engines": {
- "bun": ">=1.0.0",
- "deno": ">=1.30.0",
- "node": ">=8.0.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wellwelwel"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/meilisearch": {
- "version": "0.55.0",
- "resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.55.0.tgz",
- "integrity": "sha512-qSMeiezfDgIqciIeYzh5E4pXDZZD7CtHeWDCs43kN3trLgl5FtfmBAIkljL3huFaOx08feYtC8FfIFUpVwq6rg==",
- "license": "MIT"
- },
- "node_modules/merge-descriptors": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/mkdirp": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
- "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.6"
- },
- "bin": {
- "mkdirp": "bin/cmd.js"
- }
- },
- "node_modules/mocha": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
- "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
- "license": "MIT",
- "dependencies": {
- "browser-stdout": "1.3.1",
- "commander": "2.15.1",
- "debug": "3.1.0",
- "diff": "3.5.0",
- "escape-string-regexp": "1.0.5",
- "glob": "7.1.2",
- "growl": "1.10.5",
- "he": "1.1.1",
- "minimatch": "3.0.4",
- "mkdirp": "0.5.1",
- "supports-color": "5.4.0"
- },
- "bin": {
- "_mocha": "bin/_mocha",
- "mocha": "bin/mocha"
- },
- "engines": {
- "node": ">= 4.0.0"
- }
- },
- "node_modules/mocha/node_modules/debug": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
- "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/mocha/node_modules/minimist": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
- "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==",
- "license": "MIT"
- },
- "node_modules/mocha/node_modules/mkdirp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
- "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==",
- "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
- "license": "MIT",
- "dependencies": {
- "minimist": "0.0.8"
- },
- "bin": {
- "mkdirp": "bin/cmd.js"
- }
- },
- "node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "license": "MIT"
- },
- "node_modules/multer": {
- "version": "1.4.5-lts.2",
- "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
- "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
- "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
- "license": "MIT",
- "dependencies": {
- "append-field": "^1.0.0",
- "busboy": "^1.0.0",
- "concat-stream": "^1.5.2",
- "mkdirp": "^0.5.4",
- "object-assign": "^4.1.1",
- "type-is": "^1.6.4",
- "xtend": "^4.0.0"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/mysql2": {
- "version": "3.16.0",
- "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.0.tgz",
- "integrity": "sha512-AEGW7QLLSuSnjCS4pk3EIqOmogegmze9h8EyrndavUQnIUcfkVal/sK7QznE+a3bc6rzPbAiui9Jcb+96tPwYA==",
- "license": "MIT",
- "dependencies": {
- "aws-ssl-profiles": "^1.1.1",
- "denque": "^2.1.0",
- "generate-function": "^2.3.1",
- "iconv-lite": "^0.7.0",
- "long": "^5.2.1",
- "lru.min": "^1.0.0",
- "named-placeholders": "^1.1.3",
- "seq-queue": "^0.0.5",
- "sqlstring": "^2.3.2"
- },
- "engines": {
- "node": ">= 8.0"
- }
- },
- "node_modules/mysql2/node_modules/iconv-lite": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
- "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/named-placeholders": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
- "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
- "license": "MIT",
- "dependencies": {
- "lru.min": "^1.1.0"
- },
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/node-addon-api": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
- "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
- "license": "MIT",
- "engines": {
- "node": "^18 || ^20 || >= 21"
- }
- },
- "node_modules/node-cron": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
- "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
- "license": "ISC",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/node-gyp-build": {
- "version": "4.8.4",
- "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
- "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
- "license": "MIT",
- "bin": {
- "node-gyp-build": "bin.js",
- "node-gyp-build-optional": "optional.js",
- "node-gyp-build-test": "build-test.js"
- }
- },
- "node_modules/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-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
- "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
- "license": "MIT"
- },
- "node_modules/process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "license": "MIT"
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/qs": {
- "version": "6.14.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
- "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
- "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
- "license": "MIT",
- "dependencies": {
- "bytes": "~3.1.2",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "license": "MIT",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/readable-stream/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "license": "MIT"
- },
- "node_modules/redis-errors": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
- "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/redis-parser": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
- "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
- "license": "MIT",
- "dependencies": {
- "redis-errors": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/rss-parser": {
- "version": "3.13.0",
- "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
- "integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
- "license": "MIT",
- "dependencies": {
- "entities": "^2.0.3",
- "xml2js": "^0.5.0"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "license": "MIT"
- },
- "node_modules/sax": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
- "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
- "license": "BlueOak-1.0.0"
- },
- "node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/send": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
- "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "~0.5.2",
- "http-errors": "~2.0.1",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "~2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "~2.0.2"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/seq-queue": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
- "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
- },
- "node_modules/serve-static": {
- "version": "1.16.3",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
- "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
- "license": "MIT",
- "dependencies": {
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "~0.19.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "license": "ISC"
- },
- "node_modules/sharp": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
- "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "color": "^4.2.3",
- "detect-libc": "^2.0.3",
- "semver": "^7.6.3"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.33.5",
- "@img/sharp-darwin-x64": "0.33.5",
- "@img/sharp-libvips-darwin-arm64": "1.0.4",
- "@img/sharp-libvips-darwin-x64": "1.0.4",
- "@img/sharp-libvips-linux-arm": "1.0.5",
- "@img/sharp-libvips-linux-arm64": "1.0.4",
- "@img/sharp-libvips-linux-s390x": "1.0.4",
- "@img/sharp-libvips-linux-x64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
- "@img/sharp-linux-arm": "0.33.5",
- "@img/sharp-linux-arm64": "0.33.5",
- "@img/sharp-linux-s390x": "0.33.5",
- "@img/sharp-linux-x64": "0.33.5",
- "@img/sharp-linuxmusl-arm64": "0.33.5",
- "@img/sharp-linuxmusl-x64": "0.33.5",
- "@img/sharp-wasm32": "0.33.5",
- "@img/sharp-win32-ia32": "0.33.5",
- "@img/sharp-win32-x64": "0.33.5"
- }
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/simple-swizzle": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
- "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
- "node_modules/sqlstring": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
- "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/standard-as-callback": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
- "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
- "license": "MIT"
- },
- "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/streamsearch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
- "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/string_decoder/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "license": "MIT"
- },
- "node_modules/strnum": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz",
- "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "license": "MIT"
- },
- "node_modules/supports-color": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
- "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "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/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "license": "MIT",
- "dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
- "license": "MIT"
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "license": "MIT"
- },
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/which": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
- "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "which": "bin/which"
- }
- },
- "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/xml2js": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
- "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
- "license": "MIT",
- "dependencies": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4"
- }
+ "name": "fromis9-backend",
+ "version": "2.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "fromis9-backend",
+ "version": "2.0.0",
+ "dependencies": {
+ "dayjs": "^1.11.13",
+ "fastify": "^5.2.1",
+ "fastify-plugin": "^5.0.1",
+ "ioredis": "^5.4.2",
+ "mysql2": "^3.12.0",
+ "node-cron": "^3.0.3"
+ }
+ },
+ "node_modules/@fastify/ajv-compiler": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz",
+ "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
}
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.12.0",
+ "ajv-formats": "^3.0.1",
+ "fast-uri": "^3.0.0"
+ }
+ },
+ "node_modules/@fastify/error": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz",
+ "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/@fastify/fast-json-stringify-compiler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz",
+ "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stringify": "^6.0.0"
+ }
+ },
+ "node_modules/@fastify/forwarded": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz",
+ "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/@fastify/merge-json-schemas": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz",
+ "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/@fastify/proxy-addr": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz",
+ "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@fastify/forwarded": "^3.0.0",
+ "ipaddr.js": "^2.1.0"
+ }
+ },
+ "node_modules/@ioredis/commands": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.0.tgz",
+ "integrity": "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==",
+ "license": "MIT"
+ },
+ "node_modules/@pinojs/redact": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
+ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
+ "license": "MIT"
+ },
+ "node_modules/abstract-logging": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
+ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==",
+ "license": "MIT"
+ },
+ "node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/atomic-sleep": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/avvio": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz",
+ "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@fastify/error": "^4.0.0",
+ "fastq": "^1.17.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/cluster-key-slot": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
+ "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.19",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
+ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
+ "license": "MIT"
+ },
+ "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/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-decode-uri-component": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
+ "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stringify": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.2.0.tgz",
+ "integrity": "sha512-Eaf/KNIDwHkzfyeQFNfLXJnQ7cl1XQI3+zRqmPlvtkMigbXnAcasTrvJQmquBSxKfFGeRA6PFog8t+hFmpDoWw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@fastify/merge-json-schemas": "^0.2.0",
+ "ajv": "^8.12.0",
+ "ajv-formats": "^3.0.1",
+ "fast-uri": "^3.0.0",
+ "json-schema-ref-resolver": "^3.0.0",
+ "rfdc": "^1.2.0"
+ }
+ },
+ "node_modules/fast-querystring": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
+ "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-decode-uri-component": "^1.0.1"
+ }
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fastify": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.1.tgz",
+ "integrity": "sha512-ZW7S4fxlZhE+tYWVokFzjh+i56R+buYKNGhrVl6DtN8sxkyMEzpJnzvO8A/ZZrsg5w6X37u6I4EOQikYS5DXpA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@fastify/ajv-compiler": "^4.0.5",
+ "@fastify/error": "^4.0.0",
+ "@fastify/fast-json-stringify-compiler": "^5.0.0",
+ "@fastify/proxy-addr": "^5.0.0",
+ "abstract-logging": "^2.0.1",
+ "avvio": "^9.0.0",
+ "fast-json-stringify": "^6.0.0",
+ "find-my-way": "^9.0.0",
+ "light-my-request": "^6.0.0",
+ "pino": "^10.1.0",
+ "process-warning": "^5.0.0",
+ "rfdc": "^1.3.1",
+ "secure-json-parse": "^4.0.0",
+ "semver": "^7.6.0",
+ "toad-cache": "^3.7.0"
+ }
+ },
+ "node_modules/fastify-plugin": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz",
+ "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/find-my-way": {
+ "version": "9.4.0",
+ "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.4.0.tgz",
+ "integrity": "sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-querystring": "^1.0.0",
+ "safe-regex2": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "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/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/ioredis": {
+ "version": "5.9.2",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz",
+ "integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ioredis/commands": "1.5.0",
+ "cluster-key-slot": "^1.1.0",
+ "debug": "^4.3.4",
+ "denque": "^2.1.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.isarguments": "^3.1.0",
+ "redis-errors": "^1.2.0",
+ "redis-parser": "^3.0.0",
+ "standard-as-callback": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=12.22.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ioredis"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz",
+ "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "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/json-schema-ref-resolver": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz",
+ "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/light-my-request": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz",
+ "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "process-warning": "^4.0.0",
+ "set-cookie-parser": "^2.6.0"
+ }
+ },
+ "node_modules/light-my-request/node_modules/process-warning": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz",
+ "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isarguments": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+ "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
+ "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.3",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz",
+ "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==",
+ "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/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.16.1",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.1.tgz",
+ "integrity": "sha512-b75qsDB3ieYEzMsT1uRGsztM/sy6vWPY40uPZlVVl8eefAotFCoS7jaDB5DxDNtlW5kdVGd9jptSpkvujNxI2A==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.0",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.6",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "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/node-cron": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
+ "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
+ "license": "ISC",
+ "dependencies": {
+ "uuid": "8.3.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/on-exit-leak-free": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/pino": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-10.2.0.tgz",
+ "integrity": "sha512-NFnZqUliT+OHkRXVSf8vdOr13N1wv31hRryVjqbreVh/SDCNaI6mnRDDq89HVRCbem1SAl7yj04OANeqP0nT6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@pinojs/redact": "^0.4.0",
+ "atomic-sleep": "^1.0.0",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^3.0.0",
+ "pino-std-serializers": "^7.0.0",
+ "process-warning": "^5.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^4.0.1",
+ "thread-stream": "^4.0.0"
+ },
+ "bin": {
+ "pino": "bin.js"
+ }
+ },
+ "node_modules/pino-abstract-transport": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
+ "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.0.0"
+ }
+ },
+ "node_modules/pino-std-serializers": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
+ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
+ "license": "MIT"
+ },
+ "node_modules/process-warning": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
+ "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/quick-format-unescaped": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
+ "license": "MIT"
+ },
+ "node_modules/real-require": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12.13.0"
+ }
+ },
+ "node_modules/redis-errors": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
+ "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/redis-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
+ "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
+ "license": "MIT",
+ "dependencies": {
+ "redis-errors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ret": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz",
+ "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "license": "MIT"
+ },
+ "node_modules/safe-regex2": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz",
+ "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "ret": "~0.5.0"
+ }
+ },
+ "node_modules/safe-stable-stringify": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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/secure-json-parse": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
+ "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/sonic-boom": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
+ "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
+ "license": "MIT",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/standard-as-callback": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
+ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
+ "license": "MIT"
+ },
+ "node_modules/thread-stream": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
+ "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
+ "license": "MIT",
+ "dependencies": {
+ "real-require": "^0.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/toad-cache": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz",
+ "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
}
+ }
}
diff --git a/backend/package.json b/backend/package.json
index 643e866..fce3e25 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,25 +1,17 @@
{
- "name": "fromis9-backend",
- "private": true,
- "version": "1.0.0",
- "type": "module",
- "scripts": {
- "start": "node server.js"
- },
- "dependencies": {
- "@aws-sdk/client-s3": "^3.700.0",
- "bcrypt": "^6.0.0",
- "dayjs": "^1.11.19",
- "express": "^4.18.2",
- "inko": "^1.1.1",
- "ioredis": "^5.4.0",
- "jsonwebtoken": "^9.0.3",
- "meilisearch": "^0.55.0",
- "multer": "^1.4.5-lts.1",
- "mysql2": "^3.11.0",
- "node-cron": "^4.2.1",
- "rss-parser": "^3.13.0",
- "sharp": "^0.33.5",
- "fluent-ffmpeg": "^2.1.3"
- }
-}
\ No newline at end of file
+ "name": "fromis9-backend",
+ "version": "2.0.0",
+ "type": "module",
+ "scripts": {
+ "start": "node src/server.js",
+ "dev": "node --watch src/server.js"
+ },
+ "dependencies": {
+ "fastify": "^5.2.1",
+ "fastify-plugin": "^5.0.1",
+ "mysql2": "^3.12.0",
+ "ioredis": "^5.4.2",
+ "node-cron": "^3.0.3",
+ "dayjs": "^1.11.13"
+ }
+}
diff --git a/backend/routes/admin.js b/backend/routes/admin.js
deleted file mode 100644
index d3303aa..0000000
--- a/backend/routes/admin.js
+++ /dev/null
@@ -1,2147 +0,0 @@
-import express from "express";
-import bcrypt from "bcrypt";
-import jwt from "jsonwebtoken";
-import multer from "multer";
-import sharp from "sharp";
-import ffmpeg from "fluent-ffmpeg";
-import fs from "fs/promises";
-import os from "os";
-import path from "path";
-import {
- S3Client,
- PutObjectCommand,
- DeleteObjectCommand,
-} from "@aws-sdk/client-s3";
-import pool from "../lib/db.js";
-import { syncNewVideos, syncAllVideos } from "../services/youtube-bot.js";
-import { syncAllTweets } from "../services/x-bot.js";
-import { syncAllSchedules } from "../services/meilisearch-bot.js";
-import { startBot, stopBot } from "../services/youtube-scheduler.js";
-import {
- addOrUpdateSchedule,
- deleteSchedule as deleteScheduleFromSearch,
-} from "../services/meilisearch.js";
-
-const router = express.Router();
-
-// JWT 설정
-const JWT_SECRET = process.env.JWT_SECRET || "fromis9-admin-secret-key-2026";
-const JWT_EXPIRES_IN = "30d";
-
-// Multer 설정 (메모리 저장)
-const upload = multer({
- storage: multer.memoryStorage(),
- limits: { fileSize: 50 * 1024 * 1024 }, // 50MB (동영상 지원)
- fileFilter: (req, file, cb) => {
- // 이미지 또는 MP4 비디오 허용
- if (file.mimetype.startsWith("image/") || file.mimetype === "video/mp4") {
- cb(null, true);
- } else {
- cb(new Error("이미지 또는 MP4 파일만 업로드 가능합니다."), false);
- }
- },
-});
-
-// S3 클라이언트 (RustFS)
-const s3Client = new S3Client({
- endpoint: process.env.RUSTFS_ENDPOINT,
- region: "us-east-1",
- credentials: {
- accessKeyId: process.env.RUSTFS_ACCESS_KEY,
- secretAccessKey: process.env.RUSTFS_SECRET_KEY,
- },
- forcePathStyle: true,
-});
-
-const BUCKET = process.env.RUSTFS_BUCKET || "fromis-9";
-
-// 토큰 검증 미들웨어
-export const authenticateToken = (req, res, next) => {
- const authHeader = req.headers["authorization"];
- const token = authHeader && authHeader.split(" ")[1];
-
- if (!token) {
- return res.status(401).json({ error: "인증이 필요합니다." });
- }
-
- jwt.verify(token, JWT_SECRET, (err, user) => {
- if (err) {
- return res.status(403).json({ error: "유효하지 않은 토큰입니다." });
- }
- req.user = user;
- next();
- });
-};
-
-// 관리자 로그인
-router.post("/login", async (req, res) => {
- try {
- const { username, password } = req.body;
-
- if (!username || !password) {
- return res
- .status(400)
- .json({ error: "아이디와 비밀번호를 입력해주세요." });
- }
-
- const [users] = await pool.query(
- "SELECT * FROM admin_users WHERE username = ?",
- [username]
- );
-
- if (users.length === 0) {
- return res
- .status(401)
- .json({ error: "아이디 또는 비밀번호가 올바르지 않습니다." });
- }
-
- const user = users[0];
- const isValidPassword = await bcrypt.compare(password, user.password_hash);
-
- if (!isValidPassword) {
- return res
- .status(401)
- .json({ error: "아이디 또는 비밀번호가 올바르지 않습니다." });
- }
-
- const token = jwt.sign(
- { id: user.id, username: user.username },
- JWT_SECRET,
- { expiresIn: JWT_EXPIRES_IN }
- );
-
- res.json({
- message: "로그인 성공",
- token,
- user: { id: user.id, username: user.username },
- });
- } catch (error) {
- console.error("로그인 오류:", error);
- res.status(500).json({ error: "로그인 처리 중 오류가 발생했습니다." });
- }
-});
-
-// 토큰 검증 엔드포인트
-router.get("/verify", authenticateToken, (req, res) => {
- res.json({ valid: true, user: req.user });
-});
-
-// 초기 관리자 계정 생성
-router.post("/init", async (req, res) => {
- try {
- const [existing] = await pool.query(
- "SELECT COUNT(*) as count FROM admin_users"
- );
-
- if (existing[0].count > 0) {
- return res.status(400).json({ error: "이미 관리자 계정이 존재합니다." });
- }
-
- const password = "auddnek0403!";
- const passwordHash = await bcrypt.hash(password, 10);
-
- await pool.query(
- "INSERT INTO admin_users (username, password_hash) VALUES (?, ?)",
- ["admin", passwordHash]
- );
-
- res.json({ message: "관리자 계정이 생성되었습니다." });
- } catch (error) {
- console.error("계정 생성 오류:", error);
- res.status(500).json({ error: "계정 생성 중 오류가 발생했습니다." });
- }
-});
-
-// ==================== 앨범 관리 API ====================
-
-// 앨범 생성
-router.post(
- "/albums",
- authenticateToken,
- upload.single("cover"),
- async (req, res) => {
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const data = JSON.parse(req.body.data);
- const {
- title,
- album_type,
- album_type_short,
- release_date,
- folder_name,
- description,
- tracks,
- } = data;
-
- // 필수 필드 검증
- if (!title || !album_type || !release_date || !folder_name) {
- return res
- .status(400)
- .json({ error: "필수 필드를 모두 입력해주세요." });
- }
-
- let coverOriginalUrl = null;
- let coverMediumUrl = null;
- let coverThumbUrl = null;
-
- // 커버 이미지 업로드 (3개 해상도)
- if (req.file) {
- // 3가지 크기로 변환 (병렬)
- const [originalBuffer, mediumBuffer, thumbBuffer] = await Promise.all([
- sharp(req.file.buffer).webp({ lossless: true }).toBuffer(),
- sharp(req.file.buffer)
- .resize(800, null, { withoutEnlargement: true })
- .webp({ quality: 85 })
- .toBuffer(),
- sharp(req.file.buffer)
- .resize(400, null, { withoutEnlargement: true })
- .webp({ quality: 80 })
- .toBuffer(),
- ]);
-
- const basePath = `album/${folder_name}/cover`;
-
- // S3 업로드 (병렬)
- await Promise.all([
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/original/cover.webp`,
- Body: originalBuffer,
- ContentType: "image/webp",
- })
- ),
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/medium_800/cover.webp`,
- Body: mediumBuffer,
- ContentType: "image/webp",
- })
- ),
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/thumb_400/cover.webp`,
- Body: thumbBuffer,
- ContentType: "image/webp",
- })
- ),
- ]);
-
- const publicUrl =
- process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT;
- coverOriginalUrl = `${publicUrl}/${BUCKET}/${basePath}/original/cover.webp`;
- coverMediumUrl = `${publicUrl}/${BUCKET}/${basePath}/medium_800/cover.webp`;
- coverThumbUrl = `${publicUrl}/${BUCKET}/${basePath}/thumb_400/cover.webp`;
- }
-
- // 앨범 삽입
- const [albumResult] = await connection.query(
- `INSERT INTO albums (title, album_type, album_type_short, release_date, folder_name, cover_original_url, cover_medium_url, cover_thumb_url, description)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [
- title,
- album_type,
- album_type_short || null,
- release_date,
- folder_name,
- coverOriginalUrl,
- coverMediumUrl,
- coverThumbUrl,
- description || null,
- ]
- );
-
- const albumId = albumResult.insertId;
-
- // 트랙 삽입
- if (tracks && tracks.length > 0) {
- for (const track of tracks) {
- await connection.query(
- `INSERT INTO tracks (album_id, track_number, title, duration, is_title_track, lyricist, composer, arranger, lyrics, music_video_url)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [
- albumId,
- track.track_number,
- track.title,
- track.duration || null,
- track.is_title_track ? 1 : 0,
- track.lyricist || null,
- track.composer || null,
- track.arranger || null,
- track.lyrics || null,
- track.music_video_url || null,
- ]
- );
- }
- }
-
- await connection.commit();
-
- res.json({ message: "앨범이 생성되었습니다.", albumId });
- } catch (error) {
- await connection.rollback();
- console.error("앨범 생성 오류:", error);
- res.status(500).json({ error: "앨범 생성 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
- }
-);
-
-// 앨범 수정
-router.put(
- "/albums/:id",
- authenticateToken,
- upload.single("cover"),
- async (req, res) => {
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const albumId = req.params.id;
- const data = JSON.parse(req.body.data);
- const {
- title,
- album_type,
- album_type_short,
- release_date,
- folder_name,
- description,
- tracks,
- } = data;
-
- // 기존 앨범 조회
- const [existingAlbums] = await connection.query(
- "SELECT * FROM albums WHERE id = ?",
- [albumId]
- );
- if (existingAlbums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- let coverOriginalUrl = existingAlbums[0].cover_original_url;
- let coverMediumUrl = existingAlbums[0].cover_medium_url;
- let coverThumbUrl = existingAlbums[0].cover_thumb_url;
-
- // 커버 이미지 업로드 (3개 해상도)
- if (req.file) {
- // 3가지 크기로 변환 (병렬)
- const [originalBuffer, mediumBuffer, thumbBuffer] = await Promise.all([
- sharp(req.file.buffer).webp({ lossless: true }).toBuffer(),
- sharp(req.file.buffer)
- .resize(800, null, { withoutEnlargement: true })
- .webp({ quality: 85 })
- .toBuffer(),
- sharp(req.file.buffer)
- .resize(400, null, { withoutEnlargement: true })
- .webp({ quality: 80 })
- .toBuffer(),
- ]);
-
- const basePath = `album/${folder_name}/cover`;
-
- // S3 업로드 (병렬)
- await Promise.all([
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/original/cover.webp`,
- Body: originalBuffer,
- ContentType: "image/webp",
- })
- ),
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/medium_800/cover.webp`,
- Body: mediumBuffer,
- ContentType: "image/webp",
- })
- ),
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/thumb_400/cover.webp`,
- Body: thumbBuffer,
- ContentType: "image/webp",
- })
- ),
- ]);
-
- const publicUrl =
- process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT;
- coverOriginalUrl = `${publicUrl}/${BUCKET}/${basePath}/original/cover.webp`;
- coverMediumUrl = `${publicUrl}/${BUCKET}/${basePath}/medium_800/cover.webp`;
- coverThumbUrl = `${publicUrl}/${BUCKET}/${basePath}/thumb_400/cover.webp`;
- }
-
- // 앨범 업데이트
- await connection.query(
- `UPDATE albums SET title = ?, album_type = ?, album_type_short = ?, release_date = ?, folder_name = ?, cover_original_url = ?, cover_medium_url = ?, cover_thumb_url = ?, description = ?
- WHERE id = ?`,
- [
- title,
- album_type,
- album_type_short || null,
- release_date,
- folder_name,
- coverOriginalUrl,
- coverMediumUrl,
- coverThumbUrl,
- description || null,
- albumId,
- ]
- );
-
- // 기존 트랙 삭제 후 새 트랙 삽입
- await connection.query("DELETE FROM tracks WHERE album_id = ?", [
- albumId,
- ]);
-
- if (tracks && tracks.length > 0) {
- for (const track of tracks) {
- await connection.query(
- `INSERT INTO tracks (album_id, track_number, title, duration, is_title_track, lyricist, composer, arranger, lyrics, music_video_url)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [
- albumId,
- track.track_number,
- track.title,
- track.duration || null,
- track.is_title_track ? 1 : 0,
- track.lyricist || null,
- track.composer || null,
- track.arranger || null,
- track.lyrics || null,
- track.music_video_url || null,
- ]
- );
- }
- }
-
- await connection.commit();
-
- res.json({ message: "앨범이 수정되었습니다." });
- } catch (error) {
- await connection.rollback();
- console.error("앨범 수정 오류:", error);
- res.status(500).json({ error: "앨범 수정 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
- }
-);
-
-// 앨범 삭제
-router.delete("/albums/:id", authenticateToken, async (req, res) => {
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const albumId = req.params.id;
-
- // 기존 앨범 조회
- const [existingAlbums] = await connection.query(
- "SELECT * FROM albums WHERE id = ?",
- [albumId]
- );
- if (existingAlbums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- const album = existingAlbums[0];
-
- // RustFS에서 커버 이미지 삭제 (3가지 크기)
- if (album.cover_original_url && album.folder_name) {
- const basePath = `album/${album.folder_name}/cover`;
- const sizes = ["original", "medium_800", "thumb_400"];
- for (const size of sizes) {
- try {
- await s3Client.send(
- new DeleteObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/${size}/cover.webp`,
- })
- );
- } catch (s3Error) {
- console.error(`S3 커버 삭제 오류 (${size}):`, s3Error);
- }
- }
- }
-
- // 트랙 삭제
- await connection.query("DELETE FROM tracks WHERE album_id = ?", [albumId]);
-
- // 앨범 삭제
- await connection.query("DELETE FROM albums WHERE id = ?", [albumId]);
-
- await connection.commit();
-
- res.json({ message: "앨범이 삭제되었습니다." });
- } catch (error) {
- await connection.rollback();
- console.error("앨범 삭제 오류:", error);
- res.status(500).json({ error: "앨범 삭제 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
-});
-
-// ============================================
-// 앨범 사진 관리 API
-// ============================================
-
-// 앨범 사진 목록 조회
-router.get("/albums/:albumId/photos", async (req, res) => {
- try {
- const { albumId } = req.params;
-
- // 앨범 존재 확인
- const [albums] = await pool.query(
- "SELECT folder_name FROM albums WHERE id = ?",
- [albumId]
- );
- if (albums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- const folderName = albums[0].folder_name;
-
- // 사진 조회 (멤버 정보 포함)
- const [photos] = await pool.query(
- `
- SELECT
- p.id, p.original_url, p.medium_url, p.thumb_url, p.photo_type, p.concept_name,
- p.sort_order, p.width, p.height, p.file_size,
- GROUP_CONCAT(pm.member_id) as member_ids
- FROM album_photos p
- LEFT JOIN album_photo_members pm ON p.id = pm.photo_id
- WHERE p.album_id = ?
- GROUP BY p.id
- ORDER BY p.sort_order ASC
- `,
- [albumId]
- );
-
- // 멤버 배열 파싱
- const result = photos.map((photo) => ({
- ...photo,
- members: photo.member_ids ? photo.member_ids.split(",").map(Number) : [],
- }));
-
- res.json(result);
- } catch (error) {
- console.error("사진 조회 오류:", error);
- res.status(500).json({ error: "사진 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 앨범 티저 목록 조회
-router.get("/albums/:albumId/teasers", async (req, res) => {
- try {
- const { albumId } = req.params;
-
- // 앨범 존재 확인
- const [albums] = await pool.query(
- "SELECT folder_name FROM albums WHERE id = ?",
- [albumId]
- );
- if (albums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- // 티저 조회
- const [teasers] = await pool.query(
- `SELECT id, original_url, medium_url, thumb_url, video_url, sort_order, media_type
- FROM album_teasers
- WHERE album_id = ?
- ORDER BY sort_order ASC`,
- [albumId]
- );
-
- res.json(teasers);
- } catch (error) {
- console.error("티저 조회 오류:", error);
- res.status(500).json({ error: "티저 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 티저 삭제
-router.delete(
- "/albums/:albumId/teasers/:teaserId",
- authenticateToken,
- async (req, res) => {
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const { albumId, teaserId } = req.params;
-
- // 티저 정보 조회
- const [teasers] = await connection.query(
- "SELECT t.*, a.folder_name FROM album_teasers t JOIN albums a ON t.album_id = a.id WHERE t.id = ? AND t.album_id = ?",
- [teaserId, albumId]
- );
-
- if (teasers.length === 0) {
- return res.status(404).json({ error: "티저를 찾을 수 없습니다." });
- }
-
- const teaser = teasers[0];
- const filename = teaser.original_url.split("/").pop();
- const basePath = `album/${teaser.folder_name}/teaser`;
-
- // RustFS에서 썸네일 삭제 (3가지 크기 모두)
- const sizes = ["original", "medium_800", "thumb_400"];
- for (const size of sizes) {
- try {
- await s3Client.send(
- new DeleteObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/${size}/${filename}`,
- })
- );
- } catch (s3Error) {
- console.error(`S3 삭제 오류 (${size}):`, s3Error);
- }
- }
-
- // 비디오 파일 삭제 (video_url이 있는 경우)
- if (teaser.video_url) {
- const videoFilename = teaser.video_url.split("/").pop();
- try {
- await s3Client.send(
- new DeleteObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/video/${videoFilename}`,
- })
- );
- } catch (s3Error) {
- console.error("S3 비디오 삭제 오류:", s3Error);
- }
- }
-
- // 티저 삭제
- await connection.query("DELETE FROM album_teasers WHERE id = ?", [
- teaserId,
- ]);
-
- await connection.commit();
-
- res.json({ message: "티저가 삭제되었습니다." });
- } catch (error) {
- await connection.rollback();
- console.error("티저 삭제 오류:", error);
- res.status(500).json({ error: "티저 삭제 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
- }
-);
-
-// 사진 업로드 (SSE로 실시간 진행률 전송)
-router.post(
- "/albums/:albumId/photos",
- authenticateToken,
- upload.array("photos", 200),
- async (req, res) => {
- // SSE 헤더 설정
- res.setHeader("Content-Type", "text/event-stream");
- res.setHeader("Cache-Control", "no-cache");
- res.setHeader("Connection", "keep-alive");
-
- const sendProgress = (current, total, message) => {
- res.write(`data: ${JSON.stringify({ current, total, message })}\n\n`);
- };
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const { albumId } = req.params;
- const metadata = JSON.parse(req.body.metadata || "[]");
- const startNumber = parseInt(req.body.startNumber) || null;
- const photoType = req.body.photoType || "concept"; // 'concept' | 'teaser'
-
- // 앨범 정보 조회
- const [albums] = await connection.query(
- "SELECT folder_name FROM albums WHERE id = ?",
- [albumId]
- );
- if (albums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- const folderName = albums[0].folder_name;
-
- // 시작 번호 결정 (클라이언트 지정 또는 기존 사진 다음 번호)
- let nextOrder;
- if (startNumber && startNumber > 0) {
- nextOrder = startNumber;
- } else {
- const [existingPhotos] = await connection.query(
- "SELECT MAX(sort_order) as maxOrder FROM album_photos WHERE album_id = ?",
- [albumId]
- );
- nextOrder = (existingPhotos[0].maxOrder || 0) + 1;
- }
-
- const uploadedPhotos = [];
- const totalFiles = req.files.length;
-
- for (let i = 0; i < req.files.length; i++) {
- const file = req.files[i];
- const meta = metadata[i] || {};
- const orderNum = String(nextOrder + i).padStart(2, "0");
- const isVideo = file.mimetype === "video/mp4";
- const extension = isVideo ? "mp4" : "webp";
- const filename = `${orderNum}.${extension}`;
-
- // 진행률 전송
- sendProgress(i + 1, totalFiles, `${filename} 처리 중...`);
-
- let originalUrl, mediumUrl, thumbUrl, videoUrl;
- let originalBuffer, originalMeta;
-
- // 컨셉 포토: photo/, 티저: teaser/
- const subFolder = photoType === "teaser" ? "teaser" : "photo";
- const basePath = `album/${folderName}/${subFolder}`;
-
- if (isVideo) {
- // ===== 비디오 파일 처리 (티저 전용) =====
- const tempDir = os.tmpdir();
- const tempVideoPath = path.join(tempDir, `video_${Date.now()}.mp4`);
- const tempThumbPath = path.join(tempDir, `thumb_${Date.now()}.png`);
- const thumbFilename = `${orderNum}.webp`;
-
- try {
- // 1. 임시 파일로 MP4 저장
- await fs.writeFile(tempVideoPath, file.buffer);
-
- // 2. ffmpeg로 첫 프레임 추출 (썸네일)
- await new Promise((resolve, reject) => {
- ffmpeg(tempVideoPath)
- .screenshots({
- timestamps: ["00:00:00.001"],
- filename: path.basename(tempThumbPath),
- folder: tempDir,
- })
- .on("end", resolve)
- .on("error", reject);
- });
-
- // 3. 추출된 썸네일을 Sharp로 3가지 크기로 변환
- const thumbBuffer = await fs.readFile(tempThumbPath);
- const [origBuf, medium800Buffer, thumb400Buffer] = await Promise.all([
- sharp(thumbBuffer).webp({ lossless: true }).toBuffer(),
- sharp(thumbBuffer)
- .resize(800, null, { withoutEnlargement: true })
- .webp({ quality: 85 })
- .toBuffer(),
- sharp(thumbBuffer)
- .resize(400, null, { withoutEnlargement: true })
- .webp({ quality: 80 })
- .toBuffer(),
- ]);
-
- // 4. 썸네일 이미지들과 MP4 업로드 (병렬)
- await Promise.all([
- // 썸네일 original
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/original/${thumbFilename}`,
- Body: origBuf,
- ContentType: "image/webp",
- })
- ),
- // 썸네일 medium
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/medium_800/${thumbFilename}`,
- Body: medium800Buffer,
- ContentType: "image/webp",
- })
- ),
- // 썸네일 thumb
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/thumb_400/${thumbFilename}`,
- Body: thumb400Buffer,
- ContentType: "image/webp",
- })
- ),
- // 원본 MP4
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/video/${filename}`,
- Body: file.buffer,
- ContentType: "video/mp4",
- })
- ),
- ]);
-
- // 5. URL 설정 (썸네일은 WebP, 비디오는 MP4)
- originalUrl = `${process.env.RUSTFS_PUBLIC_URL}/${BUCKET}/${basePath}/original/${thumbFilename}`;
- mediumUrl = `${process.env.RUSTFS_PUBLIC_URL}/${BUCKET}/${basePath}/medium_800/${thumbFilename}`;
- thumbUrl = `${process.env.RUSTFS_PUBLIC_URL}/${BUCKET}/${basePath}/thumb_400/${thumbFilename}`;
- videoUrl = `${process.env.RUSTFS_PUBLIC_URL}/${BUCKET}/${basePath}/video/${filename}`;
- } finally {
- // 임시 파일 정리
- await fs.unlink(tempVideoPath).catch(() => {});
- await fs.unlink(tempThumbPath).catch(() => {});
- }
- } else {
- // ===== 이미지 파일 처리 =====
- // Sharp로 이미지 처리 (병렬)
- const [origBuf, medium800Buffer, thumb400Buffer] = await Promise.all([
- sharp(file.buffer).webp({ lossless: true }).toBuffer(),
- sharp(file.buffer)
- .resize(800, null, { withoutEnlargement: true })
- .webp({ quality: 85 })
- .toBuffer(),
- sharp(file.buffer)
- .resize(400, null, { withoutEnlargement: true })
- .webp({ quality: 80 })
- .toBuffer(),
- ]);
-
- originalBuffer = origBuf;
- originalMeta = await sharp(originalBuffer).metadata();
-
- // RustFS 업로드 (병렬)
- await Promise.all([
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/original/${filename}`,
- Body: originalBuffer,
- ContentType: "image/webp",
- })
- ),
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/medium_800/${filename}`,
- Body: medium800Buffer,
- ContentType: "image/webp",
- })
- ),
- s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/thumb_400/${filename}`,
- Body: thumb400Buffer,
- ContentType: "image/webp",
- })
- ),
- ]);
-
- originalUrl = `${process.env.RUSTFS_PUBLIC_URL}/${BUCKET}/${basePath}/original/${filename}`;
- mediumUrl = `${process.env.RUSTFS_PUBLIC_URL}/${BUCKET}/${basePath}/medium_800/${filename}`;
- thumbUrl = `${process.env.RUSTFS_PUBLIC_URL}/${BUCKET}/${basePath}/thumb_400/${filename}`;
- }
-
- let photoId;
-
- // DB 저장 - 티저와 컨셉 포토 분기
- if (photoType === "teaser") {
- // 티저 이미지/비디오 → album_teasers 테이블
- const mediaType = isVideo ? "video" : "image";
- const [result] = await connection.query(
- `INSERT INTO album_teasers
- (album_id, original_url, medium_url, thumb_url, video_url, sort_order, media_type)
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
- [
- albumId,
- originalUrl,
- mediumUrl,
- thumbUrl,
- videoUrl || null,
- nextOrder + i,
- mediaType,
- ]
- );
- photoId = result.insertId;
- } else {
- // 컨셉 포토 → album_photos 테이블 (이미지만)
- const [result] = await connection.query(
- `INSERT INTO album_photos
- (album_id, original_url, medium_url, thumb_url, photo_type, concept_name, sort_order, width, height, file_size)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [
- albumId,
- originalUrl,
- mediumUrl,
- thumbUrl,
- meta.groupType || "group",
- meta.conceptName || null,
- nextOrder + i,
- originalMeta.width,
- originalMeta.height,
- originalBuffer.length,
- ]
- );
- photoId = result.insertId;
-
- // 멤버 태깅 저장 (컨셉 포토만)
- if (meta.members && meta.members.length > 0) {
- for (const memberId of meta.members) {
- await connection.query(
- "INSERT INTO album_photo_members (photo_id, member_id) VALUES (?, ?)",
- [photoId, memberId]
- );
- }
- }
- }
-
- uploadedPhotos.push({
- id: photoId,
- original_url: originalUrl,
- medium_url: mediumUrl,
- thumb_url: thumbUrl,
- video_url: videoUrl || null,
- filename,
- media_type: isVideo ? "video" : "image",
- });
- }
-
- await connection.commit();
-
- // 완료 이벤트 전송
- res.write(
- `data: ${JSON.stringify({
- done: true,
- message: `${uploadedPhotos.length}개의 사진이 업로드되었습니다.`,
- photos: uploadedPhotos,
- })}\n\n`
- );
- res.end();
- } catch (error) {
- await connection.rollback();
- console.error("사진 업로드 오류:", error);
- res.write(
- `data: ${JSON.stringify({
- error: "사진 업로드 중 오류가 발생했습니다.",
- })}\n\n`
- );
- res.end();
- } finally {
- connection.release();
- }
- }
-);
-
-// 사진 삭제
-router.delete(
- "/albums/:albumId/photos/:photoId",
- authenticateToken,
- async (req, res) => {
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const { albumId, photoId } = req.params;
-
- // 사진 정보 조회
- const [photos] = await connection.query(
- "SELECT p.*, a.folder_name FROM album_photos p JOIN albums a ON p.album_id = a.id WHERE p.id = ? AND p.album_id = ?",
- [photoId, albumId]
- );
-
- if (photos.length === 0) {
- return res.status(404).json({ error: "사진을 찾을 수 없습니다." });
- }
-
- const photo = photos[0];
- const filename = photo.original_url.split("/").pop();
- const basePath = `album/${photo.folder_name}/photo`;
-
- // RustFS에서 삭제 (3가지 크기 모두)
- const sizes = ["original", "medium_800", "thumb_400"];
- for (const size of sizes) {
- try {
- await s3Client.send(
- new DeleteObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/${size}/${filename}`,
- })
- );
- } catch (s3Error) {
- console.error(`S3 삭제 오류 (${size}):`, s3Error);
- }
- }
-
- // 멤버 태깅 삭제
- await connection.query(
- "DELETE FROM album_photo_members WHERE photo_id = ?",
- [photoId]
- );
-
- // 사진 삭제
- await connection.query("DELETE FROM album_photos WHERE id = ?", [
- photoId,
- ]);
-
- await connection.commit();
-
- res.json({ message: "사진이 삭제되었습니다." });
- } catch (error) {
- await connection.rollback();
- console.error("사진 삭제 오류:", error);
- res.status(500).json({ error: "사진 삭제 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
- }
-);
-
-// ==================== 멤버 관리 API ====================
-
-// 멤버 상세 조회 (이름으로)
-router.get("/members/:name", authenticateToken, async (req, res) => {
- try {
- const memberName = decodeURIComponent(req.params.name);
- const [members] = await pool.query("SELECT * FROM members WHERE name = ?", [
- memberName,
- ]);
-
- if (members.length === 0) {
- return res.status(404).json({ error: "멤버를 찾을 수 없습니다." });
- }
-
- res.json(members[0]);
- } catch (error) {
- console.error("멤버 조회 오류:", error);
- res.status(500).json({ error: "멤버 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 멤버 수정 (이름으로)
-router.put(
- "/members/:name",
- authenticateToken,
- upload.single("image"),
- async (req, res) => {
- try {
- const memberName = decodeURIComponent(req.params.name);
- const { name, birth_date, position, instagram, is_former } = req.body;
-
- // 기존 멤버 확인
- const [existing] = await pool.query(
- "SELECT * FROM members WHERE name = ?",
- [memberName]
- );
- if (existing.length === 0) {
- return res.status(404).json({ error: "멤버를 찾을 수 없습니다." });
- }
-
- const memberId = existing[0].id;
-
- let imageUrl = existing[0].image_url;
-
- // 새 이미지 업로드
- if (req.file) {
- const webpBuffer = await sharp(req.file.buffer)
- .webp({ quality: 90 })
- .toBuffer();
-
- const key = `member/${memberId}/profile.webp`;
-
- await s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: key,
- Body: webpBuffer,
- ContentType: "image/webp",
- })
- );
-
- const publicUrl =
- process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT;
- imageUrl = `${publicUrl}/${BUCKET}/${key}`;
- }
-
- // 멤버 업데이트
- await pool.query(
- `UPDATE members SET
- name = ?,
- birth_date = ?,
- position = ?,
- instagram = ?,
- is_former = ?,
- image_url = ?
- WHERE id = ?`,
- [
- name,
- birth_date || null,
- position || null,
- instagram || null,
- is_former === "true" || is_former === true ? 1 : 0,
- imageUrl,
- memberId,
- ]
- );
-
- res.json({ message: "멤버 정보가 수정되었습니다." });
- } catch (error) {
- console.error("멤버 수정 오류:", error);
- res.status(500).json({ error: "멤버 수정 중 오류가 발생했습니다." });
- }
- }
-);
-
-// ==================== 일정 카테고리 관리 API ====================
-
-// 카테고리 목록 조회 (인증 불필요 - 폼에서 사용)
-router.get("/schedule-categories", async (req, res) => {
- try {
- const [categories] = await pool.query(
- "SELECT * FROM schedule_categories ORDER BY sort_order ASC"
- );
- res.json(categories);
- } catch (error) {
- console.error("카테고리 조회 오류:", error);
- res.status(500).json({ error: "카테고리 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 카테고리 생성
-router.post("/schedule-categories", authenticateToken, async (req, res) => {
- try {
- const { name, color } = req.body;
-
- if (!name || !color) {
- return res.status(400).json({ error: "이름과 색상은 필수입니다." });
- }
-
- // 현재 최대 sort_order 조회
- const [maxOrder] = await pool.query(
- "SELECT MAX(sort_order) as maxOrder FROM schedule_categories"
- );
- const nextOrder = (maxOrder[0].maxOrder || 0) + 1;
-
- const [result] = await pool.query(
- "INSERT INTO schedule_categories (name, color, sort_order) VALUES (?, ?, ?)",
- [name, color, nextOrder]
- );
-
- res.json({
- message: "카테고리가 생성되었습니다.",
- id: result.insertId,
- sort_order: nextOrder,
- });
- } catch (error) {
- console.error("카테고리 생성 오류:", error);
- res.status(500).json({ error: "카테고리 생성 중 오류가 발생했습니다." });
- }
-});
-
-// 카테고리 수정
-router.put("/schedule-categories/:id", authenticateToken, async (req, res) => {
- try {
- const { id } = req.params;
- const { name, color, sort_order } = req.body;
-
- const [existing] = await pool.query(
- "SELECT * FROM schedule_categories WHERE id = ?",
- [id]
- );
- if (existing.length === 0) {
- return res.status(404).json({ error: "카테고리를 찾을 수 없습니다." });
- }
-
- await pool.query(
- "UPDATE schedule_categories SET name = ?, color = ?, sort_order = ? WHERE id = ?",
- [
- name || existing[0].name,
- color || existing[0].color,
- sort_order !== undefined ? sort_order : existing[0].sort_order,
- id,
- ]
- );
-
- res.json({ message: "카테고리가 수정되었습니다." });
- } catch (error) {
- console.error("카테고리 수정 오류:", error);
- res.status(500).json({ error: "카테고리 수정 중 오류가 발생했습니다." });
- }
-});
-
-// 카테고리 삭제
-router.delete(
- "/schedule-categories/:id",
- authenticateToken,
- async (req, res) => {
- try {
- const { id } = req.params;
-
- const [existing] = await pool.query(
- "SELECT * FROM schedule_categories WHERE id = ?",
- [id]
- );
- if (existing.length === 0) {
- return res.status(404).json({ error: "카테고리를 찾을 수 없습니다." });
- }
-
- // 기본 카테고리는 삭제 불가
- if (existing[0].is_default === 1) {
- return res
- .status(400)
- .json({ error: "기본 카테고리는 삭제할 수 없습니다." });
- }
-
- // 해당 카테고리를 사용하는 일정이 있는지 확인
- const [usedSchedules] = await pool.query(
- "SELECT COUNT(*) as count FROM schedules WHERE category_id = ?",
- [id]
- );
- if (usedSchedules[0].count > 0) {
- return res.status(400).json({
- error: `해당 카테고리를 사용하는 일정이 ${usedSchedules[0].count}개 있어 삭제할 수 없습니다.`,
- });
- }
-
- await pool.query("DELETE FROM schedule_categories WHERE id = ?", [id]);
-
- res.json({ message: "카테고리가 삭제되었습니다." });
- } catch (error) {
- console.error("카테고리 삭제 오류:", error);
- res.status(500).json({ error: "카테고리 삭제 중 오류가 발생했습니다." });
- }
- }
-);
-
-// 카테고리 순서 일괄 업데이트
-router.put(
- "/schedule-categories-order",
- authenticateToken,
- async (req, res) => {
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const { orders } = req.body; // [{ id: 1, sort_order: 1 }, { id: 2, sort_order: 2 }, ...]
-
- if (!orders || !Array.isArray(orders)) {
- return res.status(400).json({ error: "순서 데이터가 필요합니다." });
- }
-
- for (const item of orders) {
- await connection.query(
- "UPDATE schedule_categories SET sort_order = ? WHERE id = ?",
- [item.sort_order, item.id]
- );
- }
-
- await connection.commit();
- res.json({ message: "순서가 업데이트되었습니다." });
- } catch (error) {
- await connection.rollback();
- console.error("순서 업데이트 오류:", error);
- res.status(500).json({ error: "순서 업데이트 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
- }
-);
-// ==================== 일정 관리 API ====================
-
-// 일정 목록 조회
-router.get("/schedules", async (req, res) => {
- try {
- const { year, month, search } = req.query;
-
- let whereConditions = [];
- let params = [];
-
- // 검색어가 있으면 전체 일정에서 검색 (년/월 필터 무시)
- if (search && search.trim()) {
- const searchTerm = `%${search.trim()}%`;
- whereConditions.push("(s.title LIKE ? OR s.description LIKE ?)");
- params.push(searchTerm, searchTerm);
- } else {
- // 년/월 필터링 (검색이 아닐 때만)
- if (year && month) {
- whereConditions.push("YEAR(s.date) = ? AND MONTH(s.date) = ?");
- params.push(parseInt(year), parseInt(month));
- } else if (year) {
- whereConditions.push("YEAR(s.date) = ?");
- params.push(parseInt(year));
- }
- }
-
- const whereClause =
- whereConditions.length > 0
- ? `WHERE ${whereConditions.join(" AND ")}`
- : "";
-
- const [schedules] = await pool.query(
- `SELECT
- s.id, s.title, s.date, s.time, s.end_date, s.end_time,
- s.category_id, s.description, s.source_url, s.source_name,
- s.location_name, s.location_address, s.location_detail, s.location_lat, s.location_lng,
- s.created_at,
- c.name as category_name, c.color as category_color
- FROM schedules s
- LEFT JOIN schedule_categories c ON s.category_id = c.id
- ${whereClause}
- ORDER BY s.date ASC, s.time ASC`,
- params
- );
-
- // 각 일정의 이미지와 멤버 조회
- const schedulesWithDetails = await Promise.all(
- schedules.map(async (schedule) => {
- const [images] = await pool.query(
- "SELECT id, image_url, sort_order FROM schedule_images WHERE schedule_id = ? ORDER BY sort_order ASC",
- [schedule.id]
- );
- const [members] = await pool.query(
- `SELECT m.id, m.name FROM schedule_members sm
- JOIN members m ON sm.member_id = m.id
- WHERE sm.schedule_id = ?`,
- [schedule.id]
- );
- return { ...schedule, images, members };
- })
- );
-
- // 년/월 필터가 있고 검색이 아닌 경우 생일 데이터 추가
- if (year && month && !search) {
- const [birthdays] = await pool.query(
- `SELECT id, name, name_en, birth_date, image_url
- FROM members
- WHERE is_former = 0 AND MONTH(birth_date) = ?`,
- [parseInt(month)]
- );
-
- const birthdaySchedules = birthdays.map((member) => {
- const birthDate = new Date(member.birth_date);
- const birthdayThisYear = new Date(
- parseInt(year),
- birthDate.getMonth(),
- birthDate.getDate()
- );
-
- return {
- id: `birthday-${member.id}`,
- title: `HAPPY ${member.name_en} DAY`,
- description: null,
- date: birthdayThisYear,
- time: null,
- end_date: null,
- end_time: null,
- category_id: 8,
- source_url: null,
- source_name: null,
- location_name: null,
- location_address: null,
- location_detail: null,
- location_lat: null,
- location_lng: null,
- created_at: null,
- category_name: "생일",
- category_color: "#f472b6",
- images: [],
- members: [{ id: member.id, name: member.name }],
- member_names: member.name,
- is_birthday: true,
- member_image: member.image_url,
- };
- });
-
- // 일정과 생일을 합쳐서 날짜순 정렬
- const allSchedules = [...schedulesWithDetails, ...birthdaySchedules].sort(
- (a, b) => new Date(a.date) - new Date(b.date)
- );
-
- return res.json(allSchedules);
- }
-
- res.json(schedulesWithDetails);
- } catch (error) {
- console.error("일정 조회 오류:", error);
- res.status(500).json({ error: "일정 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 일정 생성
-router.post(
- "/schedules",
- authenticateToken,
- upload.array("images", 20),
- async (req, res) => {
- const connection = await pool.getConnection();
-
- try {
- await connection.beginTransaction();
-
- const data = JSON.parse(req.body.data);
- const {
- title,
- date,
- time,
- endDate,
- endTime,
- isRange,
- category,
- description,
- url,
- sourceName,
- members,
- locationName,
- locationAddress,
- locationDetail,
- locationLat,
- locationLng,
- } = data;
-
- // 필수 필드 검증
- if (!title || !date) {
- return res.status(400).json({ error: "제목과 날짜를 입력해주세요." });
- }
-
- // 일정 삽입
- const [scheduleResult] = await connection.query(
- `INSERT INTO schedules
- (title, date, time, end_date, end_time, category_id, description, source_url, source_name,
- location_name, location_address, location_detail, location_lat, location_lng)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [
- title,
- date,
- time || null,
- isRange && endDate ? endDate : null,
- isRange && endTime ? endTime : null,
- category || null,
- description || null,
- url || null,
- sourceName || null,
- locationName || null,
- locationAddress || null,
- locationDetail || null,
- locationLat || null,
- locationLng || null,
- ]
- );
-
- const scheduleId = scheduleResult.insertId;
-
- // 멤버 연결 처리 (schedule_members 테이블)
- if (members && members.length > 0) {
- const memberValues = members.map((memberId) => [scheduleId, memberId]);
- await connection.query(
- `INSERT INTO schedule_members (schedule_id, member_id) VALUES ?`,
- [memberValues]
- );
- }
-
- // 이미지 업로드 처리
- if (req.files && req.files.length > 0) {
- const publicUrl =
- process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT;
- const basePath = `schedule/${scheduleId}`;
-
- for (let i = 0; i < req.files.length; i++) {
- const file = req.files[i];
- const orderNum = String(i + 1).padStart(2, "0");
- const filename = `${orderNum}.webp`;
-
- // WebP 변환 (원본만)
- const imageBuffer = await sharp(file.buffer)
- .webp({ quality: 90 })
- .toBuffer();
-
- // RustFS 업로드 (원본만)
- await s3Client.send(
- new PutObjectCommand({
- Bucket: BUCKET,
- Key: `${basePath}/${filename}`,
- Body: imageBuffer,
- ContentType: "image/webp",
- })
- );
-
- const imageUrl = `${publicUrl}/${BUCKET}/${basePath}/${filename}`;
-
- // DB 저장
- await connection.query(
- `INSERT INTO schedule_images (schedule_id, image_url, sort_order)
- VALUES (?, ?, ?)`,
- [scheduleId, imageUrl, i + 1]
- );
- }
- }
-
- await connection.commit();
-
- // Meilisearch에 동기화
- try {
- const [categoryInfo] = await pool.query(
- "SELECT name, color FROM schedule_categories WHERE id = ?",
- [category || null]
- );
- const [memberInfo] = await pool.query(
- "SELECT id, name FROM members WHERE id IN (?)",
- [members?.length ? members : [0]]
- );
- await addOrUpdateSchedule({
- id: scheduleId,
- title,
- description,
- date,
- time,
- category_id: category,
- category_name: categoryInfo[0]?.name || "",
- category_color: categoryInfo[0]?.color || "",
- source_name: sourceName,
- source_url: url,
- members: memberInfo,
- });
- } catch (searchError) {
- console.error("Meilisearch 동기화 오류:", searchError.message);
- }
-
- res.json({ message: "일정이 생성되었습니다.", scheduleId });
- } catch (error) {
- await connection.rollback();
- console.error("일정 생성 오류:", error);
- res.status(500).json({ error: "일정 생성 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
- }
-);
-
-// 카카오 장소 검색 프록시 (CORS 우회)
-router.get("/kakao/places", authenticateToken, async (req, res) => {
- try {
- const { query } = req.query;
-
- if (!query) {
- return res.status(400).json({ error: "검색어를 입력해주세요." });
- }
-
- const response = await fetch(
- `https://dapi.kakao.com/v2/local/search/keyword.json?query=${encodeURIComponent(
- query
- )}`,
- {
- headers: {
- Authorization: `KakaoAK ${process.env.KAKAO_REST_KEY}`,
- },
- }
- );
-
- if (!response.ok) {
- throw new Error(`Kakao API error: ${response.status}`);
- }
-
- const data = await response.json();
- res.json(data);
- } catch (error) {
- console.error("카카오 장소 검색 오류:", error);
- res.status(500).json({ error: "장소 검색 중 오류가 발생했습니다." });
- }
-});
-
-// 일정 단일 조회
-router.get("/schedules/:id", authenticateToken, async (req, res) => {
- try {
- const { id } = req.params;
-
- // 일정 기본 정보 조회
- const [schedules] = await pool.query(
- `SELECT s.*, sc.name as category_name, sc.color as category_color
- FROM schedules s
- LEFT JOIN schedule_categories sc ON s.category_id = sc.id
- WHERE s.id = ?`,
- [id]
- );
-
- if (schedules.length === 0) {
- return res.status(404).json({ error: "일정을 찾을 수 없습니다." });
- }
-
- const schedule = schedules[0];
-
- // 이미지 조회
- const [images] = await pool.query(
- "SELECT id, image_url, sort_order FROM schedule_images WHERE schedule_id = ? ORDER BY sort_order ASC",
- [id]
- );
-
- // 멤버 조회
- const [members] = await pool.query(
- `SELECT m.id, m.name, m.image_url
- FROM schedule_members sm
- JOIN members m ON sm.member_id = m.id
- WHERE sm.schedule_id = ?`,
- [id]
- );
-
- res.json({ ...schedule, images, members });
- } catch (error) {
- console.error("일정 조회 오류:", error);
- res.status(500).json({ error: "일정 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 일정 수정
-router.put(
- "/schedules/:id",
- authenticateToken,
- upload.array("images", 20),
- async (req, res) => {
- const connection = await pool.getConnection();
- try {
- await connection.beginTransaction();
-
- const { id } = req.params;
- const data = JSON.parse(req.body.data || "{}");
- const {
- title,
- date,
- time,
- endDate,
- endTime,
- isRange,
- category,
- description,
- url,
- sourceName,
- members,
- locationName,
- locationAddress,
- locationDetail,
- locationLat,
- locationLng,
- existingImages, // 유지할 기존 이미지 ID 배열
- } = data;
-
- // 필수 필드 검증
- if (!title || !date) {
- return res.status(400).json({ error: "제목과 날짜를 입력해주세요." });
- }
-
- // 일정 업데이트
- await connection.query(
- `UPDATE schedules SET
- title = ?,
- date = ?,
- time = ?,
- end_date = ?,
- end_time = ?,
- category_id = ?,
- description = ?,
- source_url = ?,
- source_name = ?,
- location_name = ?,
- location_address = ?,
- location_detail = ?,
- location_lat = ?,
- location_lng = ?
- WHERE id = ?`,
- [
- title,
- date,
- time || null,
- isRange && endDate ? endDate : null,
- isRange && endTime ? endTime : null,
- category || null,
- description || null,
- url || null,
- sourceName || null,
- locationName || null,
- locationAddress || null,
- locationDetail || null,
- locationLat || null,
- locationLng || null,
- id,
- ]
- );
-
- // 멤버 업데이트 (기존 삭제 후 새로 추가)
- await connection.query(
- "DELETE FROM schedule_members WHERE schedule_id = ?",
- [id]
- );
- if (members && members.length > 0) {
- const memberValues = members.map((memberId) => [id, memberId]);
- await connection.query(
- `INSERT INTO schedule_members (schedule_id, member_id) VALUES ?`,
- [memberValues]
- );
- }
-
- // 삭제할 이미지 처리 (existingImages에 없는 이미지 삭제)
- const existingImageIds = existingImages || [];
- if (existingImageIds.length > 0) {
- // 삭제할 이미지 조회
- const [imagesToDelete] = await connection.query(
- `SELECT id, image_url FROM schedule_images WHERE schedule_id = ? AND id NOT IN (?)`,
- [id, existingImageIds]
- );
-
- // S3에서 이미지 삭제
- for (const img of imagesToDelete) {
- try {
- const key = img.image_url.replace(
- `${
- process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT
- }/${process.env.RUSTFS_BUCKET}/`,
- ""
- );
- await s3Client.send(
- new DeleteObjectCommand({
- Bucket: process.env.RUSTFS_BUCKET,
- Key: key,
- })
- );
- } catch (err) {
- console.error("이미지 삭제 오류:", err);
- }
- }
-
- // DB에서 삭제
- await connection.query(
- `DELETE FROM schedule_images WHERE schedule_id = ? AND id NOT IN (?)`,
- [id, existingImageIds]
- );
- } else {
- // 기존 이미지 모두 삭제
- const [allImages] = await connection.query(
- `SELECT id, image_url FROM schedule_images WHERE schedule_id = ?`,
- [id]
- );
-
- for (const img of allImages) {
- try {
- const key = img.image_url.replace(
- `${
- process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT
- }/${process.env.RUSTFS_BUCKET}/`,
- ""
- );
- await s3Client.send(
- new DeleteObjectCommand({
- Bucket: process.env.RUSTFS_BUCKET,
- Key: key,
- })
- );
- } catch (err) {
- console.error("이미지 삭제 오류:", err);
- }
- }
-
- await connection.query(
- `DELETE FROM schedule_images WHERE schedule_id = ?`,
- [id]
- );
- }
-
- // 새 이미지 업로드
- if (req.files && req.files.length > 0) {
- const publicUrl =
- process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT;
- const basePath = `schedule/${id}`;
-
- // 현재 최대 sort_order 조회
- const [maxOrder] = await connection.query(
- "SELECT COALESCE(MAX(sort_order), 0) as max_order FROM schedule_images WHERE schedule_id = ?",
- [id]
- );
- let currentOrder = maxOrder[0].max_order;
-
- for (let i = 0; i < req.files.length; i++) {
- const file = req.files[i];
- currentOrder++;
- const orderNum = String(currentOrder).padStart(2, "0");
- // 파일명: 01.webp, 02.webp 형식 (Date.now() 제거)
- const filename = `${orderNum}.webp`;
-
- const imageBuffer = await sharp(file.buffer)
- .webp({ quality: 90 })
- .toBuffer();
-
- await s3Client.send(
- new PutObjectCommand({
- Bucket: process.env.RUSTFS_BUCKET,
- Key: `${basePath}/${filename}`,
- Body: imageBuffer,
- ContentType: "image/webp",
- })
- );
-
- const imageUrl = `${publicUrl}/${process.env.RUSTFS_BUCKET}/${basePath}/${filename}`;
-
- await connection.query(
- "INSERT INTO schedule_images (schedule_id, image_url, sort_order) VALUES (?, ?, ?)",
- [id, imageUrl, currentOrder]
- );
- }
- }
-
- // sort_order 재정렬 (삭제로 인한 간격 제거)
- const [remainingImages] = await connection.query(
- "SELECT id FROM schedule_images WHERE schedule_id = ? ORDER BY sort_order ASC",
- [id]
- );
- for (let i = 0; i < remainingImages.length; i++) {
- await connection.query(
- "UPDATE schedule_images SET sort_order = ? WHERE id = ?",
- [i + 1, remainingImages[i].id]
- );
- }
-
- await connection.commit();
-
- // Meilisearch 동기화
- try {
- const [categoryInfo] = await pool.query(
- "SELECT name, color FROM schedule_categories WHERE id = ?",
- [category || null]
- );
- const [memberInfo] = await pool.query(
- "SELECT id, name FROM members WHERE id IN (?)",
- [members?.length ? members : [0]]
- );
- await addOrUpdateSchedule({
- id: parseInt(id),
- title,
- description,
- date,
- time,
- category_id: category,
- category_name: categoryInfo[0]?.name || "",
- category_color: categoryInfo[0]?.color || "",
- source_name: sourceName,
- source_url: url,
- members: memberInfo,
- });
- } catch (searchError) {
- console.error("Meilisearch 동기화 오류:", searchError.message);
- }
-
- res.json({ message: "일정이 수정되었습니다." });
- } catch (error) {
- await connection.rollback();
- console.error("일정 수정 오류:", error);
- res.status(500).json({ error: "일정 수정 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
- }
-);
-
-// 일정 삭제
-router.delete("/schedules/:id", authenticateToken, async (req, res) => {
- const connection = await pool.getConnection();
- try {
- await connection.beginTransaction();
-
- const { id } = req.params;
-
- // 이미지 조회
- const [images] = await connection.query(
- "SELECT image_url FROM schedule_images WHERE schedule_id = ?",
- [id]
- );
-
- // S3에서 이미지 삭제
- for (const img of images) {
- try {
- const key = img.image_url.replace(
- `${process.env.RUSTFS_PUBLIC_URL || process.env.RUSTFS_ENDPOINT}/${
- process.env.RUSTFS_BUCKET
- }/`,
- ""
- );
- await s3Client.send(
- new DeleteObjectCommand({
- Bucket: process.env.RUSTFS_BUCKET,
- Key: key,
- })
- );
- } catch (err) {
- console.error("이미지 삭제 오류:", err);
- }
- }
-
- // 일정 삭제 (CASCADE로 schedule_images, schedule_members도 자동 삭제)
- await connection.query("DELETE FROM schedules WHERE id = ?", [id]);
-
- await connection.commit();
-
- // Meilisearch에서도 삭제
- try {
- await deleteScheduleFromSearch(id);
- } catch (searchError) {
- console.error("Meilisearch 삭제 오류:", searchError.message);
- }
-
- res.json({ message: "일정이 삭제되었습니다." });
- } catch (error) {
- await connection.rollback();
- console.error("일정 삭제 오류:", error);
- res.status(500).json({ error: "일정 삭제 중 오류가 발생했습니다." });
- } finally {
- connection.release();
- }
-});
-
-// =====================================================
-// YouTube 봇 API
-// =====================================================
-
-// 봇 목록 조회
-router.get("/bots", authenticateToken, async (req, res) => {
- try {
- const [bots] = await pool.query(`
- SELECT b.*,
- yc.channel_id, yc.channel_name,
- xc.username, xc.nitter_url
- FROM bots b
- LEFT JOIN bot_youtube_config yc ON b.id = yc.bot_id
- LEFT JOIN bot_x_config xc ON b.id = xc.bot_id
- ORDER BY b.id ASC
- `);
- res.json(bots);
- } catch (error) {
- console.error("봇 목록 조회 오류:", error);
- res.status(500).json({ error: "봇 목록 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 봇 시작
-router.post("/bots/:id/start", authenticateToken, async (req, res) => {
- try {
- const { id } = req.params;
- await startBot(id);
- res.json({ message: "봇이 시작되었습니다." });
- } catch (error) {
- console.error("봇 시작 오류:", error);
- res
- .status(500)
- .json({ error: error.message || "봇 시작 중 오류가 발생했습니다." });
- }
-});
-
-// 봇 정지
-router.post("/bots/:id/stop", authenticateToken, async (req, res) => {
- try {
- const { id } = req.params;
- await stopBot(id);
- res.json({ message: "봇이 정지되었습니다." });
- } catch (error) {
- console.error("봇 정지 오류:", error);
- res
- .status(500)
- .json({ error: error.message || "봇 정지 중 오류가 발생했습니다." });
- }
-});
-
-// 전체 동기화 (초기화)
-router.post("/bots/:id/sync-all", authenticateToken, async (req, res) => {
- try {
- const { id } = req.params;
-
- // 봇 타입 조회
- const [bots] = await pool.query("SELECT type FROM bots WHERE id = ?", [id]);
- if (bots.length === 0) {
- return res.status(404).json({ error: "봇을 찾을 수 없습니다." });
- }
-
- const botType = bots[0].type;
- let result;
-
- if (botType === "youtube") {
- result = await syncAllVideos(id);
- } else if (botType === "x") {
- result = await syncAllTweets(id);
- } else if (botType === "meilisearch") {
- result = await syncAllSchedules(id);
- } else {
- return res
- .status(400)
- .json({ error: `지원하지 않는 봇 타입: ${botType}` });
- }
-
- res.json({
- message: `${result.addedCount}개 일정이 추가되었습니다.`,
- addedCount: result.addedCount,
- total: result.total,
- });
- } catch (error) {
- console.error("전체 동기화 오류:", error);
- res
- .status(500)
- .json({ error: error.message || "전체 동기화 중 오류가 발생했습니다." });
- }
-});
-
-// =====================================================
-// YouTube API 할당량 경고 Webhook
-// =====================================================
-
-// 메모리에 경고 상태 저장 (서버 재시작 시 초기화)
-let quotaWarning = {
- active: false,
- timestamp: null,
- message: null,
- stoppedBots: [], // 할당량 초과로 중지된 봇 ID 목록
-};
-
-// 자정 재시작 타이머
-let quotaResetTimer = null;
-
-/**
- * 자정(LA 시간)에 봇 재시작 예약
- * YouTube 할당량은 LA 태평양 시간 자정에 리셋됨
- */
-async function scheduleQuotaReset() {
- // 기존 타이머 취소
- if (quotaResetTimer) {
- clearTimeout(quotaResetTimer);
- }
-
- // LA 시간으로 다음 자정 계산
- const now = new Date();
- const laTime = new Date(
- now.toLocaleString("en-US", { timeZone: "America/Los_Angeles" })
- );
- const tomorrow = new Date(laTime);
- tomorrow.setDate(tomorrow.getDate() + 1);
- tomorrow.setHours(0, 1, 0, 0); // 자정 1분 후 (안전 마진)
-
- // 현재 LA 시간과 다음 자정까지의 밀리초 계산
- const nowLA = new Date(
- now.toLocaleString("en-US", { timeZone: "America/Los_Angeles" })
- );
- const msUntilReset = tomorrow.getTime() - nowLA.getTime();
-
- console.log(
- `[Quota Reset] ${Math.round(
- msUntilReset / 1000 / 60
- )}분 후 봇 재시작 예약됨`
- );
-
- quotaResetTimer = setTimeout(async () => {
- console.log("[Quota Reset] 할당량 리셋 시간 도달, 봇 재시작 중...");
-
- try {
- // 할당량 초과로 중지된 봇들만 재시작
- for (const botId of quotaWarning.stoppedBots) {
- await startBot(botId);
- console.log(`[Quota Reset] Bot ${botId} 재시작됨`);
- }
-
- // 경고 상태 초기화
- quotaWarning = {
- active: false,
- timestamp: null,
- message: null,
- stoppedBots: [],
- };
-
- console.log("[Quota Reset] 모든 봇 재시작 완료, 경고 상태 초기화");
- } catch (error) {
- console.error("[Quota Reset] 봇 재시작 오류:", error.message);
- }
- }, msUntilReset);
-}
-
-// Webhook 인증 정보
-const WEBHOOK_USERNAME = "fromis9_quota_webhook";
-const WEBHOOK_PASSWORD = "Qw8$kLm3nP2xVr7tYz!9";
-
-// Basic Auth 검증 미들웨어
-const verifyWebhookAuth = (req, res, next) => {
- const authHeader = req.headers.authorization;
-
- if (!authHeader || !authHeader.startsWith("Basic ")) {
- return res.status(401).json({ error: "인증이 필요합니다." });
- }
-
- const base64Credentials = authHeader.split(" ")[1];
- const credentials = Buffer.from(base64Credentials, "base64").toString(
- "utf-8"
- );
- const [username, password] = credentials.split(":");
-
- if (username !== WEBHOOK_USERNAME || password !== WEBHOOK_PASSWORD) {
- return res.status(401).json({ error: "인증 실패" });
- }
-
- next();
-};
-
-// Google Cloud 할당량 경고 Webhook 수신
-router.post("/quota-alert", verifyWebhookAuth, async (req, res) => {
- console.log("[Quota Alert] Google Cloud에서 할당량 경고 수신:", req.body);
-
- quotaWarning = {
- active: true,
- timestamp: new Date().toISOString(),
- message:
- "일일 할당량의 95%를 사용했습니다. (9,500 / 10,000 units) - 봇이 자동 중지되었습니다.",
- };
-
- // 모든 실행 중인 봇 중지
- try {
- const [runningBots] = await pool.query(
- "SELECT id, name FROM bots WHERE status = 'running'"
- );
-
- // 중지된 봇 ID 저장 (자정에 재시작용)
- quotaWarning.stoppedBots = runningBots.map((bot) => bot.id);
-
- for (const bot of runningBots) {
- await stopBot(bot.id);
- console.log(`[Quota Alert] Bot ${bot.name} 중지됨`);
- }
- console.log(
- `[Quota Alert] ${runningBots.length}개 봇이 할당량 초과로 중지됨`
- );
-
- // 자정에 봇 재시작 예약 (LA 시간 기준 = YouTube 할당량 리셋 시간)
- scheduleQuotaReset();
- } catch (error) {
- console.error("[Quota Alert] 봇 중지 오류:", error.message);
- }
-
- res
- .status(200)
- .json({ success: true, message: "경고가 등록되고 봇이 중지되었습니다." });
-});
-
-// 할당량 경고 상태 조회 (프론트엔드용)
-router.get("/quota-warning", authenticateToken, (req, res) => {
- res.json(quotaWarning);
-});
-
-// 할당량 경고 해제 (수동)
-router.delete("/quota-warning", authenticateToken, (req, res) => {
- quotaWarning = {
- active: false,
- timestamp: null,
- message: null,
- };
- res.json({ success: true, message: "경고가 해제되었습니다." });
-});
-
-export default router;
diff --git a/backend/routes/albums.js b/backend/routes/albums.js
deleted file mode 100644
index 4eed765..0000000
--- a/backend/routes/albums.js
+++ /dev/null
@@ -1,180 +0,0 @@
-import express from "express";
-import pool from "../lib/db.js";
-
-const router = express.Router();
-
-// 앨범 상세 정보 조회 헬퍼 함수 (트랙, 티저, 컨셉포토 포함)
-async function getAlbumDetails(album) {
- // 트랙 정보 조회 (가사 포함)
- const [tracks] = await pool.query(
- "SELECT * FROM tracks WHERE album_id = ? ORDER BY track_number",
- [album.id]
- );
- album.tracks = tracks;
-
- // 티저 이미지/비디오 조회 (3개 해상도 URL + video_url + media_type 포함)
- const [teasers] = await pool.query(
- "SELECT original_url, medium_url, thumb_url, video_url, media_type FROM album_teasers WHERE album_id = ? ORDER BY sort_order",
- [album.id]
- );
- album.teasers = teasers;
-
- // 컨셉 포토 조회 (멤버 정보 + 3개 해상도 URL + 크기 정보 포함)
- const [photos] = await pool.query(
- `SELECT
- p.id, p.original_url, p.medium_url, p.thumb_url, p.photo_type, p.concept_name, p.sort_order,
- p.width, p.height,
- GROUP_CONCAT(m.name ORDER BY m.id SEPARATOR ', ') as members
- FROM album_photos p
- LEFT JOIN album_photo_members pm ON p.id = pm.photo_id
- LEFT JOIN members m ON pm.member_id = m.id
- WHERE p.album_id = ?
- GROUP BY p.id
- ORDER BY p.sort_order`,
- [album.id]
- );
-
- // 컨셉별로 그룹화
- const conceptPhotos = {};
- for (const photo of photos) {
- const concept = photo.concept_name || "Default";
- if (!conceptPhotos[concept]) {
- conceptPhotos[concept] = [];
- }
- conceptPhotos[concept].push({
- id: photo.id,
- original_url: photo.original_url,
- medium_url: photo.medium_url,
- thumb_url: photo.thumb_url,
- width: photo.width,
- height: photo.height,
- type: photo.photo_type,
- members: photo.members,
- sortOrder: photo.sort_order,
- });
- }
- album.conceptPhotos = conceptPhotos;
-
- return album;
-}
-
-// 전체 앨범 조회 (트랙 포함)
-router.get("/", async (req, res) => {
- try {
- const [albums] = await pool.query(
- "SELECT id, title, folder_name, album_type, album_type_short, release_date, cover_original_url, cover_medium_url, cover_thumb_url FROM albums ORDER BY release_date DESC"
- );
-
- // 각 앨범에 트랙 정보 추가
- for (const album of albums) {
- const [tracks] = await pool.query(
- "SELECT id, track_number, title, is_title_track, duration, lyricist, composer, arranger FROM tracks WHERE album_id = ? ORDER BY track_number",
- [album.id]
- );
- album.tracks = tracks;
- }
-
- res.json(albums);
- } catch (error) {
- console.error("앨범 조회 오류:", error);
- res.status(500).json({ error: "앨범 정보를 가져오는데 실패했습니다." });
- }
-});
-
-// 앨범명과 트랙명으로 트랙 상세 조회 (더 구체적인 경로이므로 /by-name/:name보다 앞에 배치)
-router.get("/by-name/:albumName/track/:trackTitle", async (req, res) => {
- try {
- const albumName = decodeURIComponent(req.params.albumName);
- const trackTitle = decodeURIComponent(req.params.trackTitle);
-
- // 앨범 조회
- const [albums] = await pool.query(
- "SELECT * FROM albums WHERE folder_name = ? OR title = ?",
- [albumName, albumName]
- );
-
- if (albums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- const album = albums[0];
-
- // 해당 앨범의 트랙 조회
- const [tracks] = await pool.query(
- "SELECT * FROM tracks WHERE album_id = ? AND title = ?",
- [album.id, trackTitle]
- );
-
- if (tracks.length === 0) {
- return res.status(404).json({ error: "트랙을 찾을 수 없습니다." });
- }
-
- const track = tracks[0];
-
- // 앨범의 다른 트랙 목록 조회
- const [otherTracks] = await pool.query(
- "SELECT id, track_number, title, is_title_track, duration FROM tracks WHERE album_id = ? ORDER BY track_number",
- [album.id]
- );
-
- res.json({
- ...track,
- album: {
- id: album.id,
- title: album.title,
- folder_name: album.folder_name,
- cover_thumb_url: album.cover_thumb_url,
- cover_medium_url: album.cover_medium_url,
- release_date: album.release_date,
- album_type: album.album_type,
- },
- otherTracks,
- });
- } catch (error) {
- console.error("트랙 조회 오류:", error);
- res.status(500).json({ error: "트랙 정보를 가져오는데 실패했습니다." });
- }
-});
-
-// 앨범 folder_name 또는 title로 조회
-router.get("/by-name/:name", async (req, res) => {
- try {
- const name = decodeURIComponent(req.params.name);
- // folder_name 또는 title로 검색 (PC는 title, 모바일은 folder_name 사용)
- const [albums] = await pool.query(
- "SELECT * FROM albums WHERE folder_name = ? OR title = ?",
- [name, name]
- );
-
- if (albums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- const album = await getAlbumDetails(albums[0]);
- res.json(album);
- } catch (error) {
- console.error("앨범 조회 오류:", error);
- res.status(500).json({ error: "앨범 정보를 가져오는데 실패했습니다." });
- }
-});
-
-// ID로 앨범 조회
-router.get("/:id", async (req, res) => {
- try {
- const [albums] = await pool.query("SELECT * FROM albums WHERE id = ?", [
- req.params.id,
- ]);
-
- if (albums.length === 0) {
- return res.status(404).json({ error: "앨범을 찾을 수 없습니다." });
- }
-
- const album = await getAlbumDetails(albums[0]);
- res.json(album);
- } catch (error) {
- console.error("앨범 조회 오류:", error);
- res.status(500).json({ error: "앨범 정보를 가져오는데 실패했습니다." });
- }
-});
-
-export default router;
diff --git a/backend/routes/members.js b/backend/routes/members.js
deleted file mode 100644
index b5e7f3c..0000000
--- a/backend/routes/members.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import express from "express";
-import pool from "../lib/db.js";
-
-const router = express.Router();
-
-// 전체 멤버 조회
-router.get("/", async (req, res) => {
- try {
- const [rows] = await pool.query(
- "SELECT id, name, birth_date, position, image_url, instagram, is_former FROM members ORDER BY is_former, birth_date"
- );
- res.json(rows);
- } catch (error) {
- console.error("멤버 조회 오류:", error);
- res.status(500).json({ error: "멤버 정보를 가져오는데 실패했습니다." });
- }
-});
-
-// 특정 멤버 조회
-router.get("/:id", async (req, res) => {
- try {
- const [rows] = await pool.query("SELECT * FROM members WHERE id = ?", [
- req.params.id,
- ]);
- if (rows.length === 0) {
- return res.status(404).json({ error: "멤버를 찾을 수 없습니다." });
- }
- res.json(rows[0]);
- } catch (error) {
- console.error("멤버 조회 오류:", error);
- res.status(500).json({ error: "멤버 정보를 가져오는데 실패했습니다." });
- }
-});
-
-export default router;
diff --git a/backend/routes/schedules.js b/backend/routes/schedules.js
deleted file mode 100644
index c33e713..0000000
--- a/backend/routes/schedules.js
+++ /dev/null
@@ -1,292 +0,0 @@
-import express from "express";
-import pool from "../lib/db.js";
-import { searchSchedules } from "../services/meilisearch.js";
-import { saveSearchQuery, getSuggestions } from "../services/suggestions.js";
-import { getXProfile } from "../services/x-bot.js";
-
-const router = express.Router();
-
-// 검색어 추천 API (Bi-gram 기반)
-router.get("/suggestions", async (req, res) => {
- try {
- const { q, limit } = req.query;
-
- if (!q || q.trim().length === 0) {
- return res.json({ suggestions: [] });
- }
-
- const suggestions = await getSuggestions(q, parseInt(limit) || 10);
- res.json({ suggestions });
- } catch (error) {
- console.error("추천 검색어 오류:", error);
- res.status(500).json({ error: "추천 검색어 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 공개 일정 목록 조회 (검색 포함)
-router.get("/", async (req, res) => {
- try {
- const { search, startDate, endDate, limit, year, month } = req.query;
-
- // 검색어가 있으면 Meilisearch 사용
- if (search && search.trim()) {
- const offset = parseInt(req.query.offset) || 0;
- const pageLimit = parseInt(req.query.limit) || 100;
-
- // 첫 페이지 검색 시에만 검색어 저장 (bi-gram 학습)
- if (offset === 0) {
- saveSearchQuery(search.trim()).catch((err) =>
- console.error("검색어 저장 실패:", err.message)
- );
- }
-
- // Meilisearch에서 큰 limit으로 검색 (유사도 필터링 후 클라이언트 페이징)
- const results = await searchSchedules(search.trim(), {
- limit: 1000, // 내부적으로 1000개까지 검색
- });
-
- // 페이징 적용
- const paginatedHits = results.hits.slice(offset, offset + pageLimit);
-
- return res.json({
- schedules: paginatedHits,
- total: results.total,
- offset: offset,
- limit: pageLimit,
- hasMore: offset + paginatedHits.length < results.total,
- });
- }
-
- // 날짜 필터 및 제한 조건 구성
- let whereClause = "WHERE 1=1";
- const params = [];
-
- // 년/월 필터링 (월별 데이터 로딩용)
- if (year && month) {
- whereClause += " AND YEAR(s.date) = ? AND MONTH(s.date) = ?";
- params.push(parseInt(year), parseInt(month));
- } else if (year) {
- whereClause += " AND YEAR(s.date) = ?";
- params.push(parseInt(year));
- }
-
- if (startDate) {
- whereClause += " AND s.date >= ?";
- params.push(startDate);
- }
- if (endDate) {
- whereClause += " AND s.date <= ?";
- params.push(endDate);
- }
-
- // limit 파라미터 처리
- const limitClause = limit ? `LIMIT ${parseInt(limit)}` : "";
-
- // 검색어 없으면 DB에서 전체 조회
- const [schedules] = await pool.query(
- `
- SELECT
- s.id,
- s.title,
- s.description,
- s.date,
- s.time,
- s.category_id,
- s.source_url,
- s.source_name,
- s.location_name,
- c.name as category_name,
- c.color as category_color,
- GROUP_CONCAT(m.name ORDER BY m.id SEPARATOR ',') as member_names
- FROM schedules s
- LEFT JOIN schedule_categories c ON s.category_id = c.id
- LEFT JOIN schedule_members sm ON s.id = sm.schedule_id
- LEFT JOIN members m ON sm.member_id = m.id
- ${whereClause}
- GROUP BY s.id
- ORDER BY s.date ASC, s.time ASC
- ${limitClause}
- `,
- params
- );
-
- // 년/월 필터가 있으면 해당 월의 현재 멤버 생일을 가상 일정으로 추가
- if (year && month) {
- const [birthdays] = await pool.query(
- `SELECT id, name, name_en, birth_date, image_url
- FROM members
- WHERE is_former = 0 AND MONTH(birth_date) = ?`,
- [parseInt(month)]
- );
-
- const birthdaySchedules = birthdays.map((member) => {
- const birthDate = new Date(member.birth_date);
- const birthdayThisYear = new Date(
- parseInt(year),
- birthDate.getMonth(),
- birthDate.getDate()
- );
-
- return {
- id: `birthday-${member.id}`,
- title: `HAPPY ${member.name_en} DAY`,
- description: null,
- date: birthdayThisYear,
- time: null,
- category_id: 8,
- source_url: null,
- source_name: null,
- location_name: null,
- category_name: "생일",
- category_color: "#f472b6",
- member_names: member.name,
- is_birthday: true,
- member_image: member.image_url,
- };
- });
-
- // 일정과 생일을 합쳐서 날짜순 정렬
- const allSchedules = [...schedules, ...birthdaySchedules].sort(
- (a, b) => new Date(a.date) - new Date(b.date)
- );
-
- return res.json(allSchedules);
- }
-
- res.json(schedules);
- } catch (error) {
- console.error("일정 목록 조회 오류:", error);
- res.status(500).json({ error: "일정 목록 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 카테고리 목록 조회
-router.get("/categories", async (req, res) => {
- try {
- const [categories] = await pool.query(`
- SELECT id, name, color, sort_order
- FROM schedule_categories
- ORDER BY sort_order ASC
- `);
-
- res.json(categories);
- } catch (error) {
- console.error("카테고리 조회 오류:", error);
- res.status(500).json({ error: "카테고리 조회 중 오류가 발생했습니다." });
- }
-});
-
-// 개별 일정 조회
-router.get("/:id", async (req, res) => {
- try {
- const { id } = req.params;
-
- const [schedules] = await pool.query(
- `
- SELECT
- s.*,
- c.name as category_name,
- c.color as category_color
- FROM schedules s
- LEFT JOIN schedule_categories c ON s.category_id = c.id
- WHERE s.id = ?
- `,
- [id]
- );
-
- if (schedules.length === 0) {
- return res.status(404).json({ error: "일정을 찾을 수 없습니다." });
- }
-
- const schedule = schedules[0];
-
- // 이미지 조회
- const [images] = await pool.query(
- `SELECT image_url FROM schedule_images WHERE schedule_id = ? ORDER BY sort_order ASC`,
- [id]
- );
- schedule.images = images.map((img) => img.image_url);
-
- // 멤버 조회
- const [members] = await pool.query(
- `SELECT m.id, m.name FROM members m
- JOIN schedule_members sm ON m.id = sm.member_id
- WHERE sm.schedule_id = ?
- ORDER BY m.id`,
- [id]
- );
- schedule.members = members;
-
- // 콘서트 카테고리(id=6)인 경우 같은 제목의 관련 일정들도 조회
- if (schedule.category_id === 6) {
- const [relatedSchedules] = await pool.query(
- `
- SELECT id, date, time
- FROM schedules
- WHERE title = ? AND category_id = 6
- ORDER BY date ASC, time ASC
- `,
- [schedule.title]
- );
- schedule.related_dates = relatedSchedules;
- }
-
- res.json(schedule);
- } catch (error) {
- console.error("일정 조회 오류:", error);
- res.status(500).json({ error: "일정 조회 중 오류가 발생했습니다." });
- }
-});
-
-// Meilisearch 동기화 API
-router.post("/sync-search", async (req, res) => {
- try {
- const { syncAllSchedules } = await import("../services/meilisearch.js");
-
- // DB에서 모든 일정 조회
- const [schedules] = await pool.query(`
- SELECT
- s.id,
- s.title,
- s.description,
- s.date,
- s.time,
- s.category_id,
- s.source_url,
- s.source_name,
- c.name as category_name,
- c.color as category_color,
- GROUP_CONCAT(m.name ORDER BY m.id SEPARATOR ',') as member_names
- FROM schedules s
- LEFT JOIN schedule_categories c ON s.category_id = c.id
- LEFT JOIN schedule_members sm ON s.id = sm.schedule_id
- LEFT JOIN members m ON sm.member_id = m.id
- GROUP BY s.id
- `);
-
- const count = await syncAllSchedules(schedules);
- res.json({ success: true, synced: count });
- } catch (error) {
- console.error("Meilisearch 동기화 오류:", error);
- res.status(500).json({ error: "동기화 중 오류가 발생했습니다." });
- }
-});
-
-// X 프로필 정보 조회
-router.get("/x-profile/:username", async (req, res) => {
- try {
- const { username } = req.params;
- const profile = await getXProfile(username);
-
- if (!profile) {
- return res.status(404).json({ error: "프로필을 찾을 수 없습니다." });
- }
-
- res.json(profile);
- } catch (error) {
- console.error("X 프로필 조회 오류:", error);
- res.status(500).json({ error: "프로필 조회 중 오류가 발생했습니다." });
- }
-});
-
-export default router;
diff --git a/backend/routes/stats.js b/backend/routes/stats.js
deleted file mode 100644
index 30af305..0000000
--- a/backend/routes/stats.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import express from "express";
-import pool from "../lib/db.js";
-
-const router = express.Router();
-
-// 통계 조회 (멤버 수, 앨범 수)
-router.get("/", async (req, res) => {
- try {
- const [memberCount] = await pool.query(
- "SELECT COUNT(*) as count FROM members"
- );
- const [albumCount] = await pool.query(
- "SELECT COUNT(*) as count FROM albums"
- );
-
- res.json({
- memberCount: memberCount[0].count,
- albumCount: albumCount[0].count,
- debutYear: 2018,
- fandomName: "flover",
- });
- } catch (error) {
- console.error("통계 조회 오류:", error);
- res.status(500).json({ error: "통계 정보를 가져오는데 실패했습니다." });
- }
-});
-
-export default router;
diff --git a/backend/server.js b/backend/server.js
deleted file mode 100644
index b994ba7..0000000
--- a/backend/server.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import express from "express";
-import path from "path";
-import { fileURLToPath } from "url";
-import membersRouter from "./routes/members.js";
-import albumsRouter from "./routes/albums.js";
-import statsRouter from "./routes/stats.js";
-import adminRouter from "./routes/admin.js";
-import schedulesRouter from "./routes/schedules.js";
-import { initScheduler } from "./services/youtube-scheduler.js";
-import { initMeilisearch } from "./services/meilisearch.js";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const app = express();
-const PORT = process.env.PORT || 80;
-
-// JSON 파싱
-app.use(express.json());
-
-// 정적 파일 서빙 (프론트엔드 빌드 결과물)
-app.use(express.static(path.join(__dirname, "dist")));
-
-// API 라우트
-app.get("/api/health", (req, res) => {
- res.json({ status: "ok", timestamp: new Date().toISOString() });
-});
-
-app.use("/api/members", membersRouter);
-app.use("/api/albums", albumsRouter);
-app.use("/api/stats", statsRouter);
-app.use("/api/admin", adminRouter);
-app.use("/api/schedules", schedulesRouter);
-app.use("/api/schedule-categories", (req, res, next) => {
- // /api/schedule-categories -> /api/schedules/categories로 리다이렉트
- req.url = "/categories";
- schedulesRouter(req, res, next);
-});
-
-// SPA 폴백 - 모든 요청을 index.html로
-app.get("*", (req, res) => {
- res.sendFile(path.join(__dirname, "dist", "index.html"));
-});
-
-app.listen(PORT, async () => {
- console.log(`🌸 fromis_9 서버가 포트 ${PORT}에서 실행 중입니다`);
-
- // Meilisearch 초기화 및 동기화
- try {
- await initMeilisearch();
- console.log("🔍 Meilisearch 초기화 완료");
-
- // 서버 시작 시 일정 데이터 자동 동기화
- const { syncAllSchedules } = await import("./services/meilisearch.js");
- const [schedules] = await (
- await import("./lib/db.js")
- ).default.query(`
- SELECT
- s.id, s.title, s.description, s.date, s.time, s.category_id, s.source_url, s.source_name,
- c.name as category_name, c.color as category_color,
- GROUP_CONCAT(m.name ORDER BY m.id SEPARATOR ',') as member_names
- FROM schedules s
- LEFT JOIN schedule_categories c ON s.category_id = c.id
- LEFT JOIN schedule_members sm ON s.id = sm.schedule_id
- LEFT JOIN members m ON sm.member_id = m.id
- GROUP BY s.id
- `);
- const syncedCount = await syncAllSchedules(schedules);
- console.log(`🔍 Meilisearch ${syncedCount}개 일정 동기화 완료`);
- } catch (error) {
- console.error("Meilisearch 초기화/동기화 오류:", error);
- }
-
- // YouTube 봇 스케줄러 초기화
- try {
- await initScheduler();
- console.log("📺 YouTube 봇 스케줄러 초기화 완료");
- } catch (error) {
- console.error("YouTube 스케줄러 초기화 오류:", error);
- }
-});
diff --git a/backend/services/meilisearch-bot.js b/backend/services/meilisearch-bot.js
deleted file mode 100644
index 0bec668..0000000
--- a/backend/services/meilisearch-bot.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Meilisearch 동기화 봇 서비스
- * 모든 일정을 Meilisearch에 동기화
- */
-
-import pool from "../lib/db.js";
-import { addOrUpdateSchedule } from "./meilisearch.js";
-
-/**
- * 전체 일정 Meilisearch 동기화
- */
-export async function syncAllSchedules(botId) {
- try {
- const startTime = Date.now();
-
- // 모든 일정 조회
- const [schedules] = await pool.query(`
- SELECT s.id, s.title, s.description, s.date, s.time,
- s.category_id, s.source_url, s.source_name,
- c.name as category_name, c.color as category_color
- FROM schedules s
- LEFT JOIN schedule_categories c ON s.category_id = c.id
- `);
-
- let synced = 0;
-
- for (const s of schedules) {
- // 멤버 조회
- const [members] = await pool.query(
- "SELECT m.id, m.name FROM schedule_members sm JOIN members m ON sm.member_id = m.id WHERE sm.schedule_id = ?",
- [s.id]
- );
-
- // Meilisearch 동기화
- await addOrUpdateSchedule({
- id: s.id,
- title: s.title,
- description: s.description || "",
- date: s.date,
- time: s.time,
- category_id: s.category_id,
- category_name: s.category_name || "",
- category_color: s.category_color || "",
- source_name: s.source_name,
- source_url: s.source_url,
- members: members,
- });
-
- synced++;
- }
-
- const elapsedMs = Date.now() - startTime;
- const elapsedSec = (elapsedMs / 1000).toFixed(2);
-
- // 봇 상태 업데이트 (schedules_added = 동기화 수, last_added_count = 소요시간 ms)
- await pool.query(
- `UPDATE bots SET
- last_check_at = NOW(),
- schedules_added = ?,
- last_added_count = ?,
- error_message = NULL
- WHERE id = ?`,
- [synced, elapsedMs, botId]
- );
-
- console.log(`[Meilisearch Bot] ${synced}개 동기화 완료 (${elapsedSec}초)`);
- return { synced, elapsed: elapsedSec };
- } catch (error) {
- // 오류 상태 업데이트
- await pool.query(
- `UPDATE bots SET
- last_check_at = NOW(),
- status = 'error',
- error_message = ?
- WHERE id = ?`,
- [error.message, botId]
- );
- throw error;
- }
-}
-
-export default {
- syncAllSchedules,
-};
diff --git a/backend/services/meilisearch.js b/backend/services/meilisearch.js
deleted file mode 100644
index 8308dc5..0000000
--- a/backend/services/meilisearch.js
+++ /dev/null
@@ -1,227 +0,0 @@
-import { MeiliSearch } from "meilisearch";
-
-// Meilisearch 클라이언트 초기화
-const client = new MeiliSearch({
- host: "http://fromis9-meilisearch:7700",
- apiKey: process.env.MEILI_MASTER_KEY,
-});
-
-const SCHEDULE_INDEX = "schedules";
-
-/**
- * 인덱스 초기화 및 설정
- */
-export async function initMeilisearch() {
- try {
- // 인덱스 생성 (이미 존재하면 무시)
- await client.createIndex(SCHEDULE_INDEX, { primaryKey: "id" });
-
- // 인덱스 설정
- const index = client.index(SCHEDULE_INDEX);
-
- // 검색 가능한 필드 설정 (순서가 우선순위 결정)
- await index.updateSearchableAttributes([
- "title",
- "member_names",
- "description",
- "source_name",
- "category_name",
- ]);
-
- // 필터링 가능한 필드 설정
- await index.updateFilterableAttributes(["category_id", "date"]);
-
- // 정렬 가능한 필드 설정
- await index.updateSortableAttributes(["date", "time"]);
-
- // 랭킹 규칙 설정 (동일 유사도일 때 최신 날짜 우선)
- await index.updateRankingRules([
- "words", // 검색어 포함 개수
- "typo", // 오타 수
- "proximity", // 검색어 간 거리
- "attribute", // 필드 우선순위
- "exactness", // 정확도
- "date:desc", // 동일 유사도 시 최신 날짜 우선
- ]);
-
- // 오타 허용 설정 (typo tolerance)
- await index.updateTypoTolerance({
- enabled: true,
- minWordSizeForTypos: {
- oneTypo: 2,
- twoTypos: 4,
- },
- });
-
- // 페이징 설정 (기본 1000개 제한 해제)
- await index.updatePagination({
- maxTotalHits: 10000, // 최대 10000개까지 조회 가능
- });
-
- console.log("[Meilisearch] 인덱스 초기화 완료");
- } catch (error) {
- console.error("[Meilisearch] 초기화 오류:", error.message);
- }
-}
-
-/**
- * 일정 문서 추가/업데이트
- */
-export async function addOrUpdateSchedule(schedule) {
- try {
- const index = client.index(SCHEDULE_INDEX);
-
- // 멤버 이름을 쉼표로 구분하여 저장
- const memberNames = schedule.members
- ? schedule.members.map((m) => m.name).join(",")
- : "";
-
- const document = {
- id: schedule.id,
- title: schedule.title,
- description: schedule.description || "",
- date: schedule.date,
- time: schedule.time || "",
- category_id: schedule.category_id,
- category_name: schedule.category_name || "",
- category_color: schedule.category_color || "",
- source_name: schedule.source_name || "",
- source_url: schedule.source_url || "",
- member_names: memberNames,
- };
-
- await index.addDocuments([document]);
- console.log(`[Meilisearch] 일정 추가/업데이트: ${schedule.id}`);
- } catch (error) {
- console.error("[Meilisearch] 문서 추가 오류:", error.message);
- }
-}
-
-/**
- * 일정 문서 삭제
- */
-export async function deleteSchedule(scheduleId) {
- try {
- const index = client.index(SCHEDULE_INDEX);
- await index.deleteDocument(scheduleId);
- console.log(`[Meilisearch] 일정 삭제: ${scheduleId}`);
- } catch (error) {
- console.error("[Meilisearch] 문서 삭제 오류:", error.message);
- }
-}
-
-import Inko from "inko";
-const inko = new Inko();
-
-/**
- * 영문 자판으로 입력된 검색어인지 확인 (대부분 영문으로만 구성)
- */
-function isEnglishKeyboard(text) {
- const englishChars = text.match(/[a-zA-Z]/g) || [];
- const koreanChars = text.match(/[가-힣ㄱ-ㅎㅏ-ㅣ]/g) || [];
- // 영문이 50% 이상이고 한글이 없으면 영문 자판 입력으로 간주
- return englishChars.length > 0 && koreanChars.length === 0;
-}
-
-/**
- * 일정 검색 (페이징 지원)
- */
-export async function searchSchedules(query, options = {}) {
- try {
- const index = client.index(SCHEDULE_INDEX);
-
- const searchOptions = {
- limit: options.limit || 1000, // 기본 1000개 (Meilisearch 최대)
- offset: options.offset || 0, // 페이징용 offset
- attributesToRetrieve: ["*"],
- showRankingScore: true, // 유사도 점수 포함
- };
-
- // 카테고리 필터
- if (options.categoryId) {
- searchOptions.filter = `category_id = ${options.categoryId}`;
- }
-
- // 정렬 지정 시에만 적용 (기본은 유사도순)
- if (options.sort) {
- searchOptions.sort = options.sort;
- }
-
- // 원본 검색어로 검색
- const results = await index.search(query, searchOptions);
- let allHits = [...results.hits];
-
- // 영문 자판 입력인 경우 한글로 변환하여 추가 검색
- if (isEnglishKeyboard(query)) {
- const koreanQuery = inko.en2ko(query);
- if (koreanQuery !== query) {
- const koreanResults = await index.search(koreanQuery, searchOptions);
- // 중복 제거하며 병합 (id 기준)
- const existingIds = new Set(allHits.map((h) => h.id));
- for (const hit of koreanResults.hits) {
- if (!existingIds.has(hit.id)) {
- allHits.push(hit);
- existingIds.add(hit.id);
- }
- }
- }
- }
-
- // 유사도 0.5 미만인 결과 필터링
- const filteredHits = allHits.filter((hit) => hit._rankingScore >= 0.5);
-
- // 유사도 순으로 정렬
- filteredHits.sort(
- (a, b) => (b._rankingScore || 0) - (a._rankingScore || 0)
- );
-
- // 페이징 정보 포함 반환
- return {
- hits: filteredHits,
- total: filteredHits.length, // 필터링 후 결과 수
- offset: searchOptions.offset,
- limit: searchOptions.limit,
- };
- } catch (error) {
- console.error("[Meilisearch] 검색 오류:", error.message);
- return { hits: [], total: 0, offset: 0, limit: 0 };
- }
-}
-
-/**
- * 모든 일정 동기화 (초기 데이터 로드용)
- */
-export async function syncAllSchedules(schedules) {
- try {
- const index = client.index(SCHEDULE_INDEX);
-
- // 기존 문서 모두 삭제
- await index.deleteAllDocuments();
-
- // 문서 변환
- const documents = schedules.map((schedule) => ({
- id: schedule.id,
- title: schedule.title,
- description: schedule.description || "",
- date: schedule.date,
- time: schedule.time || "",
- category_id: schedule.category_id,
- category_name: schedule.category_name || "",
- category_color: schedule.category_color || "",
- source_name: schedule.source_name || "",
- source_url: schedule.source_url || "",
- member_names: schedule.member_names || "",
- }));
-
- // 일괄 추가
- await index.addDocuments(documents);
- console.log(`[Meilisearch] ${documents.length}개 일정 동기화 완료`);
-
- return documents.length;
- } catch (error) {
- console.error("[Meilisearch] 동기화 오류:", error.message);
- return 0;
- }
-}
-
-export { client };
diff --git a/backend/services/suggestions.js b/backend/services/suggestions.js
deleted file mode 100644
index 258eeed..0000000
--- a/backend/services/suggestions.js
+++ /dev/null
@@ -1,248 +0,0 @@
-import pool from "../lib/db.js";
-import redis from "../lib/redis.js";
-import Inko from "inko";
-import { searchSchedules } from "./meilisearch.js";
-
-const inko = new Inko();
-
-// Redis 키 prefix
-const SUGGESTION_PREFIX = "suggestions:";
-const CACHE_TTL = 86400; // 24시간
-
-// 추천 검색어로 노출되기 위한 최소 비율 (최대 검색 횟수 대비)
-// 예: 0.01 = 최대 검색 횟수의 1% 이상만 노출
-const MIN_COUNT_RATIO = 0.01;
-// 최소 임계값 (데이터가 적을 때 오타 방지)
-const MIN_COUNT_FLOOR = 10;
-
-/**
- * 영문만 포함된 검색어인지 확인
- */
-function isEnglishOnly(text) {
- const englishChars = text.match(/[a-zA-Z]/g) || [];
- const koreanChars = text.match(/[가-힣ㄱ-ㅎㅏ-ㅣ]/g) || [];
- return englishChars.length > 0 && koreanChars.length === 0;
-}
-
-/**
- * 일정 검색 결과가 있는지 확인 (Meilisearch)
- */
-async function hasScheduleResults(query) {
- try {
- const result = await searchSchedules(query, { limit: 1 });
- return result.hits.length > 0;
- } catch (error) {
- console.error("[SearchSuggestion] 검색 확인 오류:", error.message);
- return false;
- }
-}
-
-/**
- * 영어 입력을 분석하여 실제 영어인지 한글 오타인지 판단
- * 1. 영어로 일정 검색 → 결과 있으면 영어
- * 2. 한글 변환 후 일정 검색 → 결과 있으면 한글
- * 3. 둘 다 없으면 원본 유지
- */
-async function resolveEnglishInput(query) {
- const koreanQuery = inko.en2ko(query);
-
- // 변환 결과가 같으면 변환 의미 없음
- if (koreanQuery === query) {
- return { resolved: query, type: "english" };
- }
-
- // 1. 영어로 검색
- const hasEnglishResult = await hasScheduleResults(query);
- if (hasEnglishResult) {
- return { resolved: query, type: "english" };
- }
-
- // 2. 한글로 검색
- const hasKoreanResult = await hasScheduleResults(koreanQuery);
- if (hasKoreanResult) {
- return { resolved: koreanQuery, type: "korean_typo" };
- }
-
- // 3. 둘 다 없으면 원본 유지
- return { resolved: query, type: "unknown" };
-}
-
-/**
- * 검색어 저장 (검색 실행 시 호출)
- * - search_queries 테이블에 Unigram 저장
- * - word_pairs 테이블에 Bi-gram 저장
- * - Redis 캐시 업데이트
- * - 영어 입력 시 일정 검색으로 언어 판단
- */
-export async function saveSearchQuery(query) {
- if (!query || query.trim().length === 0) return;
-
- let normalizedQuery = query.trim().toLowerCase();
-
- // 영문만 있는 경우 일정 검색으로 언어 판단
- if (isEnglishOnly(normalizedQuery)) {
- const { resolved, type } = await resolveEnglishInput(normalizedQuery);
- if (type === "korean_typo") {
- console.log(
- `[SearchSuggestion] 한글 오타 감지: "${normalizedQuery}" → "${resolved}"`
- );
- }
- normalizedQuery = resolved;
- }
-
- try {
- // 1. Unigram 저장 (인기도)
- await pool.query(
- `INSERT INTO search_queries (query, count)
- VALUES (?, 1)
- ON DUPLICATE KEY UPDATE count = count + 1, last_searched_at = CURRENT_TIMESTAMP`,
- [normalizedQuery]
- );
-
- // 2. Bi-gram 저장 (다음 단어 예측)
- const words = normalizedQuery.split(/\s+/).filter((w) => w.length > 0);
- for (let i = 0; i < words.length - 1; i++) {
- const word1 = words[i];
- const word2 = words[i + 1];
-
- // DB 저장
- await pool.query(
- `INSERT INTO word_pairs (word1, word2, count)
- VALUES (?, ?, 1)
- ON DUPLICATE KEY UPDATE count = count + 1`,
- [word1, word2]
- );
-
- // Redis 캐시 업데이트 (Sorted Set)
- await redis.zincrby(`${SUGGESTION_PREFIX}${word1}`, 1, word2);
- }
-
- console.log(`[SearchSuggestion] 검색어 저장: "${normalizedQuery}"`);
- } catch (error) {
- console.error("[SearchSuggestion] 검색어 저장 오류:", error.message);
- }
-}
-
-/**
- * 추천 검색어 조회
- * - 입력이 공백으로 끝나면: 마지막 단어 기반 다음 단어 예측 (Bi-gram)
- * - 그 외: prefix 매칭 (인기순)
- * - 영어 입력 시: 일정 검색으로 영어/한글 판단
- */
-export async function getSuggestions(query, limit = 10) {
- if (!query || query.trim().length === 0) {
- return [];
- }
-
- let searchQuery = query.toLowerCase();
- let koreanQuery = null;
-
- // 영문만 있는 경우, 한글 변환도 같이 검색
- if (isEnglishOnly(searchQuery)) {
- const converted = inko.en2ko(searchQuery);
- if (converted !== searchQuery) {
- koreanQuery = converted;
- }
- }
-
- try {
- const endsWithSpace = query.endsWith(" ");
- const words = searchQuery
- .trim()
- .split(/\s+/)
- .filter((w) => w.length > 0);
-
- if (endsWithSpace && words.length > 0) {
- // 다음 단어 예측 (Bi-gram)
- const lastWord = words[words.length - 1];
- return await getNextWordSuggestions(lastWord, searchQuery.trim(), limit);
- } else {
- // prefix 매칭 (인기순) - 영어 원본 + 한글 변환 둘 다
- return await getPrefixSuggestions(
- searchQuery.trim(),
- koreanQuery?.trim(),
- limit
- );
- }
- } catch (error) {
- console.error("[SearchSuggestion] 추천 조회 오류:", error.message);
- return [];
- }
-}
-
-/**
- * 다음 단어 예측 (Bi-gram 기반)
- * 동적 임계값을 사용하므로 Redis 캐시를 사용하지 않음
- */
-async function getNextWordSuggestions(lastWord, prefix, limit) {
- try {
- const [rows] = await pool.query(
- `SELECT word2, count FROM word_pairs
- WHERE word1 = ?
- AND count >= GREATEST((SELECT MAX(count) * ? FROM word_pairs), ?)
- ORDER BY count DESC
- LIMIT ?`,
- [lastWord, MIN_COUNT_RATIO, MIN_COUNT_FLOOR, limit]
- );
-
- // prefix + 다음 단어 조합으로 반환
- return rows.map((r) => `${prefix} ${r.word2}`);
- } catch (error) {
- console.error("[SearchSuggestion] Bi-gram 조회 오류:", error.message);
- return [];
- }
-}
-
-/**
- * Prefix 매칭 (인기순)
- * @param {string} prefix - 원본 검색어 (영어 또는 한글)
- * @param {string|null} koreanPrefix - 한글 변환된 검색어 (영어 입력의 경우)
- * @param {number} limit - 결과 개수
- */
-async function getPrefixSuggestions(prefix, koreanPrefix, limit) {
- try {
- let rows;
-
- if (koreanPrefix) {
- // 영어 원본과 한글 변환 둘 다 검색
- [rows] = await pool.query(
- `SELECT query FROM search_queries
- WHERE (query LIKE ? OR query LIKE ?)
- AND count >= GREATEST((SELECT MAX(count) * ? FROM search_queries), ?)
- ORDER BY count DESC, last_searched_at DESC
- LIMIT ?`,
- [`${prefix}%`, `${koreanPrefix}%`, MIN_COUNT_RATIO, MIN_COUNT_FLOOR, limit]
- );
- } else {
- // 단일 검색
- [rows] = await pool.query(
- `SELECT query FROM search_queries
- WHERE query LIKE ?
- AND count >= GREATEST((SELECT MAX(count) * ? FROM search_queries), ?)
- ORDER BY count DESC, last_searched_at DESC
- LIMIT ?`,
- [`${prefix}%`, MIN_COUNT_RATIO, MIN_COUNT_FLOOR, limit]
- );
- }
-
- return rows.map((r) => r.query);
- } catch (error) {
- console.error("[SearchSuggestion] Prefix 조회 오류:", error.message);
- return [];
- }
-}
-
-/**
- * Redis 캐시 초기화 (필요시)
- */
-export async function clearSuggestionCache() {
- try {
- const keys = await redis.keys(`${SUGGESTION_PREFIX}*`);
- if (keys.length > 0) {
- await redis.del(...keys);
- console.log(`[SearchSuggestion] ${keys.length}개 캐시 삭제`);
- }
- } catch (error) {
- console.error("[SearchSuggestion] 캐시 초기화 오류:", error.message);
- }
-}
diff --git a/backend/services/x-bot.js b/backend/services/x-bot.js
deleted file mode 100644
index e4722bf..0000000
--- a/backend/services/x-bot.js
+++ /dev/null
@@ -1,687 +0,0 @@
-/**
- * X 봇 서비스
- *
- * - Nitter를 통해 @realfromis_9 트윗 수집
- * - 트윗을 schedules 테이블에 저장
- * - 유튜브 링크 감지 시 별도 일정 추가
- */
-
-import pool from "../lib/db.js";
-import redis from "../lib/redis.js";
-import { addOrUpdateSchedule } from "./meilisearch.js";
-import {
- toKST,
- formatDate,
- formatTime,
- parseNitterDateTime,
-} from "../lib/date.js";
-
-// X 프로필 캐시 키 prefix
-const X_PROFILE_CACHE_PREFIX = "x_profile:";
-
-// YouTube API 키
-const YOUTUBE_API_KEY =
- process.env.YOUTUBE_API_KEY || "AIzaSyBmn79egY0M_z5iUkqq9Ny0zVFP6PoYCzM";
-
-// X 카테고리 ID
-const X_CATEGORY_ID = 3;
-
-// 유튜브 카테고리 ID
-const YOUTUBE_CATEGORY_ID = 2;
-
-/**
- * 트윗 텍스트에서 첫 문단 추출 (title용)
- */
-export function extractTitle(text) {
- if (!text) return "";
-
- // 빈 줄(\n\n)로 분리하여 첫 문단 추출
- const paragraphs = text.split(/\n\n+/);
- const firstParagraph = paragraphs[0]?.trim() || "";
-
- return firstParagraph;
-}
-
-/**
- * 텍스트에서 유튜브 videoId 추출
- */
-export function extractYoutubeVideoIds(text) {
- if (!text) return [];
-
- const videoIds = [];
-
- // youtu.be/{videoId} 형식
- const shortMatches = text.match(/youtu\.be\/([a-zA-Z0-9_-]{11})/g);
- if (shortMatches) {
- shortMatches.forEach((m) => {
- const id = m.replace("youtu.be/", "");
- if (id && id.length === 11) videoIds.push(id);
- });
- }
-
- // youtube.com/watch?v={videoId} 형식
- const watchMatches = text.match(
- /youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/g
- );
- if (watchMatches) {
- watchMatches.forEach((m) => {
- const id = m.match(/v=([a-zA-Z0-9_-]{11})/)?.[1];
- if (id) videoIds.push(id);
- });
- }
-
- // youtube.com/shorts/{videoId} 형식
- const shortsMatches = text.match(
- /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/g
- );
- if (shortsMatches) {
- shortsMatches.forEach((m) => {
- const id = m.match(/shorts\/([a-zA-Z0-9_-]{11})/)?.[1];
- if (id) videoIds.push(id);
- });
- }
-
- return [...new Set(videoIds)];
-}
-
-/**
- * 관리 중인 채널 ID 목록 조회
- */
-export async function getManagedChannelIds() {
- const [configs] = await pool.query(
- "SELECT channel_id FROM bot_youtube_config"
- );
- return configs.map((c) => c.channel_id);
-}
-
-/**
- * YouTube API로 영상 정보 조회
- */
-async function fetchVideoInfo(videoId) {
- try {
- const url = `https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=${videoId}&key=${YOUTUBE_API_KEY}`;
- const response = await fetch(url);
- const data = await response.json();
-
- if (!data.items || data.items.length === 0) {
- return null;
- }
-
- const video = data.items[0];
- const snippet = video.snippet;
- const duration = video.contentDetails?.duration || "";
-
- // duration 파싱
- const durationMatch = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
- let seconds = 0;
- if (durationMatch) {
- seconds =
- parseInt(durationMatch[1] || 0) * 3600 +
- parseInt(durationMatch[2] || 0) * 60 +
- parseInt(durationMatch[3] || 0);
- }
-
- const isShorts = seconds > 0 && seconds <= 60;
-
- return {
- videoId,
- title: snippet.title,
- description: snippet.description || "",
- channelId: snippet.channelId,
- channelTitle: snippet.channelTitle,
- publishedAt: new Date(snippet.publishedAt),
- isShorts,
- videoUrl: isShorts
- ? `https://www.youtube.com/shorts/${videoId}`
- : `https://www.youtube.com/watch?v=${videoId}`,
- };
- } catch (error) {
- console.error(`영상 정보 조회 오류 (${videoId}):`, error.message);
- return null;
- }
-}
-
-/**
- * Nitter HTML에서 프로필 정보 추출
- */
-function extractProfileFromHtml(html) {
- const profile = {
- displayName: null,
- avatarUrl: null,
- };
-
- // Display name 추출: 이름
- const nameMatch = html.match(
- /class="profile-card-fullname"[^>]*title="([^"]+)"/
- );
- if (nameMatch) {
- profile.displayName = nameMatch[1].trim();
- }
-
- // Avatar URL 추출:
- const avatarMatch = html.match(
- /class="profile-card-avatar"[^>]*>[\s\S]*?
]*src="([^"]+)"/
- );
- if (avatarMatch) {
- profile.avatarUrl = avatarMatch[1];
- }
-
- return profile;
-}
-
-/**
- * X 프로필 정보 캐시에 저장
- */
-async function cacheXProfile(username, profile, nitterUrl) {
- try {
- // Nitter 프록시 URL에서 원본 Twitter 이미지 URL 추출
- let avatarUrl = profile.avatarUrl;
- if (avatarUrl) {
- // /pic/https%3A%2F%2Fpbs.twimg.com%2F... 형식에서 원본 URL 추출
- const encodedMatch = avatarUrl.match(/\/pic\/(.+)/);
- if (encodedMatch) {
- avatarUrl = decodeURIComponent(encodedMatch[1]);
- } else if (avatarUrl.startsWith("/")) {
- // 상대 경로인 경우 Nitter URL 추가
- avatarUrl = `${nitterUrl}${avatarUrl}`;
- }
- }
-
- const data = {
- username,
- displayName: profile.displayName,
- avatarUrl,
- updatedAt: new Date().toISOString(),
- };
-
- // 7일간 캐시 (604800초)
- await redis.setex(
- `${X_PROFILE_CACHE_PREFIX}${username}`,
- 604800,
- JSON.stringify(data)
- );
-
- console.log(`[X 프로필] ${username} 캐시 저장 완료`);
- } catch (error) {
- console.error(`[X 프로필] 캐시 저장 실패:`, error.message);
- }
-}
-
-/**
- * X 프로필 정보 조회
- */
-export async function getXProfile(username) {
- try {
- const cached = await redis.get(`${X_PROFILE_CACHE_PREFIX}${username}`);
- if (cached) {
- return JSON.parse(cached);
- }
- return null;
- } catch (error) {
- console.error(`[X 프로필] 캐시 조회 실패:`, error.message);
- return null;
- }
-}
-
-/**
- * Nitter에서 트윗 수집 (첫 페이지만)
- */
-async function fetchTweetsFromNitter(nitterUrl, username) {
- const url = `${nitterUrl}/${username}`;
-
- const response = await fetch(url);
- const html = await response.text();
-
- // 프로필 정보 추출 및 캐싱
- const profile = extractProfileFromHtml(html);
- if (profile.displayName || profile.avatarUrl) {
- await cacheXProfile(username, profile, nitterUrl);
- }
-
- const tweets = [];
- const tweetContainers = html.split('class="timeline-item ');
-
- for (let i = 1; i < tweetContainers.length; i++) {
- const container = tweetContainers[i];
- const tweet = {};
-
- // 고정 트윗 체크 - 현재 컨테이너 내에 pinned 클래스가 있는지 확인
- tweet.isPinned = container.includes('class="pinned"');
-
- // 리트윗 체크
- tweet.isRetweet = container.includes('class="retweet-header"');
-
- // 트윗 ID 추출
- const linkMatch = container.match(/href="\/[^\/]+\/status\/(\d+)/);
- tweet.id = linkMatch ? linkMatch[1] : null;
-
- // 시간 추출
- const timeMatch = container.match(
- /