diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 5ce42b847..df2273b97 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -30,11 +30,11 @@ "typescript": "5.2.2" }, "devDependencies": { - "@commitlint/cli": "^19.2.1", + "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^17.2.0", "@jest/globals": "^29.5.0", "@storybook/addon-essentials": "^8.0.5", - "@storybook/addon-interactions": "^7.6.17", + "@storybook/addon-interactions": "^8.0.9", "@storybook/addon-links": "^8.0.4", "@storybook/addons": "^7.4.6", "@storybook/blocks": "^7.6.4", @@ -2387,13 +2387,13 @@ } }, "node_modules/@commitlint/cli": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.2.1.tgz", - "integrity": "sha512-cbkYUJsLqRomccNxvoJTyv5yn0bSy05BBizVyIcLACkRbVUqYorC351Diw/XFSWC/GtpwiwT2eOvQgFZa374bg==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.3.0.tgz", + "integrity": "sha512-LgYWOwuDR7BSTQ9OLZ12m7F/qhNY+NpAyPBgo4YNMkACE7lGuUnuQq1yi9hz1KA4+3VqpOYl8H1rY/LYK43v7g==", "dev": true, "dependencies": { - "@commitlint/format": "^19.0.3", - "@commitlint/lint": "^19.1.0", + "@commitlint/format": "^19.3.0", + "@commitlint/lint": "^19.2.2", "@commitlint/load": "^19.2.0", "@commitlint/read": "^19.2.1", "@commitlint/types": "^19.0.3", @@ -2593,9 +2593,9 @@ } }, "node_modules/@commitlint/format": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.0.3.tgz", - "integrity": "sha512-QjjyGyoiVWzx1f5xOteKHNLFyhyweVifMgopozSgx1fGNrGV8+wp7k6n1t6StHdJ6maQJ+UUtO2TcEiBFRyR6Q==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.3.0.tgz", + "integrity": "sha512-luguk5/aF68HiF4H23ACAfk8qS8AHxl4LLN5oxPc24H+2+JRPsNr1OS3Gaea0CrH7PKhArBMKBz5RX9sA5NtTg==", "dev": true, "dependencies": { "@commitlint/types": "^19.0.3", @@ -2618,9 +2618,9 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "19.0.3", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.0.3.tgz", - "integrity": "sha512-MqDrxJaRSVSzCbPsV6iOKG/Lt52Y+PVwFVexqImmYYFhe51iVJjK2hRhOG2jUAGiUHk4jpdFr0cZPzcBkSzXDQ==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.2.2.tgz", + "integrity": "sha512-eNX54oXMVxncORywF4ZPFtJoBm3Tvp111tg1xf4zWXGfhBPKpfKG6R+G3G4v5CPlRROXpAOpQ3HMhA9n1Tck1g==", "dev": true, "dependencies": { "@commitlint/types": "^19.0.3", @@ -2631,12 +2631,12 @@ } }, "node_modules/@commitlint/lint": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.1.0.tgz", - "integrity": "sha512-ESjaBmL/9cxm+eePyEr6SFlBUIYlYpI80n+Ltm7IA3MAcrmiP05UMhJdAD66sO8jvo8O4xdGn/1Mt2G5VzfZKw==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.2.2.tgz", + "integrity": "sha512-xrzMmz4JqwGyKQKTpFzlN0dx0TAiT7Ran1fqEBgEmEj+PU98crOFtysJgY+QdeSagx6EDRigQIXJVnfrI0ratA==", "dev": true, "dependencies": { - "@commitlint/is-ignored": "^19.0.3", + "@commitlint/is-ignored": "^19.2.2", "@commitlint/parse": "^19.0.3", "@commitlint/rules": "^19.0.3", "@commitlint/types": "^19.0.3" @@ -5936,14 +5936,15 @@ } }, "node_modules/@storybook/addon-interactions": { - "version": "7.6.17", - "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-7.6.17.tgz", - "integrity": "sha512-6zlX+RDQ1PlA6fp7C+hun8t7h2RXfCGs5dGrhEenp2lqnR/rYuUJRC0tmKpkZBb8kZVcbSChzkB/JYkBjBCzpQ==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.0.9.tgz", + "integrity": "sha512-AMIdNcyM6DDAWvMitBJMqp1iPZND8AXB4QT4VZHGMKG2ngHNKktriEKpTfcRkfKPGTJs9T+71dWfm6/R4tticw==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.17", - "jest-mock": "^27.0.6", + "@storybook/instrumenter": "8.0.9", + "@storybook/test": "8.0.9", + "@storybook/types": "8.0.9", "polished": "^4.2.2", "ts-dedent": "^2.2.0" }, @@ -5952,42 +5953,62 @@ "url": "https://opencollective.com/storybook" } }, - "node_modules/@storybook/addon-interactions/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "node_modules/@storybook/addon-interactions/node_modules/@storybook/channels": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.0.9.tgz", + "integrity": "sha512-7Lcfyy5CsLWWGhMPO9WG4jZ/Alzp0AjepFhEreYHRPtQrfttp6qMAjE/g1aHgun0qHCYWxwqIG4NLR/hqDNrXQ==", "dev": true, "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" + "@storybook/client-logger": "8.0.9", + "@storybook/core-events": "8.0.9", + "@storybook/global": "^5.0.0", + "telejson": "^7.2.0", + "tiny-invariant": "^1.3.1" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" } }, - "node_modules/@storybook/addon-interactions/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", + "node_modules/@storybook/addon-interactions/node_modules/@storybook/client-logger": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.0.9.tgz", + "integrity": "sha512-LzV/RHkbf07sRc1Jc0ff36RlapKf9Ul7/+9VMvVbI3hshH1CpmrZK4t/tsIdpX/EVOdJ1Gg5cES06PnleOAIPA==", "dev": true, "dependencies": { - "@types/yargs-parser": "*" + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" } }, - "node_modules/@storybook/addon-interactions/node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "node_modules/@storybook/addon-interactions/node_modules/@storybook/core-events": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.0.9.tgz", + "integrity": "sha512-DxSUx7wG9Qe3OFUBnv3OrYq48J8UWNo2DUR5/JecJCtp3n++L4fAEW3J0IF5FfxpQDMQSp1yTNjZ2PaWCMd2ag==", "dev": true, "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "ts-dedent": "^2.0.0" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/addon-interactions/node_modules/@storybook/types": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.0.9.tgz", + "integrity": "sha512-ew0EXzk9k4B557P1qIWYrnvUcgaE0WWA5qQS0AU8l+fRTp5nvl9O3SP/zNIB0SN1qDFO7dXr3idTNTyIikTcEQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "8.0.9", + "@types/express": "^4.7.0", + "file-system-cache": "2.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" } }, "node_modules/@storybook/addon-links": { @@ -7912,9 +7933,9 @@ } }, "node_modules/@storybook/csf": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.2.tgz", - "integrity": "sha512-ePrvE/pS1vsKR9Xr+o+YwdqNgHUyXvg+1Xjx0h9LrVx7Zq4zNe06pd63F5EvzTbCbJsHj7GHr9tkiaqm7U8WRA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.4.tgz", + "integrity": "sha512-B9UI/lsQMjF+oEfZCI6YXNoeuBcGZoOP5x8yKbe2tIEmsMjSztFKkpPzi5nLCnBk/MBtl6QJeI3ksJnbsWPkOw==", "dev": true, "dependencies": { "type-fest": "^2.19.0" @@ -8196,6 +8217,109 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@storybook/instrumenter": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.0.9.tgz", + "integrity": "sha512-Gw74dgpTU/2p7FG0s7DuVdqCbJ2MEcSuRJjDo7HcXRYcvWp7I6Ly+C0v7N5VaoS+kbBVerAhLKIHZgG/LZf1og==", + "dev": true, + "dependencies": { + "@storybook/channels": "8.0.9", + "@storybook/client-logger": "8.0.9", + "@storybook/core-events": "8.0.9", + "@storybook/global": "^5.0.0", + "@storybook/preview-api": "8.0.9", + "@vitest/utils": "^1.3.1", + "util": "^0.12.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/instrumenter/node_modules/@storybook/channels": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.0.9.tgz", + "integrity": "sha512-7Lcfyy5CsLWWGhMPO9WG4jZ/Alzp0AjepFhEreYHRPtQrfttp6qMAjE/g1aHgun0qHCYWxwqIG4NLR/hqDNrXQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "8.0.9", + "@storybook/core-events": "8.0.9", + "@storybook/global": "^5.0.0", + "telejson": "^7.2.0", + "tiny-invariant": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/instrumenter/node_modules/@storybook/client-logger": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.0.9.tgz", + "integrity": "sha512-LzV/RHkbf07sRc1Jc0ff36RlapKf9Ul7/+9VMvVbI3hshH1CpmrZK4t/tsIdpX/EVOdJ1Gg5cES06PnleOAIPA==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/instrumenter/node_modules/@storybook/core-events": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.0.9.tgz", + "integrity": "sha512-DxSUx7wG9Qe3OFUBnv3OrYq48J8UWNo2DUR5/JecJCtp3n++L4fAEW3J0IF5FfxpQDMQSp1yTNjZ2PaWCMd2ag==", + "dev": true, + "dependencies": { + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/instrumenter/node_modules/@storybook/preview-api": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.0.9.tgz", + "integrity": "sha512-zHfX34bkAMzzmE7vbDzaqFwSW6ExiBD0HiO1L/IsHF55f0f7xV7IH8uJyFRrDTvAoW3ReSxZDMvvPpeydFPKGA==", + "dev": true, + "dependencies": { + "@storybook/channels": "8.0.9", + "@storybook/client-logger": "8.0.9", + "@storybook/core-events": "8.0.9", + "@storybook/csf": "^0.1.4", + "@storybook/global": "^5.0.0", + "@storybook/types": "8.0.9", + "@types/qs": "^6.9.5", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "tiny-invariant": "^1.3.1", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/instrumenter/node_modules/@storybook/types": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.0.9.tgz", + "integrity": "sha512-ew0EXzk9k4B557P1qIWYrnvUcgaE0WWA5qQS0AU8l+fRTp5nvl9O3SP/zNIB0SN1qDFO7dXr3idTNTyIikTcEQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "8.0.9", + "@types/express": "^4.7.0", + "file-system-cache": "2.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, "node_modules/@storybook/manager": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.4.5.tgz", @@ -9010,6 +9134,112 @@ "integrity": "sha512-piPoDozdPaX1hNWFJQzzgWqE40gh986VvVx/QO9RU4qYRE55ld7iepDVgZ3ccGUw0R4wge0Oy1dd+3xOQNkkUQ==", "dev": true }, + "node_modules/@storybook/test": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.0.9.tgz", + "integrity": "sha512-bRd5tBJnPzR6UKbDXONWnFWtdkNOY99HMLDUWe5fTRo50GwkrpFBVqPflhdkruEeof0kAbBUbnoN2CIYgtnAFw==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "8.0.9", + "@storybook/core-events": "8.0.9", + "@storybook/instrumenter": "8.0.9", + "@storybook/preview-api": "8.0.9", + "@testing-library/dom": "^9.3.4", + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/user-event": "^14.5.2", + "@vitest/expect": "1.3.1", + "@vitest/spy": "^1.3.1", + "util": "^0.12.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/test/node_modules/@storybook/channels": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-8.0.9.tgz", + "integrity": "sha512-7Lcfyy5CsLWWGhMPO9WG4jZ/Alzp0AjepFhEreYHRPtQrfttp6qMAjE/g1aHgun0qHCYWxwqIG4NLR/hqDNrXQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "8.0.9", + "@storybook/core-events": "8.0.9", + "@storybook/global": "^5.0.0", + "telejson": "^7.2.0", + "tiny-invariant": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/test/node_modules/@storybook/client-logger": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-8.0.9.tgz", + "integrity": "sha512-LzV/RHkbf07sRc1Jc0ff36RlapKf9Ul7/+9VMvVbI3hshH1CpmrZK4t/tsIdpX/EVOdJ1Gg5cES06PnleOAIPA==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/test/node_modules/@storybook/core-events": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-8.0.9.tgz", + "integrity": "sha512-DxSUx7wG9Qe3OFUBnv3OrYq48J8UWNo2DUR5/JecJCtp3n++L4fAEW3J0IF5FfxpQDMQSp1yTNjZ2PaWCMd2ag==", + "dev": true, + "dependencies": { + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/test/node_modules/@storybook/preview-api": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.0.9.tgz", + "integrity": "sha512-zHfX34bkAMzzmE7vbDzaqFwSW6ExiBD0HiO1L/IsHF55f0f7xV7IH8uJyFRrDTvAoW3ReSxZDMvvPpeydFPKGA==", + "dev": true, + "dependencies": { + "@storybook/channels": "8.0.9", + "@storybook/client-logger": "8.0.9", + "@storybook/core-events": "8.0.9", + "@storybook/csf": "^0.1.4", + "@storybook/global": "^5.0.0", + "@storybook/types": "8.0.9", + "@types/qs": "^6.9.5", + "dequal": "^2.0.2", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "tiny-invariant": "^1.3.1", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/test/node_modules/@storybook/types": { + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-8.0.9.tgz", + "integrity": "sha512-ew0EXzk9k4B557P1qIWYrnvUcgaE0WWA5qQS0AU8l+fRTp5nvl9O3SP/zNIB0SN1qDFO7dXr3idTNTyIikTcEQ==", + "dev": true, + "dependencies": { + "@storybook/channels": "8.0.9", + "@types/express": "^4.7.0", + "file-system-cache": "2.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, "node_modules/@storybook/testing-library": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@storybook/testing-library/-/testing-library-0.2.2.tgz", @@ -9156,9 +9386,9 @@ } }, "node_modules/@testing-library/dom": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", - "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -9257,9 +9487,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.5.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", - "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", "dev": true, "engines": { "node": ">=12", @@ -10021,6 +10251,138 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vitest/expect": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", + "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.3.1", + "@vitest/utils": "1.3.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/spy": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", + "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", + "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/expect/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/expect/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@vitest/spy": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz", + "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz", + "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -10727,6 +11089,15 @@ "util": "^0.12.5" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -11792,6 +12163,24 @@ "node": ">=4" } }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11828,6 +12217,18 @@ "pnpm": ">=7" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -12929,6 +13330,18 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-equal": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", @@ -13446,9 +13859,9 @@ "dev": true }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "dependencies": { "jake": "^10.8.5" @@ -14529,6 +14942,21 @@ "node": ">=8.3.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/estree-walker/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -15323,6 +15751,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -18471,6 +18908,15 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -19588,6 +20034,15 @@ "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", "dev": true }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -22821,6 +23276,15 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index f7ba31bae..1d24fada1 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -40,11 +40,11 @@ "typescript": "5.2.2" }, "devDependencies": { - "@commitlint/cli": "^19.2.1", + "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^17.2.0", "@jest/globals": "^29.5.0", "@storybook/addon-essentials": "^8.0.5", - "@storybook/addon-interactions": "^7.6.17", + "@storybook/addon-interactions": "^8.0.9", "@storybook/addon-links": "^8.0.4", "@storybook/addons": "^7.4.6", "@storybook/blocks": "^7.6.4", diff --git a/go.mod b/go.mod index 3b6b5162e..3dfa91f69 100644 --- a/go.mod +++ b/go.mod @@ -205,12 +205,12 @@ require ( github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.14.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect diff --git a/go.sum b/go.sum index e63016894..2fd5f9afd 100644 --- a/go.sum +++ b/go.sum @@ -538,8 +538,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= @@ -567,8 +567,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= @@ -604,12 +604,12 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/handlers/accounts_handler.go b/handlers/accounts_handler.go index 4e274a799..038d8844d 100644 --- a/handlers/accounts_handler.go +++ b/handlers/accounts_handler.go @@ -1,7 +1,6 @@ package handlers import ( - "context" "database/sql" "encoding/json" "fmt" @@ -12,6 +11,7 @@ import ( "github.com/go-co-op/gocron" log "github.com/sirupsen/logrus" "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" "github.com/tailwarden/komiser/utils" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" @@ -38,7 +38,8 @@ func (handler *ApiHandler) IsOnboardedHandler(c *gin.Context) { } accounts := make([]models.Account, 0) - err := handler.db.NewRaw("SELECT * FROM accounts").Scan(handler.ctx, &accounts) + + _, err := handler.repo.HandleQuery(c, repository.ListKey, &accounts, nil) if err != nil { log.WithError(err).Error("scan failed") c.JSON(http.StatusInternalServerError, gin.H{"error": "scan failed"}) @@ -62,7 +63,7 @@ func (handler *ApiHandler) ListCloudAccountsHandler(c *gin.Context) { return } - err := handler.db.NewRaw("SELECT * FROM accounts").Scan(handler.ctx, &accounts) + _, err := handler.repo.HandleQuery(c, repository.ListKey, &accounts, nil) if err != nil { log.WithError(err).Error("scan failed") c.JSON(http.StatusInternalServerError, gin.H{"error": "scan failed"}) @@ -73,8 +74,10 @@ func (handler *ApiHandler) ListCloudAccountsHandler(c *gin.Context) { output := struct { Total int `bun:"total" json:"total"` }{} - err = handler.db.NewRaw(fmt.Sprintf("SELECT COUNT(*) as total FROM resources WHERE provider='%s' AND account='%s'", account.Provider, account.Name)).Scan(handler.ctx, &output) + + _, err := handler.repo.HandleQuery(c, repository.ResourceCountKey, &output, [][3]string{{"provider", "=", account.Provider}, {"account", "=", account.Name}}) if err != nil { + fmt.Println(err) c.JSON(http.StatusInternalServerError, gin.H{"error": "scan failed"}) return } @@ -104,12 +107,12 @@ func (handler *ApiHandler) NewCloudAccountHandler(c *gin.Context) { unsavedAccounts = append(unsavedAccounts, account) } else { - result, err := handler.db.NewInsert().Model(&account).Exec(context.Background()) + + result, err := handler.repo.HandleQuery(c, repository.InsertKey, &account, nil) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - accountId, _ := result.LastInsertId() account.Id = accountId @@ -152,7 +155,8 @@ func (handler *ApiHandler) ReScanAccount(c *gin.Context) { accountId := c.Param("id") account := new(models.Account) - res, err := handler.db.NewUpdate().Model(account).Set("status = ? ", "SCANNING").Where("id = ?", accountId).Where("status = ?", "CONNECTED").Returning("*").Exec(handler.ctx) + account.Status = "SCANNING" + res, err := handler.repo.HandleQuery(c, repository.ReScanAccountKey, account, [][3]string{{"id", "=", accountId}, {"status", "=", "CONNECTED"}}) if err != nil { log.Error("Couldn't set status", err) return @@ -169,7 +173,7 @@ func (handler *ApiHandler) DeleteCloudAccountHandler(c *gin.Context) { accountId := c.Param("id") account := new(models.Account) - _, err := handler.db.NewDelete().Model(account).Where("id = ?", accountId).Exec(handler.ctx) + _, err := handler.repo.HandleQuery(c, repository.DeleteKey, account, [][3]string{{"id", "=", accountId}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -188,7 +192,7 @@ func (handler *ApiHandler) UpdateCloudAccountHandler(c *gin.Context) { return } - _, err = handler.db.NewUpdate().Model(&account).Column("name", "provider", "credentials").Where("id = ?", accountId).Exec(handler.ctx) + _, err = handler.repo.HandleQuery(c, repository.UpdateAccountKey, &account, [][3]string{{"id", "=", accountId}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return diff --git a/handlers/alerts_handler.go b/handlers/alerts_handler.go index 10fe3692f..b7a401096 100644 --- a/handlers/alerts_handler.go +++ b/handlers/alerts_handler.go @@ -2,13 +2,14 @@ package handlers import ( "bytes" - "context" "encoding/json" + "fmt" "net/http" "time" "github.com/gin-gonic/gin" "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" ) func (handler *ApiHandler) IsSlackEnabledHandler(c *gin.Context) { @@ -33,7 +34,7 @@ func (handler *ApiHandler) NewAlertHandler(c *gin.Context) { return } - result, err := handler.db.NewInsert().Model(&alert).Exec(context.Background()) + result, err := handler.repo.HandleQuery(c, repository.InsertKey, &alert, nil) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -62,7 +63,7 @@ func (handler *ApiHandler) UpdateAlertHandler(c *gin.Context) { return } - _, err = handler.db.NewUpdate().Model(&alert).Column("name", "type", "budget", "usage", "endpoint", "secret").Where("id = ?", alertId).Exec(handler.ctx) + _, err = handler.repo.HandleQuery(c, repository.UpdateAlertKey, &alert, [][3]string{{"id", "=", fmt.Sprint(alertId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -75,7 +76,7 @@ func (handler *ApiHandler) DeleteAlertHandler(c *gin.Context) { alertId := c.Param("id") alert := new(models.Alert) - _, err := handler.db.NewDelete().Model(alert).Where("id = ?", alertId).Exec(handler.ctx) + _, err := handler.repo.HandleQuery(c, repository.DeleteKey, alert, [][3]string{{"id", "=", fmt.Sprint(alertId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return diff --git a/handlers/csv_handler.go b/handlers/csv_handler.go index b85a22c79..afcb24b25 100644 --- a/handlers/csv_handler.go +++ b/handlers/csv_handler.go @@ -14,12 +14,13 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" "github.com/uptrace/bun/dialect" ) func (handler *ApiHandler) DownloadInventoryCSV(c *gin.Context) { resources := make([]models.Resource, 0) - err := handler.db.NewSelect().Table("resources").Scan(handler.ctx, &resources) + _, err := handler.repo.HandleQuery(c, repository.ListKey, &resources, [][3]string{}) if err != nil { logrus.WithError(err).Error("Could not read from DB") c.JSON(http.StatusInternalServerError, gin.H{"error": "cloud not read from DB"}) @@ -37,7 +38,7 @@ func (handler *ApiHandler) DownloadInventoryCSVForView(c *gin.Context) { viewId := c.Param("viewId") view := new(models.View) - err := handler.db.NewSelect().Model(view).Where("id = ?", viewId).Scan(handler.ctx) + _, err := handler.repo.HandleQuery(c, repository.ListKey, view, [][3]string{{"id", "=", fmt.Sprint(viewId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return diff --git a/handlers/dashboard_handler.go b/handlers/dashboard_handler.go index b2a6b92c8..8400c718b 100644 --- a/handlers/dashboard_handler.go +++ b/handlers/dashboard_handler.go @@ -11,6 +11,7 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" "github.com/tailwarden/komiser/utils" "github.com/uptrace/bun" ) @@ -32,16 +33,16 @@ func (handler *ApiHandler) DashboardStatsHandler(c *gin.Context) { Count int `bun:"count" json:"total"` }{} - err := handler.db.NewRaw("SELECT COUNT(*) as count FROM (SELECT DISTINCT region FROM resources) AS temp").Scan(handler.ctx, ®ions) + _, err := handler.repo.HandleQuery(c, repository.RegionResourceCountKey, ®ions, [][3]string{}) if err != nil { logrus.WithError(err).Error("scan failed") } resources := struct { - Count int `bun:"count" json:"total"` + Count int `bun:"total" json:"total"` }{} - err = handler.db.NewRaw("SELECT COUNT(*) as count FROM resources").Scan(handler.ctx, &resources) + _, err = handler.repo.HandleQuery(c, repository.ResourceCountKey, &resources, [][3]string{}) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -50,7 +51,7 @@ func (handler *ApiHandler) DashboardStatsHandler(c *gin.Context) { Sum float64 `bun:"sum" json:"total"` }{} - err = handler.db.NewRaw("SELECT SUM(cost) as sum FROM resources").Scan(handler.ctx, &cost) + _, err = handler.repo.HandleQuery(c, repository.ResourceCostSumKey, &cost, [][3]string{}) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -59,7 +60,7 @@ func (handler *ApiHandler) DashboardStatsHandler(c *gin.Context) { Count int `bun:"count" json:"total"` }{} - err = handler.db.NewRaw("SELECT COUNT(*) as count FROM (SELECT DISTINCT account FROM resources) AS temp").Scan(handler.ctx, &accounts) + _, err = handler.repo.HandleQuery(c, repository.RegionResourceCountKey, ®ions, [][3]string{}) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -143,8 +144,7 @@ func (handler *ApiHandler) LocationBreakdownStatsHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, []models.OutputLocations{}) return } - - err := handler.db.NewRaw("SELECT region as label, COUNT(*) as total FROM resources GROUP BY region ORDER by total desc;").Scan(handler.ctx, &groups) + _, err := handler.repo.HandleQuery(c, repository.LocationBreakdownStatKey, &groups, [][3]string{}) if err != nil { logrus.WithError(err).Error("scan failed") } diff --git a/handlers/resources_handler.go b/handlers/resources_handler.go index da7f894fc..e3b0ac6f5 100644 --- a/handlers/resources_handler.go +++ b/handlers/resources_handler.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "database/sql" "encoding/json" "fmt" "net/http" @@ -11,44 +12,49 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/tailwarden/komiser/models" - . "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" "github.com/tailwarden/komiser/utils" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect" ) type ApiHandler struct { - db *bun.DB - ctx context.Context - telemetry bool - cfg models.Config + db *bun.DB + repo Repository + ctx context.Context + telemetry bool + cfg models.Config configPath string - analytics utils.Analytics - accounts []models.Account + analytics utils.Analytics + accounts []models.Account +} + +type Repository interface { + HandleQuery(context.Context, repository.QueryType, interface{}, [][3]string) (sql.Result, error) } func NewApiHandler(ctx context.Context, telemetry bool, analytics utils.Analytics, db *bun.DB, cfg models.Config, configPath string, accounts []models.Account) *ApiHandler { handler := ApiHandler{ - db: db, - ctx: ctx, - telemetry: telemetry, - cfg: cfg, + db: db, + ctx: ctx, + telemetry: telemetry, + cfg: cfg, configPath: configPath, - analytics: analytics, - accounts: accounts, + analytics: analytics, + accounts: accounts, } return &handler } func (handler *ApiHandler) FilterResourcesHandler(c *gin.Context) { - var filters []Filter + var filters []models.Filter limitRaw := c.Query("limit") skipRaw := c.Query("skip") query := c.Query("query") viewId := c.Query("view") - view := new(View) + view := new(models.View) if viewId != "" { err := handler.db.NewSelect().Model(view).Where("id = ?", viewId).Scan(handler.ctx) if err != nil { @@ -240,7 +246,7 @@ func (handler *ApiHandler) FilterResourcesHandler(c *gin.Context) { whereClause := strings.Join(whereQueries, " AND ") - resources := make([]Resource, 0) + resources := make([]models.Resource, 0) if len(filters) == 0 { if len(query) > 0 { @@ -305,7 +311,7 @@ func (handler *ApiHandler) FilterResourcesHandler(c *gin.Context) { } func (handler *ApiHandler) RelationStatsHandler(c *gin.Context) { - var filters []Filter + var filters []models.Filter err := json.NewDecoder(c.Request.Body).Decode(&filters) if err != nil { @@ -428,7 +434,7 @@ func (handler *ApiHandler) RelationStatsHandler(c *gin.Context) { Provider: ele.Provider, }) } - + c.JSON(http.StatusOK, out) } @@ -436,9 +442,9 @@ func (handler *ApiHandler) RelationStatsHandler(c *gin.Context) { func (handler *ApiHandler) GetResourceByIdHandler(c *gin.Context) { resourceId := c.Query("resourceId") - var resource Resource + var resource models.Resource - err := handler.db.NewSelect().Model(&resource).Where("resource_id = ?", resourceId).Scan(handler.ctx) + _, err := handler.repo.HandleQuery(c, repository.ListKey, &resource, [][3]string{{"resource_id", "=", resourceId}}) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Resource not found"}) } diff --git a/handlers/stats_handler.go b/handlers/stats_handler.go index 6f4da6853..fe2fd2d63 100644 --- a/handlers/stats_handler.go +++ b/handlers/stats_handler.go @@ -9,7 +9,8 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - . "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" "github.com/uptrace/bun/dialect" ) @@ -17,17 +18,15 @@ func (handler *ApiHandler) StatsHandler(c *gin.Context) { regions := struct { Count int `bun:"count" json:"total"` }{} - - err := handler.db.NewRaw("SELECT COUNT(*) as count FROM (SELECT DISTINCT region FROM resources) AS temp").Scan(handler.ctx, ®ions) + _, err := handler.repo.HandleQuery(c, repository.RegionResourceCountKey, ®ions, nil) if err != nil { logrus.WithError(err).Error("scan failed") } resources := struct { - Count int `bun:"count" json:"total"` + Count int `bun:"total" json:"total"` }{} - - err = handler.db.NewRaw("SELECT COUNT(*) as count FROM resources").Scan(handler.ctx, &resources) + _, err = handler.repo.HandleQuery(c, repository.ResourceCountKey, &resources, nil) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -35,8 +34,7 @@ func (handler *ApiHandler) StatsHandler(c *gin.Context) { cost := struct { Sum float64 `bun:"sum" json:"total"` }{} - - err = handler.db.NewRaw("SELECT SUM(cost) as sum FROM resources").Scan(handler.ctx, &cost) + _, err = handler.repo.HandleQuery(c, repository.ResourceCostSumKey, &cost, nil) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -63,7 +61,7 @@ func (handler *ApiHandler) StatsHandler(c *gin.Context) { } func (handler *ApiHandler) FilterStatsHandler(c *gin.Context) { - var filters []Filter + var filters []models.Filter err := json.NewDecoder(c.Request.Body).Decode(&filters) if err != nil { @@ -321,8 +319,7 @@ func (handler *ApiHandler) ListRegionsHandler(c *gin.Context) { } outputs := make([]Output, 0) - - err := handler.db.NewRaw("SELECT DISTINCT(region) FROM resources").Scan(handler.ctx, &outputs) + _, err := handler.repo.HandleQuery(c, repository.ListRegionsKey, &outputs, nil) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -347,8 +344,7 @@ func (handler *ApiHandler) ListProvidersHandler(c *gin.Context) { } outputs := make([]Output, 0) - - err := handler.db.NewRaw("SELECT DISTINCT(provider) FROM resources").Scan(handler.ctx, &outputs) + _, err := handler.repo.HandleQuery(c, repository.ListProvidersKey, &outputs, nil) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -368,8 +364,7 @@ func (handler *ApiHandler) ListServicesHandler(c *gin.Context) { } outputs := make([]Output, 0) - - err := handler.db.NewRaw("SELECT DISTINCT(service) FROM resources").Scan(handler.ctx, &outputs) + _, err := handler.repo.HandleQuery(c, repository.ListServicesKey, &outputs, nil) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -394,8 +389,7 @@ func (handler *ApiHandler) ListAccountsHandler(c *gin.Context) { } outputs := make([]Output, 0) - - err := handler.db.NewRaw("SELECT DISTINCT(account) FROM resources").Scan(handler.ctx, &outputs) + _, err := handler.repo.HandleQuery(c, repository.ListAccountsKey, &outputs, nil) if err != nil { logrus.WithError(err).Error("scan failed") } diff --git a/handlers/tags_handler.go b/handlers/tags_handler.go index d199edeac..c69412ed9 100644 --- a/handlers/tags_handler.go +++ b/handlers/tags_handler.go @@ -2,15 +2,16 @@ package handlers import ( "encoding/json" + "fmt" "net/http" - "strconv" "github.com/gin-gonic/gin" - . "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" ) func (handler *ApiHandler) BulkUpdateTagsHandler(c *gin.Context) { - var input BulkUpdateTag + var input models.BulkUpdateTag err := json.NewDecoder(c.Request.Body).Decode(&input) if err != nil { @@ -18,10 +19,10 @@ func (handler *ApiHandler) BulkUpdateTagsHandler(c *gin.Context) { return } - resource := Resource{Tags: input.Tags} + resource := models.Resource{Tags: input.Tags} for _, resourceId := range input.Resources { - _, err = handler.db.NewUpdate().Model(&resource).Column("tags").Where("id = ?", resourceId).Exec(handler.ctx) + _, err = handler.repo.HandleQuery(c, repository.UpdateTagsKey, &resource, [][3]string{{"id", "=", fmt.Sprint(resourceId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "error while updating tags"}) return @@ -36,25 +37,19 @@ func (handler *ApiHandler) BulkUpdateTagsHandler(c *gin.Context) { } func (handler *ApiHandler) UpdateTagsHandler(c *gin.Context) { - tags := make([]Tag, 0) + tags := make([]models.Tag, 0) resourceId := c.Param("id") - id, err := strconv.Atoi(resourceId) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "resource id should be an integer"}) - return - } - - err = json.NewDecoder(c.Request.Body).Decode(&tags) + err := json.NewDecoder(c.Request.Body).Decode(&tags) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - resource := Resource{Tags: tags} + resource := models.Resource{Tags: tags} - _, err = handler.db.NewUpdate().Model(&resource).Column("tags").Where("id = ?", id).Exec(handler.ctx) + _, err = handler.repo.HandleQuery(c, repository.UpdateTagsKey, &resource, [][3]string{{"id", "=", fmt.Sprint(resourceId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "error while updating tags"}) return diff --git a/handlers/views_handler.go b/handlers/views_handler.go index 416b800ee..7a77faad1 100644 --- a/handlers/views_handler.go +++ b/handlers/views_handler.go @@ -1,7 +1,6 @@ package handlers import ( - "context" "encoding/json" "fmt" "net/http" @@ -10,6 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/tailwarden/komiser/models" + "github.com/tailwarden/komiser/repository" ) func (handler *ApiHandler) NewViewHandler(c *gin.Context) { @@ -20,8 +20,7 @@ func (handler *ApiHandler) NewViewHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - - result, err := handler.db.NewInsert().Model(&view).Exec(context.Background()) + result, err := handler.repo.HandleQuery(c, repository.InsertKey, &view, [][3]string{}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -40,7 +39,7 @@ func (handler *ApiHandler) NewViewHandler(c *gin.Context) { func (handler *ApiHandler) ListViewsHandler(c *gin.Context) { views := make([]models.View, 0) - err := handler.db.NewRaw("SELECT * FROM views").Scan(handler.ctx, &views) + _, err := handler.repo.HandleQuery(c, repository.ListKey, &views, [][3]string{}) if err != nil { logrus.WithError(err).Error("scan failed") c.JSON(http.StatusInternalServerError, gin.H{"error": "scan failed"}) @@ -60,7 +59,7 @@ func (handler *ApiHandler) UpdateViewHandler(c *gin.Context) { return } - _, err = handler.db.NewUpdate().Model(&view).Column("name", "filters", "exclude").Where("id = ?", viewId).Exec(handler.ctx) + _, err = handler.repo.HandleQuery(c, repository.UpdateViewKey, &view, [][3]string{{"id", "=", fmt.Sprint(viewId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -73,7 +72,7 @@ func (handler *ApiHandler) DeleteViewHandler(c *gin.Context) { viewId := c.Param("id") view := new(models.View) - _, err := handler.db.NewDelete().Model(view).Where("id = ?", viewId).Exec(handler.ctx) + _, err := handler.repo.HandleQuery(c, repository.DeleteKey, view, [][3]string{{"id", "=", fmt.Sprint(viewId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -91,8 +90,7 @@ func (handler *ApiHandler) HideResourcesFromViewHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - - _, err = handler.db.NewUpdate().Model(&view).Column("exclude").Where("id = ?", viewId).Exec(handler.ctx) + _, err = handler.repo.HandleQuery(c, repository.UpdateViewExcludeKey, &view, [][3]string{{"id", "=", fmt.Sprint(viewId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -110,8 +108,7 @@ func (handler *ApiHandler) UnhideResourcesFromViewHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - - _, err = handler.db.NewUpdate().Model(&view).Column("exclude").Where("id = ?", viewId).Exec(handler.ctx) + _, err = handler.repo.HandleQuery(c, repository.UpdateViewExcludeKey, &view, [][3]string{{"id", "=", fmt.Sprint(viewId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -124,7 +121,7 @@ func (handler *ApiHandler) ListHiddenResourcesHandler(c *gin.Context) { viewId := c.Param("id") view := new(models.View) - err := handler.db.NewSelect().Model(view).Where("id = ?", viewId).Scan(handler.ctx) + _, err := handler.repo.HandleQuery(c, repository.ListKey, &view, [][3]string{{"id", "=", fmt.Sprint(viewId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -134,7 +131,8 @@ func (handler *ApiHandler) ListHiddenResourcesHandler(c *gin.Context) { if len(view.Exclude) > 0 { s, _ := json.Marshal(view.Exclude) - err = handler.db.NewRaw(fmt.Sprintf("SELECT * FROM resources WHERE id IN (%s)", strings.Trim(string(s), "[]"))).Scan(handler.ctx, &resources) + + _, err = handler.repo.HandleQuery(c, repository.ListKey, &resources, [][3]string{{"id", "IN", strings.Trim(string(s), "[]")}}) if err != nil { logrus.WithError(err).Error("scan failed") } @@ -149,11 +147,11 @@ func (handler *ApiHandler) ListViewAlertsHandler(c *gin.Context) { alerts := make([]models.Alert, 0) - err := handler.db.NewRaw(fmt.Sprintf("SELECT * FROM alerts WHERE view_id = %s", viewId)).Scan(handler.ctx, &alerts) + _, err := handler.repo.HandleQuery(c, repository.ListKey, &alerts, [][3]string{{"view_id", "=", fmt.Sprint(viewId)}}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, alerts) -} \ No newline at end of file +} diff --git a/repository/core.go b/repository/core.go new file mode 100644 index 000000000..20d517a4f --- /dev/null +++ b/repository/core.go @@ -0,0 +1,118 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/uptrace/bun" +) + +type QueryType string + +const ( + RAW QueryType = "RAW" + SELECT QueryType = "SELECT" + INSERT QueryType = "INSERT" + DELETE QueryType = "DELETE" + UPDATE QueryType = "UPDATE" +) + +type Object struct { + Query string `json:"query"` + Type QueryType `json:"type"` + Params []string `json:"params"` +} + +const ( + ListKey = "LIST" + InsertKey = "INSERT" + DeleteKey = "DELETE" + UpdateAccountKey = "UPDATE_ACCOUNT" + UpdateAlertKey = "UPDATE_ALERT" + UpdateViewKey = "UPDATE_VIEW" + UpdateViewExcludeKey = "UPDATE_VIEW_EXCLUDE" + ReScanAccountKey = "RE_SCAN_ACCOUNT" + ResourceCountKey = "RESOURCE_COUNT" + ResourceCostSumKey = "RESOURCE_COST_SUM" + AccountsResourceCountKey = "ACCOUNTS_RESOURCE_COUNT" + RegionResourceCountKey = "REGION_RESOURCE_COUNT" + FilterResourceCountKey = "FILTER_RESOURCE_COUNT" + LocationBreakdownStatKey = "LOCATION_BREAKDOWN_STAT" + UpdateTagsKey = "UPDATE_TAGS" + ListRegionsKey = "LISST_REGIONS" + ListProvidersKey = "LIST_PROVIDERS" + ListServicesKey = "LIST_SERVICES" + ListAccountsKey = "LIST_ACCOUNTS" +) + +func ExecuteRaw(ctx context.Context, db *bun.DB, query string, schema interface{}, additionals [][3]string) error { + if len(additionals) > 0 { + query = fmt.Sprintf("%s where", query) + } + + for _, triplet := range additionals { + key, op, value := triplet[0], triplet[1], triplet[2] + query = fmt.Sprintf("%s %s %s '%s' and", query, key, op, value) + } + + if len(additionals) > 0 { + query = query[:len(query)-4] + } + + err := db.NewRaw(query).Scan(ctx, schema) + if err != nil { + return err + } + return nil +} + +func ExecuteSelect(ctx context.Context, db *bun.DB, schema interface{}, conditions [][3]string) error { + q := db.NewSelect().Model(schema) + + q = addWhereClause(q.QueryBuilder(), conditions).Unwrap().(*bun.SelectQuery) + + return q.Scan(ctx, schema) +} + +func ExecuteInsert(ctx context.Context, db *bun.DB, schema interface{}) (sql.Result, error) { + resp, err := db.NewInsert().Model(schema).Exec(ctx) + if err != nil { + return resp, err + } + return resp, nil +} + +func ExecuteDelete(ctx context.Context, db *bun.DB, schema interface{}, conditions [][3]string) (sql.Result, error) { + q := db.NewDelete().Model(schema) + + q = addWhereClause(q.QueryBuilder(), conditions).Unwrap().(*bun.DeleteQuery) + + resp, err := q.Exec(ctx) + if err != nil { + return resp, err + } + return resp, nil +} + +func ExecuteUpdate(ctx context.Context, db *bun.DB, schema interface{}, columns []string, conditions [][3]string) (sql.Result, error) { + q := db.NewUpdate().Model(schema).Column(columns...) + + q = addWhereClause(q.QueryBuilder(), conditions).Unwrap().(*bun.UpdateQuery) + + q = q.Returning("*") + + resp, err := q.Exec(ctx) + if err != nil { + return resp, err + } + return resp, nil +} + +func addWhereClause(query bun.QueryBuilder, conditions [][3]string) bun.QueryBuilder { + for _, triplet := range conditions { + key, op, value := triplet[0], triplet[1], triplet[2] + query = query.Where(fmt.Sprintf("%s %s ?", key, op), value) + } + return query +} diff --git a/repository/error.go b/repository/error.go new file mode 100644 index 000000000..605d82150 --- /dev/null +++ b/repository/error.go @@ -0,0 +1,5 @@ +package repository + +import "errors" + +var ErrQueryNotFound = errors.New("query not found") diff --git a/repository/sql/sql.go b/repository/sql/sql.go new file mode 100644 index 000000000..e5aaed810 --- /dev/null +++ b/repository/sql/sql.go @@ -0,0 +1,119 @@ +package sql + +import ( + "context" + "database/sql" + + "github.com/tailwarden/komiser/repository" + "github.com/uptrace/bun" +) + +type Repository struct { + db *bun.DB +} + +func NewRepository(db *bun.DB) *Repository { + return &Repository{db: db} +} + +var Queries = map[string]repository.Object{ + repository.ListKey: { + Type: repository.SELECT, + }, + repository.InsertKey: { + Type: repository.INSERT, + }, + repository.DeleteKey: { + Type: repository.DELETE, + }, + repository.UpdateAccountKey: { + Type: repository.UPDATE, + Params: []string{"name", "provider", "credentials"}, + }, + repository.UpdateAlertKey: { + Type: repository.UPDATE, + Params: []string{"name", "type", "budget", "usage", "endpoint", "secret"}, + }, + repository.UpdateViewKey: { + Type: repository.UPDATE, + Params: []string{"name", "filters", "exclude"}, + }, + repository.UpdateViewExcludeKey: { + Type: repository.UPDATE, + Params: []string{"exclude"}, + }, + repository.ReScanAccountKey: { + Type: repository.UPDATE, + Params: []string{"status"}, + }, + repository.ResourceCountKey: { + Query: "SELECT COUNT(*) as total FROM resources", + Type: repository.RAW, + }, + repository.ResourceCostSumKey: { + Query: "SELECT SUM(cost) as sum FROM resources", + Type: repository.RAW, + }, + repository.AccountsResourceCountKey: { + Query: "SELECT COUNT(*) as count FROM (SELECT DISTINCT account FROM resources) AS temp", + Type: repository.RAW, + }, + repository.RegionResourceCountKey: { + Query: "SELECT COUNT(*) as count FROM (SELECT DISTINCT region FROM resources) AS temp", + Type: repository.RAW, + }, + repository.FilterResourceCountKey: { + Query: "SELECT filters as label, COUNT(*) as total FROM resources", + Type: repository.RAW, + }, + repository.LocationBreakdownStatKey: { + Query: "SELECT region as label, COUNT(*) as total FROM resources GROUP BY region ORDER by total desc;", + Type: repository.RAW, + }, + repository.UpdateTagsKey: { + Type: repository.UPDATE, + Params: []string{"tags"}, + }, + repository.ListRegionsKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(region) FROM resources", + }, + repository.ListProvidersKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(provider) FROM resources", + }, + repository.ListServicesKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(service) FROM resources", + }, + repository.ListAccountsKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(account) FROM resources", + }, +} + +func (repo *Repository) HandleQuery(ctx context.Context, queryTitle string, schema interface{}, conditions [][3]string) (sql.Result, error) { + var resp sql.Result + var err error + query, ok := Queries[queryTitle] + if !ok { + return nil, repository.ErrQueryNotFound + } + switch query.Type { + case repository.RAW: + err = repository.ExecuteRaw(ctx, repo.db, query.Query, schema, conditions) + + case repository.SELECT: + err = repository.ExecuteSelect(ctx, repo.db, schema, conditions) + + case repository.INSERT: + resp, err = repository.ExecuteInsert(ctx, repo.db, schema) + + case repository.DELETE: + resp, err = repository.ExecuteDelete(ctx, repo.db, schema, conditions) + + case repository.UPDATE: + resp, err = repository.ExecuteUpdate(ctx, repo.db, schema, query.Params, conditions) + } + return resp, err +} diff --git a/repository/sqlite/sqlite.go b/repository/sqlite/sqlite.go new file mode 100644 index 000000000..b175900dc --- /dev/null +++ b/repository/sqlite/sqlite.go @@ -0,0 +1,120 @@ +package sql + +import ( + "context" + "database/sql" + + "github.com/tailwarden/komiser/repository" + "github.com/uptrace/bun" +) + +type Repository struct { + db *bun.DB + queries map[string]repository.Object +} + +func NewRepository(db *bun.DB) *Repository { + return &Repository{db: db, queries: Queries} +} + +var Queries = map[string]repository.Object{ + repository.ListKey: { + Type: repository.SELECT, + }, + repository.InsertKey: { + Type: repository.INSERT, + }, + repository.DeleteKey: { + Type: repository.DELETE, + }, + repository.UpdateAccountKey: { + Type: repository.UPDATE, + Params: []string{"name", "provider", "credentials"}, + }, + repository.UpdateAlertKey: { + Type: repository.UPDATE, + Params: []string{"name", "type", "budget", "usage", "endpoint", "secret"}, + }, + repository.UpdateViewKey: { + Type: repository.UPDATE, + Params: []string{"name", "filters", "exclude"}, + }, + repository.UpdateViewExcludeKey: { + Type: repository.UPDATE, + Params: []string{"exclude"}, + }, + repository.ReScanAccountKey: { + Type: repository.UPDATE, + Params: []string{"status"}, + }, + repository.ResourceCountKey: { + Query: "SELECT COUNT(*) as total FROM resources", + Type: repository.RAW, + }, + repository.ResourceCostSumKey: { + Query: "SELECT SUM(cost) as sum FROM resources", + Type: repository.RAW, + }, + repository.AccountsResourceCountKey: { + Query: "SELECT COUNT(*) as count FROM (SELECT DISTINCT account FROM resources) AS temp", + Type: repository.RAW, + }, + repository.RegionResourceCountKey: { + Query: "SELECT COUNT(*) as count FROM (SELECT DISTINCT region FROM resources) AS temp", + Type: repository.RAW, + }, + repository.FilterResourceCountKey: { + Query: "SELECT filters as label, COUNT(*) as total FROM resources", + Type: repository.RAW, + }, + repository.LocationBreakdownStatKey: { + Query: "SELECT region as label, COUNT(*) as total FROM resources GROUP BY region ORDER by total desc;", + Type: repository.RAW, + }, + repository.UpdateTagsKey: { + Type: repository.UPDATE, + Params: []string{"tags"}, + }, + repository.ListRegionsKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(region) FROM resources", + }, + repository.ListProvidersKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(provider) FROM resources", + }, + repository.ListServicesKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(service) FROM resources", + }, + repository.ListAccountsKey: { + Type: repository.RAW, + Query: "SELECT DISTINCT(account) FROM resources", + }, +} + +func (repo *Repository) HandleQuery(ctx context.Context, queryTitle string, schema interface{}, conditions [][3]string) (sql.Result, error) { + var resp sql.Result + var err error + query, ok := Queries[queryTitle] + if !ok { + return nil, repository.ErrQueryNotFound + } + switch query.Type { + case repository.RAW: + err = repository.ExecuteRaw(ctx, repo.db, query.Query, schema, conditions) + + case repository.SELECT: + err = repository.ExecuteSelect(ctx, repo.db, schema, conditions) + + case repository.INSERT: + resp, err = repository.ExecuteInsert(ctx, repo.db, schema) + + case repository.DELETE: + resp, err = repository.ExecuteDelete(ctx, repo.db, schema, conditions) + + case repository.UPDATE: + resp, err = repository.ExecuteUpdate(ctx, repo.db, schema, query.Params, conditions) + } + return resp, err +}