배치관리 중간커밋
This commit is contained in:
Generated
+343
-39
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.7.1",
|
||||
"@prisma/client": "^6.16.2",
|
||||
"axios": "^1.11.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"compression": "^1.7.4",
|
||||
@@ -24,7 +24,6 @@
|
||||
"mysql2": "^3.15.0",
|
||||
"nodemailer": "^6.9.7",
|
||||
"pg": "^8.16.3",
|
||||
"prisma": "^5.7.1",
|
||||
"redis": "^4.6.10",
|
||||
"winston": "^3.11.0"
|
||||
},
|
||||
@@ -50,6 +49,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"prettier": "^3.1.0",
|
||||
"prisma": "^6.16.2",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
@@ -1965,66 +1965,88 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
|
||||
"integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.2.tgz",
|
||||
"integrity": "sha512-E00PxBcalMfYO/TWnXobBVUai6eW/g5OsifWQsQDzJYm7yaY+IRLo7ZLsaefi0QkTpxfuhFcQ/w180i6kX3iJw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prisma": "*"
|
||||
"prisma": "*",
|
||||
"typescript": ">=5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"prisma": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.2.tgz",
|
||||
"integrity": "sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"c12": "3.1.0",
|
||||
"deepmerge-ts": "7.1.5",
|
||||
"effect": "3.16.12",
|
||||
"empathic": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
|
||||
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.2.tgz",
|
||||
"integrity": "sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
|
||||
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.2.tgz",
|
||||
"integrity": "sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.22.0",
|
||||
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||
"@prisma/fetch-engine": "5.22.0",
|
||||
"@prisma/get-platform": "5.22.0"
|
||||
"@prisma/debug": "6.16.2",
|
||||
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
|
||||
"@prisma/fetch-engine": "6.16.2",
|
||||
"@prisma/get-platform": "6.16.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
|
||||
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
|
||||
"version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43.tgz",
|
||||
"integrity": "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
|
||||
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.2.tgz",
|
||||
"integrity": "sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.22.0",
|
||||
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||
"@prisma/get-platform": "5.22.0"
|
||||
"@prisma/debug": "6.16.2",
|
||||
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
|
||||
"@prisma/get-platform": "6.16.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
|
||||
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.2.tgz",
|
||||
"integrity": "sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.22.0"
|
||||
"@prisma/debug": "6.16.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
@@ -2764,6 +2786,13 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
@@ -3942,6 +3971,65 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/c12": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
|
||||
"integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"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"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"magicast": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/c12/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -4093,6 +4181,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/citty": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"consola": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/cjs-module-lexer": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
|
||||
@@ -4295,6 +4393,23 @@
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
|
||||
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@@ -4458,6 +4573,23 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deepmerge-ts": {
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
||||
"integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/defu": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -4485,6 +4617,13 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
@@ -4662,6 +4801,17 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "3.16.12",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz",
|
||||
"integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
"fast-check": "^3.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.207",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz",
|
||||
@@ -4689,6 +4839,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/empathic": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
|
||||
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/enabled": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
|
||||
@@ -5125,6 +5285,36 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
|
||||
"integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "3.23.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
|
||||
"integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
|
||||
"devOptional": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pure-rand": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -5413,6 +5603,7 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -5530,6 +5721,24 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/giget": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -6672,6 +6881,16 @@
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz",
|
||||
"integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "17.13.3",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
|
||||
@@ -7273,6 +7492,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch-native": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
|
||||
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -7395,6 +7621,26 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||
"integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"citty": "^0.1.6",
|
||||
"consola": "^3.4.2",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"tinyexec": "^1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"nypm": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.16.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -7416,6 +7662,13 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/ohash": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -7626,6 +7879,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.16.3",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||
@@ -7814,6 +8081,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.2",
|
||||
"exsolve": "^1.0.7",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
@@ -7908,22 +8187,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
||||
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
||||
"version": "6.16.2",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.2.tgz",
|
||||
"integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/engines": "5.22.0"
|
||||
"@prisma/config": "6.16.2",
|
||||
"@prisma/engines": "6.16.2"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.3"
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
@@ -7986,7 +8272,7 @@
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -8059,6 +8345,17 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/rc9": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"destr": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@@ -8838,6 +9135,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
|
||||
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
@@ -9075,7 +9379,7 @@
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.7.1",
|
||||
"@prisma/client": "^6.16.2",
|
||||
"axios": "^1.11.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"compression": "^1.7.4",
|
||||
@@ -42,7 +42,6 @@
|
||||
"mysql2": "^3.15.0",
|
||||
"nodemailer": "^6.9.7",
|
||||
"pg": "^8.16.3",
|
||||
"prisma": "^5.7.1",
|
||||
"redis": "^4.6.10",
|
||||
"winston": "^3.11.0"
|
||||
},
|
||||
@@ -68,6 +67,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"prettier": "^3.1.0",
|
||||
"prisma": "^6.16.2",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -65,6 +65,58 @@ model external_db_connections {
|
||||
@@index([connection_name], map: "idx_external_db_connections_name")
|
||||
}
|
||||
|
||||
// 배치관리 테이블들
|
||||
model batch_configs {
|
||||
id Int @id @default(autoincrement())
|
||||
batch_name String @db.VarChar(100)
|
||||
description String?
|
||||
cron_schedule String @db.VarChar(50) // 크론탭 형식
|
||||
is_active String? @default("Y") @db.Char(1)
|
||||
company_code String? @default("*") @db.VarChar(20)
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
updated_date DateTime? @default(now()) @updatedAt @db.Timestamp(6)
|
||||
updated_by String? @db.VarChar(50)
|
||||
|
||||
// 관계 설정
|
||||
batch_mappings batch_mappings[]
|
||||
|
||||
@@index([batch_name], map: "idx_batch_configs_name")
|
||||
@@index([is_active], map: "idx_batch_configs_active")
|
||||
}
|
||||
|
||||
model batch_mappings {
|
||||
id Int @id @default(autoincrement())
|
||||
batch_config_id Int
|
||||
|
||||
// FROM 정보
|
||||
from_connection_type String @db.VarChar(20) // 'internal' 또는 'external'
|
||||
from_connection_id Int? // external_db_connections.id (외부 DB인 경우)
|
||||
from_table_name String @db.VarChar(100)
|
||||
from_column_name String @db.VarChar(100)
|
||||
from_column_type String? @db.VarChar(50)
|
||||
|
||||
// TO 정보
|
||||
to_connection_type String @db.VarChar(20) // 'internal' 또는 'external'
|
||||
to_connection_id Int? // external_db_connections.id (외부 DB인 경우)
|
||||
to_table_name String @db.VarChar(100)
|
||||
to_column_name String @db.VarChar(100)
|
||||
to_column_type String? @db.VarChar(50)
|
||||
|
||||
// 매핑 순서 (같은 FROM 컬럼에서 여러 TO로 매핑될 때 순서)
|
||||
mapping_order Int? @default(1)
|
||||
|
||||
created_date DateTime? @default(now()) @db.Timestamp(6)
|
||||
created_by String? @db.VarChar(50)
|
||||
|
||||
// 관계 설정
|
||||
batch_config batch_configs @relation(fields: [batch_config_id], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([batch_config_id], map: "idx_batch_mappings_config")
|
||||
@@index([from_connection_type, from_connection_id], map: "idx_batch_mappings_from")
|
||||
@@index([to_connection_type, to_connection_id], map: "idx_batch_mappings_to")
|
||||
}
|
||||
|
||||
model admin_supply_mng {
|
||||
objid Decimal @id @default(0) @db.Decimal
|
||||
supply_code String? @default("NULL::character varying") @db.VarChar(100)
|
||||
|
||||
@@ -31,6 +31,7 @@ import layoutRoutes from "./routes/layoutRoutes";
|
||||
import dataRoutes from "./routes/dataRoutes";
|
||||
import testButtonDataflowRoutes from "./routes/testButtonDataflowRoutes";
|
||||
import externalDbConnectionRoutes from "./routes/externalDbConnectionRoutes";
|
||||
import batchRoutes from "./routes/batchRoutes";
|
||||
import ddlRoutes from "./routes/ddlRoutes";
|
||||
import entityReferenceRoutes from "./routes/entityReferenceRoutes";
|
||||
// import userRoutes from './routes/userRoutes';
|
||||
@@ -127,6 +128,7 @@ app.use("/api/screen", screenStandardRoutes);
|
||||
app.use("/api/data", dataRoutes);
|
||||
app.use("/api/test-button-dataflow", testButtonDataflowRoutes);
|
||||
app.use("/api/external-db-connections", externalDbConnectionRoutes);
|
||||
app.use("/api/batch-configs", batchRoutes);
|
||||
app.use("/api/ddl", ddlRoutes);
|
||||
app.use("/api/entity-reference", entityReferenceRoutes);
|
||||
// app.use('/api/users', userRoutes);
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
// 배치관리 컨트롤러
|
||||
// 작성일: 2024-12-24
|
||||
|
||||
import { Request, Response } from "express";
|
||||
import { BatchService } from "../services/batchService";
|
||||
import { BatchConfigFilter, BatchMappingRequest } from "../types/batchTypes";
|
||||
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
user?: {
|
||||
userId: string;
|
||||
username: string;
|
||||
companyCode: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class BatchController {
|
||||
/**
|
||||
* 배치 설정 목록 조회
|
||||
* GET /api/batch-configs
|
||||
*/
|
||||
static async getBatchConfigs(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const filter: BatchConfigFilter = {
|
||||
is_active: req.query.is_active as string,
|
||||
company_code: req.query.company_code as string,
|
||||
search: req.query.search as string,
|
||||
};
|
||||
|
||||
// 빈 값 제거
|
||||
Object.keys(filter).forEach((key) => {
|
||||
if (!filter[key as keyof BatchConfigFilter]) {
|
||||
delete filter[key as keyof BatchConfigFilter];
|
||||
}
|
||||
});
|
||||
|
||||
const result = await BatchService.getBatchConfigs(filter);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
} else {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("배치 설정 목록 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 배치 설정 조회
|
||||
* GET /api/batch-configs/:id
|
||||
*/
|
||||
static async getBatchConfigById(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "유효하지 않은 배치 설정 ID입니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await BatchService.getBatchConfigById(id);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
} else {
|
||||
return res.status(404).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("배치 설정 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 설정 생성
|
||||
* POST /api/batch-configs
|
||||
*/
|
||||
static async createBatchConfig(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const data: BatchMappingRequest = req.body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!data.batch_name || !data.cron_schedule || !data.mappings) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "필수 필드가 누락되었습니다. (batch_name, cron_schedule, mappings)",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await BatchService.createBatchConfig(
|
||||
data,
|
||||
req.user?.userId
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(201).json(result);
|
||||
} else {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("배치 설정 생성 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 설정 수정
|
||||
* PUT /api/batch-configs/:id
|
||||
*/
|
||||
static async updateBatchConfig(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
const data: Partial<BatchMappingRequest> = req.body;
|
||||
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "유효하지 않은 배치 설정 ID입니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await BatchService.updateBatchConfig(
|
||||
id,
|
||||
data,
|
||||
req.user?.userId
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
} else {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("배치 설정 수정 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 설정 삭제
|
||||
* DELETE /api/batch-configs/:id
|
||||
*/
|
||||
static async deleteBatchConfig(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
|
||||
if (isNaN(id)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "유효하지 않은 배치 설정 ID입니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await BatchService.deleteBatchConfig(
|
||||
id,
|
||||
req.user?.userId
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
} else {
|
||||
return res.status(404).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("배치 설정 삭제 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용 가능한 커넥션 목록 조회
|
||||
* GET /api/batch-configs/connections
|
||||
*/
|
||||
static async getAvailableConnections(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const result = await BatchService.getAvailableConnections();
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
} else {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("커넥션 목록 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 커넥션의 테이블 목록 조회
|
||||
* GET /api/batch-configs/connections/:type/tables
|
||||
* GET /api/batch-configs/connections/:type/:id/tables
|
||||
*/
|
||||
static async getTablesFromConnection(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const connectionType = req.params.type as 'internal' | 'external';
|
||||
const connectionId = req.params.id ? parseInt(req.params.id) : undefined;
|
||||
|
||||
if (connectionType !== 'internal' && connectionType !== 'external') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "유효하지 않은 커넥션 타입입니다. (internal 또는 external)",
|
||||
});
|
||||
}
|
||||
|
||||
if (connectionType === 'external' && (!connectionId || isNaN(connectionId))) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "외부 커넥션의 경우 유효한 커넥션 ID가 필요합니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await BatchService.getTablesFromConnection(
|
||||
connectionType,
|
||||
connectionId
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
} else {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("테이블 목록 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 테이블의 컬럼 정보 조회
|
||||
* GET /api/batch-configs/connections/:type/tables/:tableName/columns
|
||||
* GET /api/batch-configs/connections/:type/:id/tables/:tableName/columns
|
||||
*/
|
||||
static async getTableColumns(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const connectionType = req.params.type as 'internal' | 'external';
|
||||
const connectionId = req.params.id ? parseInt(req.params.id) : undefined;
|
||||
const tableName = req.params.tableName;
|
||||
|
||||
if (connectionType !== 'internal' && connectionType !== 'external') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "유효하지 않은 커넥션 타입입니다. (internal 또는 external)",
|
||||
});
|
||||
}
|
||||
|
||||
if (connectionType === 'external' && (!connectionId || isNaN(connectionId))) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "외부 커넥션의 경우 유효한 커넥션 ID가 필요합니다.",
|
||||
});
|
||||
}
|
||||
|
||||
if (!tableName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "테이블명이 필요합니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await BatchService.getTableColumns(
|
||||
connectionType,
|
||||
tableName,
|
||||
connectionId
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
} else {
|
||||
return res.status(400).json(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("컬럼 정보 조회 오류:", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "서버 내부 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 배치관리 라우트
|
||||
// 작성일: 2024-12-24
|
||||
|
||||
import { Router } from "express";
|
||||
import { BatchController } from "../controllers/batchController";
|
||||
import { authenticateToken } from "../middleware/auth";
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* GET /api/batch-configs
|
||||
* 배치 설정 목록 조회
|
||||
*/
|
||||
router.get("/", authenticateToken, BatchController.getBatchConfigs);
|
||||
|
||||
/**
|
||||
* GET /api/batch-configs/connections
|
||||
* 사용 가능한 커넥션 목록 조회
|
||||
*/
|
||||
router.get("/connections", authenticateToken, BatchController.getAvailableConnections);
|
||||
|
||||
/**
|
||||
* GET /api/batch-configs/connections/:type/tables
|
||||
* 내부 DB 테이블 목록 조회
|
||||
*/
|
||||
router.get("/connections/:type/tables", authenticateToken, BatchController.getTablesFromConnection);
|
||||
|
||||
/**
|
||||
* GET /api/batch-configs/connections/:type/:id/tables
|
||||
* 외부 DB 테이블 목록 조회
|
||||
*/
|
||||
router.get("/connections/:type/:id/tables", authenticateToken, BatchController.getTablesFromConnection);
|
||||
|
||||
/**
|
||||
* GET /api/batch-configs/connections/:type/tables/:tableName/columns
|
||||
* 내부 DB 테이블 컬럼 정보 조회
|
||||
*/
|
||||
router.get("/connections/:type/tables/:tableName/columns", authenticateToken, BatchController.getTableColumns);
|
||||
|
||||
/**
|
||||
* GET /api/batch-configs/connections/:type/:id/tables/:tableName/columns
|
||||
* 외부 DB 테이블 컬럼 정보 조회
|
||||
*/
|
||||
router.get("/connections/:type/:id/tables/:tableName/columns", authenticateToken, BatchController.getTableColumns);
|
||||
|
||||
/**
|
||||
* GET /api/batch-configs/:id
|
||||
* 특정 배치 설정 조회
|
||||
*/
|
||||
router.get("/:id", authenticateToken, BatchController.getBatchConfigById);
|
||||
|
||||
/**
|
||||
* POST /api/batch-configs
|
||||
* 배치 설정 생성
|
||||
*/
|
||||
router.post("/", authenticateToken, BatchController.createBatchConfig);
|
||||
|
||||
/**
|
||||
* PUT /api/batch-configs/:id
|
||||
* 배치 설정 수정
|
||||
*/
|
||||
router.put("/:id", authenticateToken, BatchController.updateBatchConfig);
|
||||
|
||||
/**
|
||||
* DELETE /api/batch-configs/:id
|
||||
* 배치 설정 삭제 (논리 삭제)
|
||||
*/
|
||||
router.delete("/:id", authenticateToken, BatchController.deleteBatchConfig);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,550 @@
|
||||
// 배치관리 서비스
|
||||
// 작성일: 2024-12-24
|
||||
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import {
|
||||
BatchConfig,
|
||||
BatchMapping,
|
||||
BatchConfigFilter,
|
||||
BatchMappingRequest,
|
||||
BatchValidationResult,
|
||||
ApiResponse,
|
||||
ConnectionInfo,
|
||||
TableInfo,
|
||||
ColumnInfo,
|
||||
} from "../types/batchTypes";
|
||||
import { ExternalDbConnectionService } from "./externalDbConnectionService";
|
||||
import { DbConnectionManager } from "./dbConnectionManager";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export class BatchService {
|
||||
/**
|
||||
* 배치 설정 목록 조회
|
||||
*/
|
||||
static async getBatchConfigs(
|
||||
filter: BatchConfigFilter
|
||||
): Promise<ApiResponse<BatchConfig[]>> {
|
||||
try {
|
||||
const where: any = {};
|
||||
|
||||
// 필터 조건 적용
|
||||
if (filter.is_active) {
|
||||
where.is_active = filter.is_active;
|
||||
}
|
||||
|
||||
if (filter.company_code) {
|
||||
where.company_code = filter.company_code;
|
||||
}
|
||||
|
||||
// 검색 조건 적용
|
||||
if (filter.search && filter.search.trim()) {
|
||||
where.OR = [
|
||||
{
|
||||
batch_name: {
|
||||
contains: filter.search.trim(),
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: {
|
||||
contains: filter.search.trim(),
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const batchConfigs = await prisma.batch_configs.findMany({
|
||||
where,
|
||||
include: {
|
||||
batch_mappings: true,
|
||||
},
|
||||
orderBy: [{ is_active: "desc" }, { batch_name: "asc" }],
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: batchConfigs as BatchConfig[],
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("배치 설정 목록 조회 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정 목록 조회에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 배치 설정 조회
|
||||
*/
|
||||
static async getBatchConfigById(
|
||||
id: number
|
||||
): Promise<ApiResponse<BatchConfig>> {
|
||||
try {
|
||||
const batchConfig = await prisma.batch_configs.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
batch_mappings: {
|
||||
orderBy: [
|
||||
{ from_table_name: "asc" },
|
||||
{ from_column_name: "asc" },
|
||||
{ mapping_order: "asc" },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!batchConfig) {
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정을 찾을 수 없습니다.",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: batchConfig as BatchConfig,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("배치 설정 조회 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정 조회에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 설정 생성
|
||||
*/
|
||||
static async createBatchConfig(
|
||||
data: BatchMappingRequest,
|
||||
userId?: string
|
||||
): Promise<ApiResponse<BatchConfig>> {
|
||||
try {
|
||||
// 매핑 유효성 검사
|
||||
const validation = await this.validateBatchMappings(data.mappings);
|
||||
if (!validation.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: "매핑 유효성 검사 실패",
|
||||
error: validation.errors.join(", "),
|
||||
};
|
||||
}
|
||||
|
||||
// 트랜잭션으로 배치 설정과 매핑 생성
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// 배치 설정 생성
|
||||
const batchConfig = await tx.batch_configs.create({
|
||||
data: {
|
||||
batch_name: data.batch_name,
|
||||
description: data.description,
|
||||
cron_schedule: data.cron_schedule,
|
||||
created_by: userId,
|
||||
updated_by: userId,
|
||||
},
|
||||
});
|
||||
|
||||
// 배치 매핑 생성
|
||||
const mappings = await Promise.all(
|
||||
data.mappings.map((mapping, index) =>
|
||||
tx.batch_mappings.create({
|
||||
data: {
|
||||
batch_config_id: batchConfig.id,
|
||||
from_connection_type: mapping.from_connection_type,
|
||||
from_connection_id: mapping.from_connection_id,
|
||||
from_table_name: mapping.from_table_name,
|
||||
from_column_name: mapping.from_column_name,
|
||||
from_column_type: mapping.from_column_type,
|
||||
to_connection_type: mapping.to_connection_type,
|
||||
to_connection_id: mapping.to_connection_id,
|
||||
to_table_name: mapping.to_table_name,
|
||||
to_column_name: mapping.to_column_name,
|
||||
to_column_type: mapping.to_column_type,
|
||||
mapping_order: mapping.mapping_order || index + 1,
|
||||
created_by: userId,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
...batchConfig,
|
||||
batch_mappings: mappings,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result as BatchConfig,
|
||||
message: "배치 설정이 성공적으로 생성되었습니다.",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("배치 설정 생성 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정 생성에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 설정 수정
|
||||
*/
|
||||
static async updateBatchConfig(
|
||||
id: number,
|
||||
data: Partial<BatchMappingRequest>,
|
||||
userId?: string
|
||||
): Promise<ApiResponse<BatchConfig>> {
|
||||
try {
|
||||
// 기존 배치 설정 확인
|
||||
const existingConfig = await prisma.batch_configs.findUnique({
|
||||
where: { id },
|
||||
include: { batch_mappings: true },
|
||||
});
|
||||
|
||||
if (!existingConfig) {
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정을 찾을 수 없습니다.",
|
||||
};
|
||||
}
|
||||
|
||||
// 매핑이 제공된 경우 유효성 검사
|
||||
if (data.mappings) {
|
||||
const validation = await this.validateBatchMappings(data.mappings);
|
||||
if (!validation.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: "매핑 유효성 검사 실패",
|
||||
error: validation.errors.join(", "),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 트랜잭션으로 업데이트
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// 배치 설정 업데이트
|
||||
const updateData: any = {
|
||||
updated_by: userId,
|
||||
};
|
||||
|
||||
if (data.batch_name) updateData.batch_name = data.batch_name;
|
||||
if (data.description !== undefined) updateData.description = data.description;
|
||||
if (data.cron_schedule) updateData.cron_schedule = data.cron_schedule;
|
||||
|
||||
const batchConfig = await tx.batch_configs.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
// 매핑이 제공된 경우 기존 매핑 삭제 후 새로 생성
|
||||
if (data.mappings) {
|
||||
await tx.batch_mappings.deleteMany({
|
||||
where: { batch_config_id: id },
|
||||
});
|
||||
|
||||
const mappings = await Promise.all(
|
||||
data.mappings.map((mapping, index) =>
|
||||
tx.batch_mappings.create({
|
||||
data: {
|
||||
batch_config_id: id,
|
||||
from_connection_type: mapping.from_connection_type,
|
||||
from_connection_id: mapping.from_connection_id,
|
||||
from_table_name: mapping.from_table_name,
|
||||
from_column_name: mapping.from_column_name,
|
||||
from_column_type: mapping.from_column_type,
|
||||
to_connection_type: mapping.to_connection_type,
|
||||
to_connection_id: mapping.to_connection_id,
|
||||
to_table_name: mapping.to_table_name,
|
||||
to_column_name: mapping.to_column_name,
|
||||
to_column_type: mapping.to_column_type,
|
||||
mapping_order: mapping.mapping_order || index + 1,
|
||||
created_by: userId,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
...batchConfig,
|
||||
batch_mappings: mappings,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...batchConfig,
|
||||
batch_mappings: existingConfig.batch_mappings,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result as BatchConfig,
|
||||
message: "배치 설정이 성공적으로 수정되었습니다.",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("배치 설정 수정 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정 수정에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 설정 삭제 (논리 삭제)
|
||||
*/
|
||||
static async deleteBatchConfig(
|
||||
id: number,
|
||||
userId?: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
try {
|
||||
const existingConfig = await prisma.batch_configs.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existingConfig) {
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정을 찾을 수 없습니다.",
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.batch_configs.update({
|
||||
where: { id },
|
||||
data: {
|
||||
is_active: "N",
|
||||
updated_by: userId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "배치 설정이 성공적으로 삭제되었습니다.",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("배치 설정 삭제 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "배치 설정 삭제에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용 가능한 커넥션 목록 조회
|
||||
*/
|
||||
static async getAvailableConnections(): Promise<ApiResponse<ConnectionInfo[]>> {
|
||||
try {
|
||||
const connections: ConnectionInfo[] = [];
|
||||
|
||||
// 내부 DB 추가
|
||||
connections.push({
|
||||
type: 'internal',
|
||||
name: 'Internal Database',
|
||||
db_type: 'postgresql',
|
||||
});
|
||||
|
||||
// 외부 DB 연결 조회
|
||||
const externalConnections = await ExternalDbConnectionService.getConnections({
|
||||
is_active: 'Y',
|
||||
});
|
||||
|
||||
if (externalConnections.success && externalConnections.data) {
|
||||
externalConnections.data.forEach((conn) => {
|
||||
connections.push({
|
||||
type: 'external',
|
||||
id: conn.id,
|
||||
name: conn.connection_name,
|
||||
db_type: conn.db_type,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: connections,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("커넥션 목록 조회 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "커넥션 목록 조회에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 커넥션의 테이블 목록 조회
|
||||
*/
|
||||
static async getTablesFromConnection(
|
||||
connectionType: 'internal' | 'external',
|
||||
connectionId?: number
|
||||
): Promise<ApiResponse<string[]>> {
|
||||
try {
|
||||
let tables: string[] = [];
|
||||
|
||||
if (connectionType === 'internal') {
|
||||
// 내부 DB 테이블 조회
|
||||
const result = await prisma.$queryRaw<Array<{ table_name: string }>>`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_type = 'BASE TABLE'
|
||||
ORDER BY table_name
|
||||
`;
|
||||
tables = result.map(row => row.table_name);
|
||||
} else if (connectionType === 'external' && connectionId) {
|
||||
// 외부 DB 테이블 조회
|
||||
const tablesResult = await ExternalDbConnectionService.getTables(connectionId);
|
||||
if (tablesResult.success && tablesResult.data) {
|
||||
tables = tablesResult.data;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: tables,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("테이블 목록 조회 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "테이블 목록 조회에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 테이블의 컬럼 정보 조회
|
||||
*/
|
||||
static async getTableColumns(
|
||||
connectionType: 'internal' | 'external',
|
||||
tableName: string,
|
||||
connectionId?: number
|
||||
): Promise<ApiResponse<ColumnInfo[]>> {
|
||||
try {
|
||||
let columns: ColumnInfo[] = [];
|
||||
|
||||
if (connectionType === 'internal') {
|
||||
// 내부 DB 컬럼 조회
|
||||
const result = await prisma.$queryRaw<Array<{
|
||||
column_name: string;
|
||||
data_type: string;
|
||||
is_nullable: string;
|
||||
column_default: string | null;
|
||||
}>>`
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable,
|
||||
column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = ${tableName}
|
||||
ORDER BY ordinal_position
|
||||
`;
|
||||
|
||||
columns = result.map(row => ({
|
||||
column_name: row.column_name,
|
||||
data_type: row.data_type,
|
||||
is_nullable: row.is_nullable === 'YES',
|
||||
column_default: row.column_default,
|
||||
}));
|
||||
} else if (connectionType === 'external' && connectionId) {
|
||||
// 외부 DB 컬럼 조회
|
||||
const columnsResult = await ExternalDbConnectionService.getTableColumns(
|
||||
connectionId,
|
||||
tableName
|
||||
);
|
||||
if (columnsResult.success && columnsResult.data) {
|
||||
columns = columnsResult.data.map(col => ({
|
||||
column_name: col.column_name,
|
||||
data_type: col.data_type,
|
||||
is_nullable: col.is_nullable,
|
||||
column_default: col.column_default,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: columns,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("컬럼 정보 조회 오류:", error);
|
||||
return {
|
||||
success: false,
|
||||
message: "컬럼 정보 조회에 실패했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 배치 매핑 유효성 검사
|
||||
*/
|
||||
private static async validateBatchMappings(
|
||||
mappings: BatchMapping[]
|
||||
): Promise<BatchValidationResult> {
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
if (!mappings || mappings.length === 0) {
|
||||
errors.push("최소 하나 이상의 매핑이 필요합니다.");
|
||||
return { isValid: false, errors, warnings };
|
||||
}
|
||||
|
||||
// n:1 매핑 검사 (여러 FROM이 같은 TO로 매핑되는 것 방지)
|
||||
const toMappings = new Map<string, number>();
|
||||
|
||||
mappings.forEach((mapping, index) => {
|
||||
const toKey = `${mapping.to_connection_type}:${mapping.to_connection_id || 'internal'}:${mapping.to_table_name}:${mapping.to_column_name}`;
|
||||
|
||||
if (toMappings.has(toKey)) {
|
||||
errors.push(
|
||||
`매핑 ${index + 1}: TO 컬럼 '${mapping.to_table_name}.${mapping.to_column_name}'에 중복 매핑이 있습니다. n:1 매핑은 허용되지 않습니다.`
|
||||
);
|
||||
} else {
|
||||
toMappings.set(toKey, index);
|
||||
}
|
||||
});
|
||||
|
||||
// 1:n 매핑 경고 (같은 FROM에서 여러 TO로 매핑)
|
||||
const fromMappings = new Map<string, number[]>();
|
||||
|
||||
mappings.forEach((mapping, index) => {
|
||||
const fromKey = `${mapping.from_connection_type}:${mapping.from_connection_id || 'internal'}:${mapping.from_table_name}:${mapping.from_column_name}`;
|
||||
|
||||
if (!fromMappings.has(fromKey)) {
|
||||
fromMappings.set(fromKey, []);
|
||||
}
|
||||
fromMappings.get(fromKey)!.push(index);
|
||||
});
|
||||
|
||||
fromMappings.forEach((indices, fromKey) => {
|
||||
if (indices.length > 1) {
|
||||
const [, , tableName, columnName] = fromKey.split(':');
|
||||
warnings.push(
|
||||
`FROM 컬럼 '${tableName}.${columnName}'에서 ${indices.length}개의 TO 컬럼으로 매핑됩니다. (1:n 매핑)`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 배치관리 타입 정의
|
||||
// 작성일: 2024-12-24
|
||||
|
||||
export interface BatchConfig {
|
||||
id?: number;
|
||||
batch_name: string;
|
||||
description?: string;
|
||||
cron_schedule: string;
|
||||
is_active?: string;
|
||||
company_code?: string;
|
||||
created_date?: Date;
|
||||
created_by?: string;
|
||||
updated_date?: Date;
|
||||
updated_by?: string;
|
||||
batch_mappings?: BatchMapping[];
|
||||
}
|
||||
|
||||
export interface BatchMapping {
|
||||
id?: number;
|
||||
batch_config_id?: number;
|
||||
|
||||
// FROM 정보
|
||||
from_connection_type: 'internal' | 'external';
|
||||
from_connection_id?: number;
|
||||
from_table_name: string;
|
||||
from_column_name: string;
|
||||
from_column_type?: string;
|
||||
|
||||
// TO 정보
|
||||
to_connection_type: 'internal' | 'external';
|
||||
to_connection_id?: number;
|
||||
to_table_name: string;
|
||||
to_column_name: string;
|
||||
to_column_type?: string;
|
||||
|
||||
mapping_order?: number;
|
||||
created_date?: Date;
|
||||
created_by?: string;
|
||||
}
|
||||
|
||||
export interface BatchConfigFilter {
|
||||
batch_name?: string;
|
||||
is_active?: string;
|
||||
company_code?: string;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface ConnectionInfo {
|
||||
type: 'internal' | 'external';
|
||||
id?: number;
|
||||
name: string;
|
||||
db_type?: string;
|
||||
}
|
||||
|
||||
export interface TableInfo {
|
||||
table_name: string;
|
||||
columns: ColumnInfo[];
|
||||
}
|
||||
|
||||
export interface ColumnInfo {
|
||||
column_name: string;
|
||||
data_type: string;
|
||||
is_nullable?: boolean;
|
||||
column_default?: string;
|
||||
}
|
||||
|
||||
export interface BatchMappingRequest {
|
||||
batch_name: string;
|
||||
description?: string;
|
||||
cron_schedule: string;
|
||||
mappings: BatchMapping[];
|
||||
}
|
||||
|
||||
export interface BatchValidationResult {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
warnings?: string[];
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user