From abf64b1894a7e6be1655fb2ad542d44c1a2e925b Mon Sep 17 00:00:00 2001 From: RikThePixel Date: Thu, 11 Jan 2024 23:05:23 +0100 Subject: [PATCH 1/6] Setup messages service --- .../workflows/pull-request-microservices.yml | 2 + .github/workflows/release-microservices.yml | 14 +- apps/advertisements/.env.example | 2 +- .../service => apps/messages}/.dockerignore | 32 +- .../service => apps/messages}/.editorconfig | 12 +- apps/messages/.env.example | 26 ++ .../service => apps/messages}/.eslintrc.cjs | 2 +- .../service => apps/messages}/.gitignore | 28 +- .../messages}/.prettierrc.json | 12 +- .../service => apps/messages}/Dockerfile | 42 +- .../service => apps/messages}/jest.config.js | 382 +++++++++--------- .../messages/migrations/20231201215943_int.js | 66 +++ .../service => apps/messages}/package.json | 23 +- .../messages}/src/configs/auth.ts | 0 .../messages}/src/configs/broker.ts | 21 +- .../messages}/src/configs/db.ts | 5 +- apps/messages/src/entities/Advertisement.ts | 13 + apps/messages/src/entities/Category.ts | 5 + .../messages/src/entities/CategoryProperty.ts | 6 + .../src/entities/CategoryPropertyOption.ts | 6 + .../entities/CategoryPropertyOptionValue.ts | 6 + apps/messages/src/entities/Image.ts | 6 + apps/messages/src/entities/User.ts | 5 + apps/messages/src/exceptions/Exception.ts | 5 + .../src/exceptions/common/BadRequest.ts | 15 + .../src/exceptions/common/NotFound.ts | 12 + apps/messages/src/helpers/catch.ts | 10 + apps/messages/src/helpers/identifiers.ts | 37 ++ apps/messages/src/helpers/router.ts | 14 + apps/messages/src/helpers/sanitize.ts | 5 + .../service => apps/messages}/src/index.ts | 13 +- .../messages}/src/loggers/BaseLogger.ts | 0 .../messages}/src/loggers/ConsoleLogger.ts | 0 .../messages}/src/middleware/auth.ts | 0 .../messages}/src/middleware/errorHandling.ts | 4 +- .../messages}/src/middleware/requestLogger.ts | 0 .../messages}/src/middleware/validate.ts | 147 ++++--- apps/messages/src/providers/di.ts | 40 ++ .../repositories/user/KnexUserRepository.ts | 30 ++ .../src/repositories/user/UserRepository.ts | 9 + .../src/responses/health/HealthResponse.ts | 8 + .../messages}/src/routes/index.ts | 0 apps/messages/src/routes/v1/health.ts | 22 + .../messages}/src/routes/v1/index.ts | 2 +- apps/messages/src/services/UserService.ts | 18 + .../src/subscribers/BaseSubscription.ts | 82 ++++ .../src/subscribers/UsersSubscription.ts | 32 ++ apps/messages/src/types/utility.ts | 11 + .../messages}/tests/unit/sample.spec.ts | 8 +- .../service => apps/messages}/tsconfig.json | 11 +- docker-compose.db.yml | 19 +- docker-compose.dev.yml | 9 +- docker-compose.k8s.yml | 5 + k8s/account-deployment.yml | 2 +- k8s/advertisement-deployment.yml | 2 +- k8s/gateway-deployment.yml | 2 +- k8s/kustomization.yml | 4 + k8s/messaging-deployment.yml | 101 +++++ main.tf | 39 ++ package.json | 2 + scaffolding/service/.env.example | 13 - scaffolding/service/db/.env.example | 5 - scaffolding/service/knexfile.js | 25 -- scaffolding/service/src/helpers/router.ts | 6 - scaffolding/service/src/providers/di.ts | 20 - scaffolding/service/src/routes/v1/health.ts | 24 -- 66 files changed, 1079 insertions(+), 450 deletions(-) rename {scaffolding/service => apps/messages}/.dockerignore (90%) rename {scaffolding/service => apps/messages}/.editorconfig (95%) create mode 100644 apps/messages/.env.example rename {scaffolding/service => apps/messages}/.eslintrc.cjs (81%) rename {scaffolding/service => apps/messages}/.gitignore (58%) rename {scaffolding/service => apps/messages}/.prettierrc.json (93%) rename {scaffolding/service => apps/messages}/Dockerfile (69%) rename {scaffolding/service => apps/messages}/jest.config.js (97%) create mode 100644 apps/messages/migrations/20231201215943_int.js rename {scaffolding/service => apps/messages}/package.json (76%) rename {scaffolding/service => apps/messages}/src/configs/auth.ts (100%) rename {scaffolding/service => apps/messages}/src/configs/broker.ts (50%) rename {scaffolding/service => apps/messages}/src/configs/db.ts (77%) create mode 100644 apps/messages/src/entities/Advertisement.ts create mode 100644 apps/messages/src/entities/Category.ts create mode 100644 apps/messages/src/entities/CategoryProperty.ts create mode 100644 apps/messages/src/entities/CategoryPropertyOption.ts create mode 100644 apps/messages/src/entities/CategoryPropertyOptionValue.ts create mode 100644 apps/messages/src/entities/Image.ts create mode 100644 apps/messages/src/entities/User.ts create mode 100644 apps/messages/src/exceptions/Exception.ts create mode 100644 apps/messages/src/exceptions/common/BadRequest.ts create mode 100644 apps/messages/src/exceptions/common/NotFound.ts create mode 100644 apps/messages/src/helpers/catch.ts create mode 100644 apps/messages/src/helpers/identifiers.ts create mode 100644 apps/messages/src/helpers/router.ts create mode 100644 apps/messages/src/helpers/sanitize.ts rename {scaffolding/service => apps/messages}/src/index.ts (74%) rename {scaffolding/service => apps/messages}/src/loggers/BaseLogger.ts (100%) rename {scaffolding/service => apps/messages}/src/loggers/ConsoleLogger.ts (100%) rename {scaffolding/service => apps/messages}/src/middleware/auth.ts (100%) rename {scaffolding/service => apps/messages}/src/middleware/errorHandling.ts (81%) rename {scaffolding/service => apps/messages}/src/middleware/requestLogger.ts (100%) rename {scaffolding/service => apps/messages}/src/middleware/validate.ts (54%) create mode 100644 apps/messages/src/providers/di.ts create mode 100644 apps/messages/src/repositories/user/KnexUserRepository.ts create mode 100644 apps/messages/src/repositories/user/UserRepository.ts create mode 100644 apps/messages/src/responses/health/HealthResponse.ts rename {scaffolding/service => apps/messages}/src/routes/index.ts (100%) create mode 100644 apps/messages/src/routes/v1/health.ts rename {scaffolding/service => apps/messages}/src/routes/v1/index.ts (96%) create mode 100644 apps/messages/src/services/UserService.ts create mode 100644 apps/messages/src/subscribers/BaseSubscription.ts create mode 100644 apps/messages/src/subscribers/UsersSubscription.ts create mode 100644 apps/messages/src/types/utility.ts rename {scaffolding/service => apps/messages}/tests/unit/sample.spec.ts (93%) rename {scaffolding/service => apps/messages}/tsconfig.json (89%) create mode 100644 k8s/messaging-deployment.yml delete mode 100644 scaffolding/service/.env.example delete mode 100644 scaffolding/service/db/.env.example delete mode 100644 scaffolding/service/knexfile.js delete mode 100644 scaffolding/service/src/helpers/router.ts delete mode 100644 scaffolding/service/src/providers/di.ts delete mode 100644 scaffolding/service/src/routes/v1/health.ts diff --git a/.github/workflows/pull-request-microservices.yml b/.github/workflows/pull-request-microservices.yml index 0722c78..91e46f5 100644 --- a/.github/workflows/pull-request-microservices.yml +++ b/.github/workflows/pull-request-microservices.yml @@ -31,6 +31,8 @@ jobs: - "apps/accounts/**" advertisements: - "apps/advertisements/**" + messages: + - "apps/messages/**" gateway: - "apps/gateway/**" diff --git a/.github/workflows/release-microservices.yml b/.github/workflows/release-microservices.yml index dfacd15..9e7e440 100644 --- a/.github/workflows/release-microservices.yml +++ b/.github/workflows/release-microservices.yml @@ -16,7 +16,7 @@ jobs: environment: production strategy: matrix: - app: [advertisements, accounts, gateway] + app: [advertisements, accounts, messages, gateway] steps: - uses: actions/checkout@v4 @@ -65,6 +65,9 @@ jobs: - name: Delete advertisements secrets continue-on-error: true run: kubectl delete secret advertisement-service-secrets + - name: Delete messages secrets + continue-on-error: true + run: kubectl delete secret messaging-service-secrets - name: Set secrets run: | @@ -91,6 +94,15 @@ jobs: --from-literal=STORAGE_DRIVER="azure" \ --from-literal=STORAGE_AZURE_CONNECTION_STRING="$(az storage account show-connection-string --name ${{ vars.ADVERTISEMENTS_AZURE_STORAGE_ACCOUNT }} -o tsv)" + kubectl create secret generic messaging-service-secrets \ + --from-literal=DATABASE_USER="${{ secrets.DB_ADMIN_USER }}@${{ vars.MESSAGES_AZURE_DB_NAME }}" \ + --from-literal=DATABASE_PASSWORD="${{ secrets.DB_ADMIN_PASSWORD }}" \ + --from-literal=DATABASE_HOST="${{ vars.MESSAGES_AZURE_DB_NAME }}.mysql.database.azure.com" \ + --from-literal=DATABASE_PORT="3306" \ + --from-literal=DATABASE_NAME="messages" \ + --from-literal=STORAGE_DRIVER="azure" \ + --from-literal=STORAGE_AZURE_CONNECTION_STRING="$(az storage account show-connection-string --name ${{ vars.MESSAGES_AZURE_STORAGE_ACCOUNT }} -o tsv)" + - name: Deploys application uses: Azure/k8s-deploy@v4 with: diff --git a/apps/advertisements/.env.example b/apps/advertisements/.env.example index a3f8740..a5b7693 100644 --- a/apps/advertisements/.env.example +++ b/apps/advertisements/.env.example @@ -23,4 +23,4 @@ STORAGE_DRIVER=azure STORAGE_PATH=/storage # Used when STORAGE_DRIVER is set to 'file' ## Storage - STORAGE_DRIVER=azure -STORAGE_AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net;AccountName=squaremarketblobstorage;AccountKey=tEZpZsHtEg6AGukAoXTaa7A+xoSUyhMmeoDy4xlMKzS2oj3AUrwPX0q1mF3kwUXpVihLarWvz6wq+AStYwbReA==;BlobEndpoint=https://squaremarketblobstorage.blob.core.windows.net/;FileEndpoint=https://squaremarketblobstorage.file.core.windows.net/;QueueEndpoint=https://squaremarketblobstorage.queue.core.windows.net/;TableEndpoint=https://squaremarketblobstorage.table.core.windows.net/ +STORAGE_AZURE_CONNECTION_STRING= diff --git a/scaffolding/service/.dockerignore b/apps/messages/.dockerignore similarity index 90% rename from scaffolding/service/.dockerignore rename to apps/messages/.dockerignore index 4d7ab31..4f59d0c 100644 --- a/scaffolding/service/.dockerignore +++ b/apps/messages/.dockerignore @@ -1,16 +1,16 @@ -# Deps -node_modules - -## Env -.env -.env.local -.env.test - -# Docker -Dockerfile -Dockerfile.dev -docker-compose.yml -docker-compose.dev.yml - -# Misc -.prettierrc.json +# Deps +node_modules + +## Env +.env +.env.local +.env.test + +# Docker +Dockerfile +Dockerfile.dev +docker-compose.yml +docker-compose.dev.yml + +# Misc +.prettierrc.json diff --git a/scaffolding/service/.editorconfig b/apps/messages/.editorconfig similarity index 95% rename from scaffolding/service/.editorconfig rename to apps/messages/.editorconfig index 778a13e..4f62593 100644 --- a/scaffolding/service/.editorconfig +++ b/apps/messages/.editorconfig @@ -1,6 +1,6 @@ -[*.{js,ts,jsx,tsx}] -end_of_line = crlf -indent_style = space -indent_size = 2 -tab_width = 2 -trim_trailing_whitespace = true +[*.{js,ts,jsx,tsx}] +end_of_line = crlf +indent_style = space +indent_size = 2 +tab_width = 2 +trim_trailing_whitespace = true diff --git a/apps/messages/.env.example b/apps/messages/.env.example new file mode 100644 index 0000000..4e9aeb3 --- /dev/null +++ b/apps/messages/.env.example @@ -0,0 +1,26 @@ +# Server settings +SERVER_NAME=messages +SERVER_PORT=8003 # Port that the server starts on + +# Auth +AUTH_ISSUER_URL=https://square-market.eu.auth0.com/ +AUTH_AUDIENCE=http://localhost:8080 + +# Database +DATABASE_NAME=messages +DATABASE_USER=myuser +DATABASE_PASSWORD=mypassword +DATABASE_HOST=localhost +DATABASE_PORT=3403 + +# MQ +MQ_CONNECTION_STRING=amqp://guest:guest@localhost:5672 + +# Storage +STORAGE_DRIVER=azure + +## Storage - STORAGE_DRIVER=file +STORAGE_PATH=/storage # Used when STORAGE_DRIVER is set to 'file' + +## Storage - STORAGE_DRIVER=azure +STORAGE_AZURE_CONNECTION_STRING= diff --git a/scaffolding/service/.eslintrc.cjs b/apps/messages/.eslintrc.cjs similarity index 81% rename from scaffolding/service/.eslintrc.cjs rename to apps/messages/.eslintrc.cjs index 711c9b9..ffba2b3 100644 --- a/scaffolding/service/.eslintrc.cjs +++ b/apps/messages/.eslintrc.cjs @@ -2,7 +2,7 @@ module.exports = { root: true, env: { browser: true, es2020: true }, extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - ignorePatterns: ["dist", ".eslintrc.cjs"], + ignorePatterns: ["dist", ".eslintrc.cjs", "db"], parser: "@typescript-eslint/parser", plugins: [], rules: { diff --git a/scaffolding/service/.gitignore b/apps/messages/.gitignore similarity index 58% rename from scaffolding/service/.gitignore rename to apps/messages/.gitignore index d195070..f5aa70f 100644 --- a/scaffolding/service/.gitignore +++ b/apps/messages/.gitignore @@ -1,14 +1,14 @@ -# Deps -node_modules - -# Build -dist - -# ENV -/.env -/.env.local -/.env.test -/db/.env -/db/.env.local -/db/.env.test - +# Deps +node_modules + +# Build +dist + +# Storage +/storage + +# ENV +/.env +/.env.local +/.env.test +/.env.production diff --git a/scaffolding/service/.prettierrc.json b/apps/messages/.prettierrc.json similarity index 93% rename from scaffolding/service/.prettierrc.json rename to apps/messages/.prettierrc.json index 084af3a..8ffe1e2 100644 --- a/scaffolding/service/.prettierrc.json +++ b/apps/messages/.prettierrc.json @@ -1,6 +1,6 @@ -{ - "tabWidth": 2, - "semi": true, - "singleQuote": true, - "arrowParens": "always" -} +{ + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "arrowParens": "always" +} diff --git a/scaffolding/service/Dockerfile b/apps/messages/Dockerfile similarity index 69% rename from scaffolding/service/Dockerfile rename to apps/messages/Dockerfile index b79f757..e997c18 100644 --- a/scaffolding/service/Dockerfile +++ b/apps/messages/Dockerfile @@ -1,19 +1,23 @@ -FROM node:20-alpine as build - -WORKDIR /app - -COPY package.json package.json -COPY package-lock.json package-lock.json - -RUN npm ci && npm run build - -FROM node:20-alpine as runner - -WORKDIR /app - -COPY --from=build /app/node_modules/ node_modules/ -COPY --from=build /app/package.json package.json -COPY --from=build /app/package-lock.json package-lock.json -COPY --from=build /app/dist dist - -CMD ["npm", "run", "start"] +FROM node:20-alpine as build + +WORKDIR /app + +COPY ./package.json package.json + +RUN npm i + +COPY ./src ./src +COPY ./tsconfig.json tsconfig.json + +RUN npm run build + +FROM node:20-alpine as runner + +WORKDIR /app + +COPY --from=build /app/node_modules/ node_modules/ +COPY --from=build /app/package.json package.json +COPY --from=build /app/package-lock.json package-lock.json +COPY --from=build /app/dist dist + +CMD ["npm", "run", "start"] diff --git a/scaffolding/service/jest.config.js b/apps/messages/jest.config.js similarity index 97% rename from scaffolding/service/jest.config.js rename to apps/messages/jest.config.js index bb07573..ca619f1 100644 --- a/scaffolding/service/jest.config.js +++ b/apps/messages/jest.config.js @@ -1,191 +1,191 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - - // All imported modules in your tests should be mocked automatically - // automock: false, - - // Stop running tests after `n` failures - // bail: 0, - - // The directory where Jest should store its cached dependency information - // cacheDirectory: "C:\\Users\\Rik\\AppData\\Local\\Temp\\jest", - - // Automatically clear mock calls, instances, contexts and results before every test - clearMocks: true, - - // Indicates whether the coverage information should be collected while executing the test - collectCoverage: true, - - // An array of glob patterns indicating a set of files for which coverage information should be collected - // collectCoverageFrom: undefined, - - // The directory where Jest should output its coverage files - coverageDirectory: "coverage", - - // An array of regexp pattern strings used to skip coverage collection - coveragePathIgnorePatterns: [ - "\\\\node_modules\\\\" - ], - - // Indicates which provider should be used to instrument code for coverage - coverageProvider: "v8", - - // A list of reporter names that Jest uses when writing coverage reports - // coverageReporters: [ - // "json", - // "text", - // "lcov", - // "clover" - // ], - - // An object that configures minimum threshold enforcement for coverage results - // coverageThreshold: undefined, - - // A path to a custom dependency extractor - // dependencyExtractor: undefined, - - // Make calling deprecated APIs throw helpful error messages - // errorOnDeprecated: false, - - // The default configuration for fake timers - // fakeTimers: { - // "enableGlobally": false - // }, - - // Force coverage collection from ignored files using an array of glob patterns - // forceCoverageMatch: [], - - // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: undefined, - - // A path to a module which exports an async function that is triggered once after all test suites - // globalTeardown: undefined, - - // A set of global variables that need to be available in all test environments - // globals: {}, - - // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. - // maxWorkers: "50%", - - // An array of directory names to be searched recursively up from the requiring module's location - // moduleDirectories: [ - // "node_modules" - // ], - - // An array of file extensions your modules use - // moduleFileExtensions: [ - // "js", - // "mjs", - // "cjs", - // "jsx", - // "ts", - // "tsx", - // "json", - // "node" - // ], - - // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - // moduleNameMapper: {}, - - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], - - // Activates notifications for test results - // notify: false, - - // An enum that specifies notification mode. Requires { notify: true } - // notifyMode: "failure-change", - - // Run tests from one or more projects - // projects: undefined, - - // Use this configuration option to add custom reporters to Jest - // reporters: undefined, - - // Automatically reset mock state before every test - // resetMocks: false, - - // Reset the module registry before running each individual test - // resetModules: false, - - // A path to a custom resolver - // resolver: undefined, - - // Automatically restore mock state and implementation before every test - // restoreMocks: false, - - // The root directory that Jest should scan for tests and modules within - // rootDir: undefined, - - // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "" - // ], - - // Allows you to use a custom runner instead of Jest's default test runner - // runner: "jest-runner", - - // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], - - // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: [], - - // The number of seconds after which a test is considered as slow and reported as such in the results. - // slowTestThreshold: 5, - - // A list of paths to snapshot serializer modules Jest should use for snapshot testing - // snapshotSerializers: [], - - // The test environment that will be used for testing - // testEnvironment: "jest-environment-node", - - // Options that will be passed to the testEnvironment - // testEnvironmentOptions: {}, - - // Adds a location field to test results - // testLocationInResults: false, - - // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - // testPathIgnorePatterns: [ - // "\\\\node_modules\\\\" - // ], - - // The regexp pattern or array of patterns that Jest uses to detect test files - // testRegex: [], - - // This option allows the use of a custom results processor - // testResultsProcessor: undefined, - - // This option allows use of a custom test runner - // testRunner: "jest-circus/runner", - - // A map from regular expressions to paths to transformers - // transform: undefined, - - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "\\\\node_modules\\\\", - // "\\.pnp\\.[^\\\\]+$" - // ], - - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them - // unmockedModulePathPatterns: undefined, - - // Indicates whether each individual test should be reported during the run - // verbose: undefined, - - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode - // watchPathIgnorePatterns: [], - - // Whether to use watchman for file crawling - // watchman: true, -}; +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "C:\\Users\\Rik\\AppData\\Local\\Temp\\jest", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + coveragePathIgnorePatterns: [ + "\\\\node_modules\\\\" + ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + // testEnvironment: "jest-environment-node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "\\\\node_modules\\\\", + // "\\.pnp\\.[^\\\\]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/apps/messages/migrations/20231201215943_int.js b/apps/messages/migrations/20231201215943_int.js new file mode 100644 index 0000000..43e11fa --- /dev/null +++ b/apps/messages/migrations/20231201215943_int.js @@ -0,0 +1,66 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async function (knex) { + await exports.down(knex); + /** + * @param {import("knex").Knex.AlterTableBuilder} table + */ + function addIds(table) { + table.increments('id'); + table + .uuid('uid', { + useBinaryUuid: true, + }) + .unique() + .index(); + } + + await knex.schema + .createTable('users', function (table) { + table.increments('id'); + table.string('provider_id', 500).unique().index(); + table.string('username', 255); + }) + .createTable('chats', function (table) { + addIds(table); + table + .integer('user_1_id') + .unsigned() + .references('users.id') + .onDelete('cascade'); + table + .integer('user_2_id') + .unsigned() + .references('users.id') + .onDelete('cascade'); + }) + .createTable('messages', function (table) { + addIds(table); + table + .integer('from_user_id') + .unsigned() + .references('users.id') + .onDelete('cascade'); + table + .integer('chat_id') + .unsigned() + .references('chats.id') + .onDelete('cascade'); + table.text('content'); + table.date('seen_at').nullable(); + table.date('sent_at'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async function (knex) { + await knex.schema + .dropTableIfExists('messages') + .dropTableIfExists('chats') + .dropTableIfExists('users'); +}; diff --git a/scaffolding/service/package.json b/apps/messages/package.json similarity index 76% rename from scaffolding/service/package.json rename to apps/messages/package.json index c32f00b..53a2e6f 100644 --- a/scaffolding/service/package.json +++ b/apps/messages/package.json @@ -1,5 +1,5 @@ { - "name": "service", + "name": "advertisements", "version": "1.0.0", "description": "", "author": "RikThePixel", @@ -10,9 +10,7 @@ "test:ci": "npm run test -- --ci", "dev": "nodemon -e ts --exec \"tsc && node ./dist/index.js\"", "build": "tsc --b ./tsconfig.json", - "start": "node ./dist/index.js", - "docker:dev:build": "", - "docker:dev": "" + "start": "node ./dist/index.js" }, "devDependencies": { "@types/jest": "^29.5.8", @@ -20,14 +18,16 @@ "@types/koa__multer": "^2.0.7", "@types/koa__router": "^12.0.4", "@types/node": "^20.9.0", + "@types/rascal": "^10.0.9", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "concurrently": "^8.2.2", "eslint": "^8.53.0", - "nodemon": "^3.0.1", "jest": "^29.7.0", + "nodemon": "^3.0.1", "prettier": "^3.0.3", "ts-jest": "^29.1.1", + "type-fest": "^4.8.3", "typescript": "^5.2.2" }, "volta": { @@ -35,17 +35,22 @@ "npm": "10.2.3" }, "dependencies": { + "@azure/storage-blob": "^12.17.0", "@koa/bodyparser": "^5.0.0", "@koa/multer": "^3.0.2", "@koa/router": "^12.0.1", + "dotenv": "^16.3.1", + "isomorphic-dompurify": "^1.11.0", + "jwks-rsa": "^3.1.0", "knex": "^3.0.1", "koa": "^2.14.2", + "koa-jwt": "^4.0.4", + "koa-tioc": "^0.4.0", "multer": "^1.4.5-lts.1", - "isomorphic-dompurify": "^1.11.0", "mysql2": "^3.6.5", - "koa-tioc": "^0.4.0", + "rascal": "^17.0.1", "tioc": "^0.3.0", - "zod": "^3.22.4", - "dotenv": "^16.3.1" + "validate-image-type": "^3.0.0", + "zod": "^3.22.4" } } diff --git a/scaffolding/service/src/configs/auth.ts b/apps/messages/src/configs/auth.ts similarity index 100% rename from scaffolding/service/src/configs/auth.ts rename to apps/messages/src/configs/auth.ts diff --git a/scaffolding/service/src/configs/broker.ts b/apps/messages/src/configs/broker.ts similarity index 50% rename from scaffolding/service/src/configs/broker.ts rename to apps/messages/src/configs/broker.ts index 9a0ff76..b6f191d 100644 --- a/scaffolding/service/src/configs/broker.ts +++ b/apps/messages/src/configs/broker.ts @@ -8,24 +8,23 @@ const brokerConfig = withDefaultConfig({ }, exchanges: { accounts_ex: { + assert: true, type: 'topic', }, }, - queues: ['accounts_q'], - bindings: ['accounts_ex[users.*] -> accounts_q'], - publications: { - user_created_pub: { - exchange: 'accounts_ex', - routingKey: 'users.created', - }, - user_name_change_pub: { - exchange: 'accounts_ex', - routingKey: 'users.name_change', + queues: ['messages_user_queue'], + bindings: { + 'accounts_ex-accounts_queue': { + source: 'accounts_ex', + destination: 'messages_user_queue', + destinationType: 'queue', + bindingKey: 'users.*', }, }, + publications: {}, subscriptions: { users_sub: { - queue: 'accounts_q', + queue: 'messages_user_queue', }, }, }, diff --git a/scaffolding/service/src/configs/db.ts b/apps/messages/src/configs/db.ts similarity index 77% rename from scaffolding/service/src/configs/db.ts rename to apps/messages/src/configs/db.ts index 7751bed..7a74bca 100644 --- a/scaffolding/service/src/configs/db.ts +++ b/apps/messages/src/configs/db.ts @@ -8,7 +8,10 @@ const dbConfig: Knex.Config = { database: process.env.DATABASE_NAME, user: process.env.DATABASE_USER, password: process.env.DATABASE_PASSWORD, + ssl: { + rejectUnauthorized: false, + }, }, }; -export default dbConfig +export default dbConfig; diff --git a/apps/messages/src/entities/Advertisement.ts b/apps/messages/src/entities/Advertisement.ts new file mode 100644 index 0000000..875b302 --- /dev/null +++ b/apps/messages/src/entities/Advertisement.ts @@ -0,0 +1,13 @@ +export interface Advertisement { + id: number; + uid: string; + user_id: number; + category_id?: number; + + title?: string; + description?: string; + price?: number; + currency?: string; + draft: boolean; + published_at: Date | null; +} diff --git a/apps/messages/src/entities/Category.ts b/apps/messages/src/entities/Category.ts new file mode 100644 index 0000000..50cbbe7 --- /dev/null +++ b/apps/messages/src/entities/Category.ts @@ -0,0 +1,5 @@ +export interface Category { + id: number; + uid: string; + name: string; +} diff --git a/apps/messages/src/entities/CategoryProperty.ts b/apps/messages/src/entities/CategoryProperty.ts new file mode 100644 index 0000000..115b87c --- /dev/null +++ b/apps/messages/src/entities/CategoryProperty.ts @@ -0,0 +1,6 @@ +export default interface CategoryProperty { + id: number; + uid: string; + category_id: number; + name: string; +} diff --git a/apps/messages/src/entities/CategoryPropertyOption.ts b/apps/messages/src/entities/CategoryPropertyOption.ts new file mode 100644 index 0000000..53c11df --- /dev/null +++ b/apps/messages/src/entities/CategoryPropertyOption.ts @@ -0,0 +1,6 @@ +export default interface CategoryPropertyOption { + id: number; + uid: string; + category_property_id: number; + name: string; +} diff --git a/apps/messages/src/entities/CategoryPropertyOptionValue.ts b/apps/messages/src/entities/CategoryPropertyOptionValue.ts new file mode 100644 index 0000000..d7a8786 --- /dev/null +++ b/apps/messages/src/entities/CategoryPropertyOptionValue.ts @@ -0,0 +1,6 @@ +export default interface CategoryPropertyOptionValue { + id: number + uid: string; + advertisement_id: number; + category_property_option_id: number; +} diff --git a/apps/messages/src/entities/Image.ts b/apps/messages/src/entities/Image.ts new file mode 100644 index 0000000..2bc6882 --- /dev/null +++ b/apps/messages/src/entities/Image.ts @@ -0,0 +1,6 @@ +export default interface Image { + id: number; + uid: string; + advertisement_id: number; + mime: string; +} diff --git a/apps/messages/src/entities/User.ts b/apps/messages/src/entities/User.ts new file mode 100644 index 0000000..e73c531 --- /dev/null +++ b/apps/messages/src/entities/User.ts @@ -0,0 +1,5 @@ +export interface User { + id: number; + provider_id: string; + username: string; +} diff --git a/apps/messages/src/exceptions/Exception.ts b/apps/messages/src/exceptions/Exception.ts new file mode 100644 index 0000000..dd7b0dd --- /dev/null +++ b/apps/messages/src/exceptions/Exception.ts @@ -0,0 +1,5 @@ +export default abstract class Exception extends Error { + abstract name: string; + abstract message: string; + abstract status: number; +} diff --git a/apps/messages/src/exceptions/common/BadRequest.ts b/apps/messages/src/exceptions/common/BadRequest.ts new file mode 100644 index 0000000..a6e0696 --- /dev/null +++ b/apps/messages/src/exceptions/common/BadRequest.ts @@ -0,0 +1,15 @@ +import Exception from '../Exception'; + +export default class BadRequestException extends Exception { + name = 'BadRequest'; + status = 400; + message = 'Some object caused an invalid state'; + + constructor( + private topic: string, + private reason: string, + ) { + super(); + this.message = `${this.topic} caused an invalid state due to ${this.reason}`; + } +} diff --git a/apps/messages/src/exceptions/common/NotFound.ts b/apps/messages/src/exceptions/common/NotFound.ts new file mode 100644 index 0000000..6e6e995 --- /dev/null +++ b/apps/messages/src/exceptions/common/NotFound.ts @@ -0,0 +1,12 @@ +import Exception from '../Exception'; + +export default class NotFoundException extends Exception { + name = 'NotFound'; + status = 404; + message = 'Object could not be found'; + + constructor(private topic: string) { + super(); + this.message = `${this.topic} could not be found`; + } +} diff --git a/apps/messages/src/helpers/catch.ts b/apps/messages/src/helpers/catch.ts new file mode 100644 index 0000000..843549d --- /dev/null +++ b/apps/messages/src/helpers/catch.ts @@ -0,0 +1,10 @@ + +export function catchType( + type: TType, + map: (e: TType['prototype']) => TMapResult, +) { + return (e: any) => { + if (!(e instanceof (type as any))) throw e; + return map(e as TType['prototype']); + }; +} diff --git a/apps/messages/src/helpers/identifiers.ts b/apps/messages/src/helpers/identifiers.ts new file mode 100644 index 0000000..3952603 --- /dev/null +++ b/apps/messages/src/helpers/identifiers.ts @@ -0,0 +1,37 @@ +export type UidOrId = number | string; + +export function isUid(uidOrId: UidOrId): uidOrId is string { + return typeof uidOrId === 'string'; +} + +export function isId(uidOrId: UidOrId): uidOrId is number { + return typeof uidOrId === 'number'; +} + +export function castUidOrId( + uidOrId: UidOrId, + cast: (uid: string) => Buffer, +): Buffer | number { + return isUid(uidOrId) ? cast(uidOrId) : uidOrId; +} + +export function getType( + uid_or_id: TUidOrId, +): TUidOrId extends Buffer ? 'uid' : 'id' { + return (isUid(uid_or_id) ? 'uid' : 'id') as TUidOrId extends Buffer + ? 'uid' + : 'id'; +} + +export type UidsToBuffers = { + [TKey in keyof T]: TKey extends string +// eslint-disable-next-line @typescript-eslint/no-unused-vars + ? TKey extends `${infer _}uid` + ? T[TKey] extends string + ? Buffer + : T[TKey] + : T[TKey] + : T[TKey] extends object + ? UidsToBuffers + : T[TKey]; +}; diff --git a/apps/messages/src/helpers/router.ts b/apps/messages/src/helpers/router.ts new file mode 100644 index 0000000..14d71e9 --- /dev/null +++ b/apps/messages/src/helpers/router.ts @@ -0,0 +1,14 @@ +import Router from '@koa/router'; +import { AppContext } from '..'; + +export default function makeAppRouter< + TContext extends object = object, + TState extends object = object, +>(opts: Router.RouterOptions = {}) { + return new Router(opts); +} + +export type AppRouter< + TContext extends object = object, + TState extends object = object, +> = Router; diff --git a/apps/messages/src/helpers/sanitize.ts b/apps/messages/src/helpers/sanitize.ts new file mode 100644 index 0000000..168d93b --- /dev/null +++ b/apps/messages/src/helpers/sanitize.ts @@ -0,0 +1,5 @@ +import { sanitize as domSanitize } from 'isomorphic-dompurify'; + +export default function sanitize(text: string) { + return domSanitize(text); +} diff --git a/scaffolding/service/src/index.ts b/apps/messages/src/index.ts similarity index 74% rename from scaffolding/service/src/index.ts rename to apps/messages/src/index.ts index 37ce565..0330bfa 100644 --- a/scaffolding/service/src/index.ts +++ b/apps/messages/src/index.ts @@ -1,5 +1,7 @@ -import Koa from 'koa'; import { config as loadEnv } from 'dotenv'; +loadEnv(); + +import Koa from 'koa'; import injector from 'koa-tioc'; import { bodyParser } from '@koa/bodyparser'; @@ -9,8 +11,7 @@ import depenencyProvider from './providers/di'; import router from './routes'; import errorHandling from './middleware/errorHandling'; import requestLogger from './middleware/requestLogger'; - -loadEnv(); +import IoCContainer from 'tioc'; const PORT: number = parseInt(process.env.SERVER_PORT ?? '8001'); @@ -27,6 +28,12 @@ const app = new Koa() export type AppContext = (typeof app)['context']; +const initContainer = depenencyProvider(new IoCContainer()); +initContainer.resolve('broker').then(async () => { + console.log('Broker started'); + initContainer.resolve('UsersSubscription').then((s) => s.listen()); +}); + app .use(router.routes()) .listen(PORT, '0.0.0.0', () => diff --git a/scaffolding/service/src/loggers/BaseLogger.ts b/apps/messages/src/loggers/BaseLogger.ts similarity index 100% rename from scaffolding/service/src/loggers/BaseLogger.ts rename to apps/messages/src/loggers/BaseLogger.ts diff --git a/scaffolding/service/src/loggers/ConsoleLogger.ts b/apps/messages/src/loggers/ConsoleLogger.ts similarity index 100% rename from scaffolding/service/src/loggers/ConsoleLogger.ts rename to apps/messages/src/loggers/ConsoleLogger.ts diff --git a/scaffolding/service/src/middleware/auth.ts b/apps/messages/src/middleware/auth.ts similarity index 100% rename from scaffolding/service/src/middleware/auth.ts rename to apps/messages/src/middleware/auth.ts diff --git a/scaffolding/service/src/middleware/errorHandling.ts b/apps/messages/src/middleware/errorHandling.ts similarity index 81% rename from scaffolding/service/src/middleware/errorHandling.ts rename to apps/messages/src/middleware/errorHandling.ts index c0d22a4..b8b9ada 100644 --- a/scaffolding/service/src/middleware/errorHandling.ts +++ b/apps/messages/src/middleware/errorHandling.ts @@ -2,6 +2,7 @@ import Koa, { HttpError } from 'koa'; import { InjectorContext } from 'koa-tioc'; import IoCContainer from 'tioc'; import BaseLogger from '../loggers/BaseLogger'; +import Exception from '../exceptions/Exception'; export default function errorHandling() { return async ( @@ -11,7 +12,7 @@ export default function errorHandling() { try { await next(); } catch (error) { - if (!(error instanceof HttpError)) { + if (!(error instanceof HttpError) && !(error instanceof Exception)) { const logger = ctx.container.resolve('logger'); logger.error(`Server error on path ${ctx.method}:${ctx.path}`, error); @@ -19,6 +20,7 @@ export default function errorHandling() { ctx.message = 'Internal server error'; return ctx; } + ctx.status = error.status; ctx.body = error.message; } diff --git a/scaffolding/service/src/middleware/requestLogger.ts b/apps/messages/src/middleware/requestLogger.ts similarity index 100% rename from scaffolding/service/src/middleware/requestLogger.ts rename to apps/messages/src/middleware/requestLogger.ts diff --git a/scaffolding/service/src/middleware/validate.ts b/apps/messages/src/middleware/validate.ts similarity index 54% rename from scaffolding/service/src/middleware/validate.ts rename to apps/messages/src/middleware/validate.ts index daebc5e..88fcdb8 100644 --- a/scaffolding/service/src/middleware/validate.ts +++ b/apps/messages/src/middleware/validate.ts @@ -1,60 +1,87 @@ -import Koa from 'koa'; -import { RouterParamContext } from '@koa/router'; -import z from 'zod'; - -type InferSchema = - TSchema extends z.ZodType ? z.infer : TSchema extends unknown ? undefined : undefined; - -type ZodMiddleware = Koa.Middleware< - Koa.DefaultState, - { - validated: { - query: InferSchema; - params: InferSchema; - headers: InferSchema; - body: InferSchema; - }; - } & RouterParamContext, - InferSchema< - TSchemas['response'] extends z.ZodType ? TSchemas['response'] : z.ZodTypeAny - > ->; - -interface PropSchemas< - THeaders extends z.ZodType = z.ZodType, - TBody extends z.ZodType = z.ZodType, - TParams extends z.ZodType = z.ZodType, - TQuery extends z.ZodType = z.ZodType, - TResponse extends z.ZodType = z.ZodType, -> { - headers?: THeaders; - body?: TBody; - params?: TParams; - query?: TQuery; - response?: TResponse; -} - -const validate = - ( - schemas: TSchemas, - ): ZodMiddleware => - async (ctx, next) => { - try { - ctx.validated = { - body: schemas.body?.parse(ctx.request.body), - headers: schemas.headers?.parse(ctx.request.headers), - query: schemas.query?.parse(ctx.request.query), - params: schemas.params?.parse(ctx.params), - }; - - await next(); - - if (!schemas.response) return; - schemas.response.parse(ctx.body); - } catch (err) { - if (!(err instanceof z.ZodError)) throw err; - return ctx.throw(422, err); - } - }; - -export default validate; +import Koa from 'koa'; +import { RouterParamContext } from '@koa/router'; +import z from 'zod'; +import { validateBufferMIMEType } from 'validate-image-type'; +import multer from '@koa/multer'; + +type InferSchema = + TSchema extends z.ZodType + ? z.infer + : TSchema extends unknown + ? undefined + : undefined; + +type ZodMiddleware = Koa.Middleware< + Koa.DefaultState, + { + validated: { + query: InferSchema; + params: InferSchema; + headers: InferSchema; + body: InferSchema; + images: multer.File[]; + }; + } & RouterParamContext & + Koa.DefaultContext, + InferSchema< + TSchemas['response'] extends z.ZodType ? TSchemas['response'] : z.ZodTypeAny + > +>; + +interface PropSchemas< + THeaders extends z.ZodType = z.ZodType, + TBody extends z.ZodType = z.ZodType, + TParams extends z.ZodType = z.ZodType, + TQuery extends z.ZodType = z.ZodType, + TResponse extends z.ZodType = z.ZodType, +> { + headers?: THeaders; + body?: TBody; + params?: TParams; + query?: TQuery; + response?: TResponse; + images?: { field: string; types: string[] }[]; +} + +const validate = + ( + schemas: TSchemas, + ): ZodMiddleware => + async (ctx, next) => { + try { + ctx.validated = { + body: schemas.body?.parse(ctx.request.body), + headers: schemas.headers?.parse(ctx.request.headers), + query: schemas.query?.parse(ctx.request.query), + params: schemas.params?.parse(ctx.params), + images: [], + }; + + if (ctx.files && schemas.images) { + const files = Array.isArray(ctx.files) + ? ctx.files + : Object.values(ctx.files).flatMap((f) => f); + + for (const file of files) { + const validatorField = schemas.images.find( + (validatorField) => validatorField.field === file.fieldname, + ); + if (!validatorField) continue; + const result = await validateBufferMIMEType(file.buffer, { + originalFilename: file.originalname, + allowMimeTypes: validatorField.types, + }); + + if (!result.ok) continue; + ctx.validated.images.push(file); + } + } + } catch (err) { + if (!(err instanceof z.ZodError)) throw err; + return ctx.throw(422, err.toString()); + } + + await next(); + }; + +export default validate; diff --git a/apps/messages/src/providers/di.ts b/apps/messages/src/providers/di.ts new file mode 100644 index 0000000..a8a957f --- /dev/null +++ b/apps/messages/src/providers/di.ts @@ -0,0 +1,40 @@ +import Knex from 'knex'; +import ConsoleLogger from '../loggers/ConsoleLogger'; +import IoCContainer from 'tioc'; +import { createBrokerAsPromised } from 'rascal'; +import dbConfig from '../configs/db'; +import authConfig from '../configs/auth'; +import brokerConfig from '../configs/broker'; +import auth from '../middleware/auth'; +import KnexUserRepository from '../repositories/user/KnexUserRepository'; +import UserService from '../services/UserService'; +import UsersSubscription from '../subscribers/UsersSubscription'; + +const depenencyProvider = (c: IoCContainer) => + c + .addSingleton('db', () => Knex(dbConfig)) + .addSingleton( + 'broker', + async () => await createBrokerAsPromised(brokerConfig), + ) + .addSingleton('logger', () => new ConsoleLogger()) + .addScoped('authMiddleware', () => auth(authConfig)) + .addScoped( + 'UserRespository', + (c) => new KnexUserRepository(c.resolve('db')), + ) + .addScoped( + 'UserService', + (c) => new UserService(c.resolve('UserRespository')), + ) + .addSingleton( + 'UsersSubscription', + async (c) => + new UsersSubscription( + await c.resolve('broker'), + c.resolve('logger'), + c.resolve('UserService'), + ), + ); + +export default depenencyProvider; diff --git a/apps/messages/src/repositories/user/KnexUserRepository.ts b/apps/messages/src/repositories/user/KnexUserRepository.ts new file mode 100644 index 0000000..49c78cb --- /dev/null +++ b/apps/messages/src/repositories/user/KnexUserRepository.ts @@ -0,0 +1,30 @@ +import Knex from 'knex'; +import UserRepository, { InsertableUser } from './UserRepository'; +import { User } from '../../entities/User'; + +export default class KnexUserRepository implements UserRepository { + constructor(private db: Knex.Knex) {} + + async get(providerIdOrId: number | string) { + return await this.db + .table('users') + .where( + `users.${typeof providerIdOrId === 'number' ? 'id' : 'provider_id'}`, + providerIdOrId, + ) + .first( + 'users.id', + 'users.provider_id', + 'users.username', + 'users.default_currency', + ); + } + + async createOrUpdate(user: InsertableUser) { + await this.db + .insert(user) + .into('users') + .onConflict(['provider_id']) + .merge(['username']); + } +} diff --git a/apps/messages/src/repositories/user/UserRepository.ts b/apps/messages/src/repositories/user/UserRepository.ts new file mode 100644 index 0000000..0fd6490 --- /dev/null +++ b/apps/messages/src/repositories/user/UserRepository.ts @@ -0,0 +1,9 @@ +import { User } from '../../entities/User'; + +export interface InsertableUser + extends Pick {} + +export default interface UserRepository { + get: (providerIdOrId: string | number) => Promise; + createOrUpdate: (user: InsertableUser) => Promise; +} diff --git a/apps/messages/src/responses/health/HealthResponse.ts b/apps/messages/src/responses/health/HealthResponse.ts new file mode 100644 index 0000000..f658abb --- /dev/null +++ b/apps/messages/src/responses/health/HealthResponse.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const healthResponseSchema = z.object({ + online: z.boolean(), + status: z.string(), +}); + +export type HealthResponse = z.infer; diff --git a/scaffolding/service/src/routes/index.ts b/apps/messages/src/routes/index.ts similarity index 100% rename from scaffolding/service/src/routes/index.ts rename to apps/messages/src/routes/index.ts diff --git a/apps/messages/src/routes/v1/health.ts b/apps/messages/src/routes/v1/health.ts new file mode 100644 index 0000000..db14fe9 --- /dev/null +++ b/apps/messages/src/routes/v1/health.ts @@ -0,0 +1,22 @@ +import makeRouter from "../../helpers/router"; +import validate from "../../middleware/validate"; +import { healthResponseSchema } from "../../responses/health/HealthResponse"; + +const healthRouter = makeRouter(); + +healthRouter.get( + 'Health', + '/', + validate({ + response: healthResponseSchema, + }), + async (ctx) => { + ctx.status = 200; + ctx.body = { + online: true, + status: 'available', + }; + }, +); + +export default healthRouter; diff --git a/scaffolding/service/src/routes/v1/index.ts b/apps/messages/src/routes/v1/index.ts similarity index 96% rename from scaffolding/service/src/routes/v1/index.ts rename to apps/messages/src/routes/v1/index.ts index e7235ce..a42a3c1 100644 --- a/scaffolding/service/src/routes/v1/index.ts +++ b/apps/messages/src/routes/v1/index.ts @@ -1,5 +1,5 @@ -import healthRouter from './health'; import makeRouter from '../../helpers/router'; +import healthRouter from './health'; const v1Router = makeRouter(); diff --git a/apps/messages/src/services/UserService.ts b/apps/messages/src/services/UserService.ts new file mode 100644 index 0000000..359f6de --- /dev/null +++ b/apps/messages/src/services/UserService.ts @@ -0,0 +1,18 @@ +import { User } from '../entities/User'; +import UserRepository from '../repositories/user/UserRepository'; + +export interface SyncUserProps { + provider_id: string; + username: string; +} + +export default class UserService { + constructor(private userRepository: UserRepository) {} + + async createOrUpdate(props: SyncUserProps) { + return this.userRepository.createOrUpdate({ + provider_id: props.provider_id, + username: props.username, + }); + } +} diff --git a/apps/messages/src/subscribers/BaseSubscription.ts b/apps/messages/src/subscribers/BaseSubscription.ts new file mode 100644 index 0000000..88cbdee --- /dev/null +++ b/apps/messages/src/subscribers/BaseSubscription.ts @@ -0,0 +1,82 @@ +import { AckOrNack, BrokerAsPromised } from 'rascal'; +import type { Message } from 'amqplib'; +import BaseLogger from '../loggers/BaseLogger'; +import { z } from 'zod'; + +type MessageHandler = ( + content: z.infer, + ackOrNack: AckOrNack, + msg: Message, +) => void | Promise; + +interface Listener { + key: string | ((routerKey: string) => boolean); + schema: z.ZodSchema; + handler: MessageHandler; +} + +export default class BaseSubscription { + private listeners: Listener[] = []; + constructor( + private broker: BrokerAsPromised, + private logger: BaseLogger, + ) {} + + async listen() { + const session = (await this.broker.subscribe('users_sub')) + .on('message', async (msg, content, ackOrNack) => { + const listener = this.listeners.find((l) => { + return ( + (typeof l.key === 'string' && l.key === msg.fields.routingKey) || + (typeof l.key === 'function' && l.key(msg.fields.routingKey)) + ); + }); + + if (!listener) { + const err = new Error( + 'Server received message from the message queue that it could not handle', + ); + this.logger.error(err); + return ackOrNack(err); + } + + const result = listener.schema.safeParse(content); + if (!result.success) { + const err = new Error( + 'Server received message that had content it did not expect', + ); + this.logger.error(err); + return ackOrNack(err); + } + + try { + await listener.handler(result.data, ackOrNack, msg); + ackOrNack(); + } catch (err) { + if (err instanceof Error) { + this.logger.error(err); + return ackOrNack(err); + } + return ackOrNack( + new Error('Message consumer encountered an unknown error'), + ); + } + }) + .on('error', () => this.logger.error); + + return session; + } + + register( + routeKey: Listener['key'], + schema: TSchema, + handler: MessageHandler, + ) { + this.listeners.push({ + key: routeKey, + schema, + handler: handler as MessageHandler, + }); + return this + } +} diff --git a/apps/messages/src/subscribers/UsersSubscription.ts b/apps/messages/src/subscribers/UsersSubscription.ts new file mode 100644 index 0000000..78e016d --- /dev/null +++ b/apps/messages/src/subscribers/UsersSubscription.ts @@ -0,0 +1,32 @@ +import { BrokerAsPromised } from 'rascal'; +import BaseLogger from '../loggers/BaseLogger'; +import BaseSubscription from './BaseSubscription'; +import { z } from 'zod'; +import UserService from '../services/UserService'; + +export default class UsersSubscription extends BaseSubscription { + constructor( + broker: BrokerAsPromised, + logger: BaseLogger, + userService: UserService, + ) { + super(broker, logger); + + this.register( + (key) => key.endsWith('sync'), + z.object({ + provider_id: z.string(), + username: z.string(), + default_currency: z.string(), + }), + async (content, ack) => { + await userService.createOrUpdate({ + provider_id: content.provider_id, + username: content.username, + default_currency: content.default_currency, + }); + ack() + }, + ) + } +} diff --git a/apps/messages/src/types/utility.ts b/apps/messages/src/types/utility.ts new file mode 100644 index 0000000..6680866 --- /dev/null +++ b/apps/messages/src/types/utility.ts @@ -0,0 +1,11 @@ +export type Prefix = TKey extends string + ? `${TPrefix}${Capitalize}` + : TKey extends Record + ? { + [K in keyof TKey as K extends symbol + ? K + : `${TPrefix}${Capitalize<`${K extends symbol + ? never + : K}`>}`]: TKey[K]; + } + : never; diff --git a/scaffolding/service/tests/unit/sample.spec.ts b/apps/messages/tests/unit/sample.spec.ts similarity index 93% rename from scaffolding/service/tests/unit/sample.spec.ts rename to apps/messages/tests/unit/sample.spec.ts index c85123a..ddc36d4 100644 --- a/scaffolding/service/tests/unit/sample.spec.ts +++ b/apps/messages/tests/unit/sample.spec.ts @@ -1,4 +1,4 @@ - -it('true', () => { - expect(true).toEqual(true); -}); + +it('true', () => { + expect(true).toEqual(true); +}); diff --git a/scaffolding/service/tsconfig.json b/apps/messages/tsconfig.json similarity index 89% rename from scaffolding/service/tsconfig.json rename to apps/messages/tsconfig.json index ca894b9..4fbae06 100644 --- a/scaffolding/service/tsconfig.json +++ b/apps/messages/tsconfig.json @@ -26,19 +26,18 @@ "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ /* Completeness */ - "skipLibCheck": true, /* Skip type checking all .d.ts files. */ - /* Watch */ - "preserveWatchOutput": true + "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "watchOptions": { "watchFile": "DynamicPriorityPolling", - "watchDirectory": "DynamicPriorityPolling" + "watchDirectory": "DynamicPriorityPolling", + "excludeDirectories": ["**/node_modules", "dist"] }, "exclude": [ - "tests", "coverage", "node_modules", - "dist" + "dist", + "migrations" ], "include": [ "src" diff --git a/docker-compose.db.yml b/docker-compose.db.yml index 721364d..58a81c6 100644 --- a/docker-compose.db.yml +++ b/docker-compose.db.yml @@ -31,4 +31,21 @@ services: - MYSQL_USER=username - MYSQL_PASSWORD=password - MYSQL_ROOT_PASSWORD=root_password - - MYSQL_DATABASE=accounts + - MYSQL_DATABASE=advertisements + + + messages_db: + image: mysql:latest + volumes: + - ./docker/messages_db:/var/lib/mysql:delegated + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 5s + retries: 3 + ports: + - 3403:3306 + environment: + - MYSQL_USER=username + - MYSQL_PASSWORD=password + - MYSQL_ROOT_PASSWORD=root_password + - MYSQL_DATABASE=messages diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4176da2..6b54708 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -15,7 +15,12 @@ services: file: docker-compose.db.yml service: accounts_db - advertisement_db: + advertisements_db: extends: file: docker-compose.db.yml - service: advertisement_db + service: advertisements_db + + messages_db: + extends: + file: docker-compose.db.yml + service: messages_db diff --git a/docker-compose.k8s.yml b/docker-compose.k8s.yml index f182a2f..51cffce 100644 --- a/docker-compose.k8s.yml +++ b/docker-compose.k8s.yml @@ -12,3 +12,8 @@ services: extends: file: docker-compose.db.yml service: advertisements_db + + messages_db: + extends: + file: docker-compose.db.yml + service: messages_db diff --git a/k8s/account-deployment.yml b/k8s/account-deployment.yml index f12a815..285d7fa 100644 --- a/k8s/account-deployment.yml +++ b/k8s/account-deployment.yml @@ -17,7 +17,7 @@ spec: spec: containers: - name: accounts-container - image: squaremarketacr.azurecr.io/squaremarket-accounts:v0.1 + image: squaremarketacr.azurecr.io/squaremarket-accounts:v0.4 imagePullPolicy: Always # resources: # requests: diff --git a/k8s/advertisement-deployment.yml b/k8s/advertisement-deployment.yml index beed956..2968763 100644 --- a/k8s/advertisement-deployment.yml +++ b/k8s/advertisement-deployment.yml @@ -17,7 +17,7 @@ spec: spec: containers: - name: advertisements-container - image: squaremarketacr.azurecr.io/squaremarket-advertisements:v0.1 + image: squaremarketacr.azurecr.io/squaremarket-advertisements:v0.4 imagePullPolicy: Always # resources: # requests: diff --git a/k8s/gateway-deployment.yml b/k8s/gateway-deployment.yml index 1f4fa22..a66a712 100644 --- a/k8s/gateway-deployment.yml +++ b/k8s/gateway-deployment.yml @@ -17,7 +17,7 @@ spec: spec: containers: - name: gateway-container - image: squaremarketacr.azurecr.io/squaremarket-gateway:v0.1 + image: squaremarketacr.azurecr.io/squaremarket-gateway:v0.4 imagePullPolicy: Always # resources: # requests: diff --git a/k8s/kustomization.yml b/k8s/kustomization.yml index 7a6e91a..9b72f58 100644 --- a/k8s/kustomization.yml +++ b/k8s/kustomization.yml @@ -4,6 +4,7 @@ kind: Kustomization resources: - account-deployment.yml - gateway-deployment.yml + - messaging-deployment.yml - advertisement-deployment.yml - message-queue-deployment.yml - gateway-ingress.yml @@ -18,6 +19,9 @@ images: - name: squaremarketacr.azurecr.io/squaremarket-advertisements newName: k3d-personal:9800/squaremarket-advertisements newTag: latest + - name: squaremarketacr.azurecr.io/squaremarket-messages + newName: k3d-personal:9800/squaremarket-messages + newTag: latest secretGenerator: - name: gateway-secrets diff --git a/k8s/messaging-deployment.yml b/k8s/messaging-deployment.yml new file mode 100644 index 0000000..1ce37b8 --- /dev/null +++ b/k8s/messaging-deployment.yml @@ -0,0 +1,101 @@ +# https://kubernetes.io/docs/concepts/workloadvertisements/controllers/deployment/ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: messages-pod + labels: + app: messages-pod +spec: + selector: + matchLabels: + app: messages-pod + replicas: 1 + template: + metadata: + labels: + app: messages-pod + spec: + containers: + - name: messages-container + image: squaremarketacr.azurecr.io/squaremarket-messages:v0.4 + imagePullPolicy: Always + # resources: + # requests: + # cpu: 250m + # memory: 1Gi + # limits: + # cpu: 500m + # memory: 1Gi + readinessProbe: + httpGet: + path: /v1/health + port: 80 + initialDelaySeconds: 5 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + env: + - name: SERVER_NAME + value: "messages" + - name: SERVER_PORT + value: "80" + - name: MQ_CONNECTION_STRING + value: "amqp://guest:guest@message-queue:5672" + - name: AUTH_ISSUER_URL + valueFrom: + secretKeyRef: + name: auth-secrets + key: AUTH_ISSUER_URL + - name: AUTH_AUDIENCE + valueFrom: + secretKeyRef: + name: auth-secrets + key: AUTH_AUDIENCE + - name: DATABASE_NAME + valueFrom: + secretKeyRef: + name: messaging-service-secrets + key: DATABASE_NAME + - name: DATABASE_USER + valueFrom: + secretKeyRef: + name: messaging-service-secrets + key: DATABASE_USER + - name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: messaging-service-secrets + key: DATABASE_PASSWORD + - name: DATABASE_HOST + valueFrom: + secretKeyRef: + name: messaging-service-secrets + key: DATABASE_HOST + - name: STORAGE_DRIVER + valueFrom: + secretKeyRef: + name: messaging-service-secrets + key: STORAGE_DRIVER + - name: STORAGE_AZURE_CONNECTION_STRING + valueFrom: + secretKeyRef: + name: messaging-service-secrets + key: STORAGE_AZURE_CONNECTION_STRING + ports: + - containerPort: 80 + restartPolicy: Always +--- +# https://kubernetes.io/docs/concepts/services-networking/service/ +apiVersion: v1 +kind: Service +metadata: + name: messages-service +spec: + selector: + app: messages-pod + type: ClusterIP + ports: + - protocol: TCP + port: 8080 + targetPort: 80 diff --git a/main.tf b/main.tf index c89f7f4..f6ca157 100644 --- a/main.tf +++ b/main.tf @@ -279,3 +279,42 @@ resource "azurerm_mysql_database" "advertisements-db" { charset = "utf8" collation = "utf8_unicode_ci" } + + +resource "azurerm_mysql_server" "messages-db-server" { + name = "squaremarket-messages" + resource_group_name = azurerm_resource_group.squaremarket-group.name + location = azurerm_resource_group.squaremarket-group.location + version = "8.0" + + sku_name = "B_Gen5_1" + storage_mb = 5120 + + administrator_login = var.db_admin_user + administrator_login_password = var.db_admin_password + + public_network_access_enabled = true + ssl_enforcement_enabled = true + ssl_minimal_tls_version_enforced = "TLS1_2" + + tags = { + "environment" = "production" + "source" = "terraform" + } +} + +resource "azurerm_mysql_firewall_rule" "messages-db-to-server" { + name = "server" + resource_group_name = azurerm_resource_group.squaremarket-group.name + server_name = azurerm_mysql_server.messages-db-server.name + start_ip_address = "0.0.0.0" + end_ip_address = "255.255.255.255" +} + +resource "azurerm_mysql_database" "messages-db" { + name = "messages" + resource_group_name = azurerm_resource_group.squaremarket-group.name + server_name = azurerm_mysql_server.messages-db-server.name + charset = "utf8" + collation = "utf8_unicode_ci" +} diff --git a/package.json b/package.json index cdedb9a..716adc4 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "dev:frontend": "npm run dev -w apps/frontend", "dev:accounts": "npm run dev -w apps/accounts", "dev:ads": "npm run dev -w apps/advertisements", + "dev:messages": "npm run dev -w apps/messages", "dev:gateway": "npm run dev -w apps/gateway", "build:frontend": "npm run build -w apps/frontend", "build:accounts": "npm run build -w apps/accounts", + "build:messages": "npm run build -w apps/messages", "build:gateway": "npm run build -w apps/gateway" }, "workspaces": [ diff --git a/scaffolding/service/.env.example b/scaffolding/service/.env.example deleted file mode 100644 index bca3d32..0000000 --- a/scaffolding/service/.env.example +++ /dev/null @@ -1,13 +0,0 @@ -# Server settings -SERVER_NAME=new_service -SERVER_PORT=5200 # Port that the server starts on - -# CORS -ALLOWED_ORIGIN=http://localhost:5200 # A allowed origin - -# Database -DATABASE_NAME=myservicedb -DATABASE_USER=myuser -DATABASE_PASSWORD=mypassword -DATABASE_HOST=localhost -DATABASE_PORT=3403 diff --git a/scaffolding/service/db/.env.example b/scaffolding/service/db/.env.example deleted file mode 100644 index 85ae502..0000000 --- a/scaffolding/service/db/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -MYSQL_USER=username -MYSQL_PASSWORD=password -MYSQL_ROOT_PASSWORD=root_password - -MYSQL_DATABASE=db_name diff --git a/scaffolding/service/knexfile.js b/scaffolding/service/knexfile.js deleted file mode 100644 index 80f635c..0000000 --- a/scaffolding/service/knexfile.js +++ /dev/null @@ -1,25 +0,0 @@ -const path = require('node:path'); -const { config: configEnv } = require('dotenv'); - -configEnv(); - -/** - * @type {Knex.Config} - */ -const config = { - client: 'mysql2', - connection: { - host: process.env.DATABASE_HOST, - port: parseInt(process.env.DATABASE_PORT ?? '0'), - database: process.env.DATABASE_NAME, - user: process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - }, - migrations: { - extension: 'js', - tableName: 'knex_migrations', - directory: path.join(__dirname, './migrations'), - }, -}; - -module.exports = config; diff --git a/scaffolding/service/src/helpers/router.ts b/scaffolding/service/src/helpers/router.ts deleted file mode 100644 index 750de20..0000000 --- a/scaffolding/service/src/helpers/router.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Router from "@koa/router"; -import { AppContext } from ".."; - -export default function makeRouter(opts: Router.RouterOptions = {}) { - return new Router(opts) -} diff --git a/scaffolding/service/src/providers/di.ts b/scaffolding/service/src/providers/di.ts deleted file mode 100644 index 3bf3fe9..0000000 --- a/scaffolding/service/src/providers/di.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Knex from 'knex'; -import ConsoleLogger from '../loggers/ConsoleLogger'; -import IoCContainer from 'tioc'; -import { createBrokerAsPromised } from 'rascal'; -import dbConfig from '../configs/db'; -import authConfig from '../configs/auth'; -import brokerConfig from '../configs/broker'; -import auth from '../middleware/auth'; - -const depenencyProvider = (c: IoCContainer) => - c - .addSingleton('db', () => Knex(dbConfig)) - .addSingleton( - 'broker', - async () => await createBrokerAsPromised(brokerConfig), - ) - .addSingleton('logger', () => new ConsoleLogger()) - .addScoped('authMiddleware', () => auth(authConfig)); - -export default depenencyProvider; diff --git a/scaffolding/service/src/routes/v1/health.ts b/scaffolding/service/src/routes/v1/health.ts deleted file mode 100644 index 4ef5b73..0000000 --- a/scaffolding/service/src/routes/v1/health.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from 'zod'; -import validate from '../../middleware/validate'; -import makeRouter from '../../helpers/router'; - -const healthRouter = makeRouter(); - -healthRouter.get( - 'Health', - '/', - validate({ - response: z.object({ - online: z.boolean(), - status: z.string(), - }), - }), - async (ctx) => { - ctx.body = { - online: true, - status: 'available', - }; - }, -); - -export default healthRouter; From bf3a2b3344740e5517c8b4c694f11a7f7b556438 Mon Sep 17 00:00:00 2001 From: RikThePixel Date: Fri, 12 Jan 2024 22:01:38 +0100 Subject: [PATCH 2/6] Update cloud environment --- apps/frontend/src/apis/messages/chats.ts | 5 + apps/frontend/src/stores/useChats.ts | 1 + apps/messages/package.json | 2 +- main.tf | 90 ++++++-- package-lock.json | 56 +++++ terraform.tfstate | 262 ++++++++++++++--------- 6 files changed, 288 insertions(+), 128 deletions(-) create mode 100644 apps/frontend/src/apis/messages/chats.ts create mode 100644 apps/frontend/src/stores/useChats.ts diff --git a/apps/frontend/src/apis/messages/chats.ts b/apps/frontend/src/apis/messages/chats.ts new file mode 100644 index 0000000..bdb7751 --- /dev/null +++ b/apps/frontend/src/apis/messages/chats.ts @@ -0,0 +1,5 @@ +import backend from '@/adapters/backend'; + +export function startConversation(userUid: string) { + return backend.post('v1/chats', { json: { user_uid: userUid } }); +} diff --git a/apps/frontend/src/stores/useChats.ts b/apps/frontend/src/stores/useChats.ts new file mode 100644 index 0000000..8085cbf --- /dev/null +++ b/apps/frontend/src/stores/useChats.ts @@ -0,0 +1 @@ + e diff --git a/apps/messages/package.json b/apps/messages/package.json index 53a2e6f..3cfb9ec 100644 --- a/apps/messages/package.json +++ b/apps/messages/package.json @@ -1,5 +1,5 @@ { - "name": "advertisements", + "name": "messages", "version": "1.0.0", "description": "", "author": "RikThePixel", diff --git a/main.tf b/main.tf index f6ca157..bd93f48 100644 --- a/main.tf +++ b/main.tf @@ -37,13 +37,7 @@ resource "azurerm_container_registry" "acr" { } } - -resource "azurerm_dns_zone" "backend" { - name = "sq.api.rikdenbreejen.nl" - resource_group_name = azurerm_resource_group.squaremarket-group.name -} - -resource "azurerm_kubernetes_cluster" "aks" { +resource "azurerm_kubernetes_cluster" "api" { name = "squaremarket-aks" kubernetes_version = "1.28.0" location = azurerm_resource_group.squaremarket-group.location @@ -79,12 +73,17 @@ resource "azurerm_kubernetes_cluster" "aks" { resource "azurerm_role_assignment" "aks_pull_acr" { scope = azurerm_container_registry.acr.id role_definition_name = "AcrPull" - principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id + principal_id = azurerm_kubernetes_cluster.api.kubelet_identity[0].object_id } resource "azurerm_dns_zone" "frontend" { name = "sq.rikdenbreejen.nl" - resource_group_name = azurerm_resource_group.squaremarket-group.name + resource_group_name = azurerm_resource_group.squaremarket-group.name +} + +resource "azurerm_dns_zone" "api" { + name = "sq.api.rikdenbreejen.nl" + resource_group_name = azurerm_resource_group.squaremarket-group.name } resource "azurerm_frontdoor" "frontdoor" { @@ -92,17 +91,22 @@ resource "azurerm_frontdoor" "frontdoor" { resource_group_name = azurerm_resource_group.squaremarket-group.name frontend_endpoint { - name = "main-frontend" - host_name = "${azurerm_dns_zone.frontend.name}" + name = "frontend" + host_name = azurerm_dns_zone.frontend.name } frontend_endpoint { - name = "main-frontdoor" + name = "api" + host_name = azurerm_dns_zone.api.name + } + + frontend_endpoint { + name = "frontdoor" host_name = "squaremarket-frontdoor.azurefd.net" } backend_pool { - name = "main-backend" + name = "frontend" backend { host_header = azurerm_storage_account.frontend-account.primary_web_host @@ -111,26 +115,52 @@ resource "azurerm_frontdoor" "frontdoor" { https_port = 443 } - load_balancing_name = "load-balancing" - health_probe_name = "health-probe" + load_balancing_name = "frontend" + health_probe_name = "frontend" } + # backend_pool { + # name = "api" + # + # backend { + # host_header = azurerm_kubernetes_cluster.api.fqdn + # address = azurerm_kubernetes_cluster.api.fqdn + # http_port = 80 + # https_port = 443 + # } + # + # load_balancing_name = "api" + # health_probe_name = "api" + # } + routing_rule { name = "https-frontend" accepted_protocols = ["Https"] patterns_to_match = ["/*"] - frontend_endpoints = ["main-frontend"] + frontend_endpoints = ["frontend"] forwarding_configuration { forwarding_protocol = "HttpsOnly" - backend_pool_name = "main-backend" + backend_pool_name = "frontend" } } + # routing_rule { + # name = "https-api" + # accepted_protocols = ["Https"] + # patterns_to_match = ["/*"] + # frontend_endpoints = ["api"] + # forwarding_configuration { + # forwarding_protocol = "HttpsOnly" + # backend_pool_name = "api" + # } + # } + routing_rule { - name = "https-redirect-frontend" + name = "https-redirect" accepted_protocols = ["Http"] patterns_to_match = ["/*"] - frontend_endpoints = ["main-frontend"] + frontend_endpoints = ["frontend"] +#, "api"] redirect_configuration { redirect_protocol = "HttpsOnly" redirect_type = "Moved" @@ -142,18 +172,34 @@ resource "azurerm_frontdoor" "frontdoor" { } backend_pool_load_balancing { - name = "load-balancing" + name = "frontend" } backend_pool_health_probe { - name = "health-probe" + name = "frontend" protocol = "Https" } + # backend_pool_load_balancing { + # name = "api" + # } + # + # backend_pool_health_probe { + # name = "api" + # protocol = "Https" + # } } resource "azurerm_frontdoor_custom_https_configuration" "frontend-https" { - frontend_endpoint_id = azurerm_frontdoor.frontdoor.frontend_endpoints["main-frontend"] + frontend_endpoint_id = azurerm_frontdoor.frontdoor.frontend_endpoints.frontend + custom_https_provisioning_enabled = true + custom_https_configuration { + certificate_source = "FrontDoor" + } +} + +resource "azurerm_frontdoor_custom_https_configuration" "api-https" { + frontend_endpoint_id = azurerm_frontdoor.frontdoor.frontend_endpoints.api custom_https_provisioning_enabled = true custom_https_configuration { certificate_source = "FrontDoor" diff --git a/package-lock.json b/package-lock.json index 944a2dc..4c0481e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -281,6 +281,58 @@ "typescript": "^5.2.2" } }, + "apps/messages": { + "version": "1.0.0", + "dependencies": { + "@azure/storage-blob": "^12.17.0", + "@koa/bodyparser": "^5.0.0", + "@koa/multer": "^3.0.2", + "@koa/router": "^12.0.1", + "dotenv": "^16.3.1", + "isomorphic-dompurify": "^1.11.0", + "jwks-rsa": "^3.1.0", + "knex": "^3.0.1", + "koa": "^2.14.2", + "koa-jwt": "^4.0.4", + "koa-tioc": "^0.4.0", + "multer": "^1.4.5-lts.1", + "mysql2": "^3.6.5", + "rascal": "^17.0.1", + "tioc": "^0.3.0", + "validate-image-type": "^3.0.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/jest": "^29.5.8", + "@types/koa": "^2.13.11", + "@types/koa__multer": "^2.0.7", + "@types/koa__router": "^12.0.4", + "@types/node": "^20.9.0", + "@types/rascal": "^10.0.9", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "concurrently": "^8.2.2", + "eslint": "^8.53.0", + "jest": "^29.7.0", + "nodemon": "^3.0.1", + "prettier": "^3.0.3", + "ts-jest": "^29.1.1", + "type-fest": "^4.8.3", + "typescript": "^5.2.2" + } + }, + "apps/messages/node_modules/type-fest": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.9.0.tgz", + "integrity": "sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "dev": true, @@ -10036,6 +10088,10 @@ "node": ">= 8" } }, + "node_modules/messages": { + "resolved": "apps/messages", + "link": true + }, "node_modules/methods": { "version": "1.1.2", "license": "MIT", diff --git a/terraform.tfstate b/terraform.tfstate index 38735ed..af0ca00 100644 --- a/terraform.tfstate +++ b/terraform.tfstate @@ -1,7 +1,7 @@ { "version": 4, "terraform_version": "1.6.4", - "serial": 69, + "serial": 135, "lineage": "b638d247-bec4-7079-c94c-62b1fc867340", "outputs": {}, "resources": [ @@ -65,51 +65,6 @@ } ] }, - { - "mode": "managed", - "type": "azurerm_dns_zone", - "name": "backend", - "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]", - "instances": [ - { - "schema_version": 1, - "attributes": { - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/dnszones/sq.api.rikdenbreejen.nl", - "max_number_of_record_sets": 10000, - "name": "sq.api.rikdenbreejen.nl", - "name_servers": [ - "ns1-34.azure-dns.com.", - "ns2-34.azure-dns.net.", - "ns3-34.azure-dns.org.", - "ns4-34.azure-dns.info." - ], - "number_of_record_sets": 2, - "resource_group_name": "squaremarket-group", - "soa_record": [ - { - "email": "azuredns-hostmaster.microsoft.com", - "expire_time": 2419200, - "fqdn": "sq.api.rikdenbreejen.nl.", - "host_name": "ns1-34.azure-dns.com.", - "minimum_ttl": 300, - "refresh_time": 3600, - "retry_time": 300, - "serial_number": 1, - "tags": {}, - "ttl": 3600 - } - ], - "tags": {}, - "timeouts": null - }, - "sensitive_attributes": [], - "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjoxODAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", - "dependencies": [ - "azurerm_resource_group.squaremarket-group" - ] - } - ] - }, { "mode": "managed", "type": "azurerm_dns_zone", @@ -177,37 +132,37 @@ "weight": 50 } ], - "health_probe_name": "health-probe", - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/main-backend", - "load_balancing_name": "load-balancing", - "name": "main-backend" + "health_probe_name": "frontend", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/frontend", + "load_balancing_name": "frontend", + "name": "frontend" } ], "backend_pool_health_probe": [ { "enabled": true, - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/health-probe", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/frontend", "interval_in_seconds": 120, - "name": "health-probe", + "name": "frontend", "path": "/", "probe_method": "GET", "protocol": "Https" } ], "backend_pool_health_probes": { - "health-probe": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/health-probe" + "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/frontend" }, "backend_pool_load_balancing": [ { "additional_latency_milliseconds": 0, - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/load-balancing", - "name": "load-balancing", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/frontend", + "name": "frontend", "sample_size": 4, "successful_samples_required": 2 } ], "backend_pool_load_balancing_settings": { - "load-balancing": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/load-balancing" + "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/frontend" }, "backend_pool_settings": [ { @@ -216,27 +171,27 @@ } ], "backend_pools": { - "main-backend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/main-backend" + "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/frontend" }, "cname": "squaremarket-frontdoor.azurefd.net", "explicit_resource_order": [ { "backend_pool_health_probe_ids": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/health-probe" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/frontend" ], "backend_pool_ids": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/main-backend" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/frontend" ], "backend_pool_load_balancing_ids": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/load-balancing" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/frontend" ], "frontend_endpoint_ids": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/main-frontend", - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/main-frontdoor" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontend", + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontdoor" ], "routing_rule_ids": [ "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-frontend", - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect-frontend" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect" ] } ], @@ -244,26 +199,26 @@ "frontend_endpoint": [ { "host_name": "sq.rikdenbreejen.nl", - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/main-frontend", - "name": "main-frontend", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontend", + "name": "frontend", "session_affinity_enabled": false, "session_affinity_ttl_seconds": 0, "web_application_firewall_policy_link_id": "" }, { "host_name": "squaremarket-frontdoor.azurefd.net", - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/main-frontdoor", - "name": "main-frontdoor", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontdoor", + "name": "frontdoor", "session_affinity_enabled": false, "session_affinity_ttl_seconds": 0, "web_application_firewall_policy_link_id": "" } ], "frontend_endpoints": { - "main-frontdoor": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/main-frontdoor", - "main-frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/main-frontend" + "frontdoor": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontdoor", + "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontend" }, - "header_frontdoor_id": "a0cc8bb5-e54e-44a8-9d13-2b6d2b9f8852", + "header_frontdoor_id": "90491286-212c-4740-9de1-1184e1c5bfcc", "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor", "load_balancer_enabled": true, "name": "squaremarket-frontdoor", @@ -276,7 +231,7 @@ "enabled": true, "forwarding_configuration": [ { - "backend_pool_name": "main-backend", + "backend_pool_name": "frontend", "cache_duration": "", "cache_enabled": false, "cache_query_parameter_strip_directive": "StripAll", @@ -287,7 +242,7 @@ } ], "frontend_endpoints": [ - "main-frontend" + "frontend" ], "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-frontend", "name": "https-frontend", @@ -303,10 +258,10 @@ "enabled": true, "forwarding_configuration": [], "frontend_endpoints": [ - "main-frontend" + "frontend" ], - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect-frontend", - "name": "https-redirect-frontend", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect", + "name": "https-redirect", "patterns_to_match": [ "/*" ], @@ -324,7 +279,7 @@ ], "routing_rules": { "https-frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-frontend", - "https-redirect-frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect-frontend" + "https-redirect": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect" }, "tags": {}, "timeouts": null @@ -360,8 +315,8 @@ } ], "custom_https_provisioning_enabled": true, - "frontend_endpoint_id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/main-frontend", - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/customHttpsConfiguration/main-frontend", + "frontend_endpoint_id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontend", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/customHttpsConfiguration/frontend", "timeouts": null }, "sensitive_attributes": [], @@ -378,14 +333,14 @@ { "mode": "managed", "type": "azurerm_kubernetes_cluster", - "name": "aks", + "name": "api", "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]", "instances": [ { "schema_version": 2, "attributes": { "aci_connector_linux": [], - "api_server_authorized_ip_ranges": [], + "api_server_authorized_ip_ranges": null, "auto_scaler_profile": [], "automatic_channel_upgrade": "", "azure_active_directory_role_based_access_control": [], @@ -406,7 +361,7 @@ "node_count": 2, "node_labels": {}, "node_public_ip_prefix_id": "", - "node_taints": [], + "node_taints": null, "only_critical_addons_enabled": false, "orchestrator_version": "1.28.0", "os_disk_size_gb": 128, @@ -414,7 +369,7 @@ "os_sku": "Ubuntu", "pod_subnet_id": "", "proximity_placement_group_id": "", - "tags": {}, + "tags": null, "type": "VirtualMachineScaleSets", "ultra_ssd_enabled": false, "upgrade_settings": [ @@ -424,22 +379,22 @@ ], "vm_size": "Standard_DS2_v2", "vnet_subnet_id": "", - "zones": [] + "zones": null } ], "disk_encryption_set_id": "", "dns_prefix": "squaremarket-aks", "dns_prefix_private_cluster": "", "enable_pod_security_policy": false, - "fqdn": "squaremarket-aks-x3z8ynwg.hcp.northeurope.azmk8s.io", + "fqdn": "squaremarket-aks-t989as5n.hcp.northeurope.azmk8s.io", "http_application_routing_enabled": false, "http_application_routing_zone_name": "", "http_proxy_config": [], "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.ContainerService/managedClusters/squaremarket-aks", "identity": [ { - "identity_ids": [], - "principal_id": "e41a5341-eb33-470e-b1e5-ebd813c3093d", + "identity_ids": null, + "principal_id": "13076939-1fcc-42ff-bfef-cb44524d7462", "tenant_id": "0172c9f5-f568-42ac-9eb8-24ef84881faa", "type": "SystemAssigned" } @@ -450,19 +405,19 @@ "kube_admin_config_raw": "", "kube_config": [ { - "client_certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZIakNDQXdhZ0F3SUJBZ0lSQUtZa1JQTFkwdmxmSzRLZVJSN2J4dG93RFFZSktvWklodmNOQVFFTEJRQXcKRFRFTE1Ba0dBMVVFQXhNQ1kyRXdIaGNOTWpRd01UQTRNVEkwTURNeVdoY05Nall3TVRBNE1USTFNRE15V2pBdwpNUmN3RlFZRFZRUUtFdzV6ZVhOMFpXMDZiV0Z6ZEdWeWN6RVZNQk1HQTFVRUF4TU1iV0Z6ZEdWeVkyeHBaVzUwCk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBdnVMSkhJejNkY1Y2d2h5N0xhSVkKZW1zT3RIWVIvckRrQnUySkJjUXRUTDBOOVE2TFRKSlY5T1NzTHp4NExQQzhTc2M3MUlvK0dPV01FRGdYc24zNAovSlB1T0JLMWZKQ2t1TklBanR6NXNHWjhvcDg3REtRMFBjL0Y0dU1kN0xDdW82RnBtR2RZTTcwRTZsb2s4N24rCnpROG5HUDFBRE1oaFNHSnFuU0dsV2gvWURWTi9ZbWV6N2NuYmJwZ0FzMHN2dXVkY2I5Ylc4TWJJS1c5aGNPS2YKQmZkamNNVzRId2E2VG43K0VCZHkyYlBYamR5Q2pYMkUyWlI5TUtOTTZybmpCUk5vWGhxK3drM1ZOSTdoVzlSeQp0bEUra2RhZG5MTG92a2Y4UnBGUkt2eGMwV2tSdFBsWnc5NHVidDdXM1NmQjZnQ3JoODgrQlZ1VzRpUGJxR0NNCmNYaWFFL1ZXMnNpTmNrQWtCRVhIMEJsdTlXQTJzNGNzeUg4Q0c4bEVvMmFhVUFiOGpiVzh1OEtsVGg0WThibjMKdWNWQVpPZXd6QzRqdXhEMzRTTEhKVUdsUkw3OUxHdVdRdDlOdWVQV2dMb2p2SG5RelVmUGRQVzNlZE13OHUyNQp0ZDRPZTZrVC90eEczZjYybVV2YWltY3Naejc0b3BzY1pjdS9RVC9LcUk2dWFHRnF6WWZxYkVhVXYvVitOTmpkClo0YjhmTS9qZ1AvQlEzODhwbnRJLytYQS9vVHZENGRiUmhXWDBLWUppUzQ3RS9WVmpRZWVKakNCZ1lZV1craFcKbURCTlFzWi80eW5HVnRDV2tBWmJPZnUvZjJ4b1ozelY1ZnBMTzZmcFEvcXlIQ1k3WTE1SGZ2YWkvcUNUR0FsWQpraUZZTy9nMWd5NCtIVGRFK04yREFHVUNBd0VBQWFOV01GUXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkCkpRUU1NQW9HQ0NzR0FRVUZCd01DTUF3R0ExVWRFd0VCL3dRQ01BQXdId1lEVlIwakJCZ3dGb0FVUEU1Z0o5Q3kKMGpXNzJRaWxrN0YwbGQvM2ErY3dEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBSWFseTIxUm5vTVVvSGxhTytwdwptRENGZThkaDhLb2RmaTdrV09iT2loYTJleEgxQjZyZkpsU3dHdFRYSWJMOFIzSTFEc2hnb0ZIUDNhemh2Z3YxCllJakVxdnR3ZEEzcjcydXNOUm01WEtLTlBQN2JTclFXUGR0eUl0c2VjTHV6SkM3NVhCWFptYkphWUptalVpaksKNGpCYXpVcTM3dkN5NlYyMUNrR3NTaTByZk81c3E5VmV3QjZQZDNpd3JJYnplMC9BMjZKejlOeG5EeVlWcEptWQppQ3JvNDhWNGltS3B2K1VzNlYzYTZORDRtMVN4WmxhKysvbmkwVlpqZHgvbUFleVZ0bXpESUZneURSVjkvaWk2Cm9yNUJ5bFM0Y1JxaW9SVmxvTmlubHNLV0o5MTRBczhMTmtUM3BqemZ2OXVjcFN6dGFnd0Q2TFNtNFlWaFFiSmIKczZBZjlLelFYMnJQbGdEd1FhVis1Y0VCbzhOZlA2YmxmdlRkWDJPOStGSUx2THFKZ1V5OXlDclVaMmtmcWI5cgpQN2RqNmNXQVJlSHB2bDdQVENuYUdWUUJGeXh3aDNibndReDFyOTlaTjh3WVoremZqVmhaRzE0VnhzYUNBMEZqCjdLNEJJaG16Q094eDhqS0ZhSGpwM1c5cDkvaGRjZXRBeEVWOEczWkNWUGpWOVByVnEweWRQK3lmeDdhNmtIc1UKaG1BMlp6NWJSTHB6NFNObGlXV211MDFPbEpsSGlaNWJXNnBkTDM1Ry9uYncxaU5DeWhFMEl6WjZGR05zaXJkcApLb1JjaHhTdGhweWRMalY0ODdQZnJ4cUxSS082a2x2aDRnVjhBYkNhYWxjcHh3ejJnajN1clpVUzhQNUlBaEM3CjREazJjalkxZVhBYkVHZnJXYnd0d0d2QwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", - "client_key": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS2dJQkFBS0NBZ0VBdnVMSkhJejNkY1Y2d2h5N0xhSVllbXNPdEhZUi9yRGtCdTJKQmNRdFRMME45UTZMClRKSlY5T1NzTHp4NExQQzhTc2M3MUlvK0dPV01FRGdYc24zNC9KUHVPQksxZkpDa3VOSUFqdHo1c0daOG9wODcKREtRMFBjL0Y0dU1kN0xDdW82RnBtR2RZTTcwRTZsb2s4N24relE4bkdQMUFETWhoU0dKcW5TR2xXaC9ZRFZOLwpZbWV6N2NuYmJwZ0FzMHN2dXVkY2I5Ylc4TWJJS1c5aGNPS2ZCZmRqY01XNEh3YTZUbjcrRUJkeTJiUFhqZHlDCmpYMkUyWlI5TUtOTTZybmpCUk5vWGhxK3drM1ZOSTdoVzlSeXRsRStrZGFkbkxMb3ZrZjhScEZSS3Z4YzBXa1IKdFBsWnc5NHVidDdXM1NmQjZnQ3JoODgrQlZ1VzRpUGJxR0NNY1hpYUUvVlcyc2lOY2tBa0JFWEgwQmx1OVdBMgpzNGNzeUg4Q0c4bEVvMmFhVUFiOGpiVzh1OEtsVGg0WThibjN1Y1ZBWk9ld3pDNGp1eEQzNFNMSEpVR2xSTDc5CkxHdVdRdDlOdWVQV2dMb2p2SG5RelVmUGRQVzNlZE13OHUyNXRkNE9lNmtUL3R4RzNmNjJtVXZhaW1jc1p6NzQKb3BzY1pjdS9RVC9LcUk2dWFHRnF6WWZxYkVhVXYvVitOTmpkWjRiOGZNL2pnUC9CUTM4OHBudEkvK1hBL29UdgpENGRiUmhXWDBLWUppUzQ3RS9WVmpRZWVKakNCZ1lZV1craFdtREJOUXNaLzR5bkdWdENXa0FaYk9mdS9mMnhvClozelY1ZnBMTzZmcFEvcXlIQ1k3WTE1SGZ2YWkvcUNUR0FsWWtpRllPL2cxZ3k0K0hUZEUrTjJEQUdVQ0F3RUEKQVFLQ0FnRUF1QmMzSlZXK0FkS21CVFo0UGhZcHpVWWFOR2Y2Q3NyRjN2aU9RUVZsT3JESkdTRWhyb01pYXI5dQpYMC9PQm1sVTdqa2V0bWRzWTg1ZE9mY2V3NEZCOHpVbmRPUjJLdC9FUElvckI1eU5JVloxdE5BanZwbE4ySjdjCmxXZTdvRnowejlJSGFIZWlWS05pYmpEcG1qUXlTOGpsUi9CVktQN0c4MndXcmR1WkFFWGRzMXhuYVp2aHB0NFoKeFpyS2NoOTh0S0FVUWJmTUt3RktZTXVPQ2JSRThSZ1l5dFdrTUNmbzNYTVR5cWtTbG1vZTVDMVpxdUwya0cvbwpIRlFoQmU2bnhtbGxLZnhaSkVLejc3QXNIc2ZRcVl2SThCVXVnOFQzT1N0clhSTWdXSGh4ZTNQOUk2K21wb05MCi9KamtWdnpVd3J4S3NQdHBNUG1YbGhoRzZ5ZlJ5S0xISjY4d2JKNURnMldoSW1iT3RIR2xYSmpsbzh5RUNpc3oKd3ZINldyZHlEZmp2anJtaDI2eDFzTW8xL2x5OFNRdE5qYndjK25IdXNkMnFWakZPN3AzUkJzaWJRaTIxSmc3bQpPUVRNWE5xcVozOUNLK0hsVW9LVzJWa1ZmYWplSGtwZURtSjByRXpaTVhGZkRONndudjQ1bmxYWjI1SStTOG9mCmloVjFBS1hIZlk2Zzk0ZVdaTVE3UVk4WmFJaFNhNkppREI2OWJINkVqTzZ5V3lTT3c0bUI3OFBNMitsMlNQa0UKalpFZ0YrUnRkeDRBTU1GL2VxbmVvQ2lQdG0yUC8rVi9xSHRhSUE1cHdDMzJFdGlSZ29QUk84UWpwYWl0Q0V0MApPcjV5TEkyWjlRZ2prTW1tZVUxZDF5MllsZkRsN0dza1BPeFo3ZHdIQXFOR2t1SnA2ckVDZ2dFQkFOd0Q4TjU4CjUwWDF2VXd2d1FvWlJqMXBITUs5aXpNQTJFS28wVGxRWEVHSXdHNUIraHdrdUJWM20yQjdCazJsYTM3NG9WM00Ka0xDVUFGbDducTYzblNlNHVTSVlpZlc0SHZ5TlpXT0F5d2o3ZG5Ealo5SmRWRlZERGhoVHVMUW1hMTRZR2xjbQoyZDBDcWFqZTI1YytwTFBDSlFzdDNrNnpYTHN2bklWNUhVVllOMGNDUmJTVmFXMnNyY1lFUnZYVDI5N09nL3pVCk9UbE85enlQaTlORmJ3ZjZGbmFESWhyQjMvQlJvcXNzNjhtblFoZm1ua2RxY0xhQTV6M214eU12V0x3cWd5YkMKVW05TWNPaFpyUVo5c29UUFRSWjRhc3EyN3ZleFl0cEFnQXYxUHlUQ3d0WVhWNUc3NkdmZ3JBRm5JdmNKUHVGdApGZ0FpaDh4RWVsWllvbzhDZ2dFQkFONGJNS2dZOFlYTXl0NXozWlhLUXllWWRmQlZ2UHd1a1lJenJjb2N3ejIxClBTOEIvb0pIQ0xDRzFYWCt0SUZTK3FDNUNtR09GQ0FPMUx3aWFCZEZKSW83K3BwUERRLzZyQWJkQWRBU3NaQ0gKemhyR1RqbTlCbjU5ckJEKy94aVhDWGNNMlQzUzk4bnN6ZUgvL2lIQW9xNE14ZzFreWV1RWlZNmdzK3U0ZXR6eQpzM2ZWQ2gyai9mWEM1Y1NBZmdwMXRQdldYRHZVSFpZeHRHZ2pEMUhVbE1VUVQzS0RFSG1SVmtWYzl2UHJ1Z2ozCjlwQkVzMGpQNkM2TDliWGF1TVBFTDVvK1ZPaWUyWVVGSTdlVFdkT3FpL2FYU05GYzV3eWNsbUhsWnQwUUt0eVUKOXV0dlNvL1dsSHJERVpMc2tqSWR2WEpXZVRUVFBYbVdTSGUvWTBvTTE4c0NnZ0VBWE9sZEl0ZmtZdXJ2MGVpbwp3TGNEcnBOaERldzFpSWhteTErM1cxalFYT3gvTnhDdHFvem5JRW11V0FRY0dWWXVHeVNzSUl4dTNlTGljdWQ5Cmo1TUN6dTkzWHlxMXVBclZLeG9iSXZZVXgrcEpqa3E2WVdBQWFURTBGcG5rZWs5dk1BRkZ2bUF0eU9PQytrbUMKdWxBK0JadUxRVGxSbERLeWltb0UwMmplTDVhZFlXTllSSGFFVzBoODZnNk1XT3ZyQWlCOEpKMEt2V1RYaTcraQo4TWwxcGd5Nng3bUJBWU8yS2lxUFkzSkk4Sk90bWFKT1hTSmFEdmhJcng5eUlTMnhiMlVVbFoyaWFsWUJxRjJXCmNCTk0vRUQrOE9wUlR1SUo4SmY4Z3VhU0xIcVkrOUV6cm0yVkVYb2xIeGtHQ21HZnBtdEZIcldPZ09nVUtQTVMKSnJ5dmlRS0NBUUVBcEdHTFNWM3NveEpHc3cwZDJsYXdEUVlSZzZaeEVGT0c3YzVCdVd4QU05T0hUTnMwK2thSQpiOTNhN2xGc1ZxcUxRNWl2empQUlFVaTR1ZVRkRlJ2ZlIwL00wWmZnR3d0emRrRHJyb3VuVDBpemlhMXJCK0NXCmhkZU5HYWNnTlpyVExvRWI1MTNVZG1ZRW1sZFVScmJEci8xVU4yVjQ3QTVUb0hQZnRYMzdzTHNQaUlqcnk3UXIKcVZCdHJWbEw3NEhlU09VRnJtSDI0OG8xc3p6L2daakw0bTJIMzBDUldPZnY2Ylk4TDVzdlBLUUlvQTI0Z3hNYwp6QlpaN0JYTTJWYkJ4RzZOZVB6WUVGTVZkREJpTXYzdVNMcis4YlRJYTlVWWw2dW96Y1BCZFMvcTc2U1drbzVGCnBmWHBjOHdUQjNScldjd1dPTHArclJaRTNCdVI2S1JJc1FLQ0FRRUF3Uyt6bHdxOTl3U1FJby9LZXc5eHI1TDYKYlRudXYrbktRa1U1ak4xM0NHLzFpSUVITFJKUXFZNjRoeWpwMVQzWTZCeXYxdllDenQ2RHhLWUNqRUJ4b25aeApkQko1THhCWnNLWWsyRHVPUllselZIZGNZWVdXUTR6LzNhb0hSa1M5OFhvUjBPZmNTWUFZdmtSdnQ5OEdrU2VmCkd4MmxFb3Nac2dsTmMrUWhnSHI2bHhsQmNRbWlhSmRwZ0gySkk2dXQyT0UvdE15Sk1VTUo2VkRSTXZuNjZEWnQKSVROT3prYkJEYkJZbHg4NDVubjBvL0VOeVV4Mnp5aG5vQkpZZ1dmYlBxOWdQNmwwUlB2cFU1bGhGdUsvUko0Ygp6eE1hbWtINnlBM2lDd1VqdDFEY0tqdUVoYVhUbUxZM0xLWHIvdkRpV0pIWm9rQm5TN1JKb0RLTjVnVWN5dz09Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==", - "cluster_ca_certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU2VENDQXRHZ0F3SUJBZ0lSQU0xNWRIbjN1MitoYmxGQUdpS2RYZGd3RFFZSktvWklodmNOQVFFTEJRQXcKRFRFTE1Ba0dBMVVFQXhNQ1kyRXdJQmNOTWpRd01UQTRNVEkwTURNeVdoZ1BNakExTkRBeE1EZ3hNalV3TXpKYQpNQTB4Q3pBSkJnTlZCQU1UQW1OaE1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBCm9NV0xHYmo1alNQaUQrUXhvRWxQV2ZrdktpcXRZWFc3RVM1ODNRbFJETzFKdHRYWFZvWjBtN1NUbG4xVHN2MUkKNTdMWVZyMWEwV1cyL3ZSa3pJN1UrQ2M3RTRzSldDT1VxWXNJekExTGpFOGRBVVhWWk9UUWI4K3BJWXV4TzduZgpFQzQySWFyZnVXSzczdlB3RGNiVEw3b0FRV2c2UkxTVWRQTThoc3JLQlpZMitBMHFQTHYzOUtXRXM4YW5tc0Z5CnIzemVnZmtSWmo5TWZaSnJjTUo3K0paUExVS1p6WGxIbnFZU2trTmdobG1uMW9mdjZWTUp4bFh0OVFlY1I3QUEKcjA3OU9ISzlPWU52K0UycG1qOGc4NkI0a05MNWgzQk5KaEtvTzhobzFHdnVEVFZoa21mUGRqWUpZcHErMWtqUQpuZUhkWHU4M3JLa3ZzeUNvZ1d2eVZmREZ5ZkZQV25uYlVOVEViZU04bWY5MGZ1TlgyZEJrVFVyNVNtR1dJbHhVCnR2Y20yWUpNd0N6dkcvRUozTHdrRDAvdkFKaDZKK3lxYnJhdjJncHB3VVVDakhieFFtbDl4SFZ0NElkdXBoSHYKSWp0SXBWV0xmZXBJdENEb2FvNk1Jcy94ZVI1eTl2VHpCQWdWVWhHSmt0WXFIUXl3TitBTlFVeFZraGg1MjJSOQpQeW8wRnZUK202ZDE4aFZ3L1kwYWRpTWFxeG5Sem04WWVrRUZ0b2VDcHllT0krT1JVM2Y0QW5FZEFKQUhaODllCnB2bEtTUXRjaTRpTHNRSnJZWGJCSjI1SDl4aHhNTmZmVVBBM29GV0h4K2cvbTVSZHpTSnArUmpQTFc4bWZ5TG4KR2NNeG9URGFrR0NubG12eWIwMmlrVU9kTWlHM2JkakQ5bUhubmtOVHMzc0NBd0VBQWFOQ01FQXdEZ1lEVlIwUApBUUgvQkFRREFnS2tNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGRHhPWUNmUXN0STF1OWtJCnBaT3hkSlhmOTJ2bk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQkNXcTh4K0NBdjdod2luOWhhVTM1TXdOdWEKd0ZqdkUzZ1VUS0t4cWZ2cEZ4SkNDajA0NzFMOHJ4NWx1SGViMmVCcTIwT3BoRjNUR1Zhc1Q5NTBrcVN5eklMawpYdklUKzhqc2lqNEhCTmhVRkhqbGN0V1ZQSVFpeGR0RkZBWWtNa091bHVhWlQ5RlBSNlVISklTQTFXSnYrWFhhCkgwOWNKWEJlYmlQRytIQkpwVjdLckVtL1JnR2Vvc1BXT2J0TmlzaVV3TzExUFBST01NektkSThCbmhUQ3RweS8KcGo4dmtoMVRvamFyVHdUYkRMSW8xd0U3SXA5L29lTXhEN2kvdzNXZEtWL3YrK3pWWWd5MTBSZFIycUJ0NXZpMQorYm5GanNDR1NKWTA3VVF0RlRzWDNyeitZRmc4ME5tbk82bUZUYWZGQlpybHBOUTRSZDkxVXRTSzMzdG95UXBvCmlaRXRuRXF0WWtsczZlRTlNMURyL3JkYnpySDJGYk9GTlZDQjRZNnpNNzErbWY0QytQaGlPd2k4MFJFcnR4TVoKU0JvZVhSZzUyOUhJdzJvNWpOYlB2NDZOMTNhMU0yNkNESkVSWHRCZ0FPM1JVay9kVG9ici9JTGN0UWtiYUUreQpBOFJZM0dERDFJeXhSUUhaR0ZjWmV5WGJmYkRMbmNPUGEwcFUwckZLcDdxeXZhVElmYUZGNzhqQUYzV2wvS0NMCjVEckcrV1hJZkN6ekoybjZFZzhqbXJlVjlLOTY3Wk5NSGNJNmVzelN1WXRwL0orQ0VNNjUwa0hoL2ZJY01iUHIKWXJDYWF3eHdwRWZNK1Z0MTlXencvT21YcWc0RU5nTTd3N05jdTlXTUdTazg1M2lqaFA0aHlCS3Ewc1c2VHFkSwoyM1RyVDR5cjdySmJSOURaRVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", - "host": "https://squaremarket-aks-x3z8ynwg.hcp.northeurope.azmk8s.io:443", - "password": "1tz37c4ameoyvmakjv9630y4xfgz04hkft390u1zt5mb17xbggmpojzjx0gs1at1kz6cadg9k3s6304b0wp4wccvoqocjqz57nbyb6v0dv7d60a41c4ts3bivijdatgr", + "client_certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZIVENDQXdXZ0F3SUJBZ0lRRXAvV2JlRFViNFR2Z0tnaGVFbXdKekFOQmdrcWhraUc5dzBCQVFzRkFEQU4KTVFzd0NRWURWUVFERXdKallUQWVGdzB5TkRBeE1USXlNRE0xTURGYUZ3MHlOakF4TVRJeU1EUTFNREZhTURBeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1SVXdFd1lEVlFRREV3eHRZWE4wWlhKamJHbGxiblF3CmdnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURPMkZlYk5JK1NuYjJ2emI1ZGJhckUKcFhjU05ETURlMDFsYUlKL3VlYkY5ZVFKV1diUERxcVl3WVdjUlo0SlNKTjNFa0Nwb1lrN3I0UDJvUEZGYWZuagpES3NFTERBSm50M2NlY3NDZkJnRFZkUWpiOElTMW5BV2FrUXB1OFo0ZFRkU0REa2piR3V2bXZNb0VXYnc0V3MwCnVuNFdGamxEWmRFRHJVQS8wUjBpc3ZHc0NDQVQzMUMrNzA3M2pZRk1xK2MyMjBYMEhiSTlLOWF6T21LU0RIWWgKbytQSlBnZ0s5c0F4OTZwMG9kdS9KSFR5VEI3dWJSbG41a3pxczlTUXcvaUZaeUIvb0hhUW83cXZsdTc0OHRYQQp1TGlOQWl2ZXZ1bDZEOWlvMzk3R25vSE9wcjFuZDhLdDhqZXRnbGZ4VmJ6dUhsR0lEbkZhNkw2Mldwd0UyUTVsClg3SmFqTzhXV0xJTHIwOTUvMkNSbHVyV0sxWERpeFNRM0ZXY1VJelc3WTJiNG4zbXZhak9yWVFnRGpZTDh5OXAKRXJPZ2xJbWZ3cGtidEJsMUFhS2FHajhvRkFtNUxPdFhzQVNhSnlUd1dPQVFOV0RTL3ptbmF5bmNnQlRHWVlKNgppNkFkQ3E4U3UveDdLT0xGS0hBN0hOaGxJd0pDUGxpM3ZWWWQyYnpKd0Jha3Q1WUpwZ0FUcEluWXJOSUhKYW93CmM0UTArSGRZcC8yN3N5cG1BT0JxTlI5YUZVVXRqcVVNVk55R1RQQ1JuaFdURGMxU0YyR3F0bVlCVVVTdThzeC8KU3FNWVAwQ01lQ3B5aDZUL1oxa0Z0c1BQZWJWU3JqcnpHYlMwbDE1QjN3Z2RCYkNEcWZwdlJCa0ZzcHZqaFdKeQplSWRsY1RHUVpGZmJ3SkVIN2lWY013SURBUUFCbzFZd1ZEQU9CZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsCkJBd3dDZ1lJS3dZQkJRVUhBd0l3REFZRFZSMFRBUUgvQkFJd0FEQWZCZ05WSFNNRUdEQVdnQlR4eGVhN2RUeXoKQUdLcFlyRnVWL1NBRXpTOVpEQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FnRUFVN1lVMmdkYUNvejArRnlWUTJ3dwp0ZFhFMHlsTnRuK2VzTFhNK2llQy94WGxjdzNkdExQTWl5OXk4bmdMS0NCL2lNRTRNWFhEQ0l0N2RYTU9meC9tCnlqWGptTk1VVkd1ek5vUk1melgxVTd1clYwTG15cnRpQTVvMklaU1dzdHIrelVveDlndjV0dzFWdm15U2pxcUYKTnFUL0hjUytya0FzbnB4VUxXOUphSmNrSTlZVVo1OEZvSHFmaFJ1by82NUxXNzRMcWMwY2RZRlFiQktkWDFxSwo2Y2dmQVIvQTI5WElucjJmYmQraGNtSHEyNGZObzh1dnNsbW9UbzN5U2NLbDRwbjc2K1NwcGgvbGtIcEJ6d3lrCi9XdGJ2QWxlL3pwL3RtckxOQk5FUzBDaERNTXNhUzdkVmNXaXYzZ3QxN1YyS0NGbjZYeTVjNGIyWC9TV2RXekQKR2h5c3htTlh0VDIvLzFya3ZURU1iaGdWcVB2QW53RGRqRnV5NzZ6TFA2cDRIOXJWUmRMYy9CQlUvVnA0NXR6aAo4RVZwZkRsUElXeVRNVHVucmlRN1ljZW53dkFxblBFVnpFaXJ2OThsS0tRODV6MWprRmp0UXBTZWRVcFhHZGRJCkR0R0luMU5OaERNMndQVVdpNHZBdUVIaTNFWVdUYUtEVVp2eVg3czVEVzRNUlljWVhJZ3BEK3hOQjA0Z2hsWGkKNWdRV2RaUzZmYjF1V09pQmU1dkJTTUNRQzE3dFZzR2lBWHY5SnVJSklqZE9XZnlrb2U5SmlDTjZ2Mzgxajg1YQpTdStaWEZwZmx4a3ErT2VWRXJPZGVVQ3ZMYW5WV2J0TDVjZmdrV1M1eXdxMjFEa3FjYWFYR3NQSXd6Qk1RWWk2CjJCY0ZLc0diR0RyYlVROXEyYTJGRlY0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "client_key": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBenRoWG16U1BrcDI5cjgyK1hXMnF4S1YzRWpRekEzdE5aV2lDZjdubXhmWGtDVmxtCnp3NnFtTUdGbkVXZUNVaVRkeEpBcWFHSk82K0Q5cUR4UlduNTR3eXJCQ3d3Q1o3ZDNIbkxBbndZQTFYVUkyL0MKRXRad0ZtcEVLYnZHZUhVM1VndzVJMnhycjVyektCRm04T0ZyTkxwK0ZoWTVRMlhSQTYxQVA5RWRJckx4ckFnZwpFOTlRdnU5Tzk0MkJUS3ZuTnR0RjlCMnlQU3ZXc3pwaWtneDJJYVBqeVQ0SUN2YkFNZmVxZEtIYnZ5UjA4a3dlCjdtMFpaK1pNNnJQVWtNUDRoV2NnZjZCMmtLTzZyNWJ1K1BMVndMaTRqUUlyM3I3cGVnL1lxTi9leHA2QnpxYTkKWjNmQ3JmSTNyWUpYOFZXODdoNVJpQTV4V3VpK3RscWNCTmtPWlYreVdvenZGbGl5QzY5UGVmOWdrWmJxMWl0Vgp3NHNVa054Vm5GQ00xdTJObStKOTVyMm96cTJFSUE0MkMvTXZhUkt6b0pTSm44S1pHN1FaZFFHaW1oby9LQlFKCnVTenJWN0FFbWljazhGamdFRFZnMHY4NXAyc3AzSUFVeG1HQ2VvdWdIUXF2RXJ2OGV5aml4U2h3T3h6WVpTTUMKUWo1WXQ3MVdIZG04eWNBV3BMZVdDYVlBRTZTSjJLelNCeVdxTUhPRU5QaDNXS2Y5dTdNcVpnRGdhalVmV2hWRgpMWTZsREZUY2hrendrWjRWa3czTlVoZGhxclptQVZGRXJ2TE1mMHFqR0Q5QWpIZ3Fjb2VrLzJkWkJiYkR6M20xClVxNDY4eG0wdEpkZVFkOElIUVd3ZzZuNmIwUVpCYktiNDRWaWNuaUhaWEV4a0dSWDI4Q1JCKzRsWERNQ0F3RUEKQVFLQ0FnQktBanh1U3dNbjZZcktVV3BRa1lHdGRLckM5NnhkRFl3bXdac1h5bE5ZOTE4TlBWQzh1em10Q0drZQovZ1hLdW80UTU1WDlXOC8zYkJoaWNUbUNwYVdFYUs3MEFqOTU0Yjd1T1NHN0ZOMmVxK0lJZjk5SDBEUXd3UHI4Cmw3UXV1M244d0dUZFdOaTY3MDBiUkFhNkgxMjFjeGpVSEVXRzZQSTlSNHNhaWw3TFlJMTZDVG81QWdrcEdBVUEKTEVCY1I1R21IaFRNZ0tlYW5Zbm5mUGR4M0VXVVNjaWg0N0VMQXpqSGFCODBOZ21qUFk4ZWRzQUdmVUMrM1RjZQpMeFBhYkZRS24xaVFjY0NhaGdxdzh1YzBhZWF6V3lTbFIyYWMvMkFZalh6dkI1TDFMQmV6OGhNSTUxL2Z6eSsrCnhFbGxEMUZYUGIvY1Y1YVZlWHRyVWNaTE1jS2xPK1crNkZaRXcrQzVCMmFtZWk3bTRWclA2b3BRUEZEYTBZaU8KQ2NvczloemxzTnVqMndjcU5meTdzVGVzWGErbExCRFluRHNTa2t1cHlOWUJVVllEa1EvOXp6U0xibGkyZFhwdApjUGJwbFE2MDJONGQvRzVyZ1BFeDNJbHhPMGRjWjBsRUlrc0RqK0s2cnlHQVZpVVlDbm9xYndHQURtcUU0VzBLCnRmUmVjNk5OZUhTTTBDTm5GdTdnZHNJQk83dUhQeUZUM1JRTm96cXowMVc1c1Fpd3h0SG1NZ2ZjTElBZDZSQUkKK1N1TmZwRk9SVjRZYWFMN0o0cm5SRjNmOGppL3ZvOW5EZWtabDBjNHQ5aEIxbWRPZVNGd0xuU2h1RHZDMnJmZwo0YnBCemdqb3B5MUN6ZDJSK0NkMDNId0U0bUhkTDl3YkFkZHdWSjF3dVJLS0IwNWpJUUtDQVFFQTlvR2lPN01QCnRZbGFTbCtvNmNTQ1kraDBKNjlvOEdRNVNwb0tRMmJlZGp1dm9QSy92Yjc5V2YyQlR3Vlg3eVJsdG0xZFVPOFEKNnpKRko1cjBuMElaZElSeG02dTQ2a1M1alFMNTV6WTMyUlFhWGhmVWNBbzdkM2RLMkgzeXZESVFERzViTFdieQpjenJ5V1FCUmFGbzdOb2RKd0k0dEQvSkZSOVNvZTR5dFBuY3lLbUdhOVZUTkdVNlA5eE04MjQ1LytuRE5GK1dZCk5HYUZLZVUxOFpQeWZPNlZ2ZW85YVBtY0xaeG1YZGZjeXlRUGsyU3pML2VDaFovbWZDeitWdjc5ZWxQR3NaRHEKaHZWWU9BL0hnQjhKbFZZMERDT0p0YkF5RUVlUHIyZjNBWWVlZ0NKcmVZZ2hkd0J3b2JJS0hVWUplV3J2aGd1bQpjb2s1U2hrVkEwM1VBd0tDQVFFQTFzK3RtMFlVWkR5M1lDaVR3TFprSjZ0cG9YVC96RkZraTdQSHZTcHdQMmN4Ckl0R1NkbDJ1TkNEWGJoZXNvYnVHbHZSdUhSb1RCakRVUzEwZ0o4ZTNRVU1kYzNZaDJxYW5FS1VKZ3BuRVkzUEYKZGNoMG44RFhTWGViVWNQTy8zN2NtUFo3alFGUmFjY1Z5ZTdBT0JMSnN5MktFc1RMTWcvbkFIdmcwN2tLOExoVwpkc2ZLa3RjeGczS3B4NXllZmZxWlpjRytqbVZKNWJvNWFEVG1HUDAxeGZ1TGZ0MUhFUnYxVk9WM2pkYmc3Ym5MCkhEWEt5SmMvYllHWEFML1M3bnVkNFBVR2xMUkhJNG5NbjJDMXB3ckUxR0s2NTExeUQwUS94VWlOdjlRYXdxak4KNlZSTVo4VjJmalB3M3RDejRTZG9tSGx2RXUvVDNvSURaRmZMMjE0WUVRS0NBUUVBeHJFODdEUHJzSjd5eVVvTAo0OG1DZERkVkVQdmhjUXJINXlyOHBOR1E5T290S1dVaFNqZGREU0U2RGxDM1RVb2dOZ2czRkZwdVg3WFlhaEVMCnBpYm9UN1BGd3YyS2drSTg4YjE1WTNXZlZIbFF1NUVucWR1RmpVajVha0V5MjZEYXNMcHZBY3llRm1Wb0hIY1cKSnkrNzFMcjlwcGl6Rjh4bkxYdnJCaE1oUFYxa2NTYlZlWFFPRVp6eWkrdlV2UUJDaGNKUWZqZVJGSU9pU0YrNQp1R3hSSTRSSDdKbVhRRHNhT09WODJaMTZHYitLVW5yenQvRWdJc2I5U0JBWVBsVXdTVjZucEU2enkyNk0yanRWCnloNjZZY1huRS9XOU5WYTlNOFFLdkE3bjJUTFFmbVhBRFdpOTJLYmhJbVJZcExua1B4c0QwQjhwM2Q1ZjhDc0IKZGZWUXN3S0NBUUE0b2R3Rk9iNmFocDlaekpqWGo3NXI3dmNVU0FKQVhaMENiODFUQStNLzgvYXlWVW9seHFEcwp3VlBLaEpkUXRGM1JZbk85WEdoemsyQUUxbkpweW05MzF6Zm1mdGlDQ3JtMDI2M2NMcW5tQmxtaXFjeUZhWlBZCm5KM29PcFZTS3FJQitjbXM3UklIcnlNQmI5NWhhaUtmQ0h5RVlqbW40eVZsclVNaXpvQWg0cGQ1RFVkTTIwc0UKZXJCa0hkaHpJaVJhL1c4OW4xcVNKRHlLU3BmM01wcUFyY2FqRmJkc3ZWQTZwNXlUV25sR2Mxc20vcytOZnNLTQpIa1F2UUdOZDJGMVJONGdhb0VTNUM5VlVsMnlNczN3YytrS0lTTXlQVmREeU5DU2ExRktSeTJzdC9ybCtDa01wCkZveUVVY1Y2c0tjVXU3OGE0UHVjaCs1WkFBU1F1d2ZoQW9JQkFRQ29XeE5VVzFXN29hZlBzM2srL1hlMkVZN2sKUXBKTzNjcUFUNHYvRitYSys0QnVCUnRQT2U4aytRTzQ3amI3UUlWdkJDM25OZDlId2dycHVqTlorTXBrZkxtRQpVMjZJREFFNE5vTFlHVDRadWM2YWhJUFh5U0NYeW1OSXRlcmRreGRBcFRmcnl1SDZSSWFmNlJPRWhER000MDRICnR6SkFmbXJlRXR3Sm1DK0FxM1ROWmM4ckVzVkRGNitkdHZTaGlMdWJKUGpqNWIzTkoyMU1UT2wrb2FmVHBBaUYKcCt4c0JSbjNZQktwRkJtaGd2YllsNDFoRDNBTG5TWmNvTVdoNnk3TlMrK1VBNTc4V1l3cGQ4OTNkNkFncmJCZApZVHVvdVJuWGF0YXMzQUk1djFWOERVRGV3ajBPbUhTZGw3V25NU0Z1dDZpSzBwQVpHK0dGdkx5WUZMS3gKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K", + "cluster_ca_certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU2VENDQXRHZ0F3SUJBZ0lSQUlTa2ZHZlUxMDZBMzVadVBwMjduU3d3RFFZSktvWklodmNOQVFFTEJRQXcKRFRFTE1Ba0dBMVVFQXhNQ1kyRXdJQmNOTWpRd01URXlNakF6TlRBeFdoZ1BNakExTkRBeE1USXlNRFExTURGYQpNQTB4Q3pBSkJnTlZCQU1UQW1OaE1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBCnVjQnJmWEpBM1RwWnc3UEE0YXJReVpjWExqYzR2aW51SjdnbTI3ZUJodjdkVFZndG9KQzVubDJKNFpqMXVpemkKMjF4ZitlWjdrYi9yUEdNVnpNRjZtVHo1UVExZ3FTQzBzZTVBSEltcjJLbFJwWGFFMU5vWmx0YTI5Z2V2ZitEYgprVjlwOUVqd1R6ZFQ3ZDRLdmMvazJpS3E0OVhQL0JRM0c2V09YcXRvRzNldXh5eWhDVGQ2NHkxNzVDYWxKVEx1Cmt6N2dwWFo3VFJjaDRIQ2NEYlZtT0puRElaQVJtcGZzbnR0TFBVUlIrOHNaTlB6MXR1QWxyaUwwTHVLREZTSlQKbzVueHczRmZodS8vdUl2NkpoZ3ZkR1lYeGEzU1hES2dCcmRFQlE4dnh2YUhKaXhSMjIwYVhqckIwZlBSdTdKUAptZzVkQis1aFptQ0g2ei8zclcvVDR3emlGMDZscjRURDJ6K3VnQmJQUWQ5WW1jRlhvSEthWFo1YUNJalVFRDRDCkZzZU9oRXlPNURIRysrSzBZbTFTOEdGK0tMb1hhVHRTNjJnSGt5SHhva0g5Skk2YlZmMkZVNFRmNG9NNXJ0aXEKWjBuTjgxb2ZXOUtLNXJ5WHV0VisxMjNSSzdSZEtzTVBRS3dsN2UvclhmT3N4Mkp5UlNUV3ZHSWNRWGdRbk9LNwozcms5RUFlSUVITXhGa3o2Q1lWQ1d6b2xiNUlyTWZwZnZYd0FWU1JmSjNWOU5LUU5qRDAyNjhIV1dXS2dEeVpyCmp6RlpPeDBjaS9yRVJMdWcyOFNManAyUUN5cnhQSTNnVTRWK2hIZWxJaG1pV1RXM0VWS0M0cGpqaFNBUHoxSk0KeStEUmVpS0FqZk1xaGpCNVltSU9xclRUemxMdzdkWnhMTnk0aG40UHQ2VUNBd0VBQWFOQ01FQXdEZ1lEVlIwUApBUUgvQkFRREFnS2tNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGUEhGNXJ0MVBMTUFZcWxpCnNXNVg5SUFUTkwxa01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQkxtRGxvU29qazVmdDlLWEZuWWo3NldkUUcKY0IwWGFLdm1lbXNNNUxUcXNCVFM4dm1JMkhheWhaenBNTmtrUi90ZXRSeEtwcnluTlVScUE3OTZlY0xzTStySApjdi9nc1YvMjgrV3JaYjhmZ0NxZzRiUDFaTVRXTzdhT3dSa1EzOVI5MzVrTWpaNWVJT0ZSYllxaHlSM0NnQnZECjltOHdvTG9TMzU5cXhVVEFVWjlxYktpaU1ldUoxc09KQ1kvN25xcEtwRlc1c3A5dlUvaXNnd2tQbDBFd25DekcKb1pOSWhlY1NGN1V2MDFGeGYvaEVESEFadTF6Tkt5S3NJWDlmNGZuaHhXc1Z2emtGSE1EVVlvNUNNc0tUeno0NQpka3BUaEV6bGkwYy9RN3E1SlpTS2xOSUtNSkF1KzBhZjA4ZDNLempUL0RsY2N0QUVPMHZFWUdsc1l1d05KL2s3CjhMVVc1TEJSdmZpTXZGSnViVXMxdFczcXNXUU1QbjhzTDI2YWRDN2ljcTVFc1RtNzFMSlkzV0VaNDRhYzRJTlQKR1plN0NkMEtQdExsRjkwTlczODAvelNqcTZQM2EyUlBXeFRVTjVzK3BEdjZxWUtVeFlRUkR4VFIwQ0JSWVV0eQoxdlZaKys3cjloamgvWERiUlhaQVV5MUxycDFmdFE4bXpMUjRQcHgxK3hPVW1DN0VXR1FqbG9PUG05ZTdmQVpJCjFuOE1xSCtVOHkrN3A3NFArelM1ci9ObGU5MGZTd1JUSzRkVm5xMjNKckxLRmgvdGZ1YzkrUllQZDIwdmZsYWIKeGF0VnJjVTZnM2R4aWh1engzckY2NW5DaFgwOElXTVAyUjczcWNwSDRXcllQcnN3KzZsVVBmZFIvQWJHR3plcQp4S2VtWTJsNmljZ2g1TlB5MUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==", + "host": "https://squaremarket-aks-t989as5n.hcp.northeurope.azmk8s.io:443", + "password": "1ax84amdce5i5g3b0rihwasxirdsvmhwqyva45gg1l2vg5ugj25js5hw5xeqtgonxxr31phlzyjrf4xapjwmyv1ze900cfffhgxh7twbmsvpabhojoc16pw5szia6hvo", "username": "clusterUser_squaremarket-group_squaremarket-aks" } ], - "kube_config_raw": "apiVersion: v1\nclusters:\n- cluster:\n certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU2VENDQXRHZ0F3SUJBZ0lSQU0xNWRIbjN1MitoYmxGQUdpS2RYZGd3RFFZSktvWklodmNOQVFFTEJRQXcKRFRFTE1Ba0dBMVVFQXhNQ1kyRXdJQmNOTWpRd01UQTRNVEkwTURNeVdoZ1BNakExTkRBeE1EZ3hNalV3TXpKYQpNQTB4Q3pBSkJnTlZCQU1UQW1OaE1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBCm9NV0xHYmo1alNQaUQrUXhvRWxQV2ZrdktpcXRZWFc3RVM1ODNRbFJETzFKdHRYWFZvWjBtN1NUbG4xVHN2MUkKNTdMWVZyMWEwV1cyL3ZSa3pJN1UrQ2M3RTRzSldDT1VxWXNJekExTGpFOGRBVVhWWk9UUWI4K3BJWXV4TzduZgpFQzQySWFyZnVXSzczdlB3RGNiVEw3b0FRV2c2UkxTVWRQTThoc3JLQlpZMitBMHFQTHYzOUtXRXM4YW5tc0Z5CnIzemVnZmtSWmo5TWZaSnJjTUo3K0paUExVS1p6WGxIbnFZU2trTmdobG1uMW9mdjZWTUp4bFh0OVFlY1I3QUEKcjA3OU9ISzlPWU52K0UycG1qOGc4NkI0a05MNWgzQk5KaEtvTzhobzFHdnVEVFZoa21mUGRqWUpZcHErMWtqUQpuZUhkWHU4M3JLa3ZzeUNvZ1d2eVZmREZ5ZkZQV25uYlVOVEViZU04bWY5MGZ1TlgyZEJrVFVyNVNtR1dJbHhVCnR2Y20yWUpNd0N6dkcvRUozTHdrRDAvdkFKaDZKK3lxYnJhdjJncHB3VVVDakhieFFtbDl4SFZ0NElkdXBoSHYKSWp0SXBWV0xmZXBJdENEb2FvNk1Jcy94ZVI1eTl2VHpCQWdWVWhHSmt0WXFIUXl3TitBTlFVeFZraGg1MjJSOQpQeW8wRnZUK202ZDE4aFZ3L1kwYWRpTWFxeG5Sem04WWVrRUZ0b2VDcHllT0krT1JVM2Y0QW5FZEFKQUhaODllCnB2bEtTUXRjaTRpTHNRSnJZWGJCSjI1SDl4aHhNTmZmVVBBM29GV0h4K2cvbTVSZHpTSnArUmpQTFc4bWZ5TG4KR2NNeG9URGFrR0NubG12eWIwMmlrVU9kTWlHM2JkakQ5bUhubmtOVHMzc0NBd0VBQWFOQ01FQXdEZ1lEVlIwUApBUUgvQkFRREFnS2tNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGRHhPWUNmUXN0STF1OWtJCnBaT3hkSlhmOTJ2bk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQkNXcTh4K0NBdjdod2luOWhhVTM1TXdOdWEKd0ZqdkUzZ1VUS0t4cWZ2cEZ4SkNDajA0NzFMOHJ4NWx1SGViMmVCcTIwT3BoRjNUR1Zhc1Q5NTBrcVN5eklMawpYdklUKzhqc2lqNEhCTmhVRkhqbGN0V1ZQSVFpeGR0RkZBWWtNa091bHVhWlQ5RlBSNlVISklTQTFXSnYrWFhhCkgwOWNKWEJlYmlQRytIQkpwVjdLckVtL1JnR2Vvc1BXT2J0TmlzaVV3TzExUFBST01NektkSThCbmhUQ3RweS8KcGo4dmtoMVRvamFyVHdUYkRMSW8xd0U3SXA5L29lTXhEN2kvdzNXZEtWL3YrK3pWWWd5MTBSZFIycUJ0NXZpMQorYm5GanNDR1NKWTA3VVF0RlRzWDNyeitZRmc4ME5tbk82bUZUYWZGQlpybHBOUTRSZDkxVXRTSzMzdG95UXBvCmlaRXRuRXF0WWtsczZlRTlNMURyL3JkYnpySDJGYk9GTlZDQjRZNnpNNzErbWY0QytQaGlPd2k4MFJFcnR4TVoKU0JvZVhSZzUyOUhJdzJvNWpOYlB2NDZOMTNhMU0yNkNESkVSWHRCZ0FPM1JVay9kVG9ici9JTGN0UWtiYUUreQpBOFJZM0dERDFJeXhSUUhaR0ZjWmV5WGJmYkRMbmNPUGEwcFUwckZLcDdxeXZhVElmYUZGNzhqQUYzV2wvS0NMCjVEckcrV1hJZkN6ekoybjZFZzhqbXJlVjlLOTY3Wk5NSGNJNmVzelN1WXRwL0orQ0VNNjUwa0hoL2ZJY01iUHIKWXJDYWF3eHdwRWZNK1Z0MTlXencvT21YcWc0RU5nTTd3N05jdTlXTUdTazg1M2lqaFA0aHlCS3Ewc1c2VHFkSwoyM1RyVDR5cjdySmJSOURaRVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n server: https://squaremarket-aks-x3z8ynwg.hcp.northeurope.azmk8s.io:443\n name: squaremarket-aks\ncontexts:\n- context:\n cluster: squaremarket-aks\n user: clusterUser_squaremarket-group_squaremarket-aks\n name: squaremarket-aks\ncurrent-context: squaremarket-aks\nkind: Config\npreferences: {}\nusers:\n- name: clusterUser_squaremarket-group_squaremarket-aks\n user:\n client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZIakNDQXdhZ0F3SUJBZ0lSQUtZa1JQTFkwdmxmSzRLZVJSN2J4dG93RFFZSktvWklodmNOQVFFTEJRQXcKRFRFTE1Ba0dBMVVFQXhNQ1kyRXdIaGNOTWpRd01UQTRNVEkwTURNeVdoY05Nall3TVRBNE1USTFNRE15V2pBdwpNUmN3RlFZRFZRUUtFdzV6ZVhOMFpXMDZiV0Z6ZEdWeWN6RVZNQk1HQTFVRUF4TU1iV0Z6ZEdWeVkyeHBaVzUwCk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBdnVMSkhJejNkY1Y2d2h5N0xhSVkKZW1zT3RIWVIvckRrQnUySkJjUXRUTDBOOVE2TFRKSlY5T1NzTHp4NExQQzhTc2M3MUlvK0dPV01FRGdYc24zNAovSlB1T0JLMWZKQ2t1TklBanR6NXNHWjhvcDg3REtRMFBjL0Y0dU1kN0xDdW82RnBtR2RZTTcwRTZsb2s4N24rCnpROG5HUDFBRE1oaFNHSnFuU0dsV2gvWURWTi9ZbWV6N2NuYmJwZ0FzMHN2dXVkY2I5Ylc4TWJJS1c5aGNPS2YKQmZkamNNVzRId2E2VG43K0VCZHkyYlBYamR5Q2pYMkUyWlI5TUtOTTZybmpCUk5vWGhxK3drM1ZOSTdoVzlSeQp0bEUra2RhZG5MTG92a2Y4UnBGUkt2eGMwV2tSdFBsWnc5NHVidDdXM1NmQjZnQ3JoODgrQlZ1VzRpUGJxR0NNCmNYaWFFL1ZXMnNpTmNrQWtCRVhIMEJsdTlXQTJzNGNzeUg4Q0c4bEVvMmFhVUFiOGpiVzh1OEtsVGg0WThibjMKdWNWQVpPZXd6QzRqdXhEMzRTTEhKVUdsUkw3OUxHdVdRdDlOdWVQV2dMb2p2SG5RelVmUGRQVzNlZE13OHUyNQp0ZDRPZTZrVC90eEczZjYybVV2YWltY3Naejc0b3BzY1pjdS9RVC9LcUk2dWFHRnF6WWZxYkVhVXYvVitOTmpkClo0YjhmTS9qZ1AvQlEzODhwbnRJLytYQS9vVHZENGRiUmhXWDBLWUppUzQ3RS9WVmpRZWVKakNCZ1lZV1craFcKbURCTlFzWi80eW5HVnRDV2tBWmJPZnUvZjJ4b1ozelY1ZnBMTzZmcFEvcXlIQ1k3WTE1SGZ2YWkvcUNUR0FsWQpraUZZTy9nMWd5NCtIVGRFK04yREFHVUNBd0VBQWFOV01GUXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkCkpRUU1NQW9HQ0NzR0FRVUZCd01DTUF3R0ExVWRFd0VCL3dRQ01BQXdId1lEVlIwakJCZ3dGb0FVUEU1Z0o5Q3kKMGpXNzJRaWxrN0YwbGQvM2ErY3dEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBSWFseTIxUm5vTVVvSGxhTytwdwptRENGZThkaDhLb2RmaTdrV09iT2loYTJleEgxQjZyZkpsU3dHdFRYSWJMOFIzSTFEc2hnb0ZIUDNhemh2Z3YxCllJakVxdnR3ZEEzcjcydXNOUm01WEtLTlBQN2JTclFXUGR0eUl0c2VjTHV6SkM3NVhCWFptYkphWUptalVpaksKNGpCYXpVcTM3dkN5NlYyMUNrR3NTaTByZk81c3E5VmV3QjZQZDNpd3JJYnplMC9BMjZKejlOeG5EeVlWcEptWQppQ3JvNDhWNGltS3B2K1VzNlYzYTZORDRtMVN4WmxhKysvbmkwVlpqZHgvbUFleVZ0bXpESUZneURSVjkvaWk2Cm9yNUJ5bFM0Y1JxaW9SVmxvTmlubHNLV0o5MTRBczhMTmtUM3BqemZ2OXVjcFN6dGFnd0Q2TFNtNFlWaFFiSmIKczZBZjlLelFYMnJQbGdEd1FhVis1Y0VCbzhOZlA2YmxmdlRkWDJPOStGSUx2THFKZ1V5OXlDclVaMmtmcWI5cgpQN2RqNmNXQVJlSHB2bDdQVENuYUdWUUJGeXh3aDNibndReDFyOTlaTjh3WVoremZqVmhaRzE0VnhzYUNBMEZqCjdLNEJJaG16Q094eDhqS0ZhSGpwM1c5cDkvaGRjZXRBeEVWOEczWkNWUGpWOVByVnEweWRQK3lmeDdhNmtIc1UKaG1BMlp6NWJSTHB6NFNObGlXV211MDFPbEpsSGlaNWJXNnBkTDM1Ry9uYncxaU5DeWhFMEl6WjZGR05zaXJkcApLb1JjaHhTdGhweWRMalY0ODdQZnJ4cUxSS082a2x2aDRnVjhBYkNhYWxjcHh3ejJnajN1clpVUzhQNUlBaEM3CjREazJjalkxZVhBYkVHZnJXYnd0d0d2QwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS2dJQkFBS0NBZ0VBdnVMSkhJejNkY1Y2d2h5N0xhSVllbXNPdEhZUi9yRGtCdTJKQmNRdFRMME45UTZMClRKSlY5T1NzTHp4NExQQzhTc2M3MUlvK0dPV01FRGdYc24zNC9KUHVPQksxZkpDa3VOSUFqdHo1c0daOG9wODcKREtRMFBjL0Y0dU1kN0xDdW82RnBtR2RZTTcwRTZsb2s4N24relE4bkdQMUFETWhoU0dKcW5TR2xXaC9ZRFZOLwpZbWV6N2NuYmJwZ0FzMHN2dXVkY2I5Ylc4TWJJS1c5aGNPS2ZCZmRqY01XNEh3YTZUbjcrRUJkeTJiUFhqZHlDCmpYMkUyWlI5TUtOTTZybmpCUk5vWGhxK3drM1ZOSTdoVzlSeXRsRStrZGFkbkxMb3ZrZjhScEZSS3Z4YzBXa1IKdFBsWnc5NHVidDdXM1NmQjZnQ3JoODgrQlZ1VzRpUGJxR0NNY1hpYUUvVlcyc2lOY2tBa0JFWEgwQmx1OVdBMgpzNGNzeUg4Q0c4bEVvMmFhVUFiOGpiVzh1OEtsVGg0WThibjN1Y1ZBWk9ld3pDNGp1eEQzNFNMSEpVR2xSTDc5CkxHdVdRdDlOdWVQV2dMb2p2SG5RelVmUGRQVzNlZE13OHUyNXRkNE9lNmtUL3R4RzNmNjJtVXZhaW1jc1p6NzQKb3BzY1pjdS9RVC9LcUk2dWFHRnF6WWZxYkVhVXYvVitOTmpkWjRiOGZNL2pnUC9CUTM4OHBudEkvK1hBL29UdgpENGRiUmhXWDBLWUppUzQ3RS9WVmpRZWVKakNCZ1lZV1craFdtREJOUXNaLzR5bkdWdENXa0FaYk9mdS9mMnhvClozelY1ZnBMTzZmcFEvcXlIQ1k3WTE1SGZ2YWkvcUNUR0FsWWtpRllPL2cxZ3k0K0hUZEUrTjJEQUdVQ0F3RUEKQVFLQ0FnRUF1QmMzSlZXK0FkS21CVFo0UGhZcHpVWWFOR2Y2Q3NyRjN2aU9RUVZsT3JESkdTRWhyb01pYXI5dQpYMC9PQm1sVTdqa2V0bWRzWTg1ZE9mY2V3NEZCOHpVbmRPUjJLdC9FUElvckI1eU5JVloxdE5BanZwbE4ySjdjCmxXZTdvRnowejlJSGFIZWlWS05pYmpEcG1qUXlTOGpsUi9CVktQN0c4MndXcmR1WkFFWGRzMXhuYVp2aHB0NFoKeFpyS2NoOTh0S0FVUWJmTUt3RktZTXVPQ2JSRThSZ1l5dFdrTUNmbzNYTVR5cWtTbG1vZTVDMVpxdUwya0cvbwpIRlFoQmU2bnhtbGxLZnhaSkVLejc3QXNIc2ZRcVl2SThCVXVnOFQzT1N0clhSTWdXSGh4ZTNQOUk2K21wb05MCi9KamtWdnpVd3J4S3NQdHBNUG1YbGhoRzZ5ZlJ5S0xISjY4d2JKNURnMldoSW1iT3RIR2xYSmpsbzh5RUNpc3oKd3ZINldyZHlEZmp2anJtaDI2eDFzTW8xL2x5OFNRdE5qYndjK25IdXNkMnFWakZPN3AzUkJzaWJRaTIxSmc3bQpPUVRNWE5xcVozOUNLK0hsVW9LVzJWa1ZmYWplSGtwZURtSjByRXpaTVhGZkRONndudjQ1bmxYWjI1SStTOG9mCmloVjFBS1hIZlk2Zzk0ZVdaTVE3UVk4WmFJaFNhNkppREI2OWJINkVqTzZ5V3lTT3c0bUI3OFBNMitsMlNQa0UKalpFZ0YrUnRkeDRBTU1GL2VxbmVvQ2lQdG0yUC8rVi9xSHRhSUE1cHdDMzJFdGlSZ29QUk84UWpwYWl0Q0V0MApPcjV5TEkyWjlRZ2prTW1tZVUxZDF5MllsZkRsN0dza1BPeFo3ZHdIQXFOR2t1SnA2ckVDZ2dFQkFOd0Q4TjU4CjUwWDF2VXd2d1FvWlJqMXBITUs5aXpNQTJFS28wVGxRWEVHSXdHNUIraHdrdUJWM20yQjdCazJsYTM3NG9WM00Ka0xDVUFGbDducTYzblNlNHVTSVlpZlc0SHZ5TlpXT0F5d2o3ZG5Ealo5SmRWRlZERGhoVHVMUW1hMTRZR2xjbQoyZDBDcWFqZTI1YytwTFBDSlFzdDNrNnpYTHN2bklWNUhVVllOMGNDUmJTVmFXMnNyY1lFUnZYVDI5N09nL3pVCk9UbE85enlQaTlORmJ3ZjZGbmFESWhyQjMvQlJvcXNzNjhtblFoZm1ua2RxY0xhQTV6M214eU12V0x3cWd5YkMKVW05TWNPaFpyUVo5c29UUFRSWjRhc3EyN3ZleFl0cEFnQXYxUHlUQ3d0WVhWNUc3NkdmZ3JBRm5JdmNKUHVGdApGZ0FpaDh4RWVsWllvbzhDZ2dFQkFONGJNS2dZOFlYTXl0NXozWlhLUXllWWRmQlZ2UHd1a1lJenJjb2N3ejIxClBTOEIvb0pIQ0xDRzFYWCt0SUZTK3FDNUNtR09GQ0FPMUx3aWFCZEZKSW83K3BwUERRLzZyQWJkQWRBU3NaQ0gKemhyR1RqbTlCbjU5ckJEKy94aVhDWGNNMlQzUzk4bnN6ZUgvL2lIQW9xNE14ZzFreWV1RWlZNmdzK3U0ZXR6eQpzM2ZWQ2gyai9mWEM1Y1NBZmdwMXRQdldYRHZVSFpZeHRHZ2pEMUhVbE1VUVQzS0RFSG1SVmtWYzl2UHJ1Z2ozCjlwQkVzMGpQNkM2TDliWGF1TVBFTDVvK1ZPaWUyWVVGSTdlVFdkT3FpL2FYU05GYzV3eWNsbUhsWnQwUUt0eVUKOXV0dlNvL1dsSHJERVpMc2tqSWR2WEpXZVRUVFBYbVdTSGUvWTBvTTE4c0NnZ0VBWE9sZEl0ZmtZdXJ2MGVpbwp3TGNEcnBOaERldzFpSWhteTErM1cxalFYT3gvTnhDdHFvem5JRW11V0FRY0dWWXVHeVNzSUl4dTNlTGljdWQ5Cmo1TUN6dTkzWHlxMXVBclZLeG9iSXZZVXgrcEpqa3E2WVdBQWFURTBGcG5rZWs5dk1BRkZ2bUF0eU9PQytrbUMKdWxBK0JadUxRVGxSbERLeWltb0UwMmplTDVhZFlXTllSSGFFVzBoODZnNk1XT3ZyQWlCOEpKMEt2V1RYaTcraQo4TWwxcGd5Nng3bUJBWU8yS2lxUFkzSkk4Sk90bWFKT1hTSmFEdmhJcng5eUlTMnhiMlVVbFoyaWFsWUJxRjJXCmNCTk0vRUQrOE9wUlR1SUo4SmY4Z3VhU0xIcVkrOUV6cm0yVkVYb2xIeGtHQ21HZnBtdEZIcldPZ09nVUtQTVMKSnJ5dmlRS0NBUUVBcEdHTFNWM3NveEpHc3cwZDJsYXdEUVlSZzZaeEVGT0c3YzVCdVd4QU05T0hUTnMwK2thSQpiOTNhN2xGc1ZxcUxRNWl2empQUlFVaTR1ZVRkRlJ2ZlIwL00wWmZnR3d0emRrRHJyb3VuVDBpemlhMXJCK0NXCmhkZU5HYWNnTlpyVExvRWI1MTNVZG1ZRW1sZFVScmJEci8xVU4yVjQ3QTVUb0hQZnRYMzdzTHNQaUlqcnk3UXIKcVZCdHJWbEw3NEhlU09VRnJtSDI0OG8xc3p6L2daakw0bTJIMzBDUldPZnY2Ylk4TDVzdlBLUUlvQTI0Z3hNYwp6QlpaN0JYTTJWYkJ4RzZOZVB6WUVGTVZkREJpTXYzdVNMcis4YlRJYTlVWWw2dW96Y1BCZFMvcTc2U1drbzVGCnBmWHBjOHdUQjNScldjd1dPTHArclJaRTNCdVI2S1JJc1FLQ0FRRUF3Uyt6bHdxOTl3U1FJby9LZXc5eHI1TDYKYlRudXYrbktRa1U1ak4xM0NHLzFpSUVITFJKUXFZNjRoeWpwMVQzWTZCeXYxdllDenQ2RHhLWUNqRUJ4b25aeApkQko1THhCWnNLWWsyRHVPUllselZIZGNZWVdXUTR6LzNhb0hSa1M5OFhvUjBPZmNTWUFZdmtSdnQ5OEdrU2VmCkd4MmxFb3Nac2dsTmMrUWhnSHI2bHhsQmNRbWlhSmRwZ0gySkk2dXQyT0UvdE15Sk1VTUo2VkRSTXZuNjZEWnQKSVROT3prYkJEYkJZbHg4NDVubjBvL0VOeVV4Mnp5aG5vQkpZZ1dmYlBxOWdQNmwwUlB2cFU1bGhGdUsvUko0Ygp6eE1hbWtINnlBM2lDd1VqdDFEY0tqdUVoYVhUbUxZM0xLWHIvdkRpV0pIWm9rQm5TN1JKb0RLTjVnVWN5dz09Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==\n token: 1tz37c4ameoyvmakjv9630y4xfgz04hkft390u1zt5mb17xbggmpojzjx0gs1at1kz6cadg9k3s6304b0wp4wccvoqocjqz57nbyb6v0dv7d60a41c4ts3bivijdatgr\n", + "kube_config_raw": "apiVersion: v1\nclusters:\n- cluster:\n certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU2VENDQXRHZ0F3SUJBZ0lSQUlTa2ZHZlUxMDZBMzVadVBwMjduU3d3RFFZSktvWklodmNOQVFFTEJRQXcKRFRFTE1Ba0dBMVVFQXhNQ1kyRXdJQmNOTWpRd01URXlNakF6TlRBeFdoZ1BNakExTkRBeE1USXlNRFExTURGYQpNQTB4Q3pBSkJnTlZCQU1UQW1OaE1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBCnVjQnJmWEpBM1RwWnc3UEE0YXJReVpjWExqYzR2aW51SjdnbTI3ZUJodjdkVFZndG9KQzVubDJKNFpqMXVpemkKMjF4ZitlWjdrYi9yUEdNVnpNRjZtVHo1UVExZ3FTQzBzZTVBSEltcjJLbFJwWGFFMU5vWmx0YTI5Z2V2ZitEYgprVjlwOUVqd1R6ZFQ3ZDRLdmMvazJpS3E0OVhQL0JRM0c2V09YcXRvRzNldXh5eWhDVGQ2NHkxNzVDYWxKVEx1Cmt6N2dwWFo3VFJjaDRIQ2NEYlZtT0puRElaQVJtcGZzbnR0TFBVUlIrOHNaTlB6MXR1QWxyaUwwTHVLREZTSlQKbzVueHczRmZodS8vdUl2NkpoZ3ZkR1lYeGEzU1hES2dCcmRFQlE4dnh2YUhKaXhSMjIwYVhqckIwZlBSdTdKUAptZzVkQis1aFptQ0g2ei8zclcvVDR3emlGMDZscjRURDJ6K3VnQmJQUWQ5WW1jRlhvSEthWFo1YUNJalVFRDRDCkZzZU9oRXlPNURIRysrSzBZbTFTOEdGK0tMb1hhVHRTNjJnSGt5SHhva0g5Skk2YlZmMkZVNFRmNG9NNXJ0aXEKWjBuTjgxb2ZXOUtLNXJ5WHV0VisxMjNSSzdSZEtzTVBRS3dsN2UvclhmT3N4Mkp5UlNUV3ZHSWNRWGdRbk9LNwozcms5RUFlSUVITXhGa3o2Q1lWQ1d6b2xiNUlyTWZwZnZYd0FWU1JmSjNWOU5LUU5qRDAyNjhIV1dXS2dEeVpyCmp6RlpPeDBjaS9yRVJMdWcyOFNManAyUUN5cnhQSTNnVTRWK2hIZWxJaG1pV1RXM0VWS0M0cGpqaFNBUHoxSk0KeStEUmVpS0FqZk1xaGpCNVltSU9xclRUemxMdzdkWnhMTnk0aG40UHQ2VUNBd0VBQWFOQ01FQXdEZ1lEVlIwUApBUUgvQkFRREFnS2tNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGUEhGNXJ0MVBMTUFZcWxpCnNXNVg5SUFUTkwxa01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQkxtRGxvU29qazVmdDlLWEZuWWo3NldkUUcKY0IwWGFLdm1lbXNNNUxUcXNCVFM4dm1JMkhheWhaenBNTmtrUi90ZXRSeEtwcnluTlVScUE3OTZlY0xzTStySApjdi9nc1YvMjgrV3JaYjhmZ0NxZzRiUDFaTVRXTzdhT3dSa1EzOVI5MzVrTWpaNWVJT0ZSYllxaHlSM0NnQnZECjltOHdvTG9TMzU5cXhVVEFVWjlxYktpaU1ldUoxc09KQ1kvN25xcEtwRlc1c3A5dlUvaXNnd2tQbDBFd25DekcKb1pOSWhlY1NGN1V2MDFGeGYvaEVESEFadTF6Tkt5S3NJWDlmNGZuaHhXc1Z2emtGSE1EVVlvNUNNc0tUeno0NQpka3BUaEV6bGkwYy9RN3E1SlpTS2xOSUtNSkF1KzBhZjA4ZDNLempUL0RsY2N0QUVPMHZFWUdsc1l1d05KL2s3CjhMVVc1TEJSdmZpTXZGSnViVXMxdFczcXNXUU1QbjhzTDI2YWRDN2ljcTVFc1RtNzFMSlkzV0VaNDRhYzRJTlQKR1plN0NkMEtQdExsRjkwTlczODAvelNqcTZQM2EyUlBXeFRVTjVzK3BEdjZxWUtVeFlRUkR4VFIwQ0JSWVV0eQoxdlZaKys3cjloamgvWERiUlhaQVV5MUxycDFmdFE4bXpMUjRQcHgxK3hPVW1DN0VXR1FqbG9PUG05ZTdmQVpJCjFuOE1xSCtVOHkrN3A3NFArelM1ci9ObGU5MGZTd1JUSzRkVm5xMjNKckxLRmgvdGZ1YzkrUllQZDIwdmZsYWIKeGF0VnJjVTZnM2R4aWh1engzckY2NW5DaFgwOElXTVAyUjczcWNwSDRXcllQcnN3KzZsVVBmZFIvQWJHR3plcQp4S2VtWTJsNmljZ2g1TlB5MUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n server: https://squaremarket-aks-t989as5n.hcp.northeurope.azmk8s.io:443\n name: squaremarket-aks\ncontexts:\n- context:\n cluster: squaremarket-aks\n user: clusterUser_squaremarket-group_squaremarket-aks\n name: squaremarket-aks\ncurrent-context: squaremarket-aks\nkind: Config\npreferences: {}\nusers:\n- name: clusterUser_squaremarket-group_squaremarket-aks\n user:\n client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZIVENDQXdXZ0F3SUJBZ0lRRXAvV2JlRFViNFR2Z0tnaGVFbXdKekFOQmdrcWhraUc5dzBCQVFzRkFEQU4KTVFzd0NRWURWUVFERXdKallUQWVGdzB5TkRBeE1USXlNRE0xTURGYUZ3MHlOakF4TVRJeU1EUTFNREZhTURBeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1SVXdFd1lEVlFRREV3eHRZWE4wWlhKamJHbGxiblF3CmdnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURPMkZlYk5JK1NuYjJ2emI1ZGJhckUKcFhjU05ETURlMDFsYUlKL3VlYkY5ZVFKV1diUERxcVl3WVdjUlo0SlNKTjNFa0Nwb1lrN3I0UDJvUEZGYWZuagpES3NFTERBSm50M2NlY3NDZkJnRFZkUWpiOElTMW5BV2FrUXB1OFo0ZFRkU0REa2piR3V2bXZNb0VXYnc0V3MwCnVuNFdGamxEWmRFRHJVQS8wUjBpc3ZHc0NDQVQzMUMrNzA3M2pZRk1xK2MyMjBYMEhiSTlLOWF6T21LU0RIWWgKbytQSlBnZ0s5c0F4OTZwMG9kdS9KSFR5VEI3dWJSbG41a3pxczlTUXcvaUZaeUIvb0hhUW83cXZsdTc0OHRYQQp1TGlOQWl2ZXZ1bDZEOWlvMzk3R25vSE9wcjFuZDhLdDhqZXRnbGZ4VmJ6dUhsR0lEbkZhNkw2Mldwd0UyUTVsClg3SmFqTzhXV0xJTHIwOTUvMkNSbHVyV0sxWERpeFNRM0ZXY1VJelc3WTJiNG4zbXZhak9yWVFnRGpZTDh5OXAKRXJPZ2xJbWZ3cGtidEJsMUFhS2FHajhvRkFtNUxPdFhzQVNhSnlUd1dPQVFOV0RTL3ptbmF5bmNnQlRHWVlKNgppNkFkQ3E4U3UveDdLT0xGS0hBN0hOaGxJd0pDUGxpM3ZWWWQyYnpKd0Jha3Q1WUpwZ0FUcEluWXJOSUhKYW93CmM0UTArSGRZcC8yN3N5cG1BT0JxTlI5YUZVVXRqcVVNVk55R1RQQ1JuaFdURGMxU0YyR3F0bVlCVVVTdThzeC8KU3FNWVAwQ01lQ3B5aDZUL1oxa0Z0c1BQZWJWU3JqcnpHYlMwbDE1QjN3Z2RCYkNEcWZwdlJCa0ZzcHZqaFdKeQplSWRsY1RHUVpGZmJ3SkVIN2lWY013SURBUUFCbzFZd1ZEQU9CZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsCkJBd3dDZ1lJS3dZQkJRVUhBd0l3REFZRFZSMFRBUUgvQkFJd0FEQWZCZ05WSFNNRUdEQVdnQlR4eGVhN2RUeXoKQUdLcFlyRnVWL1NBRXpTOVpEQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FnRUFVN1lVMmdkYUNvejArRnlWUTJ3dwp0ZFhFMHlsTnRuK2VzTFhNK2llQy94WGxjdzNkdExQTWl5OXk4bmdMS0NCL2lNRTRNWFhEQ0l0N2RYTU9meC9tCnlqWGptTk1VVkd1ek5vUk1melgxVTd1clYwTG15cnRpQTVvMklaU1dzdHIrelVveDlndjV0dzFWdm15U2pxcUYKTnFUL0hjUytya0FzbnB4VUxXOUphSmNrSTlZVVo1OEZvSHFmaFJ1by82NUxXNzRMcWMwY2RZRlFiQktkWDFxSwo2Y2dmQVIvQTI5WElucjJmYmQraGNtSHEyNGZObzh1dnNsbW9UbzN5U2NLbDRwbjc2K1NwcGgvbGtIcEJ6d3lrCi9XdGJ2QWxlL3pwL3RtckxOQk5FUzBDaERNTXNhUzdkVmNXaXYzZ3QxN1YyS0NGbjZYeTVjNGIyWC9TV2RXekQKR2h5c3htTlh0VDIvLzFya3ZURU1iaGdWcVB2QW53RGRqRnV5NzZ6TFA2cDRIOXJWUmRMYy9CQlUvVnA0NXR6aAo4RVZwZkRsUElXeVRNVHVucmlRN1ljZW53dkFxblBFVnpFaXJ2OThsS0tRODV6MWprRmp0UXBTZWRVcFhHZGRJCkR0R0luMU5OaERNMndQVVdpNHZBdUVIaTNFWVdUYUtEVVp2eVg3czVEVzRNUlljWVhJZ3BEK3hOQjA0Z2hsWGkKNWdRV2RaUzZmYjF1V09pQmU1dkJTTUNRQzE3dFZzR2lBWHY5SnVJSklqZE9XZnlrb2U5SmlDTjZ2Mzgxajg1YQpTdStaWEZwZmx4a3ErT2VWRXJPZGVVQ3ZMYW5WV2J0TDVjZmdrV1M1eXdxMjFEa3FjYWFYR3NQSXd6Qk1RWWk2CjJCY0ZLc0diR0RyYlVROXEyYTJGRlY0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBenRoWG16U1BrcDI5cjgyK1hXMnF4S1YzRWpRekEzdE5aV2lDZjdubXhmWGtDVmxtCnp3NnFtTUdGbkVXZUNVaVRkeEpBcWFHSk82K0Q5cUR4UlduNTR3eXJCQ3d3Q1o3ZDNIbkxBbndZQTFYVUkyL0MKRXRad0ZtcEVLYnZHZUhVM1VndzVJMnhycjVyektCRm04T0ZyTkxwK0ZoWTVRMlhSQTYxQVA5RWRJckx4ckFnZwpFOTlRdnU5Tzk0MkJUS3ZuTnR0RjlCMnlQU3ZXc3pwaWtneDJJYVBqeVQ0SUN2YkFNZmVxZEtIYnZ5UjA4a3dlCjdtMFpaK1pNNnJQVWtNUDRoV2NnZjZCMmtLTzZyNWJ1K1BMVndMaTRqUUlyM3I3cGVnL1lxTi9leHA2QnpxYTkKWjNmQ3JmSTNyWUpYOFZXODdoNVJpQTV4V3VpK3RscWNCTmtPWlYreVdvenZGbGl5QzY5UGVmOWdrWmJxMWl0Vgp3NHNVa054Vm5GQ00xdTJObStKOTVyMm96cTJFSUE0MkMvTXZhUkt6b0pTSm44S1pHN1FaZFFHaW1oby9LQlFKCnVTenJWN0FFbWljazhGamdFRFZnMHY4NXAyc3AzSUFVeG1HQ2VvdWdIUXF2RXJ2OGV5aml4U2h3T3h6WVpTTUMKUWo1WXQ3MVdIZG04eWNBV3BMZVdDYVlBRTZTSjJLelNCeVdxTUhPRU5QaDNXS2Y5dTdNcVpnRGdhalVmV2hWRgpMWTZsREZUY2hrendrWjRWa3czTlVoZGhxclptQVZGRXJ2TE1mMHFqR0Q5QWpIZ3Fjb2VrLzJkWkJiYkR6M20xClVxNDY4eG0wdEpkZVFkOElIUVd3ZzZuNmIwUVpCYktiNDRWaWNuaUhaWEV4a0dSWDI4Q1JCKzRsWERNQ0F3RUEKQVFLQ0FnQktBanh1U3dNbjZZcktVV3BRa1lHdGRLckM5NnhkRFl3bXdac1h5bE5ZOTE4TlBWQzh1em10Q0drZQovZ1hLdW80UTU1WDlXOC8zYkJoaWNUbUNwYVdFYUs3MEFqOTU0Yjd1T1NHN0ZOMmVxK0lJZjk5SDBEUXd3UHI4Cmw3UXV1M244d0dUZFdOaTY3MDBiUkFhNkgxMjFjeGpVSEVXRzZQSTlSNHNhaWw3TFlJMTZDVG81QWdrcEdBVUEKTEVCY1I1R21IaFRNZ0tlYW5Zbm5mUGR4M0VXVVNjaWg0N0VMQXpqSGFCODBOZ21qUFk4ZWRzQUdmVUMrM1RjZQpMeFBhYkZRS24xaVFjY0NhaGdxdzh1YzBhZWF6V3lTbFIyYWMvMkFZalh6dkI1TDFMQmV6OGhNSTUxL2Z6eSsrCnhFbGxEMUZYUGIvY1Y1YVZlWHRyVWNaTE1jS2xPK1crNkZaRXcrQzVCMmFtZWk3bTRWclA2b3BRUEZEYTBZaU8KQ2NvczloemxzTnVqMndjcU5meTdzVGVzWGErbExCRFluRHNTa2t1cHlOWUJVVllEa1EvOXp6U0xibGkyZFhwdApjUGJwbFE2MDJONGQvRzVyZ1BFeDNJbHhPMGRjWjBsRUlrc0RqK0s2cnlHQVZpVVlDbm9xYndHQURtcUU0VzBLCnRmUmVjNk5OZUhTTTBDTm5GdTdnZHNJQk83dUhQeUZUM1JRTm96cXowMVc1c1Fpd3h0SG1NZ2ZjTElBZDZSQUkKK1N1TmZwRk9SVjRZYWFMN0o0cm5SRjNmOGppL3ZvOW5EZWtabDBjNHQ5aEIxbWRPZVNGd0xuU2h1RHZDMnJmZwo0YnBCemdqb3B5MUN6ZDJSK0NkMDNId0U0bUhkTDl3YkFkZHdWSjF3dVJLS0IwNWpJUUtDQVFFQTlvR2lPN01QCnRZbGFTbCtvNmNTQ1kraDBKNjlvOEdRNVNwb0tRMmJlZGp1dm9QSy92Yjc5V2YyQlR3Vlg3eVJsdG0xZFVPOFEKNnpKRko1cjBuMElaZElSeG02dTQ2a1M1alFMNTV6WTMyUlFhWGhmVWNBbzdkM2RLMkgzeXZESVFERzViTFdieQpjenJ5V1FCUmFGbzdOb2RKd0k0dEQvSkZSOVNvZTR5dFBuY3lLbUdhOVZUTkdVNlA5eE04MjQ1LytuRE5GK1dZCk5HYUZLZVUxOFpQeWZPNlZ2ZW85YVBtY0xaeG1YZGZjeXlRUGsyU3pML2VDaFovbWZDeitWdjc5ZWxQR3NaRHEKaHZWWU9BL0hnQjhKbFZZMERDT0p0YkF5RUVlUHIyZjNBWWVlZ0NKcmVZZ2hkd0J3b2JJS0hVWUplV3J2aGd1bQpjb2s1U2hrVkEwM1VBd0tDQVFFQTFzK3RtMFlVWkR5M1lDaVR3TFprSjZ0cG9YVC96RkZraTdQSHZTcHdQMmN4Ckl0R1NkbDJ1TkNEWGJoZXNvYnVHbHZSdUhSb1RCakRVUzEwZ0o4ZTNRVU1kYzNZaDJxYW5FS1VKZ3BuRVkzUEYKZGNoMG44RFhTWGViVWNQTy8zN2NtUFo3alFGUmFjY1Z5ZTdBT0JMSnN5MktFc1RMTWcvbkFIdmcwN2tLOExoVwpkc2ZLa3RjeGczS3B4NXllZmZxWlpjRytqbVZKNWJvNWFEVG1HUDAxeGZ1TGZ0MUhFUnYxVk9WM2pkYmc3Ym5MCkhEWEt5SmMvYllHWEFML1M3bnVkNFBVR2xMUkhJNG5NbjJDMXB3ckUxR0s2NTExeUQwUS94VWlOdjlRYXdxak4KNlZSTVo4VjJmalB3M3RDejRTZG9tSGx2RXUvVDNvSURaRmZMMjE0WUVRS0NBUUVBeHJFODdEUHJzSjd5eVVvTAo0OG1DZERkVkVQdmhjUXJINXlyOHBOR1E5T290S1dVaFNqZGREU0U2RGxDM1RVb2dOZ2czRkZwdVg3WFlhaEVMCnBpYm9UN1BGd3YyS2drSTg4YjE1WTNXZlZIbFF1NUVucWR1RmpVajVha0V5MjZEYXNMcHZBY3llRm1Wb0hIY1cKSnkrNzFMcjlwcGl6Rjh4bkxYdnJCaE1oUFYxa2NTYlZlWFFPRVp6eWkrdlV2UUJDaGNKUWZqZVJGSU9pU0YrNQp1R3hSSTRSSDdKbVhRRHNhT09WODJaMTZHYitLVW5yenQvRWdJc2I5U0JBWVBsVXdTVjZucEU2enkyNk0yanRWCnloNjZZY1huRS9XOU5WYTlNOFFLdkE3bjJUTFFmbVhBRFdpOTJLYmhJbVJZcExua1B4c0QwQjhwM2Q1ZjhDc0IKZGZWUXN3S0NBUUE0b2R3Rk9iNmFocDlaekpqWGo3NXI3dmNVU0FKQVhaMENiODFUQStNLzgvYXlWVW9seHFEcwp3VlBLaEpkUXRGM1JZbk85WEdoemsyQUUxbkpweW05MzF6Zm1mdGlDQ3JtMDI2M2NMcW5tQmxtaXFjeUZhWlBZCm5KM29PcFZTS3FJQitjbXM3UklIcnlNQmI5NWhhaUtmQ0h5RVlqbW40eVZsclVNaXpvQWg0cGQ1RFVkTTIwc0UKZXJCa0hkaHpJaVJhL1c4OW4xcVNKRHlLU3BmM01wcUFyY2FqRmJkc3ZWQTZwNXlUV25sR2Mxc20vcytOZnNLTQpIa1F2UUdOZDJGMVJONGdhb0VTNUM5VlVsMnlNczN3YytrS0lTTXlQVmREeU5DU2ExRktSeTJzdC9ybCtDa01wCkZveUVVY1Y2c0tjVXU3OGE0UHVjaCs1WkFBU1F1d2ZoQW9JQkFRQ29XeE5VVzFXN29hZlBzM2srL1hlMkVZN2sKUXBKTzNjcUFUNHYvRitYSys0QnVCUnRQT2U4aytRTzQ3amI3UUlWdkJDM25OZDlId2dycHVqTlorTXBrZkxtRQpVMjZJREFFNE5vTFlHVDRadWM2YWhJUFh5U0NYeW1OSXRlcmRreGRBcFRmcnl1SDZSSWFmNlJPRWhER000MDRICnR6SkFmbXJlRXR3Sm1DK0FxM1ROWmM4ckVzVkRGNitkdHZTaGlMdWJKUGpqNWIzTkoyMU1UT2wrb2FmVHBBaUYKcCt4c0JSbjNZQktwRkJtaGd2YllsNDFoRDNBTG5TWmNvTVdoNnk3TlMrK1VBNTc4V1l3cGQ4OTNkNkFncmJCZApZVHVvdVJuWGF0YXMzQUk1djFWOERVRGV3ajBPbUhTZGw3V25NU0Z1dDZpSzBwQVpHK0dGdkx5WUZMS3gKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K\n token: 1ax84amdce5i5g3b0rihwasxirdsvmhwqyva45gg1l2vg5ugj25js5hw5xeqtgonxxr31phlzyjrf4xapjwmyv1ze900cfffhgxh7twbmsvpabhojoc16pw5szia6hvo\n", "kubelet_identity": [ { - "client_id": "fcf05011-cf9d-41ff-b7c7-0b3b0193ec4d", - "object_id": "d72150ad-1308-4401-94b9-5e8d216f4509", + "client_id": "3404e83d-6197-4f1b-81ff-e6a289550fc1", + "object_id": "85a39aa2-9757-4018-8112-f23749e4818a", "user_assigned_identity_id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/MC_squaremarket-group_squaremarket-aks_northeurope/providers/Microsoft.ManagedIdentity/userAssignedIdentities/squaremarket-aks-agentpool" } ], @@ -479,7 +434,7 @@ "load_balancer_profile": [ { "effective_outbound_ips": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/MC_squaremarket-group_squaremarket-aks_northeurope/providers/Microsoft.Network/publicIPAddresses/780bd729-57c1-4b28-9f5c-314c42402f91" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/MC_squaremarket-group_squaremarket-aks_northeurope/providers/Microsoft.Network/publicIPAddresses/fd318c32-9e45-4152-bde1-d7e60e9beb61" ], "idle_timeout_in_minutes": 0, "managed_outbound_ip_count": 1, @@ -501,7 +456,7 @@ "node_resource_group": "MC_squaremarket-group_squaremarket-aks_northeurope", "oms_agent": [], "open_service_mesh_enabled": false, - "portal_fqdn": "squaremarket-aks-x3z8ynwg.portal.hcp.northeurope.azmk8s.io", + "portal_fqdn": "squaremarket-aks-t989as5n.portal.hcp.northeurope.azmk8s.io", "private_cluster_enabled": false, "private_cluster_public_fqdn_enabled": false, "private_dns_zone_id": "", @@ -578,6 +533,32 @@ } ] }, + { + "mode": "managed", + "type": "azurerm_mysql_database", + "name": "messages-db", + "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "charset": "utf8", + "collation": "utf8_unicode_ci", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.DBforMySQL/servers/squaremarket-messages/databases/messages", + "name": "messages", + "resource_group_name": "squaremarket-group", + "server_name": "squaremarket-messages", + "timeouts": null + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozNjAwMDAwMDAwMDAwLCJkZWxldGUiOjM2MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjozNjAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "azurerm_mysql_server.messages-db-server", + "azurerm_resource_group.squaremarket-group" + ] + } + ] + }, { "mode": "managed", "type": "azurerm_mysql_firewall_rule", @@ -630,6 +611,32 @@ } ] }, + { + "mode": "managed", + "type": "azurerm_mysql_firewall_rule", + "name": "messages-db-to-server", + "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "end_ip_address": "255.255.255.255", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.DBforMySQL/servers/squaremarket-messages/firewallRules/server", + "name": "server", + "resource_group_name": "squaremarket-group", + "server_name": "squaremarket-messages", + "start_ip_address": "0.0.0.0", + "timeouts": null + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjoxODAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "azurerm_mysql_server.messages-db-server", + "azurerm_resource_group.squaremarket-group" + ] + } + ] + }, { "mode": "managed", "type": "azurerm_mysql_server", @@ -720,6 +727,51 @@ } ] }, + { + "mode": "managed", + "type": "azurerm_mysql_server", + "name": "messages-db-server", + "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "administrator_login": "Education6661", + "administrator_login_password": "Exemplary-Footer-Juice2-Visa-Footing-Trousers", + "auto_grow_enabled": true, + "backup_retention_days": 7, + "create_mode": "Default", + "creation_source_server_id": null, + "fqdn": "squaremarket-messages.mysql.database.azure.com", + "geo_redundant_backup_enabled": false, + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.DBforMySQL/servers/squaremarket-messages", + "identity": [], + "infrastructure_encryption_enabled": false, + "location": "northeurope", + "name": "squaremarket-messages", + "public_network_access_enabled": true, + "resource_group_name": "squaremarket-group", + "restore_point_in_time": null, + "sku_name": "B_Gen5_1", + "ssl_enforcement_enabled": true, + "ssl_minimal_tls_version_enforced": "TLS1_2", + "storage_mb": 5120, + "tags": { + "environment": "production", + "source": "terraform" + }, + "threat_detection_policy": [], + "timeouts": null, + "version": "8.0" + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozNjAwMDAwMDAwMDAwLCJkZWxldGUiOjM2MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjozNjAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "azurerm_resource_group.squaremarket-group" + ] + } + ] + }, { "mode": "managed", "type": "azurerm_resource_group", @@ -756,9 +808,9 @@ "condition_version": "", "delegated_managed_identity_resource_id": "", "description": "", - "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.ContainerRegistry/registries/squaremarketacr/providers/Microsoft.Authorization/roleAssignments/07a078bd-5d4d-c1d4-4fe1-b994398b69cf", - "name": "07a078bd-5d4d-c1d4-4fe1-b994398b69cf", - "principal_id": "d72150ad-1308-4401-94b9-5e8d216f4509", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.ContainerRegistry/registries/squaremarketacr/providers/Microsoft.Authorization/roleAssignments/7510fcab-ca55-fec1-45a9-e0ada55fece4", + "name": "7510fcab-ca55-fec1-45a9-e0ada55fece4", + "principal_id": "85a39aa2-9757-4018-8112-f23749e4818a", "principal_type": "ServicePrincipal", "role_definition_id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d", "role_definition_name": "AcrPull", @@ -770,7 +822,7 @@ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjoxODAwMDAwMDAwMDAwfX0=", "dependencies": [ "azurerm_container_registry.acr", - "azurerm_kubernetes_cluster.aks", + "azurerm_kubernetes_cluster.api", "azurerm_resource_group.squaremarket-group" ] } @@ -979,11 +1031,11 @@ } ], "nfsv3_enabled": false, - "primary_access_key": "tEZpZsHtEg6AGukAoXTaa7A+xoSUyhMmeoDy4xlMKzS2oj3AUrwPX0q1mF3kwUXpVihLarWvz6wq+AStYwbReA==", - "primary_blob_connection_string": "DefaultEndpointsProtocol=https;BlobEndpoint=https://squaremarketblobstorage.blob.core.windows.net/;AccountName=squaremarketblobstorage;AccountKey=tEZpZsHtEg6AGukAoXTaa7A+xoSUyhMmeoDy4xlMKzS2oj3AUrwPX0q1mF3kwUXpVihLarWvz6wq+AStYwbReA==", + "primary_access_key": "/sdUcvMUXtbqdUh+ftAkO/2XaCCAtx6n0sZ0sjwzR3ArCz4UtX0fpgnkrgtpsE2qY2qeVAy21dFc+ASttggrbw==", + "primary_blob_connection_string": "DefaultEndpointsProtocol=https;BlobEndpoint=https://squaremarketblobstorage.blob.core.windows.net/;AccountName=squaremarketblobstorage;AccountKey=/sdUcvMUXtbqdUh+ftAkO/2XaCCAtx6n0sZ0sjwzR3ArCz4UtX0fpgnkrgtpsE2qY2qeVAy21dFc+ASttggrbw==", "primary_blob_endpoint": "https://squaremarketblobstorage.blob.core.windows.net/", "primary_blob_host": "squaremarketblobstorage.blob.core.windows.net", - "primary_connection_string": "DefaultEndpointsProtocol=https;AccountName=squaremarketblobstorage;AccountKey=tEZpZsHtEg6AGukAoXTaa7A+xoSUyhMmeoDy4xlMKzS2oj3AUrwPX0q1mF3kwUXpVihLarWvz6wq+AStYwbReA==;EndpointSuffix=core.windows.net", + "primary_connection_string": "DefaultEndpointsProtocol=https;AccountName=squaremarketblobstorage;AccountKey=/sdUcvMUXtbqdUh+ftAkO/2XaCCAtx6n0sZ0sjwzR3ArCz4UtX0fpgnkrgtpsE2qY2qeVAy21dFc+ASttggrbw==;EndpointSuffix=core.windows.net", "primary_dfs_endpoint": "https://squaremarketblobstorage.dfs.core.windows.net/", "primary_dfs_host": "squaremarketblobstorage.dfs.core.windows.net", "primary_file_endpoint": "https://squaremarketblobstorage.file.core.windows.net/", @@ -1028,11 +1080,11 @@ ], "resource_group_name": "squaremarket-group", "routing": [], - "secondary_access_key": "8/iovB9UWJkg0KZIf5F+zx3DG/5A5H593f9gI8yQj7iGHqYGkNJHgpNQjgpvZ3W2PvjnidO9FuJP+AStL/oalA==", + "secondary_access_key": "zbQmccjub/0uthJ8XsheloBC/ca/xYPEyFydD+odbGq7aKo77F1+SBX3gyk4r5iW8YwTPkLTKlIH+AStTIgJzw==", "secondary_blob_connection_string": "", "secondary_blob_endpoint": null, "secondary_blob_host": null, - "secondary_connection_string": "DefaultEndpointsProtocol=https;AccountName=squaremarketblobstorage;AccountKey=8/iovB9UWJkg0KZIf5F+zx3DG/5A5H593f9gI8yQj7iGHqYGkNJHgpNQjgpvZ3W2PvjnidO9FuJP+AStL/oalA==;EndpointSuffix=core.windows.net", + "secondary_connection_string": "DefaultEndpointsProtocol=https;AccountName=squaremarketblobstorage;AccountKey=zbQmccjub/0uthJ8XsheloBC/ca/xYPEyFydD+odbGq7aKo77F1+SBX3gyk4r5iW8YwTPkLTKlIH+AStTIgJzw==;EndpointSuffix=core.windows.net", "secondary_dfs_endpoint": null, "secondary_dfs_host": null, "secondary_file_endpoint": null, From 3fa74753169d42060fbaf67cb93ab1dbf99fef2e Mon Sep 17 00:00:00 2001 From: RikThePixel Date: Sun, 14 Jan 2024 11:39:17 +0100 Subject: [PATCH 3/6] Add chatroom websocket endpoint --- .github/workflows/release-microservices.yml | 2 + apps/frontend/src/apis/messages/chats.ts | 9 +- apps/frontend/src/helpers/Resource.ts | 8 +- apps/frontend/src/lib/auth/index.ts | 3 + .../src/pages/messages/index.page.tsx | 18 +- .../responses/ads/messages/ChatsResponse.ts | 13 ++ apps/frontend/src/stores/useChats.ts | 68 +++++++- apps/gateway/.env.example | 1 + apps/gateway/src/index.ts | 6 + apps/messages/package.json | 3 + apps/messages/src/entities/Advertisement.ts | 13 -- apps/messages/src/entities/Category.ts | 5 - .../messages/src/entities/CategoryProperty.ts | 6 - .../src/entities/CategoryPropertyOption.ts | 6 - .../entities/CategoryPropertyOptionValue.ts | 6 - apps/messages/src/entities/Chat.ts | 6 + apps/messages/src/entities/Image.ts | 6 - apps/messages/src/entities/Message.ts | 9 + apps/messages/src/middleware/auth.ts | 3 + apps/messages/src/providers/di.ts | 18 ++ .../src/repositories/chat/ChatRespository.ts | 13 ++ .../repositories/chat/KnexChatRepository.ts | 52 ++++++ apps/messages/src/routes/v1/chats.ts | 40 +++++ apps/messages/src/routes/v1/index.ts | 2 + apps/messages/src/services/ChatService.ts | 13 ++ apps/messages/src/services/UserService.ts | 1 - .../src/subscribers/UsersSubscription.ts | 5 +- k8s/azure-gateway-ingress.yml | 1 - k8s/gateway-deployment.yml | 2 + knexfile.js | 2 +- main.tf | 92 ++++++----- package-lock.json | 32 +++- terraform.tfstate | 155 ++++++++++++++++-- 33 files changed, 506 insertions(+), 113 deletions(-) create mode 100644 apps/frontend/src/lib/auth/index.ts create mode 100644 apps/frontend/src/responses/ads/messages/ChatsResponse.ts delete mode 100644 apps/messages/src/entities/Advertisement.ts delete mode 100644 apps/messages/src/entities/Category.ts delete mode 100644 apps/messages/src/entities/CategoryProperty.ts delete mode 100644 apps/messages/src/entities/CategoryPropertyOption.ts delete mode 100644 apps/messages/src/entities/CategoryPropertyOptionValue.ts create mode 100644 apps/messages/src/entities/Chat.ts delete mode 100644 apps/messages/src/entities/Image.ts create mode 100644 apps/messages/src/entities/Message.ts create mode 100644 apps/messages/src/repositories/chat/ChatRespository.ts create mode 100644 apps/messages/src/repositories/chat/KnexChatRepository.ts create mode 100644 apps/messages/src/routes/v1/chats.ts create mode 100644 apps/messages/src/services/ChatService.ts diff --git a/.github/workflows/release-microservices.yml b/.github/workflows/release-microservices.yml index 9e7e440..51ba35c 100644 --- a/.github/workflows/release-microservices.yml +++ b/.github/workflows/release-microservices.yml @@ -53,6 +53,8 @@ jobs: resource-group: ${{ vars.AZURE_RESOURCE_GROUP }} cluster-name: ${{ vars.AZURE_CLUSTER_NAME }} + - name: "Ensure app routing" + run: az aks approuting enable -g ${{ vars.AZURE_RESOURCE_GROUP }} -n ${{ vars.AZURE_CLUSTER_NAME }} - name: Delete auth secrets continue-on-error: true run: kubectl delete secret auth-secrets diff --git a/apps/frontend/src/apis/messages/chats.ts b/apps/frontend/src/apis/messages/chats.ts index bdb7751..12c7702 100644 --- a/apps/frontend/src/apis/messages/chats.ts +++ b/apps/frontend/src/apis/messages/chats.ts @@ -1,5 +1,12 @@ import backend from '@/adapters/backend'; +import { chatsResponseSchema } from '@/responses/ads/messages/ChatsResponse'; -export function startConversation(userUid: string) { +export function startChat(userUid: string) { return backend.post('v1/chats', { json: { user_uid: userUid } }); } + +export async function getChats(signal?: AbortSignal) { + return await backend + .get('v1/chats', { signal }) + .then((res) => chatsResponseSchema.parse(res.json())); +} diff --git a/apps/frontend/src/helpers/Resource.ts b/apps/frontend/src/helpers/Resource.ts index 5d62aaf..ba47c35 100644 --- a/apps/frontend/src/helpers/Resource.ts +++ b/apps/frontend/src/helpers/Resource.ts @@ -44,10 +44,14 @@ export default class Resource< TError = Error, TLoading = undefined, TIdle = undefined, - >(promise: Promise): Promise> { + >( + promise: Promise, + ): Promise> { return promise .then((val) => Resource.wrapValue(val)) - .catch((err: TError) => Resource.wrapError(err)); + .catch((err: TError) => + Resource.wrapError(err), + ); } /** diff --git a/apps/frontend/src/lib/auth/index.ts b/apps/frontend/src/lib/auth/index.ts new file mode 100644 index 0000000..9ad028f --- /dev/null +++ b/apps/frontend/src/lib/auth/index.ts @@ -0,0 +1,3 @@ +import useAuth from "./stores/useAuth"; + +export const getToken = () => useAuth.getState().getToken(); diff --git a/apps/frontend/src/pages/messages/index.page.tsx b/apps/frontend/src/pages/messages/index.page.tsx index 7e48340..a0437bd 100644 --- a/apps/frontend/src/pages/messages/index.page.tsx +++ b/apps/frontend/src/pages/messages/index.page.tsx @@ -1,13 +1,8 @@ import PageContainer from '@/components/page/Container'; -import { - Center, - Group, - Paper, - Stack, - Text, - TextInput, -} from '@mantine/core'; +import useChats from '@/stores/useChats'; +import { Center, Group, Paper, Stack, Text, TextInput } from '@mantine/core'; import { useHover } from '@mantine/hooks'; +import { useEffect } from 'react'; import { MdChevronRight, MdSearch } from 'react-icons/md'; import { Link } from 'wouter'; @@ -20,6 +15,7 @@ interface Conversation { function ConversationRow({ conversation }: { conversation: Conversation }) { const { ref, hovered } = useHover(); + return ( @@ -43,6 +39,12 @@ function ConversationRow({ conversation }: { conversation: Conversation }) { } export default function MessagesPage() { + const { connectToChat } = useChats(); + + useEffect(() => { + connectToChat('9f783b23-b243-11ee-abb1-0242ac130002'); + }, [connectToChat]); + const conversations = Array(10) .fill(1) .map((_, idx) => { diff --git a/apps/frontend/src/responses/ads/messages/ChatsResponse.ts b/apps/frontend/src/responses/ads/messages/ChatsResponse.ts new file mode 100644 index 0000000..de5c8de --- /dev/null +++ b/apps/frontend/src/responses/ads/messages/ChatsResponse.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +export const chatsResponseSchema = z + .object({ + uid: z.string(), + user: z.object({ + uid: z.string(), + username: z.string(), + }), + }) + .array(); + +export type ChatsResponse = z.infer; diff --git a/apps/frontend/src/stores/useChats.ts b/apps/frontend/src/stores/useChats.ts index 8085cbf..3f0f753 100644 --- a/apps/frontend/src/stores/useChats.ts +++ b/apps/frontend/src/stores/useChats.ts @@ -1 +1,67 @@ - e +import { getChats, startChat } from '@/apis/messages/chats'; +import Resource from '@/helpers/Resource'; +import { getToken } from '@/lib/auth'; +import { ChatsResponse } from '@/responses/ads/messages/ChatsResponse'; +import { create } from 'zustand'; + +interface ChatsState { + chats: Resource; + chat: Resource<{}>; + + startChat(userUid: string): Promise; + getChats(): Promise; + + connectToChat(uid: string): Promise; + disconnect(): Promise; +} + +const useChats = create((set, get) => ({ + chats: Resource.idle(), + chat: Resource.idle(), + + async startChat(userUid) { + await startChat(userUid); + }, + + async getChats() { + const resource = get().chats.abort().reload(); + + set({ + chats: await Resource.wrapPromise(getChats(resource.signal())), + }); + }, + + async connectToChat(uid) { + const token = await getToken(); + if (!token) return; + + const resource = get().chat.abort().reload(); + set({ chat: resource }); + + const url = new URL(import.meta.env.VITE_BACKEND_URL); + url.pathname = `v1/chats/${uid}`; + url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:'; + url.searchParams.set('token', token); + // url.port = '8003'; + + const socket = new WebSocket(url); + + socket.onopen = function (e) { + if (resource.signal()?.aborted) return socket.close(); + }; + + socket.onmessage = function (e) { + if (resource.signal()?.aborted) return socket.close(); + const data = JSON.parse(e.data.toString()) + console.log(data); + }; + + socket.onclose = function (e) { + if (e.code === 1000) return; // Client closed connection + } + }, + + async disconnect() {}, +})); + +export default useChats; diff --git a/apps/gateway/.env.example b/apps/gateway/.env.example index 8d4ec91..44e46e3 100644 --- a/apps/gateway/.env.example +++ b/apps/gateway/.env.example @@ -12,3 +12,4 @@ ALLOWED_ORIGIN=http://localhost:5200 # A allowed origin # Services ACCOUNTS_SERVICE_URL=http://accounts:8001 ADVERTISEMENTS_SERVICE_URL=http://localhost:8002 +MESSAGES_SERVICE_URL=http://localhost:8003 diff --git a/apps/gateway/src/index.ts b/apps/gateway/src/index.ts index 651a546..38368d3 100644 --- a/apps/gateway/src/index.ts +++ b/apps/gateway/src/index.ts @@ -19,6 +19,7 @@ const AUTH_CONFIG: AuthOptions = { const SERVICES = { accounts: process.env.ACCOUNTS_SERVICE_URL ?? 'http://localhost:8001', ads: process.env.ADVERTISEMENTS_SERVICE_URL ?? 'http://localhost:8002', + messages: process.env.MESSAGES_SERVICE_URL ?? 'http://localhost:8003', } as const; gateway({ @@ -58,6 +59,11 @@ gateway({ ], AUTH_CONFIG, ), + { + proxyType: 'websocket', + prefix: '/v1/chats/*', + target: SERVICES.messages.replace("http", "ws"), + }, { prefix: '/v1/posts', prefixRewrite: '/v1', diff --git a/apps/messages/package.json b/apps/messages/package.json index 3cfb9ec..247a08b 100644 --- a/apps/messages/package.json +++ b/apps/messages/package.json @@ -19,6 +19,7 @@ "@types/koa__router": "^12.0.4", "@types/node": "^20.9.0", "@types/rascal": "^10.0.9", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "concurrently": "^8.2.2", @@ -44,6 +45,7 @@ "jwks-rsa": "^3.1.0", "knex": "^3.0.1", "koa": "^2.14.2", + "koa-imp-ws": "^1.1.1", "koa-jwt": "^4.0.4", "koa-tioc": "^0.4.0", "multer": "^1.4.5-lts.1", @@ -51,6 +53,7 @@ "rascal": "^17.0.1", "tioc": "^0.3.0", "validate-image-type": "^3.0.0", + "ws": "^8.16.0", "zod": "^3.22.4" } } diff --git a/apps/messages/src/entities/Advertisement.ts b/apps/messages/src/entities/Advertisement.ts deleted file mode 100644 index 875b302..0000000 --- a/apps/messages/src/entities/Advertisement.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface Advertisement { - id: number; - uid: string; - user_id: number; - category_id?: number; - - title?: string; - description?: string; - price?: number; - currency?: string; - draft: boolean; - published_at: Date | null; -} diff --git a/apps/messages/src/entities/Category.ts b/apps/messages/src/entities/Category.ts deleted file mode 100644 index 50cbbe7..0000000 --- a/apps/messages/src/entities/Category.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface Category { - id: number; - uid: string; - name: string; -} diff --git a/apps/messages/src/entities/CategoryProperty.ts b/apps/messages/src/entities/CategoryProperty.ts deleted file mode 100644 index 115b87c..0000000 --- a/apps/messages/src/entities/CategoryProperty.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface CategoryProperty { - id: number; - uid: string; - category_id: number; - name: string; -} diff --git a/apps/messages/src/entities/CategoryPropertyOption.ts b/apps/messages/src/entities/CategoryPropertyOption.ts deleted file mode 100644 index 53c11df..0000000 --- a/apps/messages/src/entities/CategoryPropertyOption.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface CategoryPropertyOption { - id: number; - uid: string; - category_property_id: number; - name: string; -} diff --git a/apps/messages/src/entities/CategoryPropertyOptionValue.ts b/apps/messages/src/entities/CategoryPropertyOptionValue.ts deleted file mode 100644 index d7a8786..0000000 --- a/apps/messages/src/entities/CategoryPropertyOptionValue.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface CategoryPropertyOptionValue { - id: number - uid: string; - advertisement_id: number; - category_property_option_id: number; -} diff --git a/apps/messages/src/entities/Chat.ts b/apps/messages/src/entities/Chat.ts new file mode 100644 index 0000000..e6b0f32 --- /dev/null +++ b/apps/messages/src/entities/Chat.ts @@ -0,0 +1,6 @@ +export interface Chat { + id: number; + uid: string; + user_0_id: number; + user_1_id: number; +} diff --git a/apps/messages/src/entities/Image.ts b/apps/messages/src/entities/Image.ts deleted file mode 100644 index 2bc6882..0000000 --- a/apps/messages/src/entities/Image.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface Image { - id: number; - uid: string; - advertisement_id: number; - mime: string; -} diff --git a/apps/messages/src/entities/Message.ts b/apps/messages/src/entities/Message.ts new file mode 100644 index 0000000..a1eaf15 --- /dev/null +++ b/apps/messages/src/entities/Message.ts @@ -0,0 +1,9 @@ +export interface Message { + id: number; + uid: string; + from_user_id: number; + chat_id: number; + content: string; + seen_at: Date | null; + sent_at: Date; +} diff --git a/apps/messages/src/middleware/auth.ts b/apps/messages/src/middleware/auth.ts index 0e7c920..b1b1ea1 100644 --- a/apps/messages/src/middleware/auth.ts +++ b/apps/messages/src/middleware/auth.ts @@ -5,6 +5,7 @@ import jwt from 'koa-jwt'; export interface AuthOptions { issuerUrl: string; audience: string; + getToken?(ctx: Koa.Context, opts: jwt.Options): string | null; } interface DecodedJwt { @@ -25,6 +26,7 @@ export interface AuthState { export default function auth({ issuerUrl, audience, + getToken, }: AuthOptions): Koa.Middleware { const middleware = jwt({ secret: JwksRsa.koaJwtSecret({ @@ -33,6 +35,7 @@ export default function auth({ jwksRequestsPerMinute: 5, jwksUri: `${issuerUrl}.well-known/jwks.json`, }), + getToken: getToken, audience, issuer: issuerUrl, algorithms: ['RS256'], diff --git a/apps/messages/src/providers/di.ts b/apps/messages/src/providers/di.ts index a8a957f..e065fea 100644 --- a/apps/messages/src/providers/di.ts +++ b/apps/messages/src/providers/di.ts @@ -9,6 +9,8 @@ import auth from '../middleware/auth'; import KnexUserRepository from '../repositories/user/KnexUserRepository'; import UserService from '../services/UserService'; import UsersSubscription from '../subscribers/UsersSubscription'; +import KnexChatRepository from '../repositories/chat/KnexChatRepository'; +import ChatService from '../services/ChatService'; const depenencyProvider = (c: IoCContainer) => c @@ -19,14 +21,30 @@ const depenencyProvider = (c: IoCContainer) => ) .addSingleton('logger', () => new ConsoleLogger()) .addScoped('authMiddleware', () => auth(authConfig)) + .addScoped('wsAuthMiddleware', () => + auth({ + ...authConfig, + getToken(ctx) { + const query = ctx.request.query; + return 'token' in query && !Array.isArray(query.token) + ? query.token ?? null + : null; + }, + }), + ) .addScoped( 'UserRespository', (c) => new KnexUserRepository(c.resolve('db')), ) + .addScoped('ChatRepository', (c) => new KnexChatRepository(c.resolve('db'))) .addScoped( 'UserService', (c) => new UserService(c.resolve('UserRespository')), ) + .addScoped( + 'ChatService', + (c) => new ChatService(c.resolve('ChatRepository')), + ) .addSingleton( 'UsersSubscription', async (c) => diff --git a/apps/messages/src/repositories/chat/ChatRespository.ts b/apps/messages/src/repositories/chat/ChatRespository.ts new file mode 100644 index 0000000..8b10846 --- /dev/null +++ b/apps/messages/src/repositories/chat/ChatRespository.ts @@ -0,0 +1,13 @@ +import { Chat } from '../../entities/Chat'; +import { Message } from '../../entities/Message'; +import { User } from '../../entities/User'; + +export interface ChatWithUsersAndMessages + extends Omit { + users: User[]; + messages: (Omit & { username: string })[] +} + +export default interface ChatRepository { + get(uid: string): Promise; +} diff --git a/apps/messages/src/repositories/chat/KnexChatRepository.ts b/apps/messages/src/repositories/chat/KnexChatRepository.ts new file mode 100644 index 0000000..18a268b --- /dev/null +++ b/apps/messages/src/repositories/chat/KnexChatRepository.ts @@ -0,0 +1,52 @@ +import { Knex } from 'knex'; +import ChatRepository, { ChatWithUsersAndMessages } from './ChatRespository'; +import { Chat } from '../../entities/Chat'; +import { UidsToBuffers } from '../../helpers/identifiers'; +import { User } from '../../entities/User'; + +export default class KnexChatRepository implements ChatRepository { + constructor(private db: Knex) {} + + async get(uid: string): Promise { + return await this.db.transaction(async function (trx) { + const chat = await trx + .table('chats') + .select>('id', 'uid', 'user_0_id', 'user_1_id') + .where('chats.uid', trx.fn.uuidToBin(uid)) + .first(); + + if (!chat) return null; + + const usersTask = trx + .table('users') + .select('id', 'provider_id', 'username') + .whereIn('users.uid', [chat.user_0_id, chat.user_1_id]); + + const messagesTask = trx + .table('messages') + .join('users', 'messages.from_user_id', '=', 'users.id') + .where('messages.chat_id', chat.id) + .select>( + 'messages.id', + 'messages.uid', + 'messages.chat_id', + 'messages.content', + 'messages.seen_at', + 'messages.sent_at', + 'users.username as username', + ) + .then((msgs) => + msgs.map((msg) => ({ ...msg, uid: trx.fn.binToUuid(msg.uid) })), + ); + + const [users, messages] = await Promise.all([usersTask, messagesTask]); + + return { + id: chat.id, + uid: trx.fn.binToUuid(chat.uid), + users, + messages, + }; + }); + } +} diff --git a/apps/messages/src/routes/v1/chats.ts b/apps/messages/src/routes/v1/chats.ts new file mode 100644 index 0000000..c9be9b4 --- /dev/null +++ b/apps/messages/src/routes/v1/chats.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; +import makeRouter, { AppRouter } from '../../helpers/router'; +import validate from '../../middleware/validate'; +import websocket, { WebSocketContext } from 'koa-imp-ws'; +import { AuthState } from '../../middleware/auth'; + +const wsChatsRouter = makeRouter().use( + (ctx, next) => ctx.container.resolve('wsAuthMiddleware')(ctx, next), +); + +wsChatsRouter.get( + 'Connect to chat', + '/:uid', + validate({ + params: z.object({ uid: z.string().uuid() }), + query: z.object({ token: z.string() }), + }), + websocket(), + async (ctx) => { + const socket = await ctx.ws(); + + if (!socket) { + ctx.status = 400; + ctx.body = 'User must request websocket connection'; + return; + } + + const chatService = ctx.container.resolve("ChatService") + const chat = await chatService.get(ctx.validated.params.uid) + chat.users + + // socket.on('message', function (data) { + // if (data === "init") { + // + // } + // }); + }, +); + +export default wsChatsRouter as unknown as AppRouter; diff --git a/apps/messages/src/routes/v1/index.ts b/apps/messages/src/routes/v1/index.ts index a42a3c1..b662ff7 100644 --- a/apps/messages/src/routes/v1/index.ts +++ b/apps/messages/src/routes/v1/index.ts @@ -1,8 +1,10 @@ import makeRouter from '../../helpers/router'; +import wsChatsRouter from './chats'; import healthRouter from './health'; const v1Router = makeRouter(); v1Router.use(healthRouter.prefix('/health').routes()); +v1Router.use(wsChatsRouter.prefix('/chats').routes()); export default v1Router; diff --git a/apps/messages/src/services/ChatService.ts b/apps/messages/src/services/ChatService.ts new file mode 100644 index 0000000..961fb78 --- /dev/null +++ b/apps/messages/src/services/ChatService.ts @@ -0,0 +1,13 @@ +import NotFoundException from '../exceptions/common/NotFound'; +import ChatRepository from '../repositories/chat/ChatRespository'; + +export default class ChatService { + constructor(private chatRespository: ChatRepository) {} + + async get(uid: string) { + return this.chatRespository.get(uid).then((chat) => { + if (!chat) throw new NotFoundException('chat'); + return chat; + }); + } +} diff --git a/apps/messages/src/services/UserService.ts b/apps/messages/src/services/UserService.ts index 359f6de..8406f4e 100644 --- a/apps/messages/src/services/UserService.ts +++ b/apps/messages/src/services/UserService.ts @@ -1,4 +1,3 @@ -import { User } from '../entities/User'; import UserRepository from '../repositories/user/UserRepository'; export interface SyncUserProps { diff --git a/apps/messages/src/subscribers/UsersSubscription.ts b/apps/messages/src/subscribers/UsersSubscription.ts index 78e016d..155e84f 100644 --- a/apps/messages/src/subscribers/UsersSubscription.ts +++ b/apps/messages/src/subscribers/UsersSubscription.ts @@ -23,10 +23,9 @@ export default class UsersSubscription extends BaseSubscription { await userService.createOrUpdate({ provider_id: content.provider_id, username: content.username, - default_currency: content.default_currency, }); - ack() + ack(); }, - ) + ); } } diff --git a/k8s/azure-gateway-ingress.yml b/k8s/azure-gateway-ingress.yml index 97f1393..5ca9ae5 100644 --- a/k8s/azure-gateway-ingress.yml +++ b/k8s/azure-gateway-ingress.yml @@ -5,7 +5,6 @@ metadata: spec: ingressClassName: webapprouting.kubernetes.azure.com rules: - - host: sq.api.rikdenbreejen.nl http: paths: - path: / diff --git a/k8s/gateway-deployment.yml b/k8s/gateway-deployment.yml index a66a712..8f4ba62 100644 --- a/k8s/gateway-deployment.yml +++ b/k8s/gateway-deployment.yml @@ -59,6 +59,8 @@ spec: value: http://accounts-service:8080 - name: ADVERTISEMENTS_SERVICE_URL value: http://advertisements-service:8080 + - name: MESSAGES_SERVICE_URL + value: http://messages-service:8080 ports: - containerPort: 80 restartPolicy: Always diff --git a/knexfile.js b/knexfile.js index b0577c4..dadb829 100644 --- a/knexfile.js +++ b/knexfile.js @@ -1,7 +1,7 @@ const path = require("node:path"); const { config: configEnv } = require("dotenv"); -const environments = ["advertisements", "accounts"]; +const environments = ["advertisements", "accounts", "messages"]; const config = Object.fromEntries( environments diff --git a/main.tf b/main.tf index bd93f48..2603894 100644 --- a/main.tf +++ b/main.tf @@ -64,6 +64,13 @@ resource "azurerm_kubernetes_cluster" "api" { network_policy = "calico" } + addon_profile { + web_app_routing { + enabled = true + dns_zone_resource_id = azurerm_dns_zone.example.id + } + } + tags = { "environment" = "production" "source" = "terraform" @@ -86,6 +93,10 @@ resource "azurerm_dns_zone" "api" { resource_group_name = azurerm_resource_group.squaremarket-group.name } +output "api_fqdn" { + value = azurerm_kubernetes_cluster.api.fqdn +} + resource "azurerm_frontdoor" "frontdoor" { name = "squaremarket-frontdoor" resource_group_name = azurerm_resource_group.squaremarket-group.name @@ -119,19 +130,19 @@ resource "azurerm_frontdoor" "frontdoor" { health_probe_name = "frontend" } - # backend_pool { - # name = "api" - # - # backend { - # host_header = azurerm_kubernetes_cluster.api.fqdn - # address = azurerm_kubernetes_cluster.api.fqdn - # http_port = 80 - # https_port = 443 - # } - # - # load_balancing_name = "api" - # health_probe_name = "api" - # } + backend_pool { + name = "api" + + backend { + host_header = azurerm_dns_zone.api.name + address = azurerm_kubernetes_cluster.api.fqdn + http_port = 80 + https_port = 443 + } + + load_balancing_name = "api" + health_probe_name = "api" + } routing_rule { name = "https-frontend" @@ -144,23 +155,22 @@ resource "azurerm_frontdoor" "frontdoor" { } } - # routing_rule { - # name = "https-api" - # accepted_protocols = ["Https"] - # patterns_to_match = ["/*"] - # frontend_endpoints = ["api"] - # forwarding_configuration { - # forwarding_protocol = "HttpsOnly" - # backend_pool_name = "api" - # } - # } + routing_rule { + name = "https-api" + accepted_protocols = ["Https"] + patterns_to_match = ["/*"] + frontend_endpoints = ["api"] + forwarding_configuration { + forwarding_protocol = "HttpsOnly" + backend_pool_name = "api" + } + } routing_rule { name = "https-redirect" accepted_protocols = ["Http"] patterns_to_match = ["/*"] - frontend_endpoints = ["frontend"] -#, "api"] + frontend_endpoints = ["frontend", "api"] redirect_configuration { redirect_protocol = "HttpsOnly" redirect_type = "Moved" @@ -180,14 +190,16 @@ resource "azurerm_frontdoor" "frontdoor" { protocol = "Https" } - # backend_pool_load_balancing { - # name = "api" - # } - # - # backend_pool_health_probe { - # name = "api" - # protocol = "Https" - # } + backend_pool_load_balancing { + name = "api" + } + + backend_pool_health_probe { + name = "api" + path = "/services.json" + protocol = "Https" + probe_method = "HEAD" + } } resource "azurerm_frontdoor_custom_https_configuration" "frontend-https" { @@ -198,13 +210,13 @@ resource "azurerm_frontdoor_custom_https_configuration" "frontend-https" { } } -resource "azurerm_frontdoor_custom_https_configuration" "api-https" { - frontend_endpoint_id = azurerm_frontdoor.frontdoor.frontend_endpoints.api - custom_https_provisioning_enabled = true - custom_https_configuration { - certificate_source = "FrontDoor" - } -} +# resource "azurerm_frontdoor_custom_https_configuration" "api-https" { +# frontend_endpoint_id = azurerm_frontdoor.frontdoor.frontend_endpoints.api +# custom_https_provisioning_enabled = true +# custom_https_configuration { +# certificate_source = "FrontDoor" +# } +# } resource "azurerm_storage_account" "frontend-account" { name = "squaremarketfrontend" diff --git a/package-lock.json b/package-lock.json index 4c0481e..43bd1fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -293,6 +293,7 @@ "jwks-rsa": "^3.1.0", "knex": "^3.0.1", "koa": "^2.14.2", + "koa-imp-ws": "^1.1.1", "koa-jwt": "^4.0.4", "koa-tioc": "^0.4.0", "multer": "^1.4.5-lts.1", @@ -300,6 +301,7 @@ "rascal": "^17.0.1", "tioc": "^0.3.0", "validate-image-type": "^3.0.0", + "ws": "^8.16.0", "zod": "^3.22.4" }, "devDependencies": { @@ -309,6 +311,7 @@ "@types/koa__router": "^12.0.4", "@types/node": "^20.9.0", "@types/rascal": "^10.0.9", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "concurrently": "^8.2.2", @@ -3742,6 +3745,15 @@ "@types/node": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.31", "dev": true, @@ -9335,6 +9347,21 @@ "node": ">= 10" } }, + "node_modules/koa-imp-ws": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/koa-imp-ws/-/koa-imp-ws-1.1.1.tgz", + "integrity": "sha512-UpyELkhQaTiPietjLlkrU+KNHqaMy935pxyF1Laci+uVaNpeAIbed64UcfXVpR8Mkd842CEvSViMjXx4uVcZzg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=17" + }, + "peerDependencies": { + "koa": ">=2", + "ws": ">=8" + } + }, "node_modules/koa-jwt": { "version": "4.0.4", "license": "MIT", @@ -15812,8 +15839,9 @@ } }, "node_modules/ws": { - "version": "8.14.2", - "license": "MIT", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { "node": ">=10.0.0" }, diff --git a/terraform.tfstate b/terraform.tfstate index af0ca00..d36d323 100644 --- a/terraform.tfstate +++ b/terraform.tfstate @@ -1,9 +1,14 @@ { "version": 4, "terraform_version": "1.6.4", - "serial": 135, + "serial": 144, "lineage": "b638d247-bec4-7079-c94c-62b1fc867340", - "outputs": {}, + "outputs": { + "api_fqdn": { + "value": "squaremarket-aks-t989as5n.hcp.northeurope.azmk8s.io", + "type": "string" + } + }, "resources": [ { "mode": "managed", @@ -65,6 +70,51 @@ } ] }, + { + "mode": "managed", + "type": "azurerm_dns_zone", + "name": "api", + "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/dnszones/sq.api.rikdenbreejen.nl", + "max_number_of_record_sets": 10000, + "name": "sq.api.rikdenbreejen.nl", + "name_servers": [ + "ns1-34.azure-dns.com.", + "ns2-34.azure-dns.net.", + "ns3-34.azure-dns.org.", + "ns4-34.azure-dns.info." + ], + "number_of_record_sets": 2, + "resource_group_name": "squaremarket-group", + "soa_record": [ + { + "email": "azuredns-hostmaster.microsoft.com", + "expire_time": 2419200, + "fqdn": "sq.api.rikdenbreejen.nl.", + "host_name": "ns1-34.azure-dns.com.", + "minimum_ttl": 300, + "refresh_time": 3600, + "retry_time": 300, + "serial_number": 1, + "tags": {}, + "ttl": 3600 + } + ], + "tags": {}, + "timeouts": null + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInJlYWQiOjMwMDAwMDAwMDAwMCwidXBkYXRlIjoxODAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", + "dependencies": [ + "azurerm_resource_group.squaremarket-group" + ] + } + ] + }, { "mode": "managed", "type": "azurerm_dns_zone", @@ -136,6 +186,23 @@ "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/frontend", "load_balancing_name": "frontend", "name": "frontend" + }, + { + "backend": [ + { + "address": "squaremarket-aks-t989as5n.hcp.northeurope.azmk8s.io", + "enabled": true, + "host_header": "sq.api.rikdenbreejen.nl", + "http_port": 80, + "https_port": 443, + "priority": 1, + "weight": 50 + } + ], + "health_probe_name": "api", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/api", + "load_balancing_name": "api", + "name": "api" } ], "backend_pool_health_probe": [ @@ -147,9 +214,19 @@ "path": "/", "probe_method": "GET", "protocol": "Https" + }, + { + "enabled": true, + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/api", + "interval_in_seconds": 120, + "name": "api", + "path": "/services.json", + "probe_method": "HEAD", + "protocol": "Https" } ], "backend_pool_health_probes": { + "api": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/api", "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/frontend" }, "backend_pool_load_balancing": [ @@ -159,9 +236,17 @@ "name": "frontend", "sample_size": 4, "successful_samples_required": 2 + }, + { + "additional_latency_milliseconds": 0, + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/api", + "name": "api", + "sample_size": 4, + "successful_samples_required": 2 } ], "backend_pool_load_balancing_settings": { + "api": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/api", "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/frontend" }, "backend_pool_settings": [ @@ -171,26 +256,32 @@ } ], "backend_pools": { + "api": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/api", "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/frontend" }, "cname": "squaremarket-frontdoor.azurefd.net", "explicit_resource_order": [ { "backend_pool_health_probe_ids": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/frontend" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/frontend", + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/healthProbeSettings/api" ], "backend_pool_ids": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/frontend" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/frontend", + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/backendPools/api" ], "backend_pool_load_balancing_ids": [ - "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/frontend" + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/frontend", + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/loadBalancingSettings/api" ], "frontend_endpoint_ids": [ "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontend", + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/api", "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontdoor" ], "routing_rule_ids": [ "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-frontend", + "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-api", "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect" ] } @@ -205,6 +296,14 @@ "session_affinity_ttl_seconds": 0, "web_application_firewall_policy_link_id": "" }, + { + "host_name": "sq.api.rikdenbreejen.nl", + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/api", + "name": "api", + "session_affinity_enabled": false, + "session_affinity_ttl_seconds": 0, + "web_application_firewall_policy_link_id": "" + }, { "host_name": "squaremarket-frontdoor.azurefd.net", "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontdoor", @@ -215,6 +314,7 @@ } ], "frontend_endpoints": { + "api": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/api", "frontdoor": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontdoor", "frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/frontendEndpoints/frontend" }, @@ -251,6 +351,33 @@ ], "redirect_configuration": [] }, + { + "accepted_protocols": [ + "Https" + ], + "enabled": true, + "forwarding_configuration": [ + { + "backend_pool_name": "api", + "cache_duration": "", + "cache_enabled": false, + "cache_query_parameter_strip_directive": "StripAll", + "cache_query_parameters": [], + "cache_use_dynamic_compression": false, + "custom_forwarding_path": "", + "forwarding_protocol": "HttpsOnly" + } + ], + "frontend_endpoints": [ + "api" + ], + "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-api", + "name": "https-api", + "patterns_to_match": [ + "/*" + ], + "redirect_configuration": [] + }, { "accepted_protocols": [ "Http" @@ -258,7 +385,8 @@ "enabled": true, "forwarding_configuration": [], "frontend_endpoints": [ - "frontend" + "frontend", + "api" ], "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect", "name": "https-redirect", @@ -278,6 +406,7 @@ } ], "routing_rules": { + "https-api": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-api", "https-frontend": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-frontend", "https-redirect": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.Network/frontDoors/squaremarket-frontdoor/routingRules/https-redirect" }, @@ -287,7 +416,9 @@ "sensitive_attributes": [], "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoyMTYwMDAwMDAwMDAwMCwiZGVsZXRlIjoyMTYwMDAwMDAwMDAwMCwicmVhZCI6MzAwMDAwMDAwMDAwLCJ1cGRhdGUiOjIxNjAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIyIn0=", "dependencies": [ + "azurerm_dns_zone.api", "azurerm_dns_zone.frontend", + "azurerm_kubernetes_cluster.api", "azurerm_resource_group.squaremarket-group", "azurerm_storage_account.frontend-account" ] @@ -322,8 +453,10 @@ "sensitive_attributes": [], "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoyMTYwMDAwMDAwMDAwMCwiZGVsZXRlIjoyMTYwMDAwMDAwMDAwMCwicmVhZCI6MzAwMDAwMDAwMDAwLCJ1cGRhdGUiOjIxNjAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", "dependencies": [ + "azurerm_dns_zone.api", "azurerm_dns_zone.frontend", "azurerm_frontdoor.frontdoor", + "azurerm_kubernetes_cluster.api", "azurerm_resource_group.squaremarket-group", "azurerm_storage_account.frontend-account" ] @@ -340,7 +473,7 @@ "schema_version": 2, "attributes": { "aci_connector_linux": [], - "api_server_authorized_ip_ranges": null, + "api_server_authorized_ip_ranges": [], "auto_scaler_profile": [], "automatic_channel_upgrade": "", "azure_active_directory_role_based_access_control": [], @@ -361,7 +494,7 @@ "node_count": 2, "node_labels": {}, "node_public_ip_prefix_id": "", - "node_taints": null, + "node_taints": [], "only_critical_addons_enabled": false, "orchestrator_version": "1.28.0", "os_disk_size_gb": 128, @@ -369,7 +502,7 @@ "os_sku": "Ubuntu", "pod_subnet_id": "", "proximity_placement_group_id": "", - "tags": null, + "tags": {}, "type": "VirtualMachineScaleSets", "ultra_ssd_enabled": false, "upgrade_settings": [ @@ -379,7 +512,7 @@ ], "vm_size": "Standard_DS2_v2", "vnet_subnet_id": "", - "zones": null + "zones": [] } ], "disk_encryption_set_id": "", @@ -393,7 +526,7 @@ "id": "/subscriptions/a210845b-25ae-4bcf-8aea-919dfd0a4467/resourceGroups/squaremarket-group/providers/Microsoft.ContainerService/managedClusters/squaremarket-aks", "identity": [ { - "identity_ids": null, + "identity_ids": [], "principal_id": "13076939-1fcc-42ff-bfef-cb44524d7462", "tenant_id": "0172c9f5-f568-42ac-9eb8-24ef84881faa", "type": "SystemAssigned" From 98ba4c6aa4e6126c6fec2e7436988fff16eb0134 Mon Sep 17 00:00:00 2001 From: RikThePixel Date: Sun, 14 Jan 2024 20:55:47 +0100 Subject: [PATCH 4/6] Make messaging stuff --- apps/frontend/src/apis/messages/chats.ts | 153 +++++++++++++++++- .../src/components/page/Container.tsx | 2 +- .../src/lib/auth/models/authenticated-user.ts | 36 +++-- .../src/pages/ads/[uid]/index.page.tsx | 24 ++- .../src/pages/messages/[id]/index.page.tsx | 116 ------------- .../[uid]/components/MessageBubble.tsx | 32 ++++ .../[uid]/components/MessageInput.tsx | 46 ++++++ .../src/pages/messages/[uid]/index.page.tsx | 123 ++++++++++++++ .../src/pages/messages/index.page.tsx | 118 ++++++++++---- .../responses/messages/ChatCreatedResponse.ts | 5 + .../responses/messages/ChatMessageResponse.ts | 14 ++ .../src/responses/messages/ChatResponse.ts | 25 +++ .../{ads => }/messages/ChatsResponse.ts | 10 +- apps/frontend/src/stores/useChats.ts | 66 ++++---- apps/gateway/src/index.ts | 7 +- apps/messages/.env.example | 9 -- .../messages/migrations/20231201215943_int.js | 4 +- apps/messages/src/configs/broker.ts | 33 +++- apps/messages/src/entities/Chat.ts | 2 +- apps/messages/src/index.ts | 8 +- apps/messages/src/messages/BaseMessage.ts | 38 +++++ .../src/messages/SendMessageMessage.ts | 19 +++ apps/messages/src/providers/di.ts | 28 +++- .../src/repositories/chat/ChatRespository.ts | 19 ++- .../repositories/chat/KnexChatRepository.ts | 80 ++++++++- .../message/KnexMessageRepository.ts | 17 ++ .../repositories/message/MessageRepository.ts | 11 ++ .../repositories/user/KnexUserRepository.ts | 22 ++- .../src/repositories/user/UserRepository.ts | 1 + .../src/requests/ChatMessageRequest.ts | 5 + .../messages/src/requests/StartChatRequest.ts | 5 + .../messages/src/requests/WebsocketRequest.ts | 5 + apps/messages/src/routes/v1/chats.ts | 103 ++++++++++-- apps/messages/src/services/ChatService.ts | 85 +++++++++- .../src/subscribers/BaseSubscription.ts | 12 +- .../src/subscribers/MessagesSubscription.ts | 36 +++++ .../src/subscribers/UsersSubscription.ts | 1 + 37 files changed, 1067 insertions(+), 253 deletions(-) delete mode 100644 apps/frontend/src/pages/messages/[id]/index.page.tsx create mode 100644 apps/frontend/src/pages/messages/[uid]/components/MessageBubble.tsx create mode 100644 apps/frontend/src/pages/messages/[uid]/components/MessageInput.tsx create mode 100644 apps/frontend/src/pages/messages/[uid]/index.page.tsx create mode 100644 apps/frontend/src/responses/messages/ChatCreatedResponse.ts create mode 100644 apps/frontend/src/responses/messages/ChatMessageResponse.ts create mode 100644 apps/frontend/src/responses/messages/ChatResponse.ts rename apps/frontend/src/responses/{ads => }/messages/ChatsResponse.ts (57%) create mode 100644 apps/messages/src/messages/BaseMessage.ts create mode 100644 apps/messages/src/messages/SendMessageMessage.ts create mode 100644 apps/messages/src/repositories/message/KnexMessageRepository.ts create mode 100644 apps/messages/src/repositories/message/MessageRepository.ts create mode 100644 apps/messages/src/requests/ChatMessageRequest.ts create mode 100644 apps/messages/src/requests/StartChatRequest.ts create mode 100644 apps/messages/src/requests/WebsocketRequest.ts create mode 100644 apps/messages/src/subscribers/MessagesSubscription.ts diff --git a/apps/frontend/src/apis/messages/chats.ts b/apps/frontend/src/apis/messages/chats.ts index 12c7702..0f1b5ef 100644 --- a/apps/frontend/src/apis/messages/chats.ts +++ b/apps/frontend/src/apis/messages/chats.ts @@ -1,12 +1,159 @@ import backend from '@/adapters/backend'; -import { chatsResponseSchema } from '@/responses/ads/messages/ChatsResponse'; +import { chatsResponseSchema } from '@/responses/messages/ChatsResponse'; +import { getToken } from '@/lib/auth'; +import { + ChatResponse, + chatResponseSchema, +} from '@/responses/messages/ChatResponse'; +import { + ChatMessageResponse, + chatMessageResponseSchema, +} from '@/responses/messages/ChatMessageResponse'; +import { chatCreatedResponse } from '@/responses/messages/ChatCreatedResponse'; export function startChat(userUid: string) { - return backend.post('v1/chats', { json: { user_uid: userUid } }); + return backend + .post('v1/chats', { json: { user: userUid } }) + .then(async (res) => chatCreatedResponse.parse(await res.json())); } export async function getChats(signal?: AbortSignal) { return await backend .get('v1/chats', { signal }) - .then((res) => chatsResponseSchema.parse(res.json())); + .then(async (res) => chatsResponseSchema.parse(await res.json())); +} + +export class ChatConnection { + constructor( + private target: EventTarget, + private socket: WebSocket, + ) {} + + addEventListener( + type: 'open', + callback: (event: OpenedEvent) => void | null, + options?: boolean | AddEventListenerOptions | undefined, + ): void; + addEventListener( + type: 'chat-message', + callback: (event: ChatMessageEvent) => void | null, + options?: boolean | AddEventListenerOptions | undefined, + ): void; + addEventListener( + type: 'chat-init', + callback: (event: ChatInitEvent) => void | null, + options?: boolean | AddEventListenerOptions | undefined, + ): void; + addEventListener( + type: 'close', + callback: (event: ClosedEvent) => void | null, + options?: boolean | AddEventListenerOptions | undefined, + ): void; + addEventListener( + type: string, + callback: (event: any) => void | null, + options?: boolean | AddEventListenerOptions | undefined, + ): void { + this.target.addEventListener(type, callback, options); + } + + removeEventListener( + type: 'open' | 'close', + callback: EventListenerOrEventListenerObject | null, + options?: boolean | EventListenerOptions | undefined, + ): void { + this.target.removeEventListener(type, callback, options); + } + + sendMessage(content: string) { + this.socket.send( + JSON.stringify({ + type: 'chat-message', + content: content, + }), + ); + } + + disconnect() { + this.socket.close(); + } +} + +class OpenedEvent extends Event { + constructor() { + super('open'); + } +} +class ClosedEvent extends Event { + constructor(public closedByClient: boolean) { + super('close'); + } +} + +class ChatInitEvent extends Event { + constructor(public data: ChatResponse) { + super('chat-init'); + } +} + +class ChatMessageEvent extends Event { + constructor(public data: ChatMessageResponse) { + super('chat-message'); + } +} + +const events = { + 'chat-message': (data: unknown) => + new ChatMessageEvent(chatMessageResponseSchema.parse(data)), + 'chat-init': (data: unknown) => + new ChatInitEvent(chatResponseSchema.parse(data)), +} as const; + +export async function connectToChat(uid: string, signal?: AbortSignal) { + const token = await getToken(); + if (!token) { + throw new Error('User must be authenticated to connect to this chat'); + } + + const url = new URL(import.meta.env.VITE_BACKEND_URL); + url.pathname = `v1/chats/${uid}`; + url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:'; + url.searchParams.set('token', token); + + const socket = new WebSocket(url); + const target = new EventTarget(); + + signal?.addEventListener('abort', function () { + socket.close(); + }); + + socket.onopen = function () { + if (signal?.aborted) return socket.close(); + target.dispatchEvent(new OpenedEvent()); + }; + + socket.onmessage = function (e) { + if (signal?.aborted) return socket.close(); + const data = JSON.parse(e.data.toString()) as unknown; + if ( + typeof data !== 'object' || + !data || + !('type' in data) || + typeof data.type !== 'string' + ) { + return; + } + const event = Object.keys(events).includes(data.type) + ? events[data.type as keyof typeof events] + : null; + + if (!event) return; + target.dispatchEvent(event(data)); + }; + + socket.onclose = function (e) { + target.dispatchEvent(new ClosedEvent(e.code === 1000)); + }; + + return new ChatConnection(target, socket); } diff --git a/apps/frontend/src/components/page/Container.tsx b/apps/frontend/src/components/page/Container.tsx index 4ce4313..d66239a 100644 --- a/apps/frontend/src/components/page/Container.tsx +++ b/apps/frontend/src/components/page/Container.tsx @@ -3,7 +3,7 @@ import { Container } from '@mantine/core'; type PageContainerProps = Parameters[0]; const PageContainer = (props: PageContainerProps) => ( - + ); export default PageContainer; diff --git a/apps/frontend/src/lib/auth/models/authenticated-user.ts b/apps/frontend/src/lib/auth/models/authenticated-user.ts index ce6c29b..26127d6 100644 --- a/apps/frontend/src/lib/auth/models/authenticated-user.ts +++ b/apps/frontend/src/lib/auth/models/authenticated-user.ts @@ -1,15 +1,21 @@ -import { User as Auth0User } from '@auth0/auth0-spa-js'; - -export default class AuthenticatedUser { - constructor( - public parent: TParent, - public displayName: string, - ) {} - - public static fromAuth0(user: Auth0User) { - return new AuthenticatedUser( - user, - user.name ?? user.nickname ?? 'unknown name', - ); - } -} +import { User as Auth0User } from '@auth0/auth0-spa-js'; + +export default class AuthenticatedUser { + constructor( + public parent: TParent, + public username: string, + public providerId: string, + ) {} + + public static fromAuth0(user: Auth0User) { + if (!user.sub) { + throw new Error('Malformed authenticated user'); + } + + return new AuthenticatedUser( + user, + user.name ?? user.nickname ?? 'unknown name', + user.sub, + ); + } +} diff --git a/apps/frontend/src/pages/ads/[uid]/index.page.tsx b/apps/frontend/src/pages/ads/[uid]/index.page.tsx index 8bd0a4e..d53d83a 100644 --- a/apps/frontend/src/pages/ads/[uid]/index.page.tsx +++ b/apps/frontend/src/pages/ads/[uid]/index.page.tsx @@ -2,7 +2,9 @@ import { getImageUrl } from '@/apis/ads/images'; import PageContainer from '@/components/page/Container'; import useCurrencyFormatter from '@/hooks/useCurrencyFormatter'; import useTypedParams from '@/hooks/useTypedParams'; +import useAuth from '@/lib/auth/stores/useAuth'; import useAdvertisements from '@/stores/useAdvertisements'; +import useChats from '@/stores/useChats'; import { Carousel } from '@mantine/carousel'; import { Image, @@ -14,9 +16,11 @@ import { Group, SimpleGrid, Skeleton, + Button, } from '@mantine/core'; import { useEffect } from 'react'; -import { MdPerson } from 'react-icons/md'; +import { MdMessage, MdPerson } from 'react-icons/md'; +import { useLocation } from 'wouter'; import { z } from 'zod'; const PARAMS_SCHEMA = z.object({ @@ -26,6 +30,10 @@ const PARAMS_SCHEMA = z.object({ export default function AdPage() { const { uid } = useTypedParams(PARAMS_SCHEMA) ?? {}; const { advertisement, getAdvertisement } = useAdvertisements(); + const { startChat } = useChats(); + const { user } = useAuth(); + const [, setLocation] = useLocation(); + const currencyFormatter = useCurrencyFormatter( advertisement.unwrapValue()?.currency ?? 'EUR', ); @@ -75,6 +83,20 @@ export default function AdPage() { {advertisement.user.name} + {user && advertisement.user.uid !== user.providerId && ( + + )} diff --git a/apps/frontend/src/pages/messages/[id]/index.page.tsx b/apps/frontend/src/pages/messages/[id]/index.page.tsx deleted file mode 100644 index 28330f8..0000000 --- a/apps/frontend/src/pages/messages/[id]/index.page.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import PageContainer from '@/components/page/Container'; -import { - Alert, - Text, - Group, - Stack, - Divider, - ActionIcon, - Textarea, -} from '@mantine/core'; -import { MdChevronLeft, MdSend } from 'react-icons/md'; -import { Link } from 'wouter'; - -interface Message { - uid: string; - user: { - uid: string; - name: string; - }; - text: string; - createdAt: Date; -} - -interface Conversation { - user: { - uid: string; - name: string; - }; - messages: Message[]; -} - -function MessageBubble({ - message, - fromSelf, -}: { - message: Message; - fromSelf: boolean; -}) { - return ( - - {message.user.name} - {message.createdAt.toLocaleString()} - - } - > - {message.text} - - ); -} - -export default function MessagePage() { - const conversation: Conversation = { - user: { - uid: '2', - name: 'John', - }, - messages: Array(20) - .fill(1) - .map((_, idx) => ({ - uid: String(idx), - user: { - uid: idx % 2 === 1 ? '1' : '2', - name: idx % 2 === 1 ? 'Rik' : 'John', - }, - text: 'Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.', - createdAt: new Date(), - })), - }; - - return ( - - - - - - - - - {conversation.user.name} - - - - - {conversation.messages.map((message) => ( - - ))} - - - - -