diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ab4a3a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +.git +.gitignore +*.md +.env +.env.* +*.log +dist +.DS_Store diff --git a/.env b/.env index c498ab5..8c1c6b5 100644 --- a/.env +++ b/.env @@ -4,4 +4,4 @@ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # See the documentation for all the connection string options: https://pris.ly/d/connection-strings -DATABASE_URL="file:./dev.db" \ No newline at end of file +DATABASE_URL="file:./prisma/dev.db" \ No newline at end of file diff --git a/bun.lock b/bun.lock index b8e2599..1589ea6 100644 --- a/bun.lock +++ b/bun.lock @@ -5,17 +5,19 @@ "": { "name": "sung2", "dependencies": { + "@prisma/adapter-libsql": "^7.0.1", "@prisma/client": "^7.0.1", "@types/cheerio": "^1.0.0", "axios": "^1.13.2", "axios-retry": "^4.5.0", "cheerio": "^1.1.2", "dayjs": "^1.11.19", + "dotenv": "^17.2.3", "geolib": "^3.3.4", "node-schedule": "^2.1.1", "node-telegram-bot-api": "^0.66.0", "p-limit": "^7.2.0", - "prisma": "^7.0.1", + "prisma": "7.0.1", "xlsx": "^0.18.5", }, "devDependencies": { @@ -25,7 +27,7 @@ "@types/node-telegram-bot-api": "^0.64.13", }, "peerDependencies": { - "typescript": "^5", + "typescript": "^5.9.3", }, }, }, @@ -50,8 +52,36 @@ "@hono/node-server": ["@hono/node-server@1.14.2", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A=="], + "@libsql/client": ["@libsql/client@0.8.1", "", { "dependencies": { "@libsql/core": "^0.8.1", "@libsql/hrana-client": "^0.6.2", "js-base64": "^3.7.5", "libsql": "^0.3.10", "promise-limit": "^2.7.0" } }, "sha512-xGg0F4iTDFpeBZ0r4pA6icGsYa5rG6RAG+i/iLDnpCAnSuTqEWMDdPlVseiq4Z/91lWI9jvvKKiKpovqJ1kZWA=="], + + "@libsql/core": ["@libsql/core@0.8.1", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-u6nrj6HZMTPsgJ9EBhLzO2uhqhlHQJQmVHV+0yFLvfGf3oSP8w7TjZCNUgu1G8jHISx6KFi7bmcrdXW9lRt++A=="], + + "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.3.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rmOqsLcDI65zzxlUOoEiPJLhqmbFsZF6p4UJQ2kMqB+Kc0Rt5/A1OAdOZ/Wo8fQfJWjR1IbkbpEINFioyKf+nQ=="], + + "@libsql/darwin-x64": ["@libsql/darwin-x64@0.3.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-q9O55B646zU+644SMmOQL3FIfpmEvdWpRpzubwFc2trsa+zoBlSkHuzU9v/C+UNoPHQVRMP7KQctJ455I/h/xw=="], + + "@libsql/hrana-client": ["@libsql/hrana-client@0.6.2", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.2.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw=="], + + "@libsql/isomorphic-fetch": ["@libsql/isomorphic-fetch@0.2.5", "", {}, "sha512-8s/B2TClEHms2yb+JGpsVRTPBfy1ih/Pq6h6gvyaNcYnMVJvgQRY7wAa8U2nD0dppbCuDU5evTNMEhrQ17ZKKg=="], + + "@libsql/isomorphic-ws": ["@libsql/isomorphic-ws@0.1.5", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="], + + "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.3.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-mgeAUU1oqqh57k7I3cQyU6Trpdsdt607eFyEmH5QO7dv303ti+LjUvh1pp21QWV6WX7wZyjeJV1/VzEImB+jRg=="], + + "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.3.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-VEZtxghyK6zwGzU9PHohvNxthruSxBEnRrX7BSL5jQ62tN4n2JNepJ6SdzXp70pdzTfwroOj/eMwiPt94gkVRg=="], + + "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.3.19", "", { "os": "linux", "cpu": "x64" }, "sha512-2t/J7LD5w2f63wGihEO+0GxfTyYIyLGEvTFEsMO16XI5o7IS9vcSHrxsvAJs4w2Pf907uDjmc7fUfMg6L82BrQ=="], + + "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.3.19", "", { "os": "linux", "cpu": "x64" }, "sha512-BLsXyJaL8gZD8+3W2LU08lDEd9MIgGds0yPy5iNPp8tfhXx3pV/Fge2GErN0FC+nzt4DYQtjL+A9GUMglQefXQ=="], + + "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.3.19", "", { "os": "win32", "cpu": "x64" }, "sha512-ay1X9AobE4BpzG0XPw1gplyLZPGHIgJOovvW23gUrukRegiUP62uzhpRbKNogLlUOynyXeq//prHgPXiebUfWg=="], + "@mrleebo/prisma-ast": ["@mrleebo/prisma-ast@0.12.1", "", { "dependencies": { "chevrotain": "^10.5.0", "lilconfig": "^2.1.0" } }, "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w=="], + "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], + + "@prisma/adapter-libsql": ["@prisma/adapter-libsql@7.0.1", "", { "dependencies": { "@libsql/client": "^0.8.1", "@prisma/driver-adapter-utils": "7.0.1", "async-mutex": "0.5.0" } }, "sha512-R/XF0PkjQ52ZClIUOklgM7x897wtlMAdWuEqQiQkNgY/L0kCPd6MURmzcHepBYgtPr9l4/1TSmpylSa+YO+KBw=="], + "@prisma/client": ["@prisma/client@7.0.1", "", { "dependencies": { "@prisma/client-runtime-utils": "7.0.1" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g=="], "@prisma/client-runtime-utils": ["@prisma/client-runtime-utils@7.0.1", "", {}, "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw=="], @@ -62,6 +92,8 @@ "@prisma/dev": ["@prisma/dev@0.13.0", "", { "dependencies": { "@electric-sql/pglite": "0.3.2", "@electric-sql/pglite-socket": "0.0.6", "@electric-sql/pglite-tools": "0.2.7", "@hono/node-server": "1.14.2", "@mrleebo/prisma-ast": "0.12.1", "@prisma/get-platform": "6.8.2", "@prisma/query-plan-executor": "6.18.0", "foreground-child": "3.3.1", "get-port-please": "3.1.2", "hono": "4.7.10", "http-status-codes": "2.3.0", "pathe": "2.0.3", "proper-lockfile": "4.1.2", "remeda": "2.21.3", "std-env": "3.9.0", "valibot": "1.1.0", "zeptomatch": "2.0.2" } }, "sha512-QMmF6zFeUF78yv1HYbHvod83AQnl7u6NtKyDhTRZOJup3h1icWs8R7RUVxBJZvM2tBXNAMpLQYYM/8kPlOPegA=="], + "@prisma/driver-adapter-utils": ["@prisma/driver-adapter-utils@7.0.1", "", { "dependencies": { "@prisma/debug": "7.0.1" } }, "sha512-sBbxm/yysHLLF2iMAB+qcX/nn3WFgsiC4DQNz0uM6BwGSIs8lIvgo0u8nR9nxe5gvFgKiIH8f4z2fgOEMeXc8w=="], + "@prisma/engines": ["@prisma/engines@7.0.1", "", { "dependencies": { "@prisma/debug": "7.0.1", "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", "@prisma/fetch-engine": "7.0.1", "@prisma/get-platform": "7.0.1" } }, "sha512-f+D/vdKeImqUHysd5Bgv8LQ1whl4sbLepHyYMQQMK61cp4WjwJVryophleLUrfEJRpBLGTBI/7fnLVENxxMFPQ=="], "@prisma/engines-version": ["@prisma/engines-version@7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", "", {}, "sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ=="], @@ -78,7 +110,7 @@ "@types/axios": ["@types/axios@0.14.4", "", { "dependencies": { "axios": "*" } }, "sha512-9JgOaunvQdsQ/qW2OPmE5+hCeUB52lQSolecrFrthct55QekhmXEwT203s20RL+UHtCQc15y3VXpby9E7Kkh/g=="], - "@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="], + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], "@types/caseless": ["@types/caseless@0.12.5", "", {}, "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg=="], @@ -96,6 +128,8 @@ "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "adler-32": ["adler-32@1.3.1", "", {}, "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="], "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], @@ -112,6 +146,8 @@ "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + "async-mutex": ["async-mutex@0.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], @@ -126,15 +162,23 @@ "axios-retry": ["axios-retry@4.5.0", "", { "dependencies": { "is-retry-allowed": "^2.2.0" }, "peerDependencies": { "axios": "0.x || 1.x" } }, "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], + "better-sqlite3": ["better-sqlite3@12.5.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + "bl": ["bl@1.2.3", "", { "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww=="], "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], @@ -156,6 +200,8 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], "codepage": ["codepage@1.15.0", "", {}, "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="], @@ -182,6 +228,8 @@ "dashdash": ["dashdash@1.14.1", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], @@ -192,6 +240,10 @@ "debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], @@ -206,6 +258,8 @@ "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + "detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], @@ -214,7 +268,7 @@ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], - "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -246,6 +300,8 @@ "eventemitter3": ["eventemitter3@3.1.2", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], @@ -258,8 +314,12 @@ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "file-type": ["file-type@3.9.0", "", {}, "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], @@ -270,8 +330,12 @@ "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="], + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], @@ -296,6 +360,8 @@ "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -330,8 +396,12 @@ "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], @@ -390,6 +460,8 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="], + "jsbn": ["jsbn@0.1.1", "", {}, "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="], "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], @@ -400,6 +472,8 @@ "jsprim": ["jsprim@2.0.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ=="], + "libsql": ["libsql@0.3.19", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2", "libsql": "^0.3.15" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.3.19", "@libsql/darwin-x64": "0.3.19", "@libsql/linux-arm64-gnu": "0.3.19", "@libsql/linux-arm64-musl": "0.3.19", "@libsql/linux-x64-gnu": "0.3.19", "@libsql/linux-x64-musl": "0.3.19", "@libsql/win32-x64-msvc": "0.3.19" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA=="], + "lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], @@ -422,12 +496,26 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mysql2": ["mysql2@3.15.3", "", { "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" } }, "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg=="], "named-placeholders": ["named-placeholders@1.1.3", "", { "dependencies": { "lru-cache": "^7.14.1" } }, "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w=="], + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], "node-schedule": ["node-schedule@2.1.1", "", { "dependencies": { "cron-parser": "^4.2.0", "long-timeout": "0.1.1", "sorted-array-functions": "^1.3.0" } }, "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ=="], @@ -474,10 +562,14 @@ "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "prisma": ["prisma@7.0.1", "", { "dependencies": { "@prisma/config": "7.0.1", "@prisma/dev": "0.13.0", "@prisma/engines": "7.0.1", "@prisma/studio-core": "0.8.2", "mysql2": "3.15.3", "postgres": "3.4.7" }, "peerDependencies": { "better-sqlite3": ">=9.0.0", "typescript": ">=5.4.0" }, "optionalPeers": ["better-sqlite3", "typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-zp93MdFMSU1IHPEXbUHVUuD8wauh2BUm14OVxhxGrWJQQpXpda0rW4VSST2bci4raoldX64/wQxHKkl/wqDskQ=="], "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], @@ -494,6 +586,8 @@ "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], @@ -532,6 +626,8 @@ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], @@ -554,6 +650,10 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "sorted-array-functions": ["sorted-array-functions@1.3.0", "", {}, "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="], "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], @@ -576,6 +676,12 @@ "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], "tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="], @@ -584,6 +690,8 @@ "tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="], @@ -620,6 +728,8 @@ "verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], @@ -640,6 +750,8 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="], "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], @@ -658,12 +770,18 @@ "@types/request/form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="], + "c12/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "mysql2/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "prebuild-install/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "prebuild-install/pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -684,6 +802,12 @@ "string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + "tar-fs/pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + + "tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "verror/core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], diff --git a/lib/prisma.ts b/lib/prisma.ts new file mode 100644 index 0000000..3702c3d --- /dev/null +++ b/lib/prisma.ts @@ -0,0 +1,11 @@ +import "dotenv/config"; +import { PrismaLibSql } from "@prisma/adapter-libsql"; +import { PrismaClient } from "../src/generated/prisma/client"; + +const adapter = new PrismaLibSql({ + url: process.env.DATABASE_URL ?? "", +}); + +const prisma = new PrismaClient({ adapter }); + +export { prisma }; diff --git a/package.json b/package.json index 4fbe7aa..43d8133 100644 --- a/package.json +++ b/package.json @@ -10,20 +10,22 @@ "@types/node-telegram-bot-api": "^0.64.13" }, "peerDependencies": { - "typescript": "^5" + "typescript": "^5.9.3" }, "dependencies": { + "@prisma/adapter-libsql": "^7.0.1", "@prisma/client": "^7.0.1", "@types/cheerio": "^1.0.0", "axios": "^1.13.2", "axios-retry": "^4.5.0", "cheerio": "^1.1.2", "dayjs": "^1.11.19", + "dotenv": "^17.2.3", "geolib": "^3.3.4", "node-schedule": "^2.1.1", "node-telegram-bot-api": "^0.66.0", "p-limit": "^7.2.0", - "prisma": "^7.0.1", + "prisma": "7.0.1", "xlsx": "^0.18.5" } } diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..d73df7b --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,12 @@ +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: env("DATABASE_URL"), + }, +}); diff --git a/prisma/dev.db b/prisma/dev.db index c6a1713..697ae17 100644 Binary files a/prisma/dev.db and b/prisma/dev.db differ diff --git a/prisma/schema.prisma b/prisma/schema.prisma index eb8f03f..41adfc4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,7 +8,6 @@ generator client { datasource db { provider = "sqlite" - url = env("DATABASE_URL") } model RealEstateArticle { diff --git a/src/config.ts b/src/config.ts index db687e5..8a982ba 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,6 @@ -export const realtorIds = ["namyeong00"]; +// export const realtorIds = ["namyeong00"]; +export const realtorIds = ["diahouse1114"]; +// export const realtorIds = ["jdre0125"]; export const tradeTypes = ["A1", "B1", "B2", "B3"]; export const realestateTypes = [ "A01", diff --git a/src/etc/fetch-articles-pc.ts b/src/etc/fetch-articles-pc.ts index ef5d97b..2e206b2 100644 --- a/src/etc/fetch-articles-pc.ts +++ b/src/etc/fetch-articles-pc.ts @@ -1,5 +1,4 @@ import axios from "axios"; -import { realtorIds } from "./config"; import axiosRetry from "axios-retry"; axiosRetry(axios, { @@ -46,6 +45,7 @@ const main = async () => { const firstResponse = await axios.get( `https://new.land.naver.com/api/articles?realEstateType=&tradeType=&order=rank&page=1&zoom=0&realtorId=${realtorId}`, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -85,6 +85,7 @@ const main = async () => { axios.get( `https://new.land.naver.com/api/articles?realEstateType=&tradeType=&order=rank&page=${page}&zoom=0&realtorId=${realtorId}`, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -98,12 +99,16 @@ const main = async () => { ) ); - const responses = await Promise.all(promises); + const responses = await Promise.allSettled(promises); responses.forEach((response, index) => { - const articles = response.data.articleList || []; - console.log(`페이지 ${chunk[index]}: ${articles.length}개 매물`); - allArticles.push(...articles); + if (response.status === "fulfilled") { + const articles = response.value.data.articleList || []; + console.log(`페이지 ${chunk[index]}: ${articles.length}개 매물`); + allArticles.push(...articles); + } else { + console.error(`페이지 ${chunk[index]} 요청 실패:`, response.reason); + } }); // 다음 배치 전에 잠시 대기 (API 부하 방지) diff --git a/src/etc/fetch-detail-disabled.ts b/src/etc/fetch-detail-disabled.ts index e39645f..45238c1 100644 --- a/src/etc/fetch-detail-disabled.ts +++ b/src/etc/fetch-detail-disabled.ts @@ -1,5 +1,5 @@ -import { NaverRealEstate } from "./src/services/naver.service"; -import prisma from "./src/lib/prisma"; +import { NaverRealEstate } from "../services/naver.service"; +import { prisma } from "../../lib/prisma"; async function main() { const realtorId = "a7062525"; diff --git a/src/fetch-articles.ts b/src/fetch-articles.ts index 50e906b..d967414 100644 --- a/src/fetch-articles.ts +++ b/src/fetch-articles.ts @@ -8,15 +8,14 @@ async function main() { realtorId: realtorId, }); try { + await naver.resetActiveStatus(); + let cookie = await naver.getApiCookie(); - // 2. DB에서 Seed 불러오기 시도 - console.log("Seed 불러오는 중..."); + let seed = await naver.fetchSeed(cookie); console.log("Seed:", seed); - await naver.resetActiveStatus(); - // 3. 등록된 매물(Article) 목록 가져오기 (자동으로 DB에 저장됨) console.log("\n매물 목록 가져오는 중..."); const articles = await naver.getArticlesAndSave(); @@ -33,11 +32,14 @@ async function main() { console.log(`\n총 ${activeArticles.length}개의 매물 발견`); - for (let i = 0; i < activeArticles.length; i += 30) { - const batch = activeArticles.slice(i, i + 30); - await Promise.all( - batch.map(async (article) => { - await naver.updateBrokerInfo(article.articleNumber, cookie); + // 2. DB에서 Seed 불러오기 시도 + console.log("Seed 불러오는 중..."); + + for (let i = 0; i < activeArticles.length; i += 10) { + const batch = activeArticles.slice(i, i + 10); + await Promise.allSettled( + batch.map((article) => { + return naver.updateBrokerInfo(article.articleNumber, cookie); }) ); } @@ -47,5 +49,7 @@ async function main() { } } +const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); + // 실행 main(); diff --git a/src/fetch-detailAddress.ts b/src/fetch-detailAddress.ts index 063d295..8938dc3 100644 --- a/src/fetch-detailAddress.ts +++ b/src/fetch-detailAddress.ts @@ -1,8 +1,7 @@ import { NaverRealEstate } from "./services/naver.service"; +import { realtorIds } from "./config"; async function main() { - const realtorIds = ["namyeong00"]; - console.log("detailAddress 없는 매물 목록 가져오는 중..."); for (const realtorId of realtorIds) { diff --git a/src/getRanking.ts b/src/getRanking.ts new file mode 100644 index 0000000..49605e7 --- /dev/null +++ b/src/getRanking.ts @@ -0,0 +1,50 @@ +import dayjs from "dayjs"; +import { prisma } from "../lib/prisma"; +import { RankingService } from "./services/ranking.service"; +import { NaverRealEstate } from "./services/naver.service"; +import { realestateTypes, realtorIds } from "./config"; + +async function getRanking(articles: RealEstateArticle[], checkDate: Date) { + const { token, cookie } = await this.naver.getRankingToken(); + + const limit = pLimit(20); + const tasks = articles.map((article) => { + return limit(async () => { + try { + // cortarNo, lgeo 확인 + if (!article.cortarNo || !article.lgeo) { + const { cortarNo, lgeo } = await this.updateCortarNoAndLgeo(article); + article.cortarNo = cortarNo; + article.lgeo = lgeo; + } + + // 랭킹 조회 및 업데이트 + const ranking = await this.naver.getRanking(article, token, cookie); + await prisma.realEstateArticle.update({ + where: { id: article.id }, + data: { + ranking, + rankCheckDate: checkDate.toISOString(), + }, + }); + + console.log( + `✅ ${article.articleNumber} - 랭킹 업데이트 완료: ${ranking}위` + ); + } catch (error) { + console.error(`❌ updateRanking 오류:`, error); + } + }); + }); + console.log("🔹 예약된 작업 수:", tasks.length); + const results = await Promise.allSettled(tasks); + console.log("🟢 모든 limit 작업 완료:", results.length); + await Bun.sleep(100); +} + +(async () => { + const articleNo = process.argv[2]; + console.log(articleNo); + process.exit(); + const ranking = getRanking(); +})(); diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts deleted file mode 100644 index 1a405ea..0000000 --- a/src/lib/prisma.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { PrismaClient } from "../generated/prisma"; - -// Prisma Client 싱글톤 인스턴스 -const globalForPrisma = globalThis as unknown as { - prisma: PrismaClient | undefined; -}; - -export const prisma = - globalForPrisma.prisma ?? - new PrismaClient({ - log: ["error", "warn"], - }); - -if (process.env.NODE_ENV !== "production") { - globalForPrisma.prisma = prisma; -} - -export default prisma; diff --git a/src/app.ts b/src/sendTelegram.ts similarity index 79% rename from src/app.ts rename to src/sendTelegram.ts index 2d34068..613cdbe 100644 --- a/src/app.ts +++ b/src/sendTelegram.ts @@ -1,4 +1,4 @@ -import prisma from "./lib/prisma"; +import { prisma } from "../lib/prisma"; import dayjs from "dayjs"; import { RankingService } from "./services/ranking.service"; import { NaverRealEstate } from "./services/naver.service"; @@ -21,12 +21,13 @@ const telegramService = new TelegramService( ); async function main() { - console.time("updateRanking"); - await updateRanking(); - console.timeEnd("updateRanking"); - console.time("sendTelegram"); - await sendTelegram(); - console.timeEnd("sendTelegram"); + try { + console.time("sendTelegram"); + await sendTelegram(); + console.timeEnd("sendTelegram"); + } catch (error) { + console.log(error); + } } async function sendTelegram() { @@ -54,7 +55,9 @@ async function sendTelegram() { await telegramService.sendDocument( telegramUser.chatId, excelFilePath, - `네이버 부동산 매물 목록 (${dayjs().format("YYYY-MM-DD")})` + `${telegramUser.realtorId}_네이버 부동산 매물 목록 (${dayjs().format( + "YYYY-MM-DD" + )})` ); } } @@ -113,9 +116,8 @@ async function createExcelFile( const cellAddress = `B${index + 2}`; // 헤더가 1행이므로 데이터는 2행부터 const url = `https://fin.land.naver.com/articles/${item.articleNumber}`; worksheet[cellAddress] = { - t: "s", // string type - v: "열기", // 표시되는 텍스트 - l: { Target: url }, // hyperlink + t: "s", + f: `HYPERLINK("https://n8n.abcde.co.kr/webhook/78c0b872-a402-45b4-af20-2b4c88defb58?url=https://fin.land.naver.com/articles/${item.articleNumber}", "열기")`, }; }); @@ -142,37 +144,13 @@ async function createExcelFile( XLSX.utils.book_append_sheet(workbook, worksheet, "매물목록"); // 파일 저장 - const filePath = `./xlsx/${dayjs().format("YYYY-MM-DD_HH-mm")}_순위_${ - telegramUser.site - }_${crypto.randomUUID().slice(0, 8)}.xlsx`; + const filePath = `./xlsx/${telegramUser.realtorId}_${dayjs().format( + "YYYY-MM-DD_HH-mm" + )}_순위_${telegramUser.site}_${crypto.randomUUID().slice(0, 8)}.xlsx`; XLSX.writeFile(workbook, filePath); return filePath; } -async function updateRanking() { - try { - const checkDate = dayjs().toDate(); - for (let realtorId of realtorIds) { - const articles = await prisma.realEstateArticle.findMany({ - where: { - isActive: true, - realtorId: realtorId, - }, - }); - - const rankingService = new RankingService( - new NaverRealEstate({ - realtorId: realtorId, - }) - ); - await rankingService.updateRanking(articles, checkDate); - } - } catch (error) { - console.log(error); - process.exit(); - } -} - main(); diff --git a/src/services/naver.service.ts b/src/services/naver.service.ts index 7a3e0fe..61bc7cf 100644 --- a/src/services/naver.service.ts +++ b/src/services/naver.service.ts @@ -5,12 +5,17 @@ import type { ArticleResponse, NaverRealEstateConfig, } from "../types/naver.types"; -import prisma from "../lib/prisma"; +import { prisma } from "../../lib/prisma"; import type { RealEstateArticle } from "../generated/prisma"; import { findNearest } from "geolib"; +import http from "http"; +import https from "https"; // axios 인스턴스 생성 및 retry 설정 -const axiosInstance = axios.create(); +const axiosInstance = axios.create({ + // httpAgent: new http.Agent({ keepAlive: false }), + // httpsAgent: new https.Agent({ keepAlive: false }), +}); axiosRetry(axiosInstance, { retries: 4, // 4번 재시도 retryDelay: (retryCount: number) => { @@ -27,6 +32,7 @@ export class NaverRealEstate { async getRankingToken() { try { const response = await axios.get("https://new.land.naver.com/houses", { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -42,6 +48,7 @@ export class NaverRealEstate { e: "RETAIL", }, headers: { + connection: "close", accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", "accept-language": "ko;q=0.8", @@ -159,6 +166,7 @@ export class NaverRealEstate { method: "post", maxBodyLength: Infinity, url: "https://fin.land.naver.com/front-api/v1/realtor/articles", + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -261,6 +269,7 @@ export class NaverRealEstate { const response = await axiosInstance.post<{ result: ArticleResponse; }>(`${this.baseUrl}/front-api/v1/realtor/articles`, requestData, { + adapter: "fetch", headers: { accept: "application/json, text/plain, */*", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", @@ -309,7 +318,7 @@ export class NaverRealEstate { } // API 요청 간격 (선택사항) - const waitTime = 1000 + Math.floor(Math.random() * 1000); + const waitTime = 2000 + Math.floor(Math.random() * 1000); console.log(`${waitTime}ms 후 다음 페이지 시도...`); await new Promise((resolve) => setTimeout(resolve, waitTime)); } catch (error) { @@ -540,6 +549,7 @@ export class NaverRealEstate { } } async resetActiveStatus() { + console.log("초기화하는중"); return prisma.realEstateArticle.updateMany({ where: { realtorId: this.realtorId, @@ -577,6 +587,7 @@ export class NaverRealEstate { const response = await axios.get( `https://fin.land.naver.com/articles/${articleNumber}`, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -794,6 +805,7 @@ export class NaverRealEstate { const firstPageResponse = await axiosInstance.get( `https://m.land.naver.com/agency/info/list?rltrMbrId=${this.realtorId}&tradTpCd=&atclRletTpCd=&tradeTypeChange=false&page=1`, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -803,6 +815,7 @@ export class NaverRealEstate { }, }, headers: { + connection: "close", accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9", @@ -823,16 +836,26 @@ export class NaverRealEstate { const totalCnt = firstPageResponse.data.totalCnt; const totalPage = Math.ceil(totalCnt / 20); - for (let i = 1; i <= totalPage; i += 20) { + function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + const BATCH_SIZE = 3; + const DELAY = 10000; // 10초 + + for (let i = 1; i <= totalPage; i += BATCH_SIZE) { const group = []; - // i ~ i+19 페이지까지 묶기 - for (let page = i; page < i + 20 && page <= totalPage; page++) { + for (let page = i; page < i + BATCH_SIZE && page <= totalPage; page++) { group.push(updateAddress(page, this.realtorId)); } - // 병렬 실행 후 기다리기 - await Promise.all(group); + console.log(`Requesting pages: ${i} ~ ${i + group.length - 1}`); + + await Promise.allSettled(group); + + console.log(`Batch done. Waiting ${DELAY / 1000}s...`); + await sleep(DELAY); } console.log("✅ 모든 페이지 처리 완료"); @@ -844,6 +867,7 @@ export class NaverRealEstate { const response = await axiosInstance.get( `https://m.land.naver.com/agency/info/list?rltrMbrId=${realtorId}&tradTpCd=&atclRletTpCd=&tradeTypeChange=false&page=${page}`, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -853,6 +877,7 @@ export class NaverRealEstate { }, }, headers: { + connection: "close", accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-language": "ko-KR,ko;q=0.9", @@ -930,6 +955,7 @@ export class NaverRealEstate { const url = `https://m.land.naver.com/cluster/clusterList?view=atcl&rletTpCd=APT:OPST:VL:YR:DSD:ABYG:OBYG:JGC:JWJT:DDDGG:SGJT:JGB:OR:SG:SMS:GJCG:GM:TJ:APTHGJ&tradTpCd=A1:B1:B2:B3&z=19&lat=${article.yCoordinate}&lon=${article.xCoordinate}&btm=${btm}&lft=${lft}&top=${top}&rgt=${rgt}&pCortarNo=&addon=COMPLEX&isOnlyIsale=false`; const response = await axiosInstance.get(url, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -939,6 +965,7 @@ export class NaverRealEstate { }, }, headers: { + Connection: "close", accept: "application/json, text/javascript, */*; q=0.01", "accept-language": "ko;q=0.7", "cache-control": "no-cache", @@ -1013,6 +1040,7 @@ export class NaverRealEstate { const url = `https://new.land.naver.com/api/articles?markerId=${article.lgeo}&markerType=LGEOHASH_MIX_ARTICLE&prevScrollTop=0&order=rank&realEstateType=${article.realEstateType}&tradeType=${article.tradeType}&rentPriceMin=0&rentPriceMax=900000000&priceMin=0&priceMax=900000000&areaMin=0&areaMax=900000000&oldBuildYears&recentlyBuildYears&minHouseHoldCount&maxHouseHoldCount&showArticle=false&sameAddressGroup=false&minMaintenanceCost&maxMaintenanceCost&priceType=RETAIL&directions=&page=1&articleState`; const response = await axiosInstance.get(url, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -1022,6 +1050,7 @@ export class NaverRealEstate { }, }, headers: { + Connection: "close", accept: "*/*", "accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6,zh-CN;q=0.5,zh;q=0.4", @@ -1071,6 +1100,7 @@ export class NaverRealEstate { const url = `https://fin.land.naver.com/front-api/v1/article/agent?articleNumber=${articleNumber}`; const response = await axiosInstance.get(url, { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, @@ -1140,6 +1170,7 @@ export class NaverRealEstate { const initialResponse = await axios.get( "https://fin.land.naver.com/?content=recent", { + adapter: "fetch", proxy: { host: "gw.dataimpulse.com", port: 823, diff --git a/src/services/ranking.service.ts b/src/services/ranking.service.ts index 47f689c..8fc187c 100644 --- a/src/services/ranking.service.ts +++ b/src/services/ranking.service.ts @@ -1,6 +1,6 @@ import type { RealEstateArticle } from "../generated/prisma"; import type { NaverRealEstate } from "./naver.service"; -import prisma from "../lib/prisma"; +import { prisma } from "../../lib/prisma"; import pLimit from "p-limit"; export class RankingService { @@ -11,9 +11,12 @@ export class RankingService { async updateRanking(articles: RealEstateArticle[], checkDate: Date) { const { token, cookie } = await this.naver.getRankingToken(); - const limit = pLimit(20); - const tasks = articles.map((article) => { - return limit(async () => { + for (let i = 0; i < articles.length; i += 20) { + const batch = articles.slice(i, i + 20); + + console.log(`🚀 ${i + 1} ~ ${i + batch.length}번 매물 처리 시작`); + + const tasks = batch.map(async (article) => { try { // cortarNo, lgeo 확인 if (!article.cortarNo || !article.lgeo) { @@ -24,8 +27,10 @@ export class RankingService { article.lgeo = lgeo; } - // 랭킹 조회 및 업데이트 + // 랭킹 조회 const ranking = await this.naver.getRanking(article, token, cookie); + + // DB 업데이트 await prisma.realEstateArticle.update({ where: { id: article.id }, data: { @@ -34,17 +39,23 @@ export class RankingService { }, }); - console.log( - `✅ ${article.articleNumber} - 랭킹 업데이트 완료: ${ranking}위` - ); - } catch (error) { - console.error(`❌ updateRanking 오류:`, error); + console.log(`✅ ${article.articleNumber} - 랭킹 완료 : ${ranking}`); + } catch (err) { + console.error(`❌ 오류: ${article.articleNumber}`, err); } }); - }); - console.log("🔹 예약된 작업 수:", tasks.length); - const results = await Promise.allSettled(tasks); - console.log("🟢 모든 limit 작업 완료:", results.length); + + // **이 10개(또는 마지막 batch)가 모두 끝날 때까지 기다림** + await Promise.allSettled(tasks); + + // 다음 배치가 남아있다면 딜레이 + if (i + 20 < articles.length) { + console.log(`⏸ 10개 처리 완료 → 10초 휴식`); + await Bun.sleep(5000); + } + } + + console.log("🎉 전체 랭킹 업데이트 완료!"); await Bun.sleep(100); } diff --git a/src/services/telegram.service.ts b/src/services/telegram.service.ts index 2c8b17b..f96fd59 100644 --- a/src/services/telegram.service.ts +++ b/src/services/telegram.service.ts @@ -76,6 +76,20 @@ export const telegramUsers: TelegramUser[] = [ phone: "010-4199-9650", realtorId: "namyeong00", }, + // { + // site: "ALL", + // chatId: 6843597951, + // name: "강승원", + // phone: "010-5947-0000", + // realtorId: "diahouse1114", + // }, + // { + // site: "ALL", + // chatId: 6843597951, + // name: "강승원", + // phone: "010-5947-0000", + // realtorId: "jdre0125", + // }, ]; export const testUsers: TelegramUser[] = [ @@ -192,7 +206,7 @@ export class TelegramService { }) ); - await Promise.all(promises); + await Promise.allSettled(promises); console.log(`✅ ${users.length}명에게 메시지 브로드캐스트 완료`); } diff --git a/src/starter.ts b/src/starter.ts index a2dd3d1..f6581e1 100644 --- a/src/starter.ts +++ b/src/starter.ts @@ -22,38 +22,28 @@ function runTsFile(filePath: string): Promise { }); } -// 10시에 실행 - fetch-articles.ts 실행 후 app.ts 실행 -schedule.scheduleJob("0 0 10 * * *", async () => { - console.log("Running parserLandList.ts at 10:00 AM"); +// 11시에 실행 - fetch-articles -> fetch-detailAddress -> updateRanging -> sendTelegram +schedule.scheduleJob("0 11 * * *", async () => { + console.log("Running scheduled jobs at 11 AM"); try { await runTsFile("./fetch-articles.ts"); - // fetch-articles.ts 완료 후 app.ts 실행 await runTsFile("./fetch-detailAddress.ts"); + await runTsFile("./updateRanging.ts"); + await runTsFile("./sendTelegram.ts"); } catch (error) { - console.error("Error in scheduled job:", error); + console.error("Error in 11 AM scheduled job:", error); } }); -// 11시에 실행 -schedule.scheduleJob("0 11 * * *", () => { - console.log("Running app.ts at 11 AM"); - runTsFile("./app.ts"); -}); - -// 15시에 실행 - fetch-articles.ts 실행 후 app.ts 실행 -schedule.scheduleJob("0 0 15 * * *", async () => { - console.log("Running parserLandList.ts at 3:00 PM"); +// 16시에 실행 - fetch-articles -> fetch-detailAddress -> updateRanging -> sendTelegram +schedule.scheduleJob("0 16 * * *", async () => { + console.log("Running scheduled jobs at 4 PM"); try { await runTsFile("./fetch-articles.ts"); - // fetch-articles.ts 완료 후 app.ts 실행 await runTsFile("./fetch-detailAddress.ts"); + await runTsFile("./updateRanging.ts"); + await runTsFile("./sendTelegram.ts"); } catch (error) { - console.error("Error in scheduled job:", error); + console.error("Error in 4 PM scheduled job:", error); } }); - -// 16시에 실행 -schedule.scheduleJob("0 16 * * *", () => { - console.log("Running app.ts at 4 PM"); - runTsFile("./app.ts"); -}); diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..08d1635 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,20 @@ +import { prisma } from "../lib/prisma"; + +async function main() { + // Fetch all users with their posts + // const allUsers = await prisma.realEstateArticle.findMany(); + console.log("test"); + const test = await prisma.realEstateArticle.findMany(); + console.log(test); + // console.log("All users:", JSON.stringify(allUsers, null, 2)); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/src/updateRanking.ts b/src/updateRanking.ts new file mode 100644 index 0000000..3637e56 --- /dev/null +++ b/src/updateRanking.ts @@ -0,0 +1,33 @@ +import dayjs from "dayjs"; +import { prisma } from "../lib/prisma"; +import { RankingService } from "./services/ranking.service"; +import { NaverRealEstate } from "./services/naver.service"; +import { realestateTypes, realtorIds } from "./config"; + +async function updateRanking() { + try { + const checkDate = dayjs().toDate(); + for (let realtorId of realtorIds) { + const articles = await prisma.realEstateArticle.findMany({ + where: { + isActive: true, + realtorId: realtorId, + }, + }); + + const rankingService = new RankingService( + new NaverRealEstate({ + realtorId: realtorId, + }) + ); + await rankingService.updateRanking(articles, checkDate); + } + } catch (error) { + console.log(error); + process.exit(); + } +} + +(async () => { + await updateRanking(); +})(); diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..58dec7f --- /dev/null +++ b/test.ts @@ -0,0 +1,20 @@ +import { prisma } from "./lib/prisma"; + +async function main() { + // Fetch all users with their posts + // const allUsers = await prisma.realEstateArticle.findMany(); + console.log("test"); + const test = await prisma.realEstateArticle.findMany(); + console.log(test); + // console.log("All users:", JSON.stringify(allUsers, null, 2)); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/updateRanking.exe b/updateRanking.exe new file mode 100644 index 0000000..ed62783 Binary files /dev/null and b/updateRanking.exe differ