feat(backup): 添加远端备份功能并优化备份管理
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"targets": [],
|
||||
"globalSettings": {
|
||||
"enabled": false,
|
||||
"uploadAfterBackup": false,
|
||||
"deleteLocalAfterUpload": false,
|
||||
"retryCount": 3,
|
||||
"retryDelay": 5000,
|
||||
"timeout": 300000
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,7 @@ const Consumable = sequelize.define('Consumable', {
|
||||
consumableId: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
allowNull: false
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
|
||||
@@ -14,7 +14,6 @@ const ConsumableLogArchive = sequelize.define('ConsumableLogArchive', {
|
||||
archiveId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '归档记录唯一标识'
|
||||
},
|
||||
consumableId: {
|
||||
|
||||
@@ -7,8 +7,7 @@ const ConsumableRecord = sequelize.define('ConsumableRecord', {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
allowNull: false
|
||||
},
|
||||
consumableId: {
|
||||
type: DataTypes.STRING,
|
||||
|
||||
Generated
+493
-50
@@ -8,7 +8,8 @@
|
||||
"name": "idc-backend",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.13.4",
|
||||
"axios": "^1.13.6",
|
||||
"basic-ftp": "^5.0.3",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"csv-parser": "^3.2.0",
|
||||
@@ -21,11 +22,14 @@
|
||||
"joi": "^18.0.2",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"multer": "^2.1.1",
|
||||
"mysql2": "^3.16.0",
|
||||
"mysql2": "^3.19.1",
|
||||
"node-cron": "^4.2.1",
|
||||
"sequelize": "^6.32.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"sequelize": "^6.37.8",
|
||||
"smb2": "^0.2.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"ssh2-sftp-client": "^9.1.0",
|
||||
"three": "^0.182.0",
|
||||
"webdav": "^5.3.1",
|
||||
"winston": "^3.19.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"xlsx": "^0.18.5"
|
||||
@@ -627,6 +631,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@buttercup/fetch": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@buttercup/fetch/-/fetch-0.2.1.tgz",
|
||||
"integrity": "sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"node-fetch": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
|
||||
@@ -1850,6 +1863,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
|
||||
"integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -2546,6 +2560,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
@@ -2594,13 +2617,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
|
||||
"integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==",
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -2707,7 +2730,12 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base-64": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
|
||||
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
@@ -2740,6 +2768,24 @@
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-ftp": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",
|
||||
"integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
|
||||
@@ -2923,6 +2969,15 @@
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buildcheck": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz",
|
||||
"integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@@ -2934,6 +2989,12 @@
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/byte-length": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/byte-length/-/byte-length-1.0.2.tgz",
|
||||
"integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -3125,6 +3186,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@@ -3433,6 +3503,20 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cpu-features": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
|
||||
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"buildcheck": "~0.0.6",
|
||||
"nan": "^2.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
@@ -3460,6 +3544,15 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/csv-parser": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz",
|
||||
@@ -3478,6 +3571,15 @@
|
||||
"integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
|
||||
@@ -3841,6 +3943,18 @@
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
@@ -3855,8 +3969,7 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
|
||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
@@ -4620,6 +4733,40 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz",
|
||||
"integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-expression-matcher": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz",
|
||||
"integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-builder": "^1.0.0",
|
||||
"strnum": "^2.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/fb-watchman": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
||||
@@ -4636,6 +4783,29 @@
|
||||
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@@ -4818,6 +4988,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
|
||||
@@ -5286,6 +5468,12 @@
|
||||
"hermes-estree": "0.25.1"
|
||||
}
|
||||
},
|
||||
"node_modules/hot-patcher": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hot-patcher/-/hot-patcher-2.0.1.tgz",
|
||||
"integrity": "sha512-ECg1JFG0YzehicQaogenlcs2qg6WsXQsxtnbr1i696u5tLUjtJdQAh0u2g0Q5YV45f263Ta1GnUJsc8WIfJf4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -5700,6 +5888,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-callable": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||
@@ -7137,6 +7331,12 @@
|
||||
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/layerr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/layerr/-/layerr-3.0.0.tgz",
|
||||
"integrity": "sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/leven": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
@@ -7292,9 +7492,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
|
||||
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
@@ -7369,6 +7569,17 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/md5": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"charenc": "0.0.2",
|
||||
"crypt": "0.0.2",
|
||||
"is-buffer": "~1.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -7654,29 +7865,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "3.16.0",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.0.tgz",
|
||||
"integrity": "sha512-AEGW7QLLSuSnjCS4pk3EIqOmogegmze9h8EyrndavUQnIUcfkVal/sK7QznE+a3bc6rzPbAiui9Jcb+96tPwYA==",
|
||||
"version": "3.19.1",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.19.1.tgz",
|
||||
"integrity": "sha512-yn4zh+Uxu5J3Zvi6Ao96lJ7BSBRkspHflWQAmOPND+htbpIKDQw99TTvPzgihKO/QyMickZopO4OsnixnpcUwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-ssl-profiles": "^1.1.1",
|
||||
"aws-ssl-profiles": "^1.1.2",
|
||||
"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"
|
||||
"iconv-lite": "^0.7.2",
|
||||
"long": "^5.3.2",
|
||||
"lru.min": "^1.1.4",
|
||||
"named-placeholders": "^1.1.6",
|
||||
"sql-escaper": "^1.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2/node_modules/iconv-lite": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
|
||||
"integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
|
||||
"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"
|
||||
@@ -7701,6 +7914,13 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.25.0.tgz",
|
||||
"integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
@@ -7739,10 +7959,16 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/nested-property": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz",
|
||||
"integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.85.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz",
|
||||
"integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==",
|
||||
"version": "3.88.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.88.0.tgz",
|
||||
"integrity": "sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
@@ -7766,6 +7992,44 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
|
||||
@@ -7915,6 +8179,14 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ntlm": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ntlm/-/ntlm-0.1.3.tgz",
|
||||
"integrity": "sha512-pPlHxhAegZP4QAaOYd51vRd6VXTGfF7VLKJwuwN0iEB1aIi3SnqXYuS/bH/6wWBOq+Ehdil49mHm1Nseon085w==",
|
||||
"engines": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -8241,6 +8513,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-expression-matcher": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz",
|
||||
"integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
@@ -8268,6 +8555,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-posix": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz",
|
||||
"integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
@@ -8371,6 +8664,7 @@
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
@@ -8473,7 +8767,6 @@
|
||||
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
|
||||
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"err-code": "^2.0.2",
|
||||
"retry": "^0.12.0"
|
||||
@@ -8579,6 +8872,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@@ -8718,6 +9017,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "2.0.0-next.5",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
|
||||
@@ -8764,7 +9069,6 @@
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
@@ -8924,15 +9228,10 @@
|
||||
"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/sequelize": {
|
||||
"version": "6.37.7",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.7.tgz",
|
||||
"integrity": "sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==",
|
||||
"version": "6.37.8",
|
||||
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.8.tgz",
|
||||
"integrity": "sha512-HJ0IQFqcTsTiqbEgiuioYFMSD00TP6Cz7zoTti+zVVBwVe9fEhev9cH6WnM3XU31+ABS356durAb99ZuOthnKw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -9281,6 +9580,17 @@
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/smb2": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/smb2/-/smb2-0.2.11.tgz",
|
||||
"integrity": "sha512-p9Lx72JBuQ+7+UQPDIzBywQ9x0QBk7Z77cJ3lJ3vW2mhr/U1NppSDTU9Abc+dZn9ie13CVh3z54Aa3zx8N+Aeg==",
|
||||
"engines": [
|
||||
"node"
|
||||
],
|
||||
"dependencies": {
|
||||
"ntlm": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
|
||||
@@ -9364,6 +9674,21 @@
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/sql-escaper": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz",
|
||||
"integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=2.0.0",
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/sqlite3": {
|
||||
"version": "5.1.7",
|
||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz",
|
||||
@@ -9388,15 +9713,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||
@@ -9409,6 +9725,37 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ssh2": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
|
||||
"integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"asn1": "^0.2.6",
|
||||
"bcrypt-pbkdf": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"cpu-features": "~0.0.10",
|
||||
"nan": "^2.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssh2-sftp-client": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-9.1.0.tgz",
|
||||
"integrity": "sha512-Hzdr9OE6GxZjcmyM9tgBSIFVyrHAp9c6U2Y4yBkmYOHoQvZ7pIm27dmltvcmRfxcWiIcg8HBvG5iAikDf+ZuzQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"concat-stream": "^2.0.0",
|
||||
"promise-retry": "^2.0.1",
|
||||
"ssh2": "^1.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.24.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ssri": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
|
||||
@@ -9683,6 +10030,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
|
||||
"integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "10.2.3",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz",
|
||||
@@ -9802,6 +10161,7 @@
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
@@ -9959,6 +10319,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -10229,6 +10595,25 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz",
|
||||
"integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -10296,6 +10681,64 @@
|
||||
"makeerror": "1.0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webdav": {
|
||||
"version": "5.9.0",
|
||||
"resolved": "https://registry.npmjs.org/webdav/-/webdav-5.9.0.tgz",
|
||||
"integrity": "sha512-OMJ6wtK1WvCO++aOLoQgE96S8KT4e5aaClWHmHXfFU369r4eyELN569B7EqT4OOUb99mmO58GkyuiCv/Ag6J0Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@buttercup/fetch": "^0.2.1",
|
||||
"base-64": "^1.0.0",
|
||||
"byte-length": "^1.0.2",
|
||||
"entities": "^6.0.1",
|
||||
"fast-xml-parser": "^5.3.4",
|
||||
"hot-patcher": "^2.0.1",
|
||||
"layerr": "^3.0.0",
|
||||
"md5": "^2.3.0",
|
||||
"minimatch": "^9.0.5",
|
||||
"nested-property": "^4.0.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"path-posix": "^1.0.0",
|
||||
"url-join": "^5.0.0",
|
||||
"url-parse": "^1.5.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/webdav/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webdav/node_modules/minimatch": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"format:check": "prettier --check \"**/*.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.4",
|
||||
"axios": "^1.13.6",
|
||||
"basic-ftp": "^5.0.3",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"csv-parser": "^3.2.0",
|
||||
@@ -27,11 +28,14 @@
|
||||
"joi": "^18.0.2",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"multer": "^2.1.1",
|
||||
"mysql2": "^3.16.0",
|
||||
"mysql2": "^3.19.1",
|
||||
"node-cron": "^4.2.1",
|
||||
"sequelize": "^6.32.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"sequelize": "^6.37.8",
|
||||
"smb2": "^0.2.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"ssh2-sftp-client": "^9.1.0",
|
||||
"three": "^0.182.0",
|
||||
"webdav": "^5.3.1",
|
||||
"winston": "^3.19.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"xlsx": "^0.18.5"
|
||||
|
||||
@@ -22,6 +22,16 @@ const {
|
||||
updateAutoBackupSettings,
|
||||
executeBackupNow,
|
||||
} = require('../utils/autoBackupScheduler');
|
||||
const {
|
||||
getAllTargets,
|
||||
getTarget,
|
||||
addTarget,
|
||||
updateTarget,
|
||||
deleteTarget,
|
||||
getGlobalSettings,
|
||||
updateGlobalSettings,
|
||||
} = require('../utils/remoteBackupConfig');
|
||||
const { testRemoteConnection, PROTOCOL_TYPES, PROTOCOL_LABELS } = require('../utils/remoteBackup');
|
||||
|
||||
const tempDir = path.join(__dirname, '..', 'temp');
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
@@ -548,4 +558,300 @@ router.post('/auto/test-cron', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 远端备份配置路由 ====================
|
||||
|
||||
// 获取所有远端备份目标
|
||||
router.get('/remote/targets', (req, res) => {
|
||||
try {
|
||||
const targets = getAllTargets();
|
||||
res.json({
|
||||
success: true,
|
||||
data: { targets },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取远端备份目标失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取远端备份目标失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取单个远端备份目标
|
||||
router.get('/remote/targets/:id', (req, res) => {
|
||||
try {
|
||||
const target = getTarget(req.params.id);
|
||||
|
||||
if (!target) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '目标不存在',
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { target },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取远端备份目标失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取远端备份目标失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 添加远端备份目标
|
||||
router.post('/remote/targets', (req, res) => {
|
||||
try {
|
||||
const targetData = req.body;
|
||||
|
||||
if (!targetData.name || !targetData.protocol) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请提供目标名称和协议类型',
|
||||
});
|
||||
}
|
||||
|
||||
const target = addTarget(targetData);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '远端备份目标已添加',
|
||||
data: { target },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('添加远端备份目标失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '添加远端备份目标失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新远端备份目标
|
||||
router.put('/remote/targets/:id', (req, res) => {
|
||||
try {
|
||||
const updates = req.body;
|
||||
const target = updateTarget(req.params.id, updates);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '远端备份目标已更新',
|
||||
data: { target },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新远端备份目标失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新远端备份目标失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 删除远端备份目标
|
||||
router.delete('/remote/targets/:id', (req, res) => {
|
||||
try {
|
||||
const deleted = deleteTarget(req.params.id);
|
||||
|
||||
if (!deleted) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '目标不存在',
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '远端备份目标已删除',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除远端备份目标失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除远端备份目标失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 测试远端连接
|
||||
router.post('/remote/targets/:id/test', async (req, res) => {
|
||||
try {
|
||||
const target = getTarget(req.params.id);
|
||||
|
||||
if (!target) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '目标不存在',
|
||||
});
|
||||
}
|
||||
|
||||
const result = await testRemoteConnection(target);
|
||||
|
||||
if (result.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: '连接测试成功',
|
||||
data: result,
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: result.message,
|
||||
error: result.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('测试远端连接失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '测试远端连接失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取远端备份全局设置
|
||||
router.get('/remote/settings', (req, res) => {
|
||||
try {
|
||||
const settings = getGlobalSettings();
|
||||
res.json({
|
||||
success: true,
|
||||
data: { settings },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取远端备份设置失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取远端备份设置失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新远端备份全局设置
|
||||
router.put('/remote/settings', (req, res) => {
|
||||
try {
|
||||
const settings = updateGlobalSettings(req.body);
|
||||
res.json({
|
||||
success: true,
|
||||
message: '远端备份设置已更新',
|
||||
data: { settings },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新远端备份设置失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新远端备份设置失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取支持的协议列表
|
||||
router.get('/remote/protocols', (req, res) => {
|
||||
try {
|
||||
const protocols = Object.entries(PROTOCOL_TYPES).map(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
label: PROTOCOL_LABELS[value],
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { protocols },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取协议列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取协议列表失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 手动上传备份到远端
|
||||
router.post('/remote/upload', async (req, res) => {
|
||||
try {
|
||||
const { filename, targetIds } = req.body;
|
||||
|
||||
if (!filename) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请提供备份文件名',
|
||||
});
|
||||
}
|
||||
|
||||
const backupPath = getBackupPath();
|
||||
const filePath = path.join(backupPath, filename);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '备份文件不存在',
|
||||
});
|
||||
}
|
||||
|
||||
const { uploadToRemote } = require('../utils/remoteBackup');
|
||||
const { getTarget } = require('../utils/remoteBackupConfig');
|
||||
|
||||
const targets = targetIds
|
||||
? targetIds.map(id => getTarget(id)).filter(Boolean)
|
||||
: getEnabledTargets();
|
||||
|
||||
if (targets.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '没有可用的远端目标',
|
||||
});
|
||||
}
|
||||
|
||||
const uploadResults = [];
|
||||
|
||||
for (const target of targets) {
|
||||
try {
|
||||
const remotePath = `${target.prefix || 'backups/'}${filename}`;
|
||||
const result = await uploadToRemote(target, filePath, remotePath);
|
||||
|
||||
uploadResults.push({
|
||||
targetId: target.id,
|
||||
targetName: target.name,
|
||||
success: true,
|
||||
...result,
|
||||
});
|
||||
} catch (error) {
|
||||
uploadResults.push({
|
||||
targetId: target.id,
|
||||
targetName: target.name,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '上传完成',
|
||||
data: {
|
||||
filename,
|
||||
results: uploadResults,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('手动上传备份失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '手动上传备份失败',
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ async function syncConsumableModels() {
|
||||
const ConsumableLogArchive = require('./models/ConsumableLogArchive');
|
||||
|
||||
await Promise.all([
|
||||
Consumable.sync({ alter: true }),
|
||||
Consumable.sync(), // 暂时禁用 alter 模式
|
||||
ConsumableLog.sync(),
|
||||
ConsumableCategory.sync(),
|
||||
ConsumableRecord.sync(),
|
||||
|
||||
@@ -7,6 +7,8 @@ const cron = require('node-cron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { createBackup, createIncrementalBackup, getBackupPath } = require('./backup');
|
||||
const { uploadToRemote } = require('./remoteBackup');
|
||||
const { getEnabledTargets, getGlobalSettings } = require('./remoteBackupConfig');
|
||||
|
||||
// 全局调度器存储
|
||||
const schedulers = new Map();
|
||||
@@ -108,6 +110,10 @@ function createAutoBackupTask(settings) {
|
||||
if (result) {
|
||||
console.log('自动备份完成:', result.filename);
|
||||
console.log(`备份类型:${result.isIncremental ? '增量备份' : '全量备份'}`);
|
||||
|
||||
// 上传到远端
|
||||
await uploadToRemoteTargets(result.path, result.filename);
|
||||
|
||||
console.log('========================\n');
|
||||
} else {
|
||||
console.log('无数据变化,跳过备份');
|
||||
@@ -234,6 +240,69 @@ function updateAutoBackupSettings(newSettings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传备份到所有启用的远端目标
|
||||
*/
|
||||
async function uploadToRemoteTargets(localFilePath, filename) {
|
||||
const globalSettings = getGlobalSettings();
|
||||
|
||||
if (!globalSettings.enabled || !globalSettings.uploadAfterBackup) {
|
||||
console.log('远端备份已禁用');
|
||||
return [];
|
||||
}
|
||||
|
||||
const enabledTargets = getEnabledTargets();
|
||||
|
||||
if (enabledTargets.length === 0) {
|
||||
console.log('没有启用的远端备份目标');
|
||||
return [];
|
||||
}
|
||||
|
||||
const uploadResults = [];
|
||||
|
||||
for (const target of enabledTargets) {
|
||||
try {
|
||||
console.log(`开始上传到目标:${target.name} (${target.protocol})`);
|
||||
|
||||
const remotePath = `${target.prefix || 'backups/'}${filename}`;
|
||||
|
||||
const result = await uploadToRemote(target, localFilePath, remotePath);
|
||||
|
||||
uploadResults.push({
|
||||
targetId: target.id,
|
||||
targetName: target.name,
|
||||
protocol: target.protocol,
|
||||
success: true,
|
||||
...result,
|
||||
});
|
||||
|
||||
console.log(`上传到 ${target.name} 成功`);
|
||||
} catch (error) {
|
||||
console.error(`上传到 ${target.name} 失败:`, error.message);
|
||||
uploadResults.push({
|
||||
targetId: target.id,
|
||||
targetName: target.name,
|
||||
protocol: target.protocol,
|
||||
success: false,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要删除本地文件
|
||||
const settings = getGlobalSettings();
|
||||
if (settings.deleteLocalAfterUpload && uploadResults.every(r => r.success)) {
|
||||
try {
|
||||
fs.unlinkSync(localFilePath);
|
||||
console.log('本地备份文件已删除');
|
||||
} catch (error) {
|
||||
console.error('删除本地备份文件失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
return uploadResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即执行一次备份
|
||||
*/
|
||||
@@ -251,8 +320,16 @@ async function executeBackupNow(options = {}) {
|
||||
});
|
||||
|
||||
console.log('手动备份完成:', result.filename);
|
||||
|
||||
// 上传到远端
|
||||
const uploadResults = await uploadToRemoteTargets(result.path, result.filename);
|
||||
|
||||
console.log('====================\n');
|
||||
return { success: true, result };
|
||||
return {
|
||||
success: true,
|
||||
result,
|
||||
remoteUploads: uploadResults,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('手动备份失败:', error);
|
||||
console.error('====================\n');
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* 远端备份上传工具模块
|
||||
* 支持 FTP、SFTP、WebDAV、SMB 等协议
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
|
||||
// 协议类型枚举
|
||||
const PROTOCOL_TYPES = {
|
||||
FTP: 'ftp',
|
||||
SFTP: 'sftp',
|
||||
WEBDAV: 'webdav',
|
||||
SMB: 'smb',
|
||||
};
|
||||
|
||||
// 协议显示名称映射
|
||||
const PROTOCOL_LABELS = {
|
||||
[PROTOCOL_TYPES.FTP]: 'FTP',
|
||||
[PROTOCOL_TYPES.SFTP]: 'SFTP (SSH 文件传输)',
|
||||
[PROTOCOL_TYPES.WEBDAV]: 'WebDAV',
|
||||
[PROTOCOL_TYPES.SMB]: 'SMB/CIFS 网络共享',
|
||||
};
|
||||
|
||||
/**
|
||||
* FTP 上传实现
|
||||
*/
|
||||
async function uploadViaFTP(config, localFilePath, remotePath) {
|
||||
const { Client } = require('basic-ftp');
|
||||
|
||||
const client = new Client();
|
||||
|
||||
try {
|
||||
await client.access({
|
||||
host: config.host,
|
||||
port: config.port || 21,
|
||||
user: config.username,
|
||||
password: config.password,
|
||||
secure: config.secure || false,
|
||||
secureOptions: {
|
||||
rejectUnauthorized: config.rejectUnauthorized !== false,
|
||||
},
|
||||
});
|
||||
|
||||
await client.cd(config.rootPath || '/');
|
||||
|
||||
const dirPath = path.dirname(remotePath);
|
||||
if (dirPath !== '.') {
|
||||
await ensureRemoteDir(client, dirPath, 'ftp');
|
||||
}
|
||||
|
||||
await client.uploadFrom(localFilePath, path.basename(remotePath));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `FTP 上传成功:${remotePath}`,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`FTP 上传失败:${error.message}`);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SFTP 上传实现
|
||||
*/
|
||||
async function uploadViaSFTP(config, localFilePath, remotePath) {
|
||||
const Client = require('ssh2-sftp-client');
|
||||
const client = new Client();
|
||||
|
||||
try {
|
||||
await client.connect({
|
||||
host: config.host,
|
||||
port: config.port || 22,
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
privateKey: config.privateKey ? fs.readFileSync(config.privateKey) : undefined,
|
||||
passphrase: config.passphrase,
|
||||
readyTimeout: config.timeout || 10000,
|
||||
});
|
||||
|
||||
const dirPath = path.dirname(remotePath);
|
||||
if (dirPath !== '.') {
|
||||
await ensureRemoteDir(client, dirPath, 'sftp');
|
||||
}
|
||||
|
||||
await client.put(fs.createReadStream(localFilePath), remotePath);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `SFTP 上传成功:${remotePath}`,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`SFTP 上传失败:${error.message}`);
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebDAV 上传实现
|
||||
*/
|
||||
async function uploadViaWebDAV(config, localFilePath, remotePath) {
|
||||
const { createClient } = require('webdav');
|
||||
|
||||
const client = createClient(config.url, {
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
authType: config.authType || 'password',
|
||||
headers: {
|
||||
'User-Agent': 'IDC-Backup-Client/1.0',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const dirPath = path.dirname(remotePath);
|
||||
if (dirPath !== '/') {
|
||||
await ensureRemoteDir(client, dirPath, 'webdav');
|
||||
}
|
||||
|
||||
const fileContent = fs.readFileSync(localFilePath);
|
||||
await client.putFileContents(remotePath, fileContent, {
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `WebDAV 上传成功:${remotePath}`,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`WebDAV 上传失败:${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SMB/CIFS 网络共享上传实现
|
||||
*/
|
||||
async function uploadViaSMB(config, localFilePath, remotePath) {
|
||||
const smb = require('smb2');
|
||||
|
||||
const client = new smb({
|
||||
share: `\\\\${config.host}\\${config.share}`,
|
||||
domain: config.domain || '',
|
||||
username: config.username,
|
||||
password: config.password,
|
||||
});
|
||||
|
||||
try {
|
||||
const dirPath = path.dirname(remotePath);
|
||||
if (dirPath !== '.') {
|
||||
await ensureRemoteDir(client, dirPath, 'smb');
|
||||
}
|
||||
|
||||
await client.writeFile(remotePath, fs.readFileSync(localFilePath));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `SMB 上传成功:${remotePath}`,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`SMB 上传失败:${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保远程目录存在
|
||||
*/
|
||||
async function ensureRemoteDir(client, dirPath, protocol) {
|
||||
try {
|
||||
if (protocol === 'ftp') {
|
||||
const dirs = dirPath.split('/').filter(d => d);
|
||||
for (const dir of dirs) {
|
||||
try {
|
||||
await client.cd(dir);
|
||||
} catch {
|
||||
await client.mkdir(dir);
|
||||
await client.cd(dir);
|
||||
}
|
||||
}
|
||||
} else if (protocol === 'sftp') {
|
||||
await client.mkdir(dirPath, true);
|
||||
} else if (protocol === 'webdav') {
|
||||
const parts = dirPath.split('/').filter(p => p);
|
||||
let currentPath = '';
|
||||
for (const part of parts) {
|
||||
currentPath += '/' + part;
|
||||
try {
|
||||
await client.createDirectory(currentPath);
|
||||
} catch (error) {
|
||||
if (!error.status || error.status !== 405) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (protocol === 'smb') {
|
||||
const parts = dirPath.split(path.sep).filter(p => p);
|
||||
let currentPath = '';
|
||||
for (const part of parts) {
|
||||
currentPath += part + path.sep;
|
||||
try {
|
||||
await client.exists(currentPath);
|
||||
} catch {
|
||||
await client.mkdir(currentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`创建远程目录失败 [${protocol}]:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主上传函数 - 根据配置选择对应协议
|
||||
*/
|
||||
async function uploadToRemote(config, localFilePath, remotePath) {
|
||||
console.log(`开始上传到远端 [${config.protocol}]: ${remotePath}`);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
let result;
|
||||
|
||||
switch (config.protocol) {
|
||||
case PROTOCOL_TYPES.FTP:
|
||||
result = await uploadViaFTP(config, localFilePath, remotePath);
|
||||
break;
|
||||
case PROTOCOL_TYPES.SFTP:
|
||||
result = await uploadViaSFTP(config, localFilePath, remotePath);
|
||||
break;
|
||||
case PROTOCOL_TYPES.WEBDAV:
|
||||
result = await uploadViaWebDAV(config, localFilePath, remotePath);
|
||||
break;
|
||||
case PROTOCOL_TYPES.SMB:
|
||||
result = await uploadViaSMB(config, localFilePath, remotePath);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`不支持的协议类型:${config.protocol}`);
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
const fileSize = fs.statSync(localFilePath).size;
|
||||
|
||||
console.log(`远端上传完成 [${config.protocol}] - 耗时:${duration}ms, 文件大小:${(fileSize / 1024).toFixed(2)}KB`);
|
||||
|
||||
return {
|
||||
...result,
|
||||
protocol: config.protocol,
|
||||
protocolLabel: PROTOCOL_LABELS[config.protocol],
|
||||
duration,
|
||||
fileSize,
|
||||
uploadedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试远端连接
|
||||
*/
|
||||
async function testRemoteConnection(config) {
|
||||
console.log(`测试远端连接 [${config.protocol}]...`);
|
||||
|
||||
try {
|
||||
const testContent = `IDC Backup Connection Test - ${new Date().toISOString()}`;
|
||||
const testFile = path.join(require('os').tmpdir(), `backup-test-${Date.now()}.txt`);
|
||||
fs.writeFileSync(testFile, testContent);
|
||||
|
||||
const testRemotePath = `test/backup-connection-test-${Date.now()}.txt`;
|
||||
|
||||
const result = await uploadToRemote(config, testFile, testRemotePath);
|
||||
|
||||
try {
|
||||
fs.unlinkSync(testFile);
|
||||
} catch (e) {
|
||||
console.warn('删除测试文件失败:', e.message);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '连接测试成功',
|
||||
details: result,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `连接测试失败:${error.message}`,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PROTOCOL_TYPES,
|
||||
PROTOCOL_LABELS,
|
||||
uploadToRemote,
|
||||
testRemoteConnection,
|
||||
uploadViaFTP,
|
||||
uploadViaSFTP,
|
||||
uploadViaWebDAV,
|
||||
uploadViaSMB,
|
||||
};
|
||||
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* 远端备份配置管理模块
|
||||
* 管理多个远端备份目标的配置
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { PROTOCOL_TYPES } = require('./remoteBackup');
|
||||
|
||||
// 配置文件路径
|
||||
const CONFIG_FILE = path.join(__dirname, '..', 'config', 'remote-backup-configs.json');
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG = {
|
||||
targets: [],
|
||||
globalSettings: {
|
||||
enabled: false,
|
||||
uploadAfterBackup: true,
|
||||
deleteLocalAfterUpload: false,
|
||||
retryCount: 3,
|
||||
retryDelay: 5000,
|
||||
timeout: 300000,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 加密敏感信息
|
||||
*/
|
||||
function encrypt(text) {
|
||||
if (!text) return '';
|
||||
const algorithm = 'aes-256-cbc';
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET || 'default-secret', 'salt', 32);
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
return iv.toString('hex') + ':' + encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密敏感信息
|
||||
*/
|
||||
function decrypt(text) {
|
||||
if (!text) return '';
|
||||
try {
|
||||
const algorithm = 'aes-256-cbc';
|
||||
const key = crypto.scryptSync(process.env.JWT_SECRET || 'default-secret', 'salt', 32);
|
||||
const parts = text.split(':');
|
||||
const iv = Buffer.from(parts[0], 'hex');
|
||||
const encrypted = parts[1];
|
||||
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
} catch (error) {
|
||||
console.error('解密失败:', error.message);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载配置
|
||||
*/
|
||||
function loadConfig() {
|
||||
try {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
const content = fs.readFileSync(CONFIG_FILE, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
return { ...DEFAULT_CONFIG, ...config };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载远端备份配置失败:', error);
|
||||
}
|
||||
return { ...DEFAULT_CONFIG };
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
function saveConfig(config) {
|
||||
try {
|
||||
const configDir = path.dirname(CONFIG_FILE);
|
||||
if (!fs.existsSync(configDir)) {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('保存远端备份配置失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有远端备份目标(隐藏敏感信息)
|
||||
*/
|
||||
function getAllTargets() {
|
||||
const config = loadConfig();
|
||||
return config.targets.map(target => ({
|
||||
...target,
|
||||
password: target.password ? '********' : undefined,
|
||||
accessKeySecret: target.accessKeySecret ? '********' : undefined,
|
||||
secretKey: target.secretKey ? '********' : undefined,
|
||||
privateKey: target.privateKey ? '********' : undefined,
|
||||
passphrase: target.passphrase ? '********' : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个目标(包含解密后的敏感信息)
|
||||
*/
|
||||
function getTarget(id) {
|
||||
const config = loadConfig();
|
||||
const target = config.targets.find(t => t.id === id);
|
||||
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...target,
|
||||
password: target.password ? decrypt(target.password) : undefined,
|
||||
accessKeySecret: target.accessKeySecret ? decrypt(target.accessKeySecret) : undefined,
|
||||
secretKey: target.secretKey ? decrypt(target.secretKey) : undefined,
|
||||
privateKey: target.privateKey ? decrypt(target.privateKey) : undefined,
|
||||
passphrase: target.passphrase ? decrypt(target.passphrase) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加远端备份目标
|
||||
*/
|
||||
function addTarget(targetData) {
|
||||
const config = loadConfig();
|
||||
|
||||
const newTarget = {
|
||||
id: `target_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
name: targetData.name,
|
||||
protocol: targetData.protocol,
|
||||
enabled: targetData.enabled !== false,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
switch (targetData.protocol) {
|
||||
case PROTOCOL_TYPES.FTP:
|
||||
case PROTOCOL_TYPES.SFTP:
|
||||
Object.assign(newTarget, {
|
||||
host: targetData.host,
|
||||
port: targetData.port,
|
||||
username: targetData.username,
|
||||
password: targetData.password ? encrypt(targetData.password) : undefined,
|
||||
rootPath: targetData.rootPath || '/',
|
||||
secure: targetData.secure,
|
||||
});
|
||||
break;
|
||||
|
||||
case PROTOCOL_TYPES.WEBDAV:
|
||||
Object.assign(newTarget, {
|
||||
url: targetData.url,
|
||||
username: targetData.username,
|
||||
password: targetData.password ? encrypt(targetData.password) : undefined,
|
||||
authType: targetData.authType || 'password',
|
||||
rootPath: targetData.rootPath || '/',
|
||||
});
|
||||
break;
|
||||
|
||||
case PROTOCOL_TYPES.SMB:
|
||||
Object.assign(newTarget, {
|
||||
host: targetData.host,
|
||||
share: targetData.share,
|
||||
domain: targetData.domain,
|
||||
username: targetData.username,
|
||||
password: targetData.password ? encrypt(targetData.password) : undefined,
|
||||
rootPath: targetData.rootPath || '/',
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`不支持的协议类型:${targetData.protocol}`);
|
||||
}
|
||||
|
||||
config.targets.push(newTarget);
|
||||
|
||||
if (saveConfig(config)) {
|
||||
return newTarget;
|
||||
}
|
||||
|
||||
throw new Error('保存配置失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新远端备份目标
|
||||
*/
|
||||
function updateTarget(id, updates) {
|
||||
const config = loadConfig();
|
||||
const targetIndex = config.targets.findIndex(t => t.id === id);
|
||||
|
||||
if (targetIndex === -1) {
|
||||
throw new Error('目标不存在');
|
||||
}
|
||||
|
||||
const existingTarget = config.targets[targetIndex];
|
||||
const updatedTarget = { ...existingTarget, ...updates, updatedAt: new Date().toISOString() };
|
||||
|
||||
if (updates.password) {
|
||||
updatedTarget.password = encrypt(updates.password);
|
||||
}
|
||||
if (updates.accessKeySecret) {
|
||||
updatedTarget.accessKeySecret = encrypt(updates.accessKeySecret);
|
||||
}
|
||||
if (updates.secretKey) {
|
||||
updatedTarget.secretKey = encrypt(updates.secretKey);
|
||||
}
|
||||
if (updates.privateKey) {
|
||||
updatedTarget.privateKey = encrypt(updates.privateKey);
|
||||
}
|
||||
if (updates.passphrase) {
|
||||
updatedTarget.passphrase = encrypt(updates.passphrase);
|
||||
}
|
||||
|
||||
config.targets[targetIndex] = updatedTarget;
|
||||
|
||||
if (saveConfig(config)) {
|
||||
return updatedTarget;
|
||||
}
|
||||
|
||||
throw new Error('保存配置失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除远端备份目标
|
||||
*/
|
||||
function deleteTarget(id) {
|
||||
const config = loadConfig();
|
||||
const initialLength = config.targets.length;
|
||||
|
||||
config.targets = config.targets.filter(t => t.id !== id);
|
||||
|
||||
if (config.targets.length < initialLength) {
|
||||
if (saveConfig(config)) {
|
||||
return true;
|
||||
}
|
||||
throw new Error('保存配置失败');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局设置
|
||||
*/
|
||||
function getGlobalSettings() {
|
||||
const config = loadConfig();
|
||||
return config.globalSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新全局设置
|
||||
*/
|
||||
function updateGlobalSettings(settings) {
|
||||
const config = loadConfig();
|
||||
config.globalSettings = { ...config.globalSettings, ...settings };
|
||||
|
||||
if (saveConfig(config)) {
|
||||
return config.globalSettings;
|
||||
}
|
||||
|
||||
throw new Error('保存配置失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的目标列表
|
||||
*/
|
||||
function getEnabledTargets() {
|
||||
const config = loadConfig();
|
||||
if (!config.globalSettings.enabled) {
|
||||
return [];
|
||||
}
|
||||
return config.targets.filter(t => t.enabled);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
getAllTargets,
|
||||
getTarget,
|
||||
addTarget,
|
||||
updateTarget,
|
||||
deleteTarget,
|
||||
getGlobalSettings,
|
||||
updateGlobalSettings,
|
||||
getEnabledTargets,
|
||||
encrypt,
|
||||
decrypt,
|
||||
};
|
||||
Reference in New Issue
Block a user