From 7296f2dfa532f7bb7fdd40e343e43e1cff46adb7 Mon Sep 17 00:00:00 2001 From: Nico Boehr Date: Sun, 28 Dec 2025 21:01:51 +0100 Subject: [PATCH 1/2] add format check --- .github/workflows/prettier.yaml | 14 ++++++++++++++ server/package.json | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/prettier.yaml diff --git a/.github/workflows/prettier.yaml b/.github/workflows/prettier.yaml new file mode 100644 index 000000000..99cef609f --- /dev/null +++ b/.github/workflows/prettier.yaml @@ -0,0 +1,14 @@ +name: 'Check code format' +on: + pull_request: +defaults: + run: + working-directory: ./server +jobs: + check-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 # checkout the repo + - name: Install dependencies # install dependencies + run: yarn install --frozen-lockfile + - run: yarn format-check diff --git a/server/package.json b/server/package.json index d6380d484..b04d4f618 100644 --- a/server/package.json +++ b/server/package.json @@ -8,7 +8,8 @@ "private": true, "scripts": { "build": "nest build", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "format": "prettier --write \"src/**/*.ts\"", + "format-check": "prettier --check \"src/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", From c014b5e1c1b60cbed0f28f8d0ab62f6a0b569b38 Mon Sep 17 00:00:00 2001 From: Nico Boehr Date: Sun, 28 Dec 2025 21:01:56 +0100 Subject: [PATCH 2/2] format the code --- server/src/addons/addons.service.ts | 4 +- server/src/addons/plugins/clickhouse.ts | 4 +- server/src/addons/plugins/cloudflare.ts | 4 +- server/src/addons/plugins/cloudnativePG.ts | 56 ++--- server/src/addons/plugins/cockroachDB.ts | 4 +- server/src/addons/plugins/elasticsearch.ts | 200 +++++++++--------- server/src/addons/plugins/kuberoCouchDB.ts | 4 +- .../src/addons/plugins/kuberoElasticsearch.ts | 6 +- server/src/addons/plugins/kuberoKafka.ts | 6 +- server/src/addons/plugins/kuberoMail.ts | 4 +- server/src/addons/plugins/kuberoMemcached.ts | 6 +- server/src/addons/plugins/kuberoMongoDB.ts | 6 +- server/src/addons/plugins/kuberoMysql.ts | 7 +- server/src/addons/plugins/kuberoPostgresql.ts | 6 +- server/src/addons/plugins/kuberoRabbitMQ.ts | 6 +- server/src/addons/plugins/kuberoRedis.ts | 6 +- .../addons/plugins/kuberoaddonsMemcached.ts | 26 +-- .../src/addons/plugins/kuberoaddonsMongodb.ts | 47 ++-- .../src/addons/plugins/kuberoaddonsMysql.ts | 46 ++-- .../addons/plugins/kuberoaddonsPostgres.ts | 42 ++-- .../addons/plugins/kuberoaddonsRabbitmq.ts | 54 +++-- .../src/addons/plugins/kuberoaddonsRedis.ts | 43 ++-- server/src/addons/plugins/minio.ts | 4 +- server/src/addons/plugins/mongoDB.ts | 4 +- server/src/addons/plugins/plugin.ts | 2 - server/src/addons/plugins/postgresCluster.ts | 4 +- server/src/addons/plugins/redis.ts | 4 +- server/src/addons/plugins/redisCluster.ts | 4 +- server/src/apps/app/app.ts | 8 +- server/src/apps/apps.controller.spec.ts | 6 +- server/src/apps/apps.controller.ts | 36 +++- server/src/apps/apps.service.spec.ts | 119 ++++++++--- server/src/apps/apps.service.ts | 31 ++- server/src/auth/auth.controller.spec.ts | 4 +- server/src/auth/auth.service.spec.ts | 4 +- server/src/auth/auth.service.ts | 2 +- server/src/auth/permissions.decorator.ts | 3 +- server/src/auth/permissions.guard.ts | 22 +- server/src/cli/cli.module.ts | 5 +- .../cli/commands/reset-admin.command.spec.ts | 15 +- .../src/cli/commands/reset-admin.command.ts | 9 +- server/src/common/guards/readonly.guard.ts | 2 +- server/src/config/config.controller.ts | 12 +- server/src/database/database.service.ts | 138 +++++++----- server/src/database/podsizes.seed.ts | 2 +- server/src/database/runpacks.seed.ts | 2 +- .../deployments.controller.spec.ts | 24 ++- .../src/deployments/deployments.controller.ts | 15 +- .../deployments/deployments.service.spec.ts | 22 +- server/src/deployments/deployments.service.ts | 2 +- server/src/groups/groups.service.spec.ts | 5 +- server/src/logs/logs.controller.ts | 15 +- server/src/logs/logs.service.ts | 2 +- .../notifications-db.service.spec.ts | 36 +++- .../notifications/notifications-db.service.ts | 59 ++++-- .../notifications.controller.spec.ts | 145 ++++++++++--- .../notifications/notifications.controller.ts | 58 +++-- .../src/notifications/notifications.module.ts | 7 +- .../notifications.service.spec.ts | 11 +- .../notifications/notifications.service.ts | 15 +- .../pipelines/pipelines.controller.spec.ts | 5 +- server/src/pipelines/pipelines.controller.ts | 11 +- .../src/pipelines/pipelines.service.spec.ts | 10 +- server/src/pipelines/pipelines.service.ts | 21 +- server/src/repo/git/bitbucket.ts | 2 +- server/src/repo/git/gitea.ts | 2 +- server/src/repo/git/github.ts | 2 +- server/src/repo/git/gitlab.ts | 2 +- server/src/repo/git/gogs.ts | 2 +- server/src/repo/repo.service.spec.ts | 14 +- server/src/repo/repo.service.ts | 4 +- server/src/roles/roles.controller.ts | 5 +- server/src/roles/roles.service.ts | 24 ++- server/src/security/security.controller.ts | 16 +- server/src/security/security.service.spec.ts | 31 ++- server/src/security/security.service.ts | 33 ++- server/src/token/token.controller.spec.ts | 21 +- server/src/token/token.controller.ts | 2 +- server/src/token/token.module.ts | 8 +- server/src/token/token.service.spec.ts | 22 +- server/src/token/token.service.ts | 6 +- server/src/users/users.controller.ts | 23 +- server/src/users/users.service.spec.ts | 123 ++++++++--- server/src/users/users.service.ts | 41 ++-- 84 files changed, 1212 insertions(+), 663 deletions(-) diff --git a/server/src/addons/addons.service.ts b/server/src/addons/addons.service.ts index e4d024729..56e57a029 100644 --- a/server/src/addons/addons.service.ts +++ b/server/src/addons/addons.service.ts @@ -42,13 +42,12 @@ export class AddonsService { // Load all Custom Resource Definitions to get the list of installed operators this.CRDList = await this.kubectl.getCustomresources(); - const kuberoAddonPostgres = new KuberoAddonPostgres(this.CRDList); this.addonsList.push(kuberoAddonPostgres); const kuberoAddonRedis = new KuberoAddonRedis(this.CRDList); this.addonsList.push(kuberoAddonRedis); - + const kuberoAddonMysql = new KuberoAddonMysql(this.CRDList); this.addonsList.push(kuberoAddonMysql); @@ -120,7 +119,6 @@ export class AddonsService { const kuberoRabbitMQ = new KuberoRabbitMQ(this.CRDList); this.addonsList.push(kuberoRabbitMQ); - } public async getAddonsList(): Promise { diff --git a/server/src/addons/plugins/clickhouse.ts b/server/src/addons/plugins/clickhouse.ts index 9598cb49f..c5f4b77b3 100644 --- a/server/src/addons/plugins/clickhouse.ts +++ b/server/src/addons/plugins/clickhouse.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class ClickHouseInstallation extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/cloudflare.ts b/server/src/addons/plugins/cloudflare.ts index c524820d3..25f8c89e2 100644 --- a/server/src/addons/plugins/cloudflare.ts +++ b/server/src/addons/plugins/cloudflare.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class Tunnel extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/cloudnativePG.ts b/server/src/addons/plugins/cloudnativePG.ts index b87ca4e23..a26329c13 100644 --- a/server/src/addons/plugins/cloudnativePG.ts +++ b/server/src/addons/plugins/cloudnativePG.ts @@ -1,6 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class Cluster extends Plugin implements IPlugin { @@ -9,7 +8,8 @@ export class Cluster extends Plugin implements IPlugin { public description: string = 'CloudNativePG is the Kubernetes operator that covers the full lifecycle of a highly available PostgreSQL database cluster with a primary/standby architecture, using native streaming replication.'; public icon = '/img/addons/pgsql.svg'; - public install: string = 'kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.27/releases/cnpg-1.27.0.yaml'; + public install: string = + 'kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.27/releases/cnpg-1.27.0.yaml'; public url = 'https://artifacthub.io/packages/olm/community-operators/kubero-operator'; public docs = [ @@ -35,7 +35,11 @@ export class Cluster extends Plugin implements IPlugin { 'CloudnativePG.spec.image.tag': { type: 'combobox', label: 'Version/Tag', - options: ['ghcr.io/cloudnative-pg/postgresql:17-minimal-bookworm', 'ghcr.io/cloudnative-pg/postgresql:16-minimal-bookworm', 'ghcr.io/cloudnative-pg/postgresql:15-minimal-bookworm'], // TODO - load this dynamically + options: [ + 'ghcr.io/cloudnative-pg/postgresql:17-minimal-bookworm', + 'ghcr.io/cloudnative-pg/postgresql:16-minimal-bookworm', + 'ghcr.io/cloudnative-pg/postgresql:15-minimal-bookworm', + ], // TODO - load this dynamically name: 'spec.image.tag', required: true, default: 'ghcr.io/cloudnative-pg/postgresql:17-minimal-bookworm', @@ -104,18 +108,18 @@ export class Cluster extends Plugin implements IPlugin { public resourceDefinitions: object = { CloudnativePG: { - apiVersion: "postgresql.cnpg.io/v1", - kind: "Cluster", + apiVersion: 'postgresql.cnpg.io/v1', + kind: 'Cluster', metadata: { - name: "cluster-example-full" + name: 'cluster-example-full', }, spec: { - description: "Kubero generated postgresql cluster", - imageName: "ghcr.io/cloudnative-pg/postgresql:17.5", + description: 'Kubero generated postgresql cluster', + imageName: 'ghcr.io/cloudnative-pg/postgresql:17.5', instances: 3, startDelay: 300, stopDelay: 300, - primaryUpdateStrategy: "unsupervised", + primaryUpdateStrategy: 'unsupervised', /* postgresql: { parameters: { @@ -135,23 +139,23 @@ export class Cluster extends Plugin implements IPlugin { */ bootstrap: { initdb: { - database: "app", - owner: "app", + database: 'app', + owner: 'app', secret: { - name: "cluster-app-user" - } - } + name: 'cluster-app-user', + }, + }, }, enableSuperuserAccess: true, superuserSecret: { - name: "cluster-superuser" + name: 'cluster-superuser', }, storage: { - storageClass: "standard", - size: "1Gi" + storageClass: 'standard', + size: '1Gi', }, - backup: null - } + backup: null, + }, }, superuserSecret: { apiVersion: 'v1', @@ -161,8 +165,8 @@ export class Cluster extends Plugin implements IPlugin { }, type: 'Opaque', stringData: { - 'username': 'postgres', - 'password': 'changeme', + username: 'postgres', + password: 'changeme', }, }, appUserSecret: { @@ -173,11 +177,11 @@ export class Cluster extends Plugin implements IPlugin { }, type: 'Opaque', stringData: { - 'username': 'app', - 'password': 'app', + username: 'app', + password: 'app', }, }, - } + }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/cockroachDB.ts b/server/src/addons/plugins/cockroachDB.ts index 2372e2e37..0d11eaa6a 100644 --- a/server/src/addons/plugins/cockroachDB.ts +++ b/server/src/addons/plugins/cockroachDB.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class Cockroachdb extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/elasticsearch.ts b/server/src/addons/plugins/elasticsearch.ts index f9d27524e..4aaa504af 100644 --- a/server/src/addons/plugins/elasticsearch.ts +++ b/server/src/addons/plugins/elasticsearch.ts @@ -1,6 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class Elasticsearch extends Plugin implements IPlugin { @@ -9,7 +8,8 @@ export class Elasticsearch extends Plugin implements IPlugin { public description: string = 'Elasticsearch is a distributed, RESTful search and analytics engine capable of addressing a growing number of use cases.'; public icon = '/img/addons/elasticsearch.svg'; - public install: string = 'kubectl create -f https://download.elastic.co/downloads/eck/3.1.0/crds.yaml && kubectl apply -f https://download.elastic.co/downloads/eck/3.1.0/operator.yaml'; + public install: string = + 'kubectl create -f https://download.elastic.co/downloads/eck/3.1.0/crds.yaml && kubectl apply -f https://download.elastic.co/downloads/eck/3.1.0/operator.yaml'; public url = 'https://artifacthub.io/packages/olm/community-operators/elastic-cloud-eck'; public docs = [ @@ -49,22 +49,24 @@ export class Elasticsearch extends Plugin implements IPlugin { required: true, description: 'Number of Elasticsearch instances to create', }, - 'Elasticsearch.spec.nodeSets[0].volumeClaimTemplates[0].spec.storageClassName': { - type: 'select-storageclass', - label: 'Master Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.nodeSets[0].volumeClaimTemplates[0].spec.storageClassName', - default: 'default', - required: true, - }, - 'Elasticsearch.spec.nodeSets[0].volumeClaimTemplates[0].spec.resources.requests.storage': { - type: 'text', - label: 'Master Storage Size*', - name: 'spec.nodeSets[0].volumeClaimTemplates[0].spec.resources.requests.storage', - default: '10Gi', - required: true, - description: 'Size of the storage', - }, + 'Elasticsearch.spec.nodeSets[0].volumeClaimTemplates[0].spec.storageClassName': + { + type: 'select-storageclass', + label: 'Master Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.nodeSets[0].volumeClaimTemplates[0].spec.storageClassName', + default: 'default', + required: true, + }, + 'Elasticsearch.spec.nodeSets[0].volumeClaimTemplates[0].spec.resources.requests.storage': + { + type: 'text', + label: 'Master Storage Size*', + name: 'spec.nodeSets[0].volumeClaimTemplates[0].spec.resources.requests.storage', + default: '10Gi', + required: true, + description: 'Size of the storage', + }, 'Elasticsearch.spec.nodeSets[1].count': { type: 'number', label: 'Data Node Instances', @@ -73,22 +75,24 @@ export class Elasticsearch extends Plugin implements IPlugin { required: true, description: 'Number of Elasticsearch instances to create', }, - 'Elasticsearch.spec.nodeSets[1].volumeClaimTemplates[0].spec.storageClassName': { - type: 'select-storageclass', - label: 'Data Node Storage Class', - // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], - name: 'spec.nodeSets[1].volumeClaimTemplates[0].spec.storageClassName', - default: 'default', - required: true, - }, - 'Elasticsearch.spec.nodeSets[1].volumeClaimTemplates[0].spec.resources.requests.storage': { - type: 'text', - label: 'Data Node Storage Size*', - name: 'spec.nodeSets[1].volumeClaimTemplates[0].spec.resources.requests.storage', - default: '10Gi', - required: true, - description: 'Size of the storage', - }, + 'Elasticsearch.spec.nodeSets[1].volumeClaimTemplates[0].spec.storageClassName': + { + type: 'select-storageclass', + label: 'Data Node Storage Class', + // options: ['default', 'local-path', 'nfs-client', 'rook-ceph-block'], + name: 'spec.nodeSets[1].volumeClaimTemplates[0].spec.storageClassName', + default: 'default', + required: true, + }, + 'Elasticsearch.spec.nodeSets[1].volumeClaimTemplates[0].spec.resources.requests.storage': + { + type: 'text', + label: 'Data Node Storage Size*', + name: 'spec.nodeSets[1].volumeClaimTemplates[0].spec.resources.requests.storage', + default: '10Gi', + required: true, + description: 'Size of the storage', + }, }; public env: any[] = []; @@ -96,128 +100,118 @@ export class Elasticsearch extends Plugin implements IPlugin { // https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/nodes-orchestration public resourceDefinitions: object = { Elasticsearch: { - apiVersion: "elasticsearch.k8s.elastic.co/v1", - kind: "Elasticsearch", + apiVersion: 'elasticsearch.k8s.elastic.co/v1', + kind: 'Elasticsearch', metadata: { - name: "elasticsearch-sample" + name: 'elasticsearch-sample', }, spec: { - version: "9.1.0", + version: '9.1.0', nodeSets: [ { - name: "master-nodes", + name: 'master-nodes', count: 3, config: { node: { - roles: [ - "master" - ], + roles: ['master'], attr: { - attr_name: "attr_value" + attr_name: 'attr_value', }, store: { - allow_mmap: false - } - } + allow_mmap: false, + }, + }, }, podTemplate: { spec: { containers: [ { - name: "elasticsearch", + name: 'elasticsearch', resources: { requests: { - memory: "4Gi", - cpu: 1 + memory: '4Gi', + cpu: 1, }, limits: { - memory: "4Gi", - cpu: 2 - } - } - } - ] - } + memory: '4Gi', + cpu: 2, + }, + }, + }, + ], + }, }, volumeClaimTemplates: [ { metadata: { - name: "elasticsearch-data" + name: 'elasticsearch-data', }, spec: { - accessModes: [ - "ReadWriteOnce" - ], + accessModes: ['ReadWriteOnce'], resources: { requests: { - storage: "8Gi" - } + storage: '8Gi', + }, }, - storageClassName: "default" - } - } - ] + storageClassName: 'default', + }, + }, + ], }, { - name: "data-nodes", + name: 'data-nodes', count: 3, config: { node: { - roles: [ - "data", - "ingest", - "ml" - ], + roles: ['data', 'ingest', 'ml'], attr: { - attr_name: "attr_value" + attr_name: 'attr_value', }, store: { - allow_mmap: false - } - } + allow_mmap: false, + }, + }, }, podTemplate: { spec: { containers: [ { - name: "elasticsearch", + name: 'elasticsearch', resources: { requests: { - memory: "4Gi", - cpu: 1 + memory: '4Gi', + cpu: 1, }, limits: { - memory: "4Gi", - cpu: 2 - } - } - } - ] - } + memory: '4Gi', + cpu: 2, + }, + }, + }, + ], + }, }, volumeClaimTemplates: [ { metadata: { - name: "elasticsearch-data" + name: 'elasticsearch-data', }, spec: { - accessModes: [ - "ReadWriteOnce" - ], + accessModes: ['ReadWriteOnce'], resources: { requests: { - storage: "8Gi" - } + storage: '8Gi', + }, }, - storageClassName: "default" - } - } - ] - } - ] - } - } - } + storageClassName: 'default', + }, + }, + ], + }, + ], + }, + }, + }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/kuberoCouchDB.ts b/server/src/addons/plugins/kuberoCouchDB.ts index 51d0129e6..3043635be 100644 --- a/server/src/addons/plugins/kuberoCouchDB.ts +++ b/server/src/addons/plugins/kuberoCouchDB.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoCouchDB extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/kuberoElasticsearch.ts b/server/src/addons/plugins/kuberoElasticsearch.ts index 7238b4676..647fa16aa 100644 --- a/server/src/addons/plugins/kuberoElasticsearch.ts +++ b/server/src/addons/plugins/kuberoElasticsearch.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoElasticsearch extends Plugin implements IPlugin { @@ -18,7 +18,7 @@ export class KuberoElasticsearch extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoElasticsearch.metadata.name': { diff --git a/server/src/addons/plugins/kuberoKafka.ts b/server/src/addons/plugins/kuberoKafka.ts index d33d5b859..571a9d1a8 100644 --- a/server/src/addons/plugins/kuberoKafka.ts +++ b/server/src/addons/plugins/kuberoKafka.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoKafka extends Plugin implements IPlugin { @@ -18,7 +18,7 @@ export class KuberoKafka extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoKafka.metadata.name': { diff --git a/server/src/addons/plugins/kuberoMail.ts b/server/src/addons/plugins/kuberoMail.ts index 2650c810c..ca4699633 100644 --- a/server/src/addons/plugins/kuberoMail.ts +++ b/server/src/addons/plugins/kuberoMail.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoMail extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/kuberoMemcached.ts b/server/src/addons/plugins/kuberoMemcached.ts index c2312c7c2..65c7e826d 100644 --- a/server/src/addons/plugins/kuberoMemcached.ts +++ b/server/src/addons/plugins/kuberoMemcached.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoMemcached extends Plugin implements IPlugin { @@ -18,7 +18,7 @@ export class KuberoMemcached extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoMemcached.metadata.name': { diff --git a/server/src/addons/plugins/kuberoMongoDB.ts b/server/src/addons/plugins/kuberoMongoDB.ts index 5ac63a295..4c6d7b283 100644 --- a/server/src/addons/plugins/kuberoMongoDB.ts +++ b/server/src/addons/plugins/kuberoMongoDB.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoMongoDB extends Plugin implements IPlugin { @@ -18,7 +18,7 @@ export class KuberoMongoDB extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoMongoDB.metadata.name': { diff --git a/server/src/addons/plugins/kuberoMysql.ts b/server/src/addons/plugins/kuberoMysql.ts index ca1805c15..098991236 100644 --- a/server/src/addons/plugins/kuberoMysql.ts +++ b/server/src/addons/plugins/kuberoMysql.ts @@ -1,6 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoMysql extends Plugin implements IPlugin { @@ -19,7 +18,7 @@ export class KuberoMysql extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoMysql.metadata.name': { diff --git a/server/src/addons/plugins/kuberoPostgresql.ts b/server/src/addons/plugins/kuberoPostgresql.ts index 83ce9ce12..078ec1119 100644 --- a/server/src/addons/plugins/kuberoPostgresql.ts +++ b/server/src/addons/plugins/kuberoPostgresql.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoPostgresql extends Plugin implements IPlugin { @@ -18,7 +18,7 @@ export class KuberoPostgresql extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoPostgresql.metadata.name': { diff --git a/server/src/addons/plugins/kuberoRabbitMQ.ts b/server/src/addons/plugins/kuberoRabbitMQ.ts index 840efe85b..1cd017ee1 100644 --- a/server/src/addons/plugins/kuberoRabbitMQ.ts +++ b/server/src/addons/plugins/kuberoRabbitMQ.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoRabbitMQ extends Plugin implements IPlugin { @@ -18,7 +18,7 @@ export class KuberoRabbitMQ extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoRabbitMQ.metadata.name': { diff --git a/server/src/addons/plugins/kuberoRedis.ts b/server/src/addons/plugins/kuberoRedis.ts index 8f16f02da..c0734c90b 100644 --- a/server/src/addons/plugins/kuberoRedis.ts +++ b/server/src/addons/plugins/kuberoRedis.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoRedis extends Plugin implements IPlugin { @@ -18,7 +18,7 @@ export class KuberoRedis extends Plugin implements IPlugin { public artifact_url = 'https://artifacthub.io/api/v1/packages/olm/kubero/kubero-operator'; public beta: boolean = false; - public deprecated: boolean = true + public deprecated: boolean = true; public formfields: { [key: string]: IPluginFormFields } = { 'KuberoRedis.metadata.name': { diff --git a/server/src/addons/plugins/kuberoaddonsMemcached.ts b/server/src/addons/plugins/kuberoaddonsMemcached.ts index 0781fcdd9..dac0f8342 100644 --- a/server/src/addons/plugins/kuberoaddonsMemcached.ts +++ b/server/src/addons/plugins/kuberoaddonsMemcached.ts @@ -1,13 +1,13 @@ import { KuberoRabbitMQ } from './kuberoRabbitMQ'; -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoAddonMemcached extends Plugin implements IPlugin { public id: string = 'kubero-operator'; //same as operator name public displayName = 'Memcached'; - public description = 'Memcached is a high-performance, distributed memory object caching system, intended for use in speeding up dynamic web applications by alleviating database load.'; + public description = + 'Memcached is a high-performance, distributed memory object caching system, intended for use in speeding up dynamic web applications by alleviating database load.'; public icon = '/img/addons/memcached.svg'; public install: string = ''; public url = @@ -71,27 +71,27 @@ export class KuberoAddonMemcached extends Plugin implements IPlugin { public resourceDefinitions: object = { KuberoAddonMemcached: { - apiVersion: "application.kubero.dev/v1alpha1", - kind: "KuberoAddonMemcached", + apiVersion: 'application.kubero.dev/v1alpha1', + kind: 'KuberoAddonMemcached', metadata: { - name: "kuberoaddonmemcached-sample" + name: 'kuberoaddonmemcached-sample', }, spec: { memcached: { image: { - tag: "1.6.39" + tag: '1.6.39', }, replicaCount: 1, config: { memoryLimit: 64, maxConnections: 1024, extraArgs: [], - verbosity: 0 + verbosity: 0, }, - resources: {} - } - } - } + resources: {}, + }, + }, + }, }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/kuberoaddonsMongodb.ts b/server/src/addons/plugins/kuberoaddonsMongodb.ts index 6bcc3761f..c010f553d 100644 --- a/server/src/addons/plugins/kuberoaddonsMongodb.ts +++ b/server/src/addons/plugins/kuberoaddonsMongodb.ts @@ -1,6 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoAddonMongodb extends Plugin implements IPlugin { @@ -120,49 +119,47 @@ export class KuberoAddonMongodb extends Plugin implements IPlugin { public resourceDefinitions: object = { KuberoAddonMongodb: { - apiVersion: "application.kubero.dev/v1alpha1", - kind: "KuberoAddonMongodb", + apiVersion: 'application.kubero.dev/v1alpha1', + kind: 'KuberoAddonMongodb', metadata: { - name: "mongodb" + name: 'mongodb', }, spec: { mongodb: { image: { - tag: "" + tag: '', }, resources: {}, replicaSet: { enabled: false, - name: "repl", - clusterDomain: "cluster.local", + name: 'repl', + clusterDomain: 'cluster.local', secondaries: 2, hiddenSecondaries: { instances: 0, - volumeName: "mongodb-hidden-volume" - } + volumeName: 'mongodb-hidden-volume', + }, }, settings: { rootUsername: null, - rootPassword: null + rootPassword: null, }, userDatabase: { - name: "kubero_database", - user: "kubero_user", - password: "kubero_password" + name: 'kubero_database', + user: 'kubero_user', + password: 'kubero_password', }, storage: { - volumeName: "mongodb-volume", + volumeName: 'mongodb-volume', requestedSize: null, className: null, - accessModes: [ - "ReadWriteOnce" - ], - keepPvc: false - } - } - } - } - } + accessModes: ['ReadWriteOnce'], + keepPvc: false, + }, + }, + }, + }, + }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/kuberoaddonsMysql.ts b/server/src/addons/plugins/kuberoaddonsMysql.ts index 4e719e760..2270f065f 100644 --- a/server/src/addons/plugins/kuberoaddonsMysql.ts +++ b/server/src/addons/plugins/kuberoaddonsMysql.ts @@ -1,12 +1,12 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoAddonMysql extends Plugin implements IPlugin { public id: string = 'kubero-operator'; //same as operator name public displayName = 'MySQL'; - public description = 'MySQL is a fast, reliable, scalable, and easy to use open source relational database system. Designed to handle mission-critical, heavy-load production applications.' + public description = + 'MySQL is a fast, reliable, scalable, and easy to use open source relational database system. Designed to handle mission-critical, heavy-load production applications.'; public icon = '/img/addons/mysql.svg'; public install: string = ''; public url = @@ -94,47 +94,45 @@ export class KuberoAddonMysql extends Plugin implements IPlugin { public resourceDefinitions: object = { KuberoAddonMysql: { - apiVersion: "application.kubero.dev/v1alpha1", - kind: "KuberoAddonMysql", + apiVersion: 'application.kubero.dev/v1alpha1', + kind: 'KuberoAddonMysql', metadata: { - name: "mysql" + name: 'mysql', }, spec: { mysql: { image: { - tag: "" + tag: '', }, resources: {}, useDeployment: true, settings: { rootPassword: { - value: "postgres" - } + value: 'postgres', + }, }, userDatabase: { name: { - value: "kubero_database" + value: 'kubero_database', }, user: { - value: "kubero_user" + value: 'kubero_user', }, password: { - value: "kubero_password" - } + value: 'kubero_password', + }, }, storage: { - volumeName: "postgres-data", - requestedSize: "1Gi", + volumeName: 'postgres-data', + requestedSize: '1Gi', className: null, - accessModes: [ - "ReadWriteOnce" - ], - keepPvc: false - } - } - } + accessModes: ['ReadWriteOnce'], + keepPvc: false, + }, + }, + }, }, - } + }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/kuberoaddonsPostgres.ts b/server/src/addons/plugins/kuberoaddonsPostgres.ts index 5c96f2890..654dd5548 100644 --- a/server/src/addons/plugins/kuberoaddonsPostgres.ts +++ b/server/src/addons/plugins/kuberoaddonsPostgres.ts @@ -1,12 +1,12 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoAddonPostgres extends Plugin implements IPlugin { public id: string = 'kubero-operator'; //same as operator name public displayName = 'PostgreSQL'; - public description = 'PostgreSQL (Postgres) is an open source object-relational database known for reliability and data integrity. ACID-compliant, it supports foreign keys, joins, views, triggers and stored procedures.'; + public description = + 'PostgreSQL (Postgres) is an open source object-relational database known for reliability and data integrity. ACID-compliant, it supports foreign keys, joins, views, triggers and stored procedures.'; public icon = '/img/addons/pgsql.svg'; public install: string = ''; public url = @@ -118,37 +118,35 @@ export class KuberoAddonPostgres extends Plugin implements IPlugin { public resourceDefinitions: object = { KuberoAddonPostgres: { - apiVersion: "application.kubero.dev/v1alpha1", - kind: "KuberoAddonPostgres", + apiVersion: 'application.kubero.dev/v1alpha1', + kind: 'KuberoAddonPostgres', metadata: { - name: "postgres" + name: 'postgres', }, spec: { postgres: { image: { - tag: "17.6" + tag: '17.6', }, replicaCount: 1, auth: { enablePostgresUser: true, - postgresPassword: "", - username: "", - password: "", - database: "" + postgresPassword: '', + username: '', + password: '', + database: '', }, resources: {}, persistence: { enabled: true, - storageClass: "", - size: "8Gi", - accessModes: [ - "ReadWriteOnce" - ], - } - } - } - } - } + storageClass: '', + size: '8Gi', + accessModes: ['ReadWriteOnce'], + }, + }, + }, + }, + }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/kuberoaddonsRabbitmq.ts b/server/src/addons/plugins/kuberoaddonsRabbitmq.ts index 0bdd7f158..0314c9dcb 100644 --- a/server/src/addons/plugins/kuberoaddonsRabbitmq.ts +++ b/server/src/addons/plugins/kuberoaddonsRabbitmq.ts @@ -1,12 +1,12 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoAddonRabbitmq extends Plugin implements IPlugin { public id: string = 'kubero-operator'; //same as operator name public displayName = 'RabbitMQ'; - public description = 'RabbitMQ is an open source general-purpose message broker that is designed for consistent, highly-available messaging scenarios (both synchronous and asynchronous).'; + public description = + 'RabbitMQ is an open source general-purpose message broker that is designed for consistent, highly-available messaging scenarios (both synchronous and asynchronous).'; public icon = '/img/addons/rabbitmq.svg'; public install: string = ''; public url = @@ -86,63 +86,61 @@ export class KuberoAddonRabbitmq extends Plugin implements IPlugin { public resourceDefinitions: object = { KuberoAddonRabbitmq: { - apiVersion: "application.kubero.dev/v1alpha1", - kind: "KuberoAddonRabbitmq", + apiVersion: 'application.kubero.dev/v1alpha1', + kind: 'KuberoAddonRabbitmq', metadata: { - name: "rabbitmq" + name: 'rabbitmq', }, spec: { rabbitmq: { image: { - tag: "" + tag: '', }, replicaCount: 1, serviceMonitor: { - enabled: false + enabled: false, }, revisionHistoryLimit: null, - clusterDomain: "cluster.local", + clusterDomain: 'cluster.local', plugins: [], authentication: { user: { - value: "kubero_user" + value: 'kubero_user', }, password: { - value: "kubero_password" + value: 'kubero_password', }, erlangCookie: { - value: "kubero_erlang_cookie" - } + value: 'kubero_erlang_cookie', + }, }, options: { memoryHighWatermark: { enabled: false, - type: "relative", + type: 'relative', value: 0.4, - pagingRatio: null + pagingRatio: null, }, memory: { totalAvailableOverrideValue: null, - calculationStrategy: null - } + calculationStrategy: null, + }, }, managementPlugin: { - enabled: false + enabled: false, }, prometheusPlugin: { - enabled: true + enabled: true, }, storage: { - volumeName: "rabbitmq-volume", + volumeName: 'rabbitmq-volume', requestedSize: null, className: null, - accessModes: [ - "ReadWriteOnce" - ] - } - } - } - } + accessModes: ['ReadWriteOnce'], + }, + }, + }, + }, }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/kuberoaddonsRedis.ts b/server/src/addons/plugins/kuberoaddonsRedis.ts index 22ea50942..c532d8591 100644 --- a/server/src/addons/plugins/kuberoaddonsRedis.ts +++ b/server/src/addons/plugins/kuberoaddonsRedis.ts @@ -1,13 +1,13 @@ import { KuberoRabbitMQ } from './kuberoRabbitMQ'; -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; - +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class KuberoAddonRedis extends Plugin implements IPlugin { public id: string = 'kubero-operator'; //same as operator name public displayName = 'Redis'; - public description = 'Redis(R) is an open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.'; + public description = + 'Redis(R) is an open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets.'; public icon = '/img/addons/redis.svg'; public install: string = ''; public url = @@ -35,7 +35,7 @@ export class KuberoAddonRedis extends Plugin implements IPlugin { 'KuberoAddonRedis.spec.redis.image.tag': { type: 'combobox', label: 'Version/Tag', - options: ['6', '7','8', 'latest'], // TODO - load this dynamically + options: ['6', '7', '8', 'latest'], // TODO - load this dynamically name: 'spec.redis.image.tag', required: true, default: '8', @@ -47,7 +47,8 @@ export class KuberoAddonRedis extends Plugin implements IPlugin { name: 'spec.redis.haMode.enabled', default: false, required: false, - description: 'High availability mode (with master-slave replication and sentinel)', + description: + 'High availability mode (with master-slave replication and sentinel)', }, 'KuberoAddonRedis.spec.redis.haMode.replicas': { type: 'text', @@ -97,40 +98,38 @@ export class KuberoAddonRedis extends Plugin implements IPlugin { public resourceDefinitions: object = { KuberoAddonRedis: { - apiVersion: "application.kubero.dev/v1alpha1", - kind: "KuberoAddonRedis", + apiVersion: 'application.kubero.dev/v1alpha1', + kind: 'KuberoAddonRedis', metadata: { - name: "kuberoaddonredis-sample" + name: 'kuberoaddonredis-sample', }, spec: { redis: { image: { - tag: "" + tag: '', }, metrics: { - enabled: false + enabled: false, }, resources: {}, useDeploymentWhenNonHA: true, haMode: { enabled: false, useDnsNames: false, - masterGroupName: "redisha", + masterGroupName: 'redisha', replicas: 3, - quorum: 2 + quorum: 2, }, storage: { - volumeName: "redis-data", + volumeName: 'redis-data', requestedSize: null, className: null, - accessModes: [ - "ReadWriteOnce" - ], - keepPvc: false - } - } - } - } + accessModes: ['ReadWriteOnce'], + keepPvc: false, + }, + }, + }, + }, }; protected additionalResourceDefinitions: object = {}; diff --git a/server/src/addons/plugins/minio.ts b/server/src/addons/plugins/minio.ts index 2c6931151..5b61f8f6d 100644 --- a/server/src/addons/plugins/minio.ts +++ b/server/src/addons/plugins/minio.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class Tenant extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/mongoDB.ts b/server/src/addons/plugins/mongoDB.ts index 891ad2e18..865b8e68c 100644 --- a/server/src/addons/plugins/mongoDB.ts +++ b/server/src/addons/plugins/mongoDB.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class PerconaServerMongoDB extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/plugin.ts b/server/src/addons/plugins/plugin.ts index c3e91dcdf..3e486ed8c 100644 --- a/server/src/addons/plugins/plugin.ts +++ b/server/src/addons/plugins/plugin.ts @@ -1,8 +1,6 @@ import axios from 'axios'; import { Logger } from '@nestjs/common'; - - export abstract class Plugin { public plugin?: any; public id: string = ''; //same as operator name diff --git a/server/src/addons/plugins/postgresCluster.ts b/server/src/addons/plugins/postgresCluster.ts index 44a933f2e..075753a44 100644 --- a/server/src/addons/plugins/postgresCluster.ts +++ b/server/src/addons/plugins/postgresCluster.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class PostgresCluster extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/redis.ts b/server/src/addons/plugins/redis.ts index 775ba2db1..e5fdf7fd3 100644 --- a/server/src/addons/plugins/redis.ts +++ b/server/src/addons/plugins/redis.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class Redis extends Plugin implements IPlugin { diff --git a/server/src/addons/plugins/redisCluster.ts b/server/src/addons/plugins/redisCluster.ts index ccb34a9d7..8f8d94878 100644 --- a/server/src/addons/plugins/redisCluster.ts +++ b/server/src/addons/plugins/redisCluster.ts @@ -1,5 +1,5 @@ -import { Plugin, } from './plugin'; -import { IPlugin, IPluginFormFields } from './plugin.interface'; +import { Plugin } from './plugin'; +import { IPlugin, IPluginFormFields } from './plugin.interface'; // Classname must be same as the CRD's Name export class RedisCluster extends Plugin implements IPlugin { diff --git a/server/src/apps/app/app.ts b/server/src/apps/app/app.ts index 040be22ce..1d886bae9 100644 --- a/server/src/apps/app/app.ts +++ b/server/src/apps/app/app.ts @@ -224,7 +224,7 @@ export class App implements IApp { this.autoscaling = { enabled: app.autoscale, }; - (this.fullnameOverride = ''), + ((this.fullnameOverride = ''), (this.image = { containerPort: app.image.containerPort, pullPolicy: 'Always', @@ -234,7 +234,7 @@ export class App implements IApp { fetch: app.image.fetch, build: app.image.build, run: app.image.run, - }); + })); // function to set security context, required for backwards compatibility // Added in v1.11.0 @@ -259,7 +259,7 @@ export class App implements IApp { 'nginx'; this.ingress.enabled = true; - (this.nameOverride = ''), + ((this.nameOverride = ''), (this.nodeSelector = {}), (this.podAnnotations = {}), (this.podSecurityContext = {}), @@ -269,7 +269,7 @@ export class App implements IApp { port: 80, type: 'ClusterIP', }), - (this.tolerations = []); + (this.tolerations = [])); this.healthcheck = app.healthcheck; } diff --git a/server/src/apps/apps.controller.spec.ts b/server/src/apps/apps.controller.spec.ts index f83748da3..11f63a9da 100644 --- a/server/src/apps/apps.controller.spec.ts +++ b/server/src/apps/apps.controller.spec.ts @@ -214,7 +214,11 @@ describe('AppsController', () => { req, ); expect(result).toEqual(mockApp); - expect(mockAppsService.createApp).toHaveBeenCalledWith(mockApp, mockUser, mockUserGroups); + expect(mockAppsService.createApp).toHaveBeenCalledWith( + mockApp, + mockUser, + mockUserGroups, + ); }); }); diff --git a/server/src/apps/apps.controller.ts b/server/src/apps/apps.controller.ts index f33089be3..ad15ca45a 100644 --- a/server/src/apps/apps.controller.ts +++ b/server/src/apps/apps.controller.ts @@ -28,7 +28,6 @@ import { ReadonlyGuard } from '../common/guards/readonly.guard'; import { PermissionsGuard } from '../auth/permissions.guard'; import { Permissions } from '../auth/permissions.decorator'; - @Controller({ path: 'api/apps', version: '1' }) export class AppsController { constructor(private readonly appsService: AppsService) {} @@ -129,7 +128,12 @@ export class AppsController { strategy: req.user.strategy, username: req.user.username, }; - return this.appsService.updateApp(app, resourceVersion, user, req.user.userGroups); + return this.appsService.updateApp( + app, + resourceVersion, + user, + req.user.userGroups, + ); } @Delete('/:pipeline/:phase/:app') @@ -154,7 +158,13 @@ export class AppsController { strategy: req.user.strategy, username: req.user.username, }; - return this.appsService.deleteApp(pipeline, phase, app, user, req.user.userGroups); + return this.appsService.deleteApp( + pipeline, + phase, + app, + user, + req.user.userGroups, + ); } @Post('/pullrequest') @@ -167,10 +177,7 @@ export class AppsController { isArray: false, }) @ApiBearerAuth('bearerAuth') - async startPullRequest( - @Body() body: any, - @Request() req: any, - ) { + async startPullRequest(@Body() body: any, @Request() req: any) { return this.appsService.createPRApp( body.branch, body.branch, @@ -196,7 +203,12 @@ export class AppsController { @Param('app') app: string, @Request() req: any, ) { - return this.appsService.getTemplate(pipeline, phase, app, req.user.userGroups); + return this.appsService.getTemplate( + pipeline, + phase, + app, + req.user.userGroups, + ); } @Get('/:pipeline/:phase/:app/restart') @@ -222,7 +234,13 @@ export class AppsController { username: req.user.username, }; - return this.appsService.restartApp(pipeline, phase, app, user, req.user.userGroups); + return this.appsService.restartApp( + pipeline, + phase, + app, + user, + req.user.userGroups, + ); } @Get('/:pipeline/:phase/:app/pods') diff --git a/server/src/apps/apps.service.spec.ts b/server/src/apps/apps.service.spec.ts index 07344a7e9..cda90806d 100644 --- a/server/src/apps/apps.service.spec.ts +++ b/server/src/apps/apps.service.spec.ts @@ -211,13 +211,25 @@ describe('AppsService', () => { }); it('should call deleteApp and send notification', async () => { (pipelinesService.getContext as jest.Mock).mockResolvedValue('ctx'); - await service.deleteApp('p', 'ph', 'a', { username: 'u' } as any, mockUSerGroups); + await service.deleteApp( + 'p', + 'ph', + 'a', + { username: 'u' } as any, + mockUSerGroups, + ); expect(kubectl.deleteApp).toHaveBeenCalled(); expect(notificationsService.send).toHaveBeenCalled(); }); it('should not call deleteApp if readonly', async () => { process.env.KUBERO_READONLY = 'true'; - await service.deleteApp('p', 'ph', 'a', { username: 'u' } as any, mockUSerGroups); + await service.deleteApp( + 'p', + 'ph', + 'a', + { username: 'u' } as any, + mockUSerGroups, + ); expect(kubectl.deleteApp).not.toHaveBeenCalled(); }); }); @@ -298,14 +310,24 @@ describe('AppsService', () => { it('should call updateApp and send notification', async () => { (pipelinesService.getContext as jest.Mock).mockResolvedValue('ctx'); const app = new App(mockApp); - await service.updateApp(app, 'rv', { username: 'u' } as any, mockUSerGroups); + await service.updateApp( + app, + 'rv', + { username: 'u' } as any, + mockUSerGroups, + ); expect(kubectl.updateApp).toHaveBeenCalled(); expect(notificationsService.send).toHaveBeenCalled(); }); it('should not call updateApp if readonly', async () => { process.env.KUBERO_READONLY = 'true'; const app = new App(mockApp); - await service.updateApp(app, 'rv', { username: 'u' } as any, mockUSerGroups); + await service.updateApp( + app, + 'rv', + { username: 'u' } as any, + mockUSerGroups, + ); expect(kubectl.updateApp).not.toHaveBeenCalled(); }); }); @@ -511,7 +533,7 @@ describe('AppsService', () => { 'pipe', 'dev', 'app1', - mockUSerGroups + mockUSerGroups, ); // Fast-forward time by 2 seconds jest.advanceTimersByTime(2000); @@ -520,7 +542,7 @@ describe('AppsService', () => { 'pipe', 'dev', 'app1', - ["group1", "group2"] + ['group1', 'group2'], ); expect(result).toEqual({ app: 'app1', @@ -583,7 +605,7 @@ describe('AppsService', () => { 'feature-1', 'My PR Title', 'git@github.com:foo/bar.git', - mockUSerGroups + mockUSerGroups, ); // Erwartet: Nur das erste App-Objekt passt auf alle Kriterien @@ -593,7 +615,7 @@ describe('AppsService', () => { 'review', 'my-pr-title', // websaveTitle { username: 'unknown' }, - ["group1", "group2"], + ['group1', 'group2'], ); }); @@ -610,7 +632,7 @@ describe('AppsService', () => { 'feature-2', 'Other Title', 'git@github.com:foo/bar.git', - mockUSerGroups + mockUSerGroups, ); expect(mockDeleteApp).not.toHaveBeenCalled(); }); @@ -622,7 +644,7 @@ describe('AppsService', () => { 'feature-1', 'My PR Title', 'git@github.com:foo/bar.git', - mockUSerGroups + mockUSerGroups, ); expect(mockGetAllAppsList).toHaveBeenCalledWith('my-context'); delete process.env.KUBERO_CONTEXT; @@ -634,7 +656,7 @@ describe('AppsService', () => { 'feature-1', 'My PR Title', 'git@github.com:foo/bar.git', - mockUSerGroups + mockUSerGroups, ); expect(mockLogger.debug).toHaveBeenCalledWith('destroyPRApp'); }); @@ -733,7 +755,7 @@ describe('AppsService', () => { 'PR Title', 'git@github.com:org/repo.git', undefined, - mockUSerGroups + mockUSerGroups, ); expect(result).toEqual({ status: 'ok', message: 'app created pr-title' }); @@ -755,7 +777,7 @@ describe('AppsService', () => { }), }), expect.objectContaining({ username: 'unknown' }), - ["group1", "group2"], + ['group1', 'group2'], ); }); @@ -794,7 +816,7 @@ describe('AppsService', () => { 'PR Title', 'git@github.com:org/repo.git', 'pipeline2', - mockUSerGroups + mockUSerGroups, ); expect(service.createApp).toHaveBeenCalledWith( @@ -803,7 +825,7 @@ describe('AppsService', () => { pipeline: 'pipeline2', }), expect.anything(), - ["group1", "group2"], + ['group1', 'group2'], ); }); @@ -832,7 +854,7 @@ describe('AppsService', () => { 'PR Title', 'git@github.com:org/repo.git', undefined, - mockUSerGroups + mockUSerGroups, ); expect(result).toBeUndefined(); @@ -863,7 +885,7 @@ describe('AppsService', () => { 'Complex PR Title with 123 Special @#$ Characters!', 'git@github.com:org/repo.git', undefined, - mockUSerGroups + mockUSerGroups, ); expect(service.createApp).toHaveBeenCalledWith( @@ -871,7 +893,7 @@ describe('AppsService', () => { name: 'complex-pr-title-with-123-special-----characters-', }), expect.anything(), - ["group1", "group2"], + ['group1', 'group2'], ); }); }); @@ -903,9 +925,17 @@ describe('AppsService', () => { it('should return a YAML template for an app', async () => { mockGetApp.mockResolvedValue(mockKubectlApp); - const result = await service.getTemplate('pipeline1', 'dev', 'test-app', mockUSerGroups); + const result = await service.getTemplate( + 'pipeline1', + 'dev', + 'test-app', + mockUSerGroups, + ); - expect(mockGetApp).toHaveBeenCalledWith('pipeline1', 'dev', 'test-app', ["group1", "group2"]); + expect(mockGetApp).toHaveBeenCalledWith('pipeline1', 'dev', 'test-app', [ + 'group1', + 'group2', + ]); expect(mockYAML.stringify).toHaveBeenCalledWith(expect.any(Object), { indent: 4, resolveKnownTags: true, @@ -952,7 +982,13 @@ describe('AppsService', () => { process.env.KUBERO_READONLY = 'true'; const user = { id: '1', username: 'testuser' }; const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); - await service.restartApp('pipe', 'dev', 'app1', user as any, mockUSerGroups); + await service.restartApp( + 'pipe', + 'dev', + 'app1', + user as any, + mockUSerGroups, + ); expect(mockKubectl.restartApp).not.toHaveBeenCalled(); expect(mockNotificationsService.send).not.toHaveBeenCalled(); logSpy.mockRestore(); @@ -960,7 +996,13 @@ describe('AppsService', () => { it('should call restartApp for web and worker and send notification', async () => { const user = { id: '1', username: 'testuser' }; - await service.restartApp('pipe', 'dev', 'app1', user as any, mockUSerGroups); + await service.restartApp( + 'pipe', + 'dev', + 'app1', + user as any, + mockUSerGroups, + ); expect(mockLogger.debug).toHaveBeenCalledWith( 'restart App: app1 in pipe phase: dev', @@ -968,7 +1010,7 @@ describe('AppsService', () => { expect(mockPipelinesService.getContext).toHaveBeenCalledWith( 'pipe', 'dev', - ["group1", "group2"], + ['group1', 'group2'], ); expect(mockKubectl.restartApp).toHaveBeenCalledWith( 'pipe', @@ -1000,7 +1042,13 @@ describe('AppsService', () => { it('should do nothing if getContext returns undefined', async () => { mockPipelinesService.getContext.mockResolvedValueOnce(undefined); const user = { id: '1', username: 'testuser' }; - await service.restartApp('pipe', 'dev', 'app1', user as any, mockUSerGroups); + await service.restartApp( + 'pipe', + 'dev', + 'app1', + user as any, + mockUSerGroups, + ); expect(mockKubectl.restartApp).not.toHaveBeenCalled(); expect(mockNotificationsService.send).not.toHaveBeenCalled(); }); @@ -1031,7 +1079,12 @@ describe('AppsService', () => { it('should return empty array if no context is found', async () => { mockPipelinesService.getContext.mockResolvedValue(undefined); - const result = await service.getPods('pipe', 'dev', 'app1', mockUSerGroups); + const result = await service.getPods( + 'pipe', + 'dev', + 'app1', + mockUSerGroups, + ); expect(result).toEqual([]); expect(mockKubectl.setCurrentContext).not.toHaveBeenCalled(); expect(mockKubectl.getPods).not.toHaveBeenCalled(); @@ -1080,11 +1133,16 @@ describe('AppsService', () => { }, ]); - const result = await service.getPods('pipe', 'dev', 'app1', mockUSerGroups); + const result = await service.getPods( + 'pipe', + 'dev', + 'app1', + mockUSerGroups, + ); expect(mockPipelinesService.getContext).toHaveBeenCalledWith( 'pipe', 'dev', - ["group1", "group2"], + ['group1', 'group2'], ); expect(mockKubectl.setCurrentContext).toHaveBeenCalledWith( 'test-context', @@ -1141,7 +1199,12 @@ describe('AppsService', () => { }, }, ]); - const result = await service.getPods('pipe', 'dev', 'app1', mockUSerGroups); + const result = await service.getPods( + 'pipe', + 'dev', + 'app1', + mockUSerGroups, + ); expect(result).toEqual([]); }); }); diff --git a/server/src/apps/apps.service.ts b/server/src/apps/apps.service.ts index 90b7bbb49..4011a781c 100644 --- a/server/src/apps/apps.service.ts +++ b/server/src/apps/apps.service.ts @@ -31,7 +31,7 @@ export class AppsService { pipelineName: string, phaseName: string, appName: string, - userGroups: string[] + userGroups: string[], ) { this.logger.debug( 'get App: ' + appName + ' in ' + pipelineName + ' phase: ' + phaseName, @@ -115,7 +115,12 @@ export class AppsService { app.buildstrategy == 'nixpacks' || app.buildstrategy == 'buildpacks') ) { - this.triggerImageBuildDelayed(app.pipeline, app.phase, app.name, userGroups); + this.triggerImageBuildDelayed( + app.pipeline, + app.phase, + app.name, + userGroups, + ); } } } @@ -124,7 +129,7 @@ export class AppsService { pipeline: string, phase: string, appName: string, - userGroups: string[] + userGroups: string[], ) { // delay for 2 seconds to trigger the Image build await new Promise((resolve) => setTimeout(resolve, 2000)); @@ -137,7 +142,11 @@ export class AppsService { appName: string, userGroups: string[], ) { - const contextName = await this.pipelinesService.getContext(pipeline, phase, userGroups); + const contextName = await this.pipelinesService.getContext( + pipeline, + phase, + userGroups, + ); const namespace = pipeline + '-' + phase; const appresult = await this.getApp(pipeline, phase, appName, userGroups); @@ -425,7 +434,12 @@ export class AppsService { } // delete a pr app in all pipelines that have review apps enabled and the same ssh_url - public async deletePRApp(branch: string, title: string, ssh_url: string, userGroups: string[]) { + public async deletePRApp( + branch: string, + title: string, + ssh_url: string, + userGroups: string[], + ) { this.logger.debug('destroyPRApp'); const websaveTitle = title.toLowerCase().replace(/[^a-z0-9-]/g, '-'); //TODO improve websave title @@ -603,7 +617,12 @@ export class AppsService { } // update an app in a pipeline and phase - public async updateApp(app: App, resourceVersion: string, user: IUser, userGroups: string[]) { + public async updateApp( + app: App, + resourceVersion: string, + user: IUser, + userGroups: string[], + ) { this.logger.debug( 'update App: ' + app.name + diff --git a/server/src/auth/auth.controller.spec.ts b/server/src/auth/auth.controller.spec.ts index d70637e50..633af954b 100644 --- a/server/src/auth/auth.controller.spec.ts +++ b/server/src/auth/auth.controller.spec.ts @@ -108,7 +108,7 @@ describe('AuthController', () => { const mockReq = { user: { username: 'user' } }; const mockRes = { cookie: jest.fn(), redirect: jest.fn() }; await controller.githubCallback(mockReq as any, mockRes as any); - expect(service.loginOAuth2).toHaveBeenCalledWith({"username": "user"}); + expect(service.loginOAuth2).toHaveBeenCalledWith({ username: 'user' }); expect(mockRes.cookie).toHaveBeenCalledWith('kubero.JWT_TOKEN', 'token'); expect(mockRes.redirect).toHaveBeenCalledWith('/'); }); @@ -120,7 +120,7 @@ describe('AuthController', () => { const mockReq = { user: { username: 'user' } }; const mockRes = { cookie: jest.fn(), redirect: jest.fn() }; await controller.oauth2Callback(mockReq as any, mockRes as any); - expect(service.loginOAuth2).toHaveBeenCalledWith({"username": "user"}); + expect(service.loginOAuth2).toHaveBeenCalledWith({ username: 'user' }); expect(mockRes.cookie).toHaveBeenCalledWith('kubero.JWT_TOKEN', 'token'); expect(mockRes.redirect).toHaveBeenCalledWith('/'); }); diff --git a/server/src/auth/auth.service.spec.ts b/server/src/auth/auth.service.spec.ts index 5fb4d67ac..49648a223 100644 --- a/server/src/auth/auth.service.spec.ts +++ b/server/src/auth/auth.service.spec.ts @@ -21,13 +21,13 @@ describe('AuthService', () => { findOneOrCreate: jest.fn().mockResolvedValue({ userId: 3, username: 'oauthuser', - emails: [{ value: 'undefined@kubero.dev' }], + emails: [{ value: 'undefined@kubero.dev' }], }), }; rolesService = { getPermissions: jest.fn().mockResolvedValue([ { resource: 'app', action: 'read' }, - { resource: 'app', action: 'write' } + { resource: 'app', action: 'write' }, ]), }; kubectl = { diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index e3532f6e3..58fe2748f 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -72,7 +72,7 @@ export class AuthService { const permissions = await this.rolesService.getPermissions(user.roleId); user.permissions = permissions.map((p) => `${p.resource}:${p.action}`); - + // Defines the user object to be signed in the JWT // Add more fields if needed const u = { diff --git a/server/src/auth/permissions.decorator.ts b/server/src/auth/permissions.decorator.ts index 8da195ac7..e5902eeb4 100644 --- a/server/src/auth/permissions.decorator.ts +++ b/server/src/auth/permissions.decorator.ts @@ -1,4 +1,5 @@ import { SetMetadata } from '@nestjs/common'; export const PERMISSIONS_KEY = 'permissions'; -export const Permissions = (...permissions: string[]) => SetMetadata(PERMISSIONS_KEY, permissions); +export const Permissions = (...permissions: string[]) => + SetMetadata(PERMISSIONS_KEY, permissions); diff --git a/server/src/auth/permissions.guard.ts b/server/src/auth/permissions.guard.ts index 0b297c55f..5ea87da27 100644 --- a/server/src/auth/permissions.guard.ts +++ b/server/src/auth/permissions.guard.ts @@ -1,4 +1,9 @@ -import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common'; +import { + CanActivate, + ExecutionContext, + Injectable, + ForbiddenException, +} from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { PERMISSIONS_KEY } from './permissions.decorator'; @@ -7,10 +12,10 @@ export class PermissionsGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { - const requiredPermissions = this.reflector.getAllAndOverride(PERMISSIONS_KEY, [ - context.getHandler(), - context.getClass(), - ]); + const requiredPermissions = this.reflector.getAllAndOverride( + PERMISSIONS_KEY, + [context.getHandler(), context.getClass()], + ); /* Disabling for RBAC if ( !process.env.KUBERO_USERS && @@ -26,12 +31,13 @@ export class PermissionsGuard implements CanActivate { } const { user } = context.switchToHttp().getRequest(); - if (!user || !user.permissions) { throw new ForbiddenException('No permissions found'); } - - const hasPermission = requiredPermissions.some((perm) => user.permissions.includes(perm)); + + const hasPermission = requiredPermissions.some((perm) => + user.permissions.includes(perm), + ); if (!hasPermission) { throw new ForbiddenException('Insufficient permissions'); } diff --git a/server/src/cli/cli.module.ts b/server/src/cli/cli.module.ts index a72e98d61..4e001bbcc 100644 --- a/server/src/cli/cli.module.ts +++ b/server/src/cli/cli.module.ts @@ -3,9 +3,6 @@ import { ResetAdminCommand } from './commands/reset-admin.command'; import { DatabaseService } from '../database/database.service'; @Module({ - providers: [ - ResetAdminCommand, - DatabaseService, - ], + providers: [ResetAdminCommand, DatabaseService], }) export class CliModule {} diff --git a/server/src/cli/commands/reset-admin.command.spec.ts b/server/src/cli/commands/reset-admin.command.spec.ts index 4a1a137a1..95369e9eb 100644 --- a/server/src/cli/commands/reset-admin.command.spec.ts +++ b/server/src/cli/commands/reset-admin.command.spec.ts @@ -23,7 +23,7 @@ describe('ResetAdminCommand', () => { command = module.get(ResetAdminCommand); databaseService = module.get(DatabaseService); - + // Mock the logger logger = { log: jest.fn(), @@ -43,7 +43,9 @@ describe('ResetAdminCommand', () => { let exitSpy: jest.SpyInstance; beforeEach(() => { - exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => {}) as (code?: number) => never); + exitSpy = jest + .spyOn(process, 'exit') + .mockImplementation((() => {}) as (code?: number) => never); // We need to call the original implementation of run jest.spyOn(command, 'run').mockRestore(); }); @@ -56,7 +58,9 @@ describe('ResetAdminCommand', () => { await command.run(); expect(logger.log).toHaveBeenCalledWith('Resetting admin account...'); expect(databaseService.resetAdminUser).toHaveBeenCalled(); - expect(logger.log).toHaveBeenCalledWith('Admin account has been reset successfully'); + expect(logger.log).toHaveBeenCalledWith( + 'Admin account has been reset successfully', + ); expect(exitSpy).toHaveBeenCalledWith(0); }); @@ -66,7 +70,10 @@ describe('ResetAdminCommand', () => { await command.run(); expect(logger.log).toHaveBeenCalledWith('Resetting admin account...'); expect(databaseService.resetAdminUser).toHaveBeenCalled(); - expect(logger.error).toHaveBeenCalledWith('Failed to reset admin account', error); + expect(logger.error).toHaveBeenCalledWith( + 'Failed to reset admin account', + error, + ); expect(exitSpy).toHaveBeenCalledWith(1); }); }); diff --git a/server/src/cli/commands/reset-admin.command.ts b/server/src/cli/commands/reset-admin.command.ts index ccc1cadbf..68f96357c 100644 --- a/server/src/cli/commands/reset-admin.command.ts +++ b/server/src/cli/commands/reset-admin.command.ts @@ -2,7 +2,10 @@ import { Command, CommandRunner } from 'nest-commander'; import { Injectable, Logger } from '@nestjs/common'; import { DatabaseService } from '../../database/database.service'; -@Command({ name: 'reset-admin', description: 'Reset the admin account with a new random password' }) +@Command({ + name: 'reset-admin', + description: 'Reset the admin account with a new random password', +}) @Injectable() export class ResetAdminCommand extends CommandRunner { private readonly logger = new Logger(ResetAdminCommand.name); @@ -13,7 +16,7 @@ export class ResetAdminCommand extends CommandRunner { async run(): Promise { this.logger.log('Resetting admin account...'); - + try { await this.databaseService.resetAdminUser(); this.logger.log('Admin account has been reset successfully'); @@ -21,7 +24,7 @@ export class ResetAdminCommand extends CommandRunner { this.logger.error('Failed to reset admin account', error); process.exit(1); } - + process.exit(0); } } diff --git a/server/src/common/guards/readonly.guard.ts b/server/src/common/guards/readonly.guard.ts index fc1654056..726177675 100644 --- a/server/src/common/guards/readonly.guard.ts +++ b/server/src/common/guards/readonly.guard.ts @@ -16,7 +16,7 @@ export class ReadonlyGuard implements CanActivate { 'Kubero is in read-only mode, write operations are blocked', ); this.logger.warn( - 'KUBERO_READONLY is deprecated! Use Kubero\'s RBAC feature instead.', + "KUBERO_READONLY is deprecated! Use Kubero's RBAC feature instead.", ); throw new HttpException('Kubero is in read-only mode', 202); } diff --git a/server/src/config/config.controller.ts b/server/src/config/config.controller.ts index a74f83be7..3aceefe8e 100644 --- a/server/src/config/config.controller.ts +++ b/server/src/config/config.controller.ts @@ -1,4 +1,13 @@ -import { Body, Controller, Delete, Get, Param, Post, Put, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, + UseGuards, +} from '@nestjs/common'; //import { ApiTags } from '@nestjs/swagger'; import { ConfigService } from './config.service'; import { @@ -151,7 +160,6 @@ export class ConfigController { return this.configService.createRunpack(body); } - @Get('/clusterissuer') @UseGuards(JwtAuthGuard, PermissionsGuard) @Permissions('config:read', 'config:write') diff --git a/server/src/database/database.service.ts b/server/src/database/database.service.ts index 7c5a96611..5f1ef5d48 100644 --- a/server/src/database/database.service.ts +++ b/server/src/database/database.service.ts @@ -12,7 +12,6 @@ export class DatabaseService { private readonly prisma = new PrismaClient(); constructor() { - // Initialize the Prisma client this.prisma .$connect() @@ -70,7 +69,6 @@ export class DatabaseService { } } - private static async CreateSystemUser() { const prisma = new PrismaClient(); @@ -79,7 +77,10 @@ export class DatabaseService { where: { id: '1' }, }); if (existingUser) { - Logger.log('System user already exists. Skipping creation.', 'DatabaseService'); + Logger.log( + 'System user already exists. Skipping creation.', + 'DatabaseService', + ); return; } @@ -116,7 +117,10 @@ export class DatabaseService { where: { id: '2' }, }); if (existingUser) { - Logger.log('Admin user already exists. Skipping creation.', 'DatabaseService'); + Logger.log( + 'Admin user already exists. Skipping creation.', + 'DatabaseService', + ); return; } @@ -128,13 +132,12 @@ export class DatabaseService { try { let plainPassword: string; - if (!process.env.KUBERO_ADMIN_PASSWORD && process.env.KUBERO_ADMIN_PASSWORD !== '') { + if ( + !process.env.KUBERO_ADMIN_PASSWORD && + process.env.KUBERO_ADMIN_PASSWORD !== '' + ) { // Generate a random password - plainPassword = crypto - .randomBytes(25) - .toString('base64') - .slice(0, 19); - + plainPassword = crypto.randomBytes(25).toString('base64').slice(0, 19); } else { plainPassword = process.env.KUBERO_ADMIN_PASSWORD; } @@ -191,12 +194,12 @@ export class DatabaseService { .slice(0, 19); // Create bcrypt hash const passwordHash = await bcrypt.hash(plainPassword, 10); - + // Check if admin user exists const existingUser = await prisma.user.findUnique({ where: { id: '2' }, }); - + if (existingUser) { // Update existing admin user await prisma.user.update({ @@ -229,11 +232,11 @@ export class DatabaseService { }); console.log('\n\n\n', 'New admin account created'); } - + console.log(' username: ', adminUser); console.log(' password: ', plainPassword); console.log(' email: ', adminEmail, '\n\n\n'); - + this.logger.log('Admin user reset successfully.'); return; } catch (error) { @@ -247,12 +250,18 @@ export class DatabaseService { const existingUsers = await prisma.user.count(); if (existingUsers > 2) { - Logger.log('Legacy users already migrated. Skipping migration.', 'DatabaseService'); + Logger.log( + 'Legacy users already migrated. Skipping migration.', + 'DatabaseService', + ); return; } if (!process.env.KUBERO_USERS || process.env.KUBERO_USERS === '') { - Logger.log('No legacy users to migrate. KUBERO_USERS is not set.', 'DatabaseService'); + Logger.log( + 'No legacy users to migrate. KUBERO_USERS is not set.', + 'DatabaseService', + ); return; } @@ -298,9 +307,16 @@ export class DatabaseService { : undefined, }, }); - Logger.log(`Migrated user ${user.username} successfully.`, 'DatabaseService'); + Logger.log( + `Migrated user ${user.username} successfully.`, + 'DatabaseService', + ); } catch (error) { - Logger.error(`Failed to migrate user ${user.username}.`, error, 'DatabaseService'); + Logger.error( + `Failed to migrate user ${user.username}.`, + error, + 'DatabaseService', + ); } } @@ -323,10 +339,10 @@ export class DatabaseService { { action: 'write', resource: 'pipeline' }, { action: 'write', resource: 'user' }, { action: 'write', resource: 'config' }, - { action: 'ok', resource: 'console' }, - { action: 'ok', resource: 'logs' }, - { action: 'ok', resource: 'reboot' }, - { action: 'read', resource: 'audit' }, + { action: 'ok', resource: 'console' }, + { action: 'ok', resource: 'logs' }, + { action: 'ok', resource: 'reboot' }, + { action: 'read', resource: 'audit' }, { action: 'write', resource: 'token' }, { action: 'write', resource: 'security' }, ], @@ -349,12 +365,12 @@ export class DatabaseService { create: [ { action: 'write', resource: 'app' }, { action: 'write', resource: 'pipeline' }, - { action: 'read', resource: 'user' }, - { action: 'none', resource: 'config' }, - { action: 'ok', resource: 'console' }, - { action: 'ok', resource: 'logs' }, - { action: 'ok', resource: 'reboot' }, - { action: 'read', resource: 'audit' }, + { action: 'read', resource: 'user' }, + { action: 'none', resource: 'config' }, + { action: 'ok', resource: 'console' }, + { action: 'ok', resource: 'logs' }, + { action: 'ok', resource: 'reboot' }, + { action: 'read', resource: 'audit' }, { action: 'ok', resource: 'token' }, { action: 'write', resource: 'security' }, ], @@ -396,16 +412,19 @@ export class DatabaseService { // Ensure the 'everyone' user group exists prisma.userGroup .upsert({ - where: { name: 'everyone' }, - update: {}, - create: { - name: 'everyone', - description: 'Standard group for all users', - }, - }) - .then(() => { - Logger.log('UserGroup "everyone" seeded successfully.', 'DatabaseService'); - }); + where: { name: 'everyone' }, + update: {}, + create: { + name: 'everyone', + description: 'Standard group for all users', + }, + }) + .then(() => { + Logger.log( + 'UserGroup "everyone" seeded successfully.', + 'DatabaseService', + ); + }); // Ensure the 'admin' user group exists await prisma.userGroup @@ -431,7 +450,9 @@ export class DatabaseService { const buildpacks = config || []; for (const bp of buildpacks) { // Find existing by name - const existing = await prisma.runpack.findFirst({ where: { name: bp.name } }); + const existing = await prisma.runpack.findFirst({ + where: { name: bp.name }, + }); const createPhase = async (phase: any) => { // Create SecurityContext const sec = await prisma.securityContext.create({ @@ -439,13 +460,25 @@ export class DatabaseService { runAsUser: phase.securityContext.runAsUser, runAsGroup: phase.securityContext.runAsGroup, runAsNonRoot: phase.securityContext.runAsNonRoot, - readOnlyRootFilesystem: phase.securityContext.readOnlyRootFilesystem, - allowPrivilegeEscalation: phase.securityContext.allowPrivilegeEscalation, + readOnlyRootFilesystem: + phase.securityContext.readOnlyRootFilesystem, + allowPrivilegeEscalation: + phase.securityContext.allowPrivilegeEscalation, capabilities: { - create: [{ - add: { create: (phase.securityContext.capabilities?.add || []).map((v: string) => ({ value: v })) }, - drop: { create: (phase.securityContext.capabilities?.drop || []).map((v: string) => ({ value: v })) }, - }], + create: [ + { + add: { + create: (phase.securityContext.capabilities?.add || []).map( + (v: string) => ({ value: v }), + ), + }, + drop: { + create: ( + phase.securityContext.capabilities?.drop || [] + ).map((v: string) => ({ value: v })), + }, + }, + ], }, }, }); @@ -465,7 +498,10 @@ export class DatabaseService { const runPhase = await createPhase(bp.run); if (existing) { // Optionally update here - Logger.log(`Runpack/Buildpack '${bp.name}' already exists. Skipping.`, 'DatabaseService'); + Logger.log( + `Runpack/Buildpack '${bp.name}' already exists. Skipping.`, + 'DatabaseService', + ); continue; } await prisma.runpack.create({ @@ -488,7 +524,10 @@ export class DatabaseService { // seed pod sizes if the table is empty const existingSizes = await prisma.podSize.count(); if (existingSizes > 0) { - Logger.log('Pod sizes already exist. Skipping seeding.', 'DatabaseService'); + Logger.log( + 'Pod sizes already exist. Skipping seeding.', + 'DatabaseService', + ); return; } for (const size of config) { @@ -502,7 +541,10 @@ export class DatabaseService { memoryRequest: size.resources.requests.memory, }, }); - Logger.log(`Pod size '${size.name}' seeded successfully.`, 'DatabaseService'); + Logger.log( + `Pod size '${size.name}' seeded successfully.`, + 'DatabaseService', + ); } Logger.log('Pod sizes seeded successfully.', 'DatabaseService'); } diff --git a/server/src/database/podsizes.seed.ts b/server/src/database/podsizes.seed.ts index f66dafea2..efa95bc07 100644 --- a/server/src/database/podsizes.seed.ts +++ b/server/src/database/podsizes.seed.ts @@ -28,4 +28,4 @@ export const podsizes = ` limits: memory: 4Gi cpu: 2000m -` \ No newline at end of file +`; diff --git a/server/src/database/runpacks.seed.ts b/server/src/database/runpacks.seed.ts index 8de8c6a0f..1c0abc777 100644 --- a/server/src/database/runpacks.seed.ts +++ b/server/src/database/runpacks.seed.ts @@ -300,4 +300,4 @@ export const runpacks = ` capabilities: add: [] drop: [] -` +`; diff --git a/server/src/deployments/deployments.controller.spec.ts b/server/src/deployments/deployments.controller.spec.ts index 6b5658a81..401b3ece1 100644 --- a/server/src/deployments/deployments.controller.spec.ts +++ b/server/src/deployments/deployments.controller.spec.ts @@ -19,7 +19,7 @@ const mockJWT = { userGroups: mockUserGroups, }; -const mockReq = { user: mockJWT }; +const mockReq = { user: mockJWT }; describe('DeploymentsController', () => { let controller: DeploymentsController; @@ -51,8 +51,18 @@ describe('DeploymentsController', () => { }); it('should get deployments', async () => { - const result = await controller.getDeployments('pipe', 'phase', 'app', mockReq); - expect(service.listBuildjobs).toHaveBeenCalledWith('pipe', 'phase', 'app', mockUserGroups); + const result = await controller.getDeployments( + 'pipe', + 'phase', + 'app', + mockReq, + ); + expect(service.listBuildjobs).toHaveBeenCalledWith( + 'pipe', + 'phase', + 'app', + mockUserGroups, + ); expect(result).toEqual([{ name: 'build1' }]); }); @@ -127,7 +137,13 @@ describe('DeploymentsController', () => { // Add deployApp mock to the service service.deployApp = jest.fn(); - const result = await controller.deployTag('pipe', 'phase', 'app', 'v1.0.0', mockReq); + const result = await controller.deployTag( + 'pipe', + 'phase', + 'app', + 'v1.0.0', + mockReq, + ); expect(service.deployApp).toHaveBeenCalledWith( 'pipe', diff --git a/server/src/deployments/deployments.controller.ts b/server/src/deployments/deployments.controller.ts index b196aa58a..5db042df4 100644 --- a/server/src/deployments/deployments.controller.ts +++ b/server/src/deployments/deployments.controller.ts @@ -45,7 +45,12 @@ export class DeploymentsController { @Param('app') app: string, @Request() req: any, ) { - return this.deploymentsService.listBuildjobs(pipeline, phase, app, req.user.userGroups); + return this.deploymentsService.listBuildjobs( + pipeline, + phase, + app, + req.user.userGroups, + ); } @Post('/build/:pipeline/:phase/:app') @@ -178,7 +183,13 @@ export class DeploymentsController { @Param('tag') tag: string, @Request() req: any, ): Promise { - this.deploymentsService.deployApp(pipeline, phase, app, tag, req.user.userGroups); + this.deploymentsService.deployApp( + pipeline, + phase, + app, + tag, + req.user.userGroups, + ); return { message: `Deployment triggered for ${app} in ${pipeline} phase ${phase} with tag ${tag}`, status: 'success', diff --git a/server/src/deployments/deployments.service.spec.ts b/server/src/deployments/deployments.service.spec.ts index 3121fb7c2..dc86ff9da 100644 --- a/server/src/deployments/deployments.service.spec.ts +++ b/server/src/deployments/deployments.service.spec.ts @@ -17,7 +17,6 @@ const mockUser: IUser = { userGroups: mockUserGroups, } as any; - describe('DeploymentsService', () => { let service: DeploymentsService; let kubectl: jest.Mocked; @@ -81,7 +80,12 @@ describe('DeploymentsService', () => { it('should return empty items if no jobs', async () => { kubectl.getJobs.mockResolvedValue(undefined); appsService.getApp.mockResolvedValue(app); - const result = await service.listBuildjobs('pipe', 'phase', 'app', mockUserGroups); + const result = await service.listBuildjobs( + 'pipe', + 'phase', + 'app', + mockUserGroups, + ); expect(result).toEqual({ items: [] }); }); @@ -133,7 +137,12 @@ describe('DeploymentsService', () => { ], }); appsService.getApp.mockResolvedValue(app); - const result = await service.listBuildjobs('pipe', 'phase', 'app', mockUserGroups); + const result = await service.listBuildjobs( + 'pipe', + 'phase', + 'app', + mockUserGroups, + ); expect(Array.isArray(result)).toBe(true); expect(result[0].name).toBe('job1'); expect(result[0].app).toBe('app'); @@ -181,7 +190,12 @@ describe('DeploymentsService', () => { ], }); appsService.getApp.mockResolvedValue(app); - const result = await service.listBuildjobs('pipe', 'phase', 'app', mockUserGroups); + const result = await service.listBuildjobs( + 'pipe', + 'phase', + 'app', + mockUserGroups, + ); expect(result).toEqual([]); }); }); diff --git a/server/src/deployments/deployments.service.ts b/server/src/deployments/deployments.service.ts index ed3df2813..d1722deb4 100644 --- a/server/src/deployments/deployments.service.ts +++ b/server/src/deployments/deployments.service.ts @@ -35,7 +35,7 @@ export class DeploymentsService { pipelineName: string, phaseName: string, appName: string, - userGroups: string[] + userGroups: string[], ): Promise { const namespace = pipelineName + '-' + phaseName; const jobs = (await this.kubectl.getJobs(namespace)) as V1JobList; diff --git a/server/src/groups/groups.service.spec.ts b/server/src/groups/groups.service.spec.ts index 2b039dd6b..4b5f12f33 100644 --- a/server/src/groups/groups.service.spec.ts +++ b/server/src/groups/groups.service.spec.ts @@ -75,7 +75,10 @@ describe('GroupsService', () => { it('should update a group', async () => { const mockGroup = { id: '4', name: 'group4', description: 'desc4' }; prismaMock.userGroup.update.mockResolvedValueOnce(mockGroup); - const result = await service.update('4', { name: 'group4', description: 'desc4' }); + const result = await service.update('4', { + name: 'group4', + description: 'desc4', + }); expect(result).toBe(mockGroup); expect(prismaMock.userGroup.update).toHaveBeenCalledWith({ where: { id: '4' }, diff --git a/server/src/logs/logs.controller.ts b/server/src/logs/logs.controller.ts index fccbcca98..4978e37f5 100644 --- a/server/src/logs/logs.controller.ts +++ b/server/src/logs/logs.controller.ts @@ -36,7 +36,13 @@ export class LogsController { @Param('container') container: string, @Request() req: any, ) { - return this.logsService.getLogsHistory(pipeline, phase, app, container, req.user.userGroups); + return this.logsService.getLogsHistory( + pipeline, + phase, + app, + container, + req.user.userGroups, + ); } @Get('/:pipeline/:phase/:app/') @@ -58,6 +64,11 @@ export class LogsController { @Param('app') app: string, @Request() req: any, ) { - return this.logsService.startLogging(pipeline, phase, app, req.user.userGroups); + return this.logsService.startLogging( + pipeline, + phase, + app, + req.user.userGroups, + ); } } diff --git a/server/src/logs/logs.service.ts b/server/src/logs/logs.service.ts index de4cf559b..091786687 100644 --- a/server/src/logs/logs.service.ts +++ b/server/src/logs/logs.service.ts @@ -36,7 +36,7 @@ export class LogsService { appName: string, podName: string, container: string, - userGroups: string[] + userGroups: string[], ) { const logStream = new Stream.PassThrough(); diff --git a/server/src/notifications/notifications-db.service.spec.ts b/server/src/notifications/notifications-db.service.spec.ts index 74ccf5215..a0450bd6f 100644 --- a/server/src/notifications/notifications-db.service.spec.ts +++ b/server/src/notifications/notifications-db.service.spec.ts @@ -1,5 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { NotificationsDbService, CreateNotificationDto, NotificationDb } from './notifications-db.service'; +import { + NotificationsDbService, + CreateNotificationDto, + NotificationDb, +} from './notifications-db.service'; import { INotificationConfig } from './notifications.interface'; import { PrismaClient } from '@prisma/client'; @@ -102,7 +106,9 @@ describe('NotificationsDbService', () => { }); it('should handle database errors', async () => { - prisma.notification.findMany.mockRejectedValue(new Error('Database error')); + prisma.notification.findMany.mockRejectedValue( + new Error('Database error'), + ); await expect(service.findAll()).rejects.toThrow('Database error'); }); @@ -131,9 +137,13 @@ describe('NotificationsDbService', () => { }); it('should handle database errors', async () => { - prisma.notification.findUnique.mockRejectedValue(new Error('Database error')); + prisma.notification.findUnique.mockRejectedValue( + new Error('Database error'), + ); - await expect(service.findById(notificationId)).rejects.toThrow('Database error'); + await expect(service.findById(notificationId)).rejects.toThrow( + 'Database error', + ); }); }); @@ -238,7 +248,9 @@ describe('NotificationsDbService', () => { it('should handle database errors during creation', async () => { prisma.notification.create.mockRejectedValue(new Error('Database error')); - await expect(service.create(mockCreateNotificationDto)).rejects.toThrow('Database error'); + await expect(service.create(mockCreateNotificationDto)).rejects.toThrow( + 'Database error', + ); }); }); @@ -333,7 +345,9 @@ describe('NotificationsDbService', () => { it('should handle database errors during update', async () => { prisma.notification.update.mockRejectedValue(new Error('Database error')); - await expect(service.update(notificationId, { name: 'Updated' })).rejects.toThrow('Database error'); + await expect( + service.update(notificationId, { name: 'Updated' }), + ).rejects.toThrow('Database error'); }); }); @@ -358,7 +372,7 @@ describe('NotificationsDbService', () => { prisma.notification.findUnique.mockResolvedValue(null); await expect(service.delete(notificationId)).rejects.toThrow( - `Notification with id ${notificationId} not found` + `Notification with id ${notificationId} not found`, ); expect(prisma.notification.delete).not.toHaveBeenCalled(); }); @@ -367,7 +381,9 @@ describe('NotificationsDbService', () => { prisma.notification.findUnique.mockResolvedValue(mockNotificationDb); prisma.notification.delete.mockRejectedValue(new Error('Database error')); - await expect(service.delete(notificationId)).rejects.toThrow('Database error'); + await expect(service.delete(notificationId)).rejects.toThrow( + 'Database error', + ); }); }); @@ -522,7 +538,9 @@ describe('NotificationsDbService', () => { .mockResolvedValueOnce(mockNotificationDb); // Should not throw, but handle errors gracefully - await expect(service.migrateFromConfig(configNotifications)).resolves.not.toThrow(); + await expect( + service.migrateFromConfig(configNotifications), + ).resolves.not.toThrow(); expect(prisma.notification.create).toHaveBeenCalledTimes(2); }); diff --git a/server/src/notifications/notifications-db.service.ts b/server/src/notifications/notifications-db.service.ts index 13138d03d..fea0e94a3 100644 --- a/server/src/notifications/notifications-db.service.ts +++ b/server/src/notifications/notifications-db.service.ts @@ -42,15 +42,15 @@ export class NotificationsDbService { private readonly prisma = new PrismaClient(); async findAll(): Promise { - return await this.prisma.notification.findMany({ + return (await this.prisma.notification.findMany({ orderBy: { createdAt: 'desc' }, - }) as NotificationDb[]; + })) as NotificationDb[]; } async findById(id: string): Promise { - return await this.prisma.notification.findUnique({ + return (await this.prisma.notification.findUnique({ where: { id }, - }) as NotificationDb | null; + })) as NotificationDb | null; } async create(data: CreateNotificationDto): Promise { @@ -77,22 +77,27 @@ export class NotificationsDbService { break; } - const notification = await this.prisma.notification.create({ + const notification = (await this.prisma.notification.create({ data: notificationData, - }) as NotificationDb; + })) as NotificationDb; this.logger.log(`Notification '${notification.name}' created successfully`); return notification; } - async update(id: string, data: Partial): Promise { + async update( + id: string, + data: Partial, + ): Promise { const updateData: any = {}; if (data.name !== undefined) updateData.name = data.name; if (data.enabled !== undefined) updateData.enabled = data.enabled; if (data.type !== undefined) updateData.type = data.type; - if (data.pipelines !== undefined) updateData.pipelines = JSON.stringify(data.pipelines); - if (data.events !== undefined) updateData.events = JSON.stringify(data.events); + if (data.pipelines !== undefined) + updateData.pipelines = JSON.stringify(data.pipelines); + if (data.events !== undefined) + updateData.events = JSON.stringify(data.events); // Clear existing config fields and set new ones if (data.config && data.type) { @@ -119,19 +124,19 @@ export class NotificationsDbService { } } - const notification = await this.prisma.notification.update({ + const notification = (await this.prisma.notification.update({ where: { id }, data: updateData, - }) as NotificationDb; + })) as NotificationDb; this.logger.log(`Notification '${notification.name}' updated successfully`); return notification; } async delete(id: string): Promise { - const notification = await this.prisma.notification.findUnique({ + const notification = (await this.prisma.notification.findUnique({ where: { id }, - }) as NotificationDb | null; + })) as NotificationDb | null; if (!notification) { throw new Error(`Notification with id ${id} not found`); @@ -176,12 +181,18 @@ export class NotificationsDbService { // Get all notifications in the format expected by the notification service async getNotificationConfigs(): Promise { const notifications = await this.findAll(); - return notifications.map(notification => this.toNotificationConfig(notification)); + return notifications.map((notification) => + this.toNotificationConfig(notification), + ); } // Migration helper: Create notifications from YAML config - async migrateFromConfig(configNotifications: INotificationConfig[]): Promise { - this.logger.log('Starting migration of notifications from YAML config to database'); + async migrateFromConfig( + configNotifications: INotificationConfig[], + ): Promise { + this.logger.log( + 'Starting migration of notifications from YAML config to database', + ); for (const configNotification of configNotifications) { try { @@ -190,7 +201,9 @@ export class NotificationsDbService { }); if (existingNotification) { - this.logger.warn(`Notification '${configNotification.name}' already exists in database, skipping`); + this.logger.warn( + `Notification '${configNotification.name}' already exists in database, skipping`, + ); continue; } @@ -203,12 +216,18 @@ export class NotificationsDbService { config: configNotification.config as any, }); - this.logger.log(`Migrated notification '${configNotification.name}' to database`); + this.logger.log( + `Migrated notification '${configNotification.name}' to database`, + ); } catch (error) { - this.logger.error(`Failed to migrate notification '${configNotification.name}': ${error.message}`); + this.logger.error( + `Failed to migrate notification '${configNotification.name}': ${error.message}`, + ); } } - this.logger.log('Completed migration of notifications from YAML config to database'); + this.logger.log( + 'Completed migration of notifications from YAML config to database', + ); } } diff --git a/server/src/notifications/notifications.controller.spec.ts b/server/src/notifications/notifications.controller.spec.ts index e7dd7ae81..10c4632d6 100644 --- a/server/src/notifications/notifications.controller.spec.ts +++ b/server/src/notifications/notifications.controller.spec.ts @@ -1,7 +1,11 @@ import { Test, TestingModule } from '@nestjs/testing'; import { HttpException, HttpStatus } from '@nestjs/common'; import { NotificationsController } from './notifications.controller'; -import { NotificationsDbService, CreateNotificationDto, UpdateNotificationDto } from './notifications-db.service'; +import { + NotificationsDbService, + CreateNotificationDto, + UpdateNotificationDto, +} from './notifications-db.service'; import { INotificationConfig } from './notifications.interface'; describe('NotificationsController', () => { @@ -80,10 +84,15 @@ describe('NotificationsController', () => { }); it('should throw HttpException when service fails', async () => { - service.getNotificationConfigs.mockRejectedValue(new Error('Database error')); + service.getNotificationConfigs.mockRejectedValue( + new Error('Database error'), + ); await expect(controller.findAll()).rejects.toThrow( - new HttpException('Failed to fetch notifications', HttpStatus.INTERNAL_SERVER_ERROR) + new HttpException( + 'Failed to fetch notifications', + HttpStatus.INTERNAL_SERVER_ERROR, + ), ); }); }); @@ -102,29 +111,39 @@ describe('NotificationsController', () => { data: mockNotificationConfig, }); expect(service.findById).toHaveBeenCalledWith(notificationId); - expect(service.toNotificationConfig).toHaveBeenCalledWith(mockNotificationDb); + expect(service.toNotificationConfig).toHaveBeenCalledWith( + mockNotificationDb, + ); }); it('should throw NotFound when notification does not exist', async () => { service.findById.mockResolvedValue(null); await expect(controller.findOne(notificationId)).rejects.toThrow( - new HttpException('Notification not found', HttpStatus.NOT_FOUND) + new HttpException('Notification not found', HttpStatus.NOT_FOUND), ); }); it('should re-throw HttpException from service', async () => { - const httpError = new HttpException('Service error', HttpStatus.BAD_REQUEST); + const httpError = new HttpException( + 'Service error', + HttpStatus.BAD_REQUEST, + ); service.findById.mockRejectedValue(httpError); - await expect(controller.findOne(notificationId)).rejects.toThrow(httpError); + await expect(controller.findOne(notificationId)).rejects.toThrow( + httpError, + ); }); it('should throw Internal Server Error for other errors', async () => { service.findById.mockRejectedValue(new Error('Database error')); await expect(controller.findOne(notificationId)).rejects.toThrow( - new HttpException('Failed to fetch notification', HttpStatus.INTERNAL_SERVER_ERROR) + new HttpException( + 'Failed to fetch notification', + HttpStatus.INTERNAL_SERVER_ERROR, + ), ); }); }); @@ -160,7 +179,10 @@ describe('NotificationsController', () => { const invalidDto = { ...createDto, name: '' }; await expect(controller.create(invalidDto)).rejects.toThrow( - new HttpException('Name and type are required fields', HttpStatus.BAD_REQUEST) + new HttpException( + 'Name and type are required fields', + HttpStatus.BAD_REQUEST, + ), ); }); @@ -168,7 +190,10 @@ describe('NotificationsController', () => { const invalidDto = { ...createDto, type: undefined as any }; await expect(controller.create(invalidDto)).rejects.toThrow( - new HttpException('Name and type are required fields', HttpStatus.BAD_REQUEST) + new HttpException( + 'Name and type are required fields', + HttpStatus.BAD_REQUEST, + ), ); }); @@ -176,7 +201,10 @@ describe('NotificationsController', () => { const invalidDto = { ...createDto, type: 'invalid' as any }; await expect(controller.create(invalidDto)).rejects.toThrow( - new HttpException('Invalid notification type. Must be slack, webhook, or discord', HttpStatus.BAD_REQUEST) + new HttpException( + 'Invalid notification type. Must be slack, webhook, or discord', + HttpStatus.BAD_REQUEST, + ), ); }); @@ -188,7 +216,10 @@ describe('NotificationsController', () => { }; await expect(controller.create(invalidDto)).rejects.toThrow( - new HttpException('Slack notifications require a webhook URL', HttpStatus.BAD_REQUEST) + new HttpException( + 'Slack notifications require a webhook URL', + HttpStatus.BAD_REQUEST, + ), ); }); @@ -200,7 +231,10 @@ describe('NotificationsController', () => { }; await expect(controller.create(invalidDto)).rejects.toThrow( - new HttpException('Webhook notifications require a URL', HttpStatus.BAD_REQUEST) + new HttpException( + 'Webhook notifications require a URL', + HttpStatus.BAD_REQUEST, + ), ); }); @@ -212,12 +246,18 @@ describe('NotificationsController', () => { }; await expect(controller.create(invalidDto)).rejects.toThrow( - new HttpException('Discord notifications require a webhook URL', HttpStatus.BAD_REQUEST) + new HttpException( + 'Discord notifications require a webhook URL', + HttpStatus.BAD_REQUEST, + ), ); }); it('should re-throw HttpException from service', async () => { - const httpError = new HttpException('Service error', HttpStatus.BAD_REQUEST); + const httpError = new HttpException( + 'Service error', + HttpStatus.BAD_REQUEST, + ); service.create.mockRejectedValue(httpError); await expect(controller.create(createDto)).rejects.toThrow(httpError); @@ -227,7 +267,10 @@ describe('NotificationsController', () => { service.create.mockRejectedValue(new Error('Database error')); await expect(controller.create(createDto)).rejects.toThrow( - new HttpException('Failed to create notification', HttpStatus.INTERNAL_SERVER_ERROR) + new HttpException( + 'Failed to create notification', + HttpStatus.INTERNAL_SERVER_ERROR, + ), ); }); }); @@ -243,12 +286,18 @@ describe('NotificationsController', () => { const updatedNotificationDb = { ...mockNotificationDb, name: updateDto.name || mockNotificationDb.name, - enabled: updateDto.enabled !== undefined ? updateDto.enabled : mockNotificationDb.enabled, + enabled: + updateDto.enabled !== undefined + ? updateDto.enabled + : mockNotificationDb.enabled, }; const updatedNotificationConfig = { ...mockNotificationConfig, name: updateDto.name || mockNotificationConfig.name, - enabled: updateDto.enabled !== undefined ? updateDto.enabled : mockNotificationConfig.enabled, + enabled: + updateDto.enabled !== undefined + ? updateDto.enabled + : mockNotificationConfig.enabled, }; service.findById.mockResolvedValue(mockNotificationDb); @@ -269,8 +318,10 @@ describe('NotificationsController', () => { it('should throw NotFound when notification does not exist', async () => { service.findById.mockResolvedValue(null); - await expect(controller.update(notificationId, updateDto)).rejects.toThrow( - new HttpException('Notification not found', HttpStatus.NOT_FOUND) + await expect( + controller.update(notificationId, updateDto), + ).rejects.toThrow( + new HttpException('Notification not found', HttpStatus.NOT_FOUND), ); }); @@ -278,8 +329,13 @@ describe('NotificationsController', () => { service.findById.mockResolvedValue(mockNotificationDb); const invalidUpdateDto = { ...updateDto, type: 'invalid' as any }; - await expect(controller.update(notificationId, invalidUpdateDto)).rejects.toThrow( - new HttpException('Invalid notification type. Must be slack, webhook, or discord', HttpStatus.BAD_REQUEST) + await expect( + controller.update(notificationId, invalidUpdateDto), + ).rejects.toThrow( + new HttpException( + 'Invalid notification type. Must be slack, webhook, or discord', + HttpStatus.BAD_REQUEST, + ), ); }); @@ -290,25 +346,40 @@ describe('NotificationsController', () => { config: { channel: '#general' }, // missing URL }; - await expect(controller.update(notificationId, invalidUpdateDto)).rejects.toThrow( - new HttpException('Slack notifications require a webhook URL', HttpStatus.BAD_REQUEST) + await expect( + controller.update(notificationId, invalidUpdateDto), + ).rejects.toThrow( + new HttpException( + 'Slack notifications require a webhook URL', + HttpStatus.BAD_REQUEST, + ), ); }); it('should re-throw HttpException from service', async () => { - const httpError = new HttpException('Service error', HttpStatus.BAD_REQUEST); + const httpError = new HttpException( + 'Service error', + HttpStatus.BAD_REQUEST, + ); service.findById.mockResolvedValue(mockNotificationDb); service.update.mockRejectedValue(httpError); - await expect(controller.update(notificationId, updateDto)).rejects.toThrow(httpError); + await expect( + controller.update(notificationId, updateDto), + ).rejects.toThrow(httpError); }); it('should throw Internal Server Error for other errors', async () => { service.findById.mockResolvedValue(mockNotificationDb); service.update.mockRejectedValue(new Error('Database error')); - await expect(controller.update(notificationId, updateDto)).rejects.toThrow( - new HttpException('Failed to update notification', HttpStatus.INTERNAL_SERVER_ERROR) + await expect( + controller.update(notificationId, updateDto), + ).rejects.toThrow( + new HttpException( + 'Failed to update notification', + HttpStatus.INTERNAL_SERVER_ERROR, + ), ); }); }); @@ -332,7 +403,7 @@ describe('NotificationsController', () => { service.delete.mockRejectedValue(new Error('Notification not found')); await expect(controller.remove(notificationId)).rejects.toThrow( - new HttpException('Notification not found', HttpStatus.NOT_FOUND) + new HttpException('Notification not found', HttpStatus.NOT_FOUND), ); }); @@ -340,15 +411,21 @@ describe('NotificationsController', () => { service.delete.mockRejectedValue(new Error('Database error')); await expect(controller.remove(notificationId)).rejects.toThrow( - new HttpException('Failed to delete notification', HttpStatus.INTERNAL_SERVER_ERROR) + new HttpException( + 'Failed to delete notification', + HttpStatus.INTERNAL_SERVER_ERROR, + ), ); }); }); describe('validateNotificationConfig', () => { it('should validate slack config successfully', () => { - const config = { url: 'https://hooks.slack.com/test', channel: '#general' }; - + const config = { + url: 'https://hooks.slack.com/test', + channel: '#general', + }; + expect(() => { (controller as any).validateNotificationConfig('slack', config); }).not.toThrow(); @@ -356,7 +433,7 @@ describe('NotificationsController', () => { it('should validate webhook config successfully', () => { const config = { url: 'https://webhook.example.com', secret: 'secret' }; - + expect(() => { (controller as any).validateNotificationConfig('webhook', config); }).not.toThrow(); @@ -364,7 +441,7 @@ describe('NotificationsController', () => { it('should validate discord config successfully', () => { const config = { url: 'https://discord.com/api/webhooks/test' }; - + expect(() => { (controller as any).validateNotificationConfig('discord', config); }).not.toThrow(); diff --git a/server/src/notifications/notifications.controller.ts b/server/src/notifications/notifications.controller.ts index 5390594aa..ceb199399 100644 --- a/server/src/notifications/notifications.controller.ts +++ b/server/src/notifications/notifications.controller.ts @@ -10,7 +10,11 @@ import { HttpStatus, Logger, } from '@nestjs/common'; -import { NotificationsDbService, CreateNotificationDto, UpdateNotificationDto } from './notifications-db.service'; +import { + NotificationsDbService, + CreateNotificationDto, + UpdateNotificationDto, +} from './notifications-db.service'; import { INotificationConfig } from './notifications.interface'; export interface ApiResponse { @@ -23,12 +27,15 @@ export interface ApiResponse { export class NotificationsController { private readonly logger = new Logger(NotificationsController.name); - constructor(private readonly notificationsDbService: NotificationsDbService) {} + constructor( + private readonly notificationsDbService: NotificationsDbService, + ) {} @Get() async findAll(): Promise> { try { - const notifications = await this.notificationsDbService.getNotificationConfigs(); + const notifications = + await this.notificationsDbService.getNotificationConfigs(); return { success: true, data: notifications, @@ -43,7 +50,9 @@ export class NotificationsController { } @Get(':id') - async findOne(@Param('id') id: string): Promise> { + async findOne( + @Param('id') id: string, + ): Promise> { try { const notification = await this.notificationsDbService.findById(id); if (!notification) { @@ -67,7 +76,9 @@ export class NotificationsController { } @Post() - async create(@Body() createNotificationDto: CreateNotificationDto): Promise> { + async create( + @Body() createNotificationDto: CreateNotificationDto, + ): Promise> { try { // Validate required fields if (!createNotificationDto.name || !createNotificationDto.type) { @@ -78,7 +89,9 @@ export class NotificationsController { } // Validate notification type - if (!['slack', 'webhook', 'discord'].includes(createNotificationDto.type)) { + if ( + !['slack', 'webhook', 'discord'].includes(createNotificationDto.type) + ) { throw new HttpException( 'Invalid notification type. Must be slack, webhook, or discord', HttpStatus.BAD_REQUEST, @@ -86,10 +99,15 @@ export class NotificationsController { } // Validate config based on type - this.validateNotificationConfig(createNotificationDto.type, createNotificationDto.config); + this.validateNotificationConfig( + createNotificationDto.type, + createNotificationDto.config, + ); + + const notification = await this.notificationsDbService.create( + createNotificationDto, + ); - const notification = await this.notificationsDbService.create(createNotificationDto); - return { success: true, data: this.notificationsDbService.toNotificationConfig(notification), @@ -114,13 +132,17 @@ export class NotificationsController { ): Promise> { try { // Check if notification exists - const existingNotification = await this.notificationsDbService.findById(id); + const existingNotification = + await this.notificationsDbService.findById(id); if (!existingNotification) { throw new HttpException('Notification not found', HttpStatus.NOT_FOUND); } // Validate notification type if provided - if (updateNotificationDto.type && !['slack', 'webhook', 'discord'].includes(updateNotificationDto.type)) { + if ( + updateNotificationDto.type && + !['slack', 'webhook', 'discord'].includes(updateNotificationDto.type) + ) { throw new HttpException( 'Invalid notification type. Must be slack, webhook, or discord', HttpStatus.BAD_REQUEST, @@ -129,11 +151,17 @@ export class NotificationsController { // Validate config if provided if (updateNotificationDto.config && updateNotificationDto.type) { - this.validateNotificationConfig(updateNotificationDto.type, updateNotificationDto.config); + this.validateNotificationConfig( + updateNotificationDto.type, + updateNotificationDto.config, + ); } - const notification = await this.notificationsDbService.update(id, updateNotificationDto); - + const notification = await this.notificationsDbService.update( + id, + updateNotificationDto, + ); + return { success: true, data: this.notificationsDbService.toNotificationConfig(notification), @@ -155,7 +183,7 @@ export class NotificationsController { async remove(@Param('id') id: string): Promise { try { await this.notificationsDbService.delete(id); - + return { success: true, message: 'Notification deleted successfully', diff --git a/server/src/notifications/notifications.module.ts b/server/src/notifications/notifications.module.ts index d31ebf2db..b450cab51 100644 --- a/server/src/notifications/notifications.module.ts +++ b/server/src/notifications/notifications.module.ts @@ -8,7 +8,12 @@ import { KubernetesModule } from '../kubernetes/kubernetes.module'; @Global() @Module({ controllers: [NotificationsController], - providers: [NotificationsService, NotificationsDbService, AuditModule, KubernetesModule], + providers: [ + NotificationsService, + NotificationsDbService, + AuditModule, + KubernetesModule, + ], exports: [NotificationsService, NotificationsDbService], }) export class NotificationsModule {} diff --git a/server/src/notifications/notifications.service.spec.ts b/server/src/notifications/notifications.service.spec.ts index f5e4fce44..8dff9e138 100644 --- a/server/src/notifications/notifications.service.spec.ts +++ b/server/src/notifications/notifications.service.spec.ts @@ -21,11 +21,16 @@ describe('NotificationsService', () => { eventsGateway = { sendEvent: jest.fn() } as any; auditService = { log: jest.fn() } as any; kubectl = { createEvent: jest.fn() } as any; - notificationsDbService = { - getNotificationConfigs: jest.fn().mockResolvedValue([]) + notificationsDbService = { + getNotificationConfigs: jest.fn().mockResolvedValue([]), } as any; - service = new NotificationsService(eventsGateway, auditService, kubectl, notificationsDbService); + service = new NotificationsService( + eventsGateway, + auditService, + kubectl, + notificationsDbService, + ); service.setConfig({ notifications: [], } as any); diff --git a/server/src/notifications/notifications.service.ts b/server/src/notifications/notifications.service.ts index 3100bc9e6..715d70c60 100644 --- a/server/src/notifications/notifications.service.ts +++ b/server/src/notifications/notifications.service.ts @@ -41,7 +41,8 @@ export class NotificationsService { // Load notifications from database instead of config try { - const notifications = await this.notificationsDbService.getNotificationConfigs(); + const notifications = + await this.notificationsDbService.getNotificationConfigs(); this.sendAllCustomNotification(notifications, message); } catch (error) { this.logger.error('Failed to load notifications from database', error); @@ -242,9 +243,15 @@ export class NotificationsService { return; } - this.logger.log('Starting migration of notifications from config to database'); - await this.notificationsDbService.migrateFromConfig(this.config.notifications); - this.logger.log('Completed migration of notifications from config to database'); + this.logger.log( + 'Starting migration of notifications from config to database', + ); + await this.notificationsDbService.migrateFromConfig( + this.config.notifications, + ); + this.logger.log( + 'Completed migration of notifications from config to database', + ); } // Method to get notifications from database (for admin interface) diff --git a/server/src/pipelines/pipelines.controller.spec.ts b/server/src/pipelines/pipelines.controller.spec.ts index f257c1c53..fe7626c2d 100644 --- a/server/src/pipelines/pipelines.controller.spec.ts +++ b/server/src/pipelines/pipelines.controller.spec.ts @@ -133,7 +133,10 @@ describe('PipelinesController', () => { it('should get all apps for a pipeline', async () => { const req = { user: mockJWT }; const result = await controller.getPipelineApps('pipeline1', req); - expect(service.getPipelineWithApps).toHaveBeenCalledWith('pipeline1', ["group1", "group2"]); + expect(service.getPipelineWithApps).toHaveBeenCalledWith('pipeline1', [ + 'group1', + 'group2', + ]); expect(result).toEqual([{ name: 'app1' }]); }); }); diff --git a/server/src/pipelines/pipelines.controller.ts b/server/src/pipelines/pipelines.controller.ts index b71f9fb24..ad21a82da 100644 --- a/server/src/pipelines/pipelines.controller.ts +++ b/server/src/pipelines/pipelines.controller.ts @@ -50,9 +50,7 @@ export class PipelinesController { isArray: false, }) @ApiOperation({ summary: 'Get all pipelines' }) - async getPipelines( - @Request() req: any - ) { + async getPipelines(@Request() req: any) { return this.pipelinesService.listPipelines(req.user.userGroups); } @@ -199,8 +197,11 @@ export class PipelinesController { @ApiOperation({ summary: 'Get all apps for a pipeline' }) async getPipelineApps( @Param('pipeline') pipeline: string, - @Request() req: any + @Request() req: any, ) { - return this.pipelinesService.getPipelineWithApps(pipeline, req.user.userGroups); + return this.pipelinesService.getPipelineWithApps( + pipeline, + req.user.userGroups, + ); } } diff --git a/server/src/pipelines/pipelines.service.spec.ts b/server/src/pipelines/pipelines.service.spec.ts index 8aedd782b..f5918ea6b 100644 --- a/server/src/pipelines/pipelines.service.spec.ts +++ b/server/src/pipelines/pipelines.service.spec.ts @@ -129,7 +129,10 @@ describe('PipelinesService', () => { }, ], }); - const ctx = await service.getContext('pipe1', 'dev', ['group1', 'group2']); + const ctx = await service.getContext('pipe1', 'dev', [ + 'group1', + 'group2', + ]); expect(ctx).toBe('ctx1'); }); @@ -137,7 +140,10 @@ describe('PipelinesService', () => { service.listPipelines = jest.fn().mockResolvedValue({ items: [], }); - const ctx = await service.getContext('pipe1', 'dev', ['group1', 'group2']); + const ctx = await service.getContext('pipe1', 'dev', [ + 'group1', + 'group2', + ]); expect(ctx).toBe('missing-pipe1-dev'); }); }); diff --git a/server/src/pipelines/pipelines.service.ts b/server/src/pipelines/pipelines.service.ts index 4a99f5fef..d6c69f7ff 100644 --- a/server/src/pipelines/pipelines.service.ts +++ b/server/src/pipelines/pipelines.service.ts @@ -1,5 +1,9 @@ import { Injectable, Logger } from '@nestjs/common'; -import { IPipelineList, IPipeline, IKubectlPipelineList } from './pipelines.interface'; +import { + IPipelineList, + IPipeline, + IKubectlPipelineList, +} from './pipelines.interface'; import { KubernetesService } from '../kubernetes/kubernetes.service'; import { Buildpack } from '../config/buildpack/buildpack'; import { IUser } from '../auth/auth.interface'; @@ -16,7 +20,9 @@ export class PipelinesService { private notificationsService: NotificationsService, ) {} - public async listPipelines(userGroups: string[] = []): Promise { + public async listPipelines( + userGroups: string[] = [], + ): Promise { //this.logger.debug('listPipelines for userGroups: ' + userGroups.join(', ')); let pipelines = await this.kubectl.getPipelinesList(userGroups); @@ -29,7 +35,10 @@ export class PipelinesService { return ret; } - public async getPipelineWithApps(pipelineName: string, userGroups: string[] = []): Promise { + public async getPipelineWithApps( + pipelineName: string, + userGroups: string[] = [], + ): Promise { this.logger.debug('listApps in ' + pipelineName); await this.kubectl.setCurrentContext( @@ -49,7 +58,11 @@ export class PipelinesService { if (pipeline) { for (const phase of pipeline.phases) { if (phase.enabled == true) { - const contextName = await this.getContext(pipelineName, phase.name, userGroups); + const contextName = await this.getContext( + pipelineName, + phase.name, + userGroups, + ); if (contextName) { const namespace = pipelineName + '-' + phase.name; const apps = await this.kubectl.getAppsList(namespace, contextName); diff --git a/server/src/repo/git/bitbucket.ts b/server/src/repo/git/bitbucket.ts index c85d1586b..630b87848 100644 --- a/server/src/repo/git/bitbucket.ts +++ b/server/src/repo/git/bitbucket.ts @@ -260,7 +260,7 @@ export class BitbucketApi extends Repo { branch = refs[refs.length - 1]; ssh_url = body.repository.ssh_url; } else if (body.pull_request != undefined) { - (action = body.action), (branch = body.pull_request.head.ref); + ((action = body.action), (branch = body.pull_request.head.ref)); ssh_url = body.pull_request.head.repo.ssh_url; } else { ssh_url = body.repository.ssh_url; diff --git a/server/src/repo/git/gitea.ts b/server/src/repo/git/gitea.ts index 00258d8c2..d849839bb 100644 --- a/server/src/repo/git/gitea.ts +++ b/server/src/repo/git/gitea.ts @@ -236,7 +236,7 @@ export class GiteaApi extends Repo { branch = refs[refs.length - 1]; ssh_url = body.repository.ssh_url; } else if (body.pull_request != undefined) { - (action = body.action), (branch = body.pull_request.head.ref); + ((action = body.action), (branch = body.pull_request.head.ref)); ssh_url = body.pull_request.head.repo.ssh_url; } else { ssh_url = body.repository.ssh_url; diff --git a/server/src/repo/git/github.ts b/server/src/repo/git/github.ts index dbb580c2b..0667cf743 100644 --- a/server/src/repo/git/github.ts +++ b/server/src/repo/git/github.ts @@ -279,7 +279,7 @@ export class GithubApi extends Repo { branch = refs[refs.length - 1]; ssh_url = body.repository.ssh_url; } else if (body.pull_request != undefined) { - (action = body.action), (branch = body.pull_request.head.ref); + ((action = body.action), (branch = body.pull_request.head.ref)); ssh_url = body.pull_request.head.repo.ssh_url; } else { ssh_url = body.repository.ssh_url; diff --git a/server/src/repo/git/gitlab.ts b/server/src/repo/git/gitlab.ts index a6ffe1915..a356d00d4 100644 --- a/server/src/repo/git/gitlab.ts +++ b/server/src/repo/git/gitlab.ts @@ -267,7 +267,7 @@ export class GitlabApi extends Repo { branch = refs[refs.length - 1]; ssh_url = body.project.git_ssh_url; } else if (body.pull_request != undefined) { - (action = body.action), (branch = body.pull_request.head.ref); + ((action = body.action), (branch = body.pull_request.head.ref)); ssh_url = body.pull_request.head.repo.ssh_url; } else { ssh_url = body.project.git_ssh_url; diff --git a/server/src/repo/git/gogs.ts b/server/src/repo/git/gogs.ts index a2c5af378..ac612c910 100644 --- a/server/src/repo/git/gogs.ts +++ b/server/src/repo/git/gogs.ts @@ -241,7 +241,7 @@ export class GogsApi extends Repo { branch = refs[refs.length - 1]; ssh_url = body.repository.ssh_url; } else if (body.pull_request != undefined) { - (action = body.action), (branch = body.pull_request.head.ref); + ((action = body.action), (branch = body.pull_request.head.ref)); ssh_url = body.pull_request.head.repo.ssh_url; } else { ssh_url = body.repository.ssh_url; diff --git a/server/src/repo/repo.service.spec.ts b/server/src/repo/repo.service.spec.ts index fc11edf18..4608c97df 100644 --- a/server/src/repo/repo.service.spec.ts +++ b/server/src/repo/repo.service.spec.ts @@ -178,7 +178,7 @@ describe('RepoService', () => { 'feature-branch', 'git@github.com:user/repo.git', undefined, - ['admin'] + ['admin'], ); }); @@ -209,7 +209,7 @@ describe('RepoService', () => { 'feature-branch', 'git@github.com:user/repo.git', undefined, - ['admin'] + ['admin'], ); }); @@ -239,7 +239,7 @@ describe('RepoService', () => { 'feature-branch', 'feature-branch', 'git@github.com:user/repo.git', - ['admin'] + ['admin'], ); }); @@ -266,7 +266,7 @@ describe('RepoService', () => { await service.handleWebhook('github', headers, body); expect(console.log).toHaveBeenCalledWith( - 'webhook pull request action not handled: undefined' + 'webhook pull request action not handled: undefined', ); expect(appsService.createPRApp).not.toHaveBeenCalled(); expect(appsService.deletePRApp).not.toHaveBeenCalled(); @@ -298,7 +298,7 @@ describe('RepoService', () => { 'pr-branch', 'git@gitea.example.com:user/repo.git', undefined, - ['admin'] + ['admin'], ); }); @@ -324,7 +324,7 @@ describe('RepoService', () => { await service.handleWebhook('github', headers, {}); expect(console.log).toHaveBeenCalledWith( - 'webhook pull request action not handled: undefined' + 'webhook pull request action not handled: undefined', ); expect(appsService.createPRApp).not.toHaveBeenCalled(); expect(appsService.deletePRApp).not.toHaveBeenCalled(); @@ -363,7 +363,7 @@ describe('RepoService', () => { await (service as any).handleWebhookPullRequest(mockWebhook); expect(console.log).toHaveBeenCalledWith( - 'webhook pull request action not handled: synchronize' + 'webhook pull request action not handled: synchronize', ); expect(appsService.createPRApp).not.toHaveBeenCalled(); expect(appsService.deletePRApp).not.toHaveBeenCalled(); diff --git a/server/src/repo/repo.service.ts b/server/src/repo/repo.service.ts index 6d1f9aed1..7211cbd49 100644 --- a/server/src/repo/repo.service.ts +++ b/server/src/repo/repo.service.ts @@ -361,7 +361,7 @@ export class RepoService { webhook.branch, webhook.repo.ssh_url, undefined, - ['admin'] // return all pipelines to search for the app + ['admin'], // return all pipelines to search for the app ); // "undefined" will create the app in all pipelines break; case 'closed': @@ -369,7 +369,7 @@ export class RepoService { webhook.branch, webhook.branch, webhook.repo.ssh_url, - ['admin'] // return all pipelines to search for the app + ['admin'], // return all pipelines to search for the app ); break; default: diff --git a/server/src/roles/roles.controller.ts b/server/src/roles/roles.controller.ts index 564d0fd45..175f85eb5 100644 --- a/server/src/roles/roles.controller.ts +++ b/server/src/roles/roles.controller.ts @@ -97,10 +97,7 @@ export class RolesController { isArray: false, }) @ApiOperation({ summary: 'Update a Role' }) - async updateRole( - @Param('roleId') roleId: string, - @Body() role: any, - ) { + async updateRole(@Param('roleId') roleId: string, @Body() role: any) { return this.rolesService.updateRole(roleId, role); } } diff --git a/server/src/roles/roles.service.ts b/server/src/roles/roles.service.ts index 93c649de1..b72b8c85b 100644 --- a/server/src/roles/roles.service.ts +++ b/server/src/roles/roles.service.ts @@ -29,18 +29,20 @@ export class RolesService { async getPermissions(roleId: string): Promise { this.logger.debug(`getPermissions for roleId: ${roleId}`); - return this.prisma.role.findUnique({ - where: { id: roleId }, - select: { - permissions: { - select: { - id: true, - resource: true, - action: true, + return this.prisma.role + .findUnique({ + where: { id: roleId }, + select: { + permissions: { + select: { + id: true, + resource: true, + action: true, + }, }, }, - }, - }).then(role => role?.permissions || []); + }) + .then((role) => role?.permissions || []); } async createRole(roleData: any): Promise { @@ -71,7 +73,7 @@ export class RolesService { where: { id: roleId }, }); } - + async updateRole(roleId: string, roleData: any): Promise { //this.logger.debug(`updateRole with roleId: ${roleId} and data: ${JSON.stringify(roleData)}`); if (!roleId) { diff --git a/server/src/security/security.controller.ts b/server/src/security/security.controller.ts index b7d64fd6e..f386a5a4e 100644 --- a/server/src/security/security.controller.ts +++ b/server/src/security/security.controller.ts @@ -1,4 +1,11 @@ -import { Controller, Get, Param, Query, Request, UseGuards } from '@nestjs/common'; +import { + Controller, + Get, + Param, + Query, + Request, + UseGuards, +} from '@nestjs/common'; import { SecurityService } from './security.service'; import { ApiBearerAuth, @@ -30,7 +37,12 @@ export class SecurityController { @Param('app') app: string, @Request() req: any, ) { - return this.securityService.startScan(pipeline, phase, app, req.user.userGroups); + return this.securityService.startScan( + pipeline, + phase, + app, + req.user.userGroups, + ); } @Get(':pipeline/:phase/:app/scan/result') diff --git a/server/src/security/security.service.spec.ts b/server/src/security/security.service.spec.ts index a59b7a6e9..7258ed134 100644 --- a/server/src/security/security.service.spec.ts +++ b/server/src/security/security.service.spec.ts @@ -36,7 +36,13 @@ describe('SecurityService', () => { pipelinesService.getContext.mockResolvedValue('ctx'); appsService.getApp.mockResolvedValue(app); kubectl.getLatestPodByLabel.mockResolvedValue({}); - const result = await service.getScanResult('pipe', 'phase', 'app', false, ['group1', 'group2']); + const result = await service.getScanResult( + 'pipe', + 'phase', + 'app', + false, + ['group1', 'group2'], + ); expect(result.status).toBe('error'); expect(result.message).toBe('no vulnerability scan pod found'); }); @@ -46,7 +52,13 @@ describe('SecurityService', () => { appsService.getApp.mockResolvedValue(app); kubectl.getLatestPodByLabel.mockResolvedValue({ name: 'pod1' }); kubectl.getVulnerabilityScanLogs.mockResolvedValue(''); - const result = await service.getScanResult('pipe', 'phase', 'app', false, ['group1', 'group2']); + const result = await service.getScanResult( + 'pipe', + 'phase', + 'app', + false, + ['group1', 'group2'], + ); expect(result.status).toBe('running'); expect(result.message).toBe('no vulnerability scan logs found'); }); @@ -64,7 +76,10 @@ describe('SecurityService', () => { appsService.getApp.mockResolvedValue(app1); kubectl.getLatestPodByLabel.mockResolvedValue({ name: 'pod1' }); kubectl.getVulnerabilityScanLogs.mockResolvedValue({ Results: [] }); - const result = await service.getScanResult('pipe', 'phase', 'app', true, ['group1', 'group2']); + const result = await service.getScanResult('pipe', 'phase', 'app', true, [ + 'group1', + 'group2', + ]); expect(result.status).toBe('ok'); expect(result.message).toBe('vulnerability scan result'); expect(result.logs).toBeDefined(); @@ -119,7 +134,10 @@ describe('SecurityService', () => { }, } as IKubectlApp; appsService.getApp.mockResolvedValue(app1); - const result = await service.startScan('pipe', 'phase', 'app', ['group1', 'group2']); + const result = await service.startScan('pipe', 'phase', 'app', [ + 'group1', + 'group2', + ]); expect(kubectl.createScanImageJob).toHaveBeenCalledWith( 'pipe-phase', 'app', @@ -142,7 +160,10 @@ describe('SecurityService', () => { }, } as IKubectlApp; appsService.getApp.mockResolvedValue(app1); - const result = await service.startScan('pipe', 'phase', 'app', ['group1', 'group2']); + const result = await service.startScan('pipe', 'phase', 'app', [ + 'group1', + 'group2', + ]); expect(kubectl.createScanImageJob).toHaveBeenCalledWith( 'pipe-phase', 'app', diff --git a/server/src/security/security.service.ts b/server/src/security/security.service.ts index afb385165..c4d205db8 100644 --- a/server/src/security/security.service.ts +++ b/server/src/security/security.service.ts @@ -21,7 +21,11 @@ export class SecurityService { logdetails: boolean, userGroups: string[], ) { - const contextName = await this.pipelinesService.getContext(pipeline, phase, userGroups); + const contextName = await this.pipelinesService.getContext( + pipeline, + phase, + userGroups, + ); const namespace = pipeline + '-' + phase; const scanResult = { @@ -43,7 +47,12 @@ export class SecurityService { return scanResult; } - const appresult = await this.appsService.getApp(pipeline, phase, appName, userGroups); + const appresult = await this.appsService.getApp( + pipeline, + phase, + appName, + userGroups, + ); const app = appresult as IKubectlApp; @@ -134,11 +143,25 @@ export class SecurityService { return summary; } - public async startScan(pipeline: string, phase: string, appName: string, userGroups: string[]) { - const contextName = await this.pipelinesService.getContext(pipeline, phase, userGroups); + public async startScan( + pipeline: string, + phase: string, + appName: string, + userGroups: string[], + ) { + const contextName = await this.pipelinesService.getContext( + pipeline, + phase, + userGroups, + ); const namespace = pipeline + '-' + phase; - const appresult = await this.appsService.getApp(pipeline, phase, appName, userGroups); + const appresult = await this.appsService.getApp( + pipeline, + phase, + appName, + userGroups, + ); const app = appresult as IKubectlApp; diff --git a/server/src/token/token.controller.spec.ts b/server/src/token/token.controller.spec.ts index 9a2707dca..4e7a34489 100644 --- a/server/src/token/token.controller.spec.ts +++ b/server/src/token/token.controller.spec.ts @@ -15,9 +15,7 @@ describe('TokenController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [TokenController], - providers: [ - { provide: TokenService, useValue: mockTokenService } - ], + providers: [{ provide: TokenService, useValue: mockTokenService }], }).compile(); controller = module.get(TokenController); @@ -39,7 +37,9 @@ describe('TokenController', () => { describe('createToken', () => { it('should create a token for current user', async () => { const tokenData = { name: 'token1', expiresAt: '2025-01-01' }; - const req = { user: { userId: 'u1', username: 'test', role: 'admin', userGroups: [] } }; + const req = { + user: { userId: 'u1', username: 'test', role: 'admin', userGroups: [] }, + }; const result = await controller.createToken(tokenData, req); expect(tokenService.create).toHaveBeenCalledWith( 'token1', @@ -47,14 +47,13 @@ describe('TokenController', () => { 'u1', 'test', 'admin', - [] + [], ); expect(result).toEqual({ id: '1', name: 'token1' }); }); it('should throw if missing data', async () => { - await expect(controller.createToken({}, { user: {} })) - .rejects.toThrow(); + await expect(controller.createToken({}, { user: {} })).rejects.toThrow(); }); }); @@ -90,8 +89,12 @@ describe('TokenController', () => { }); it('should throw if id or userId missing', async () => { - await expect(controller.deleteMyToken('', { user: { userId: 'u1' } })).rejects.toThrow(); - await expect(controller.deleteMyToken('1', { user: {} })).rejects.toThrow(); + await expect( + controller.deleteMyToken('', { user: { userId: 'u1' } }), + ).rejects.toThrow(); + await expect( + controller.deleteMyToken('1', { user: {} }), + ).rejects.toThrow(); }); }); }); diff --git a/server/src/token/token.controller.ts b/server/src/token/token.controller.ts index 6d0284f9a..94d21c9b1 100644 --- a/server/src/token/token.controller.ts +++ b/server/src/token/token.controller.ts @@ -30,7 +30,7 @@ export class TokenController { @Get('/') @UseGuards(JwtAuthGuard, PermissionsGuard) - @Permissions('token:write','token:read') + @Permissions('token:write', 'token:read') @ApiBearerAuth('bearerAuth') @ApiForbiddenResponse({ description: 'Error: Unauthorized', diff --git a/server/src/token/token.module.ts b/server/src/token/token.module.ts index 49eee714f..fa62c87f3 100644 --- a/server/src/token/token.module.ts +++ b/server/src/token/token.module.ts @@ -7,7 +7,13 @@ import { JwtService } from '@nestjs/jwt'; import { RolesService } from '../roles/roles.service'; @Module({ - providers: [TokenService, AuthService, UsersService, JwtService, RolesService], + providers: [ + TokenService, + AuthService, + UsersService, + JwtService, + RolesService, + ], controllers: [TokenController], }) export class TokenModule {} diff --git a/server/src/token/token.service.spec.ts b/server/src/token/token.service.spec.ts index 1150c4556..4247222a4 100644 --- a/server/src/token/token.service.spec.ts +++ b/server/src/token/token.service.spec.ts @@ -47,20 +47,30 @@ describe('TokenService', () => { describe('create', () => { it('should create a token', async () => { - const result = await service.create('token1', '2025-01-01', 'u1', 'test', 'admin', []); + const result = await service.create( + 'token1', + '2025-01-01', + 'u1', + 'test', + 'admin', + [], + ); expect(mockPrisma.token.create).toHaveBeenCalled(); - expect(result).toEqual({"expiresAt": "2025-01-01", "name": "token1", "token": "mocked-jwt-token" }); + expect(result).toEqual({ + expiresAt: '2025-01-01', + name: 'token1', + token: 'mocked-jwt-token', + }); }); }); describe('delete', () => { it('should delete a token by id', async () => { const result = await service.delete('1'); - expect(mockPrisma.token.delete).toHaveBeenCalledWith({ where: { id: '1' } }); + expect(mockPrisma.token.delete).toHaveBeenCalledWith({ + where: { id: '1' }, + }); expect(result).toEqual({ id: '1', deleted: true }); }); }); }); - - - diff --git a/server/src/token/token.service.ts b/server/src/token/token.service.ts index 9a3f298f6..fb138f273 100644 --- a/server/src/token/token.service.ts +++ b/server/src/token/token.service.ts @@ -51,8 +51,10 @@ export class TokenService { userGroups, ); - // transoform userGroups to a string - const userGroupsString = userGroups.map((group: any) => group.name).join(','); + // transoform userGroups to a string + const userGroupsString = userGroups + .map((group: any) => group.name) + .join(','); const newToken = { name: name || '', // Optional name field role: role || 'guest', // Default to 'user' if not provided diff --git a/server/src/users/users.controller.ts b/server/src/users/users.controller.ts index 57d011391..43da0bc79 100644 --- a/server/src/users/users.controller.ts +++ b/server/src/users/users.controller.ts @@ -192,10 +192,16 @@ export class UsersController { isArray: false, }) @ApiOperation({ summary: 'Update current User password' }) - async updateMyPassword(@Request() req: any, @Body() body: { currentPassword: string; newPassword: string }) { + async updateMyPassword( + @Request() req: any, + @Body() body: { currentPassword: string; newPassword: string }, + ) { const user = req.user; if (!user || !user.userId) { - throw new HttpException('User not authenticated', HttpStatus.UNAUTHORIZED); + throw new HttpException( + 'User not authenticated', + HttpStatus.UNAUTHORIZED, + ); } if ( !body.currentPassword || @@ -209,7 +215,11 @@ export class UsersController { HttpStatus.BAD_REQUEST, ); } - return this.usersService.updateMyPassword(user.userId, body.currentPassword, body.newPassword); + return this.usersService.updateMyPassword( + user.userId, + body.currentPassword, + body.newPassword, + ); } @Post('/') @@ -274,14 +284,17 @@ export class UsersController { async updateProfile(@Request() req: any, @Body() body: Partial) { const user = req.user; if (!body || Object.keys(body).length === 0) { - throw new HttpException('No data provided to update', HttpStatus.BAD_REQUEST); + throw new HttpException( + 'No data provided to update', + HttpStatus.BAD_REQUEST, + ); } // sanitize input const data: Partial = {}; data.firstName = body.firstName; data.lastName = body.lastName; data.email = body.email; - + return this.usersService.update(user.userId, data); } diff --git a/server/src/users/users.service.spec.ts b/server/src/users/users.service.spec.ts index c17d26ff2..a034d8d92 100644 --- a/server/src/users/users.service.spec.ts +++ b/server/src/users/users.service.spec.ts @@ -34,7 +34,7 @@ describe('UsersService', () => { beforeEach(() => { jest.clearAllMocks(); - + prismaMock = { user: { findUnique: jest.fn(), @@ -54,9 +54,15 @@ describe('UsersService', () => { }; // Mock bcrypt methods - (mockedBcrypt.compare as jest.Mock).mockImplementation(() => Promise.resolve(true)); - (mockedBcrypt.hash as jest.Mock).mockImplementation(() => Promise.resolve('hashedPassword')); - (mockedBcrypt.hashSync as jest.Mock).mockImplementation(() => 'hashedPassword'); + (mockedBcrypt.compare as jest.Mock).mockImplementation(() => + Promise.resolve(true), + ); + (mockedBcrypt.hash as jest.Mock).mockImplementation(() => + Promise.resolve('hashedPassword'), + ); + (mockedBcrypt.hashSync as jest.Mock).mockImplementation( + () => 'hashedPassword', + ); service = new UsersService(); // @ts-ignore @@ -217,9 +223,13 @@ describe('UsersService', () => { id: '1', username: 'user1', email: 'user1@example.com', - tokens: [{ id: 'token1', createdAt: new Date(), expiresAt: new Date() }], + tokens: [ + { id: 'token1', createdAt: new Date(), expiresAt: new Date() }, + ], role: { id: 'role1', name: 'admin', description: 'Administrator' }, - userGroups: [{ id: 'group1', name: 'admins', description: 'Admin group' }], + userGroups: [ + { id: 'group1', name: 'admins', description: 'Admin group' }, + ], }; prismaMock.user.findUnique.mockResolvedValueOnce(mockUser); @@ -276,7 +286,7 @@ describe('UsersService', () => { 'existinguser', 'existing@example.com', 'oauth2', - 'https://example.com/avatar.jpg' + 'https://example.com/avatar.jpg', ); expect(result).toBe(existingUser); @@ -303,7 +313,7 @@ describe('UsersService', () => { 'newuser', 'new@example.com', 'oauth2', - 'https://example.com/avatar.jpg' + 'https://example.com/avatar.jpg', ); expect(result).toBe(newUser); @@ -315,17 +325,20 @@ describe('UsersService', () => { prismaMock.role.findFirst.mockResolvedValueOnce(null); await expect( - service.findOneOrCreate('newuser', 'new@example.com', 'oauth2', '') + service.findOneOrCreate('newuser', 'new@example.com', 'oauth2', ''), ).rejects.toThrow('Default role not found'); }); it('should throw error if default user group not found', async () => { prismaMock.user.findUnique.mockResolvedValueOnce(null); - prismaMock.role.findFirst.mockResolvedValueOnce({ id: 'role1', name: 'guest' }); + prismaMock.role.findFirst.mockResolvedValueOnce({ + id: 'role1', + name: 'guest', + }); prismaMock.userGroup.findFirst.mockResolvedValueOnce(null); await expect( - service.findOneOrCreate('newuser', 'new@example.com', 'oauth2', '') + service.findOneOrCreate('newuser', 'new@example.com', 'oauth2', ''), ).rejects.toThrow('Default user group not found'); }); }); @@ -351,20 +364,33 @@ describe('UsersService', () => { prismaMock.user.findUnique.mockResolvedValueOnce(mockUser); (mockedBcrypt.compare as jest.Mock).mockResolvedValueOnce(true); - (mockedBcrypt.hash as jest.Mock).mockResolvedValueOnce('hashedNewPassword'); + (mockedBcrypt.hash as jest.Mock).mockResolvedValueOnce( + 'hashedNewPassword', + ); prismaMock.user.update.mockResolvedValueOnce(updatedUser); - const result = await service.updateMyPassword('1', 'currentPass', 'newPassword123'); + const result = await service.updateMyPassword( + '1', + 'currentPass', + 'newPassword123', + ); expect(result).toBe(updatedUser); - expect(mockedBcrypt.compare).toHaveBeenCalledWith('currentPass', 'hashedCurrentPassword'); + expect(mockedBcrypt.compare).toHaveBeenCalledWith( + 'currentPass', + 'hashedCurrentPassword', + ); expect(mockedBcrypt.hash).toHaveBeenCalledWith('newPassword123', 10); }); it('should return undefined for invalid input parameters', async () => { const result1 = await service.updateMyPassword('1', '', 'newPassword123'); const result2 = await service.updateMyPassword('1', 'currentPass', ''); - const result3 = await service.updateMyPassword('1', 'currentPass', 'short'); + const result3 = await service.updateMyPassword( + '1', + 'currentPass', + 'short', + ); expect(result1).toBeUndefined(); expect(result2).toBeUndefined(); @@ -374,7 +400,11 @@ describe('UsersService', () => { it('should return undefined when user not found', async () => { prismaMock.user.findUnique.mockResolvedValueOnce(null); - const result = await service.updateMyPassword('1', 'currentPass', 'newPassword123'); + const result = await service.updateMyPassword( + '1', + 'currentPass', + 'newPassword123', + ); expect(result).toBeUndefined(); }); @@ -385,7 +415,7 @@ describe('UsersService', () => { (mockedBcrypt.compare as jest.Mock).mockResolvedValueOnce(false); await expect( - service.updateMyPassword('1', 'wrongPassword', 'newPassword123') + service.updateMyPassword('1', 'wrongPassword', 'newPassword123'), ).rejects.toThrow(HttpException); }); @@ -393,11 +423,13 @@ describe('UsersService', () => { const mockUser = { id: '1', password: 'hashedCurrentPassword' }; prismaMock.user.findUnique.mockResolvedValueOnce(mockUser); (mockedBcrypt.compare as jest.Mock).mockResolvedValueOnce(true); - (mockedBcrypt.hash as jest.Mock).mockResolvedValueOnce('hashedNewPassword'); + (mockedBcrypt.hash as jest.Mock).mockResolvedValueOnce( + 'hashedNewPassword', + ); prismaMock.user.update.mockRejectedValueOnce(new Error('Database error')); await expect( - service.updateMyPassword('1', 'currentPass', 'newPassword123') + service.updateMyPassword('1', 'currentPass', 'newPassword123'), ).rejects.toThrow('Database error'); }); }); @@ -454,7 +486,10 @@ describe('UsersService', () => { buffer: Buffer.from('fake-image-data'), mimetype: 'image/jpeg', }; - const updatedUser = { id: '1', image: 'data:image/jpeg;base64,ZmFrZS1pbWFnZS1kYXRh' }; + const updatedUser = { + id: '1', + image: 'data:image/jpeg;base64,ZmFrZS1pbWFnZS1kYXRh', + }; prismaMock.user.update.mockResolvedValueOnce(updatedUser); @@ -520,12 +555,19 @@ describe('UsersService', () => { headers: { 'content-type': 'image/jpeg' }, }); - const result = await (service as any).generateUserDataFromImageUrl('https://example.com/image.jpg'); + const result = await (service as any).generateUserDataFromImageUrl( + 'https://example.com/image.jpg', + ); - expect(result).toBe(`data:image/jpeg;base64,${imageBuffer.toString('base64')}`); - expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/image.jpg', { - responseType: 'arraybuffer', - }); + expect(result).toBe( + `data:image/jpeg;base64,${imageBuffer.toString('base64')}`, + ); + expect(mockedAxios.get).toHaveBeenCalledWith( + 'https://example.com/image.jpg', + { + responseType: 'arraybuffer', + }, + ); }); it('should throw error when image fetch fails', async () => { @@ -536,7 +578,9 @@ describe('UsersService', () => { }); await expect( - (service as any).generateUserDataFromImageUrl('https://example.com/notfound.jpg') + (service as any).generateUserDataFromImageUrl( + 'https://example.com/notfound.jpg', + ), ).rejects.toThrow('Failed to fetch image from URL'); }); @@ -548,7 +592,9 @@ describe('UsersService', () => { }); await expect( - (service as any).generateUserDataFromImageUrl('https://example.com/text.txt') + (service as any).generateUserDataFromImageUrl( + 'https://example.com/text.txt', + ), ).rejects.toThrow('Invalid image MIME type'); }); @@ -560,26 +606,37 @@ describe('UsersService', () => { headers: {}, }); - const result = await (service as any).generateUserDataFromImageUrl('https://example.com/image'); + const result = await (service as any).generateUserDataFromImageUrl( + 'https://example.com/image', + ); - expect(result).toBe(`data:image/jpeg;base64,${imageBuffer.toString('base64')}`); + expect(result).toBe( + `data:image/jpeg;base64,${imageBuffer.toString('base64')}`, + ); }); }); describe('create with error handling', () => { it('should throw error when no password provided', async () => { - const userWithoutPassword = { username: 'testuser', email: 'test@example.com' }; + const userWithoutPassword = { + username: 'testuser', + email: 'test@example.com', + }; await expect(service.create(userWithoutPassword)).rejects.toThrow( - 'Password is required for user creation.' + 'Password is required for user creation.', ); }); it('should throw error when empty password provided', async () => { - const userWithEmptyPassword = { username: 'testuser', password: '', email: 'test@example.com' }; + const userWithEmptyPassword = { + username: 'testuser', + password: '', + email: 'test@example.com', + }; await expect(service.create(userWithEmptyPassword)).rejects.toThrow( - 'Password is required for user creation.' + 'Password is required for user creation.', ); }); }); diff --git a/server/src/users/users.service.ts b/server/src/users/users.service.ts index 27770321c..8cf169aee 100644 --- a/server/src/users/users.service.ts +++ b/server/src/users/users.service.ts @@ -61,7 +61,9 @@ export class UsersService { ): Promise { let user = await this.findOneFull(username); if (!user) { - this.logger.debug(`Oauth2 User ${username} not found, creating new user.`); + this.logger.debug( + `Oauth2 User ${username} not found, creating new user.`, + ); // Define default role const role = await this.prisma.role.findFirst({ @@ -70,10 +72,14 @@ export class UsersService { }, }); if (!role) { - this.logger.warn(`Default role not found: ${process.env.DEFAULT_USER_ROLE || 'guest'}`); - throw new Error(`Default role not found: ${process.env.DEFAULT_USER_ROLE || 'guest'}`); + this.logger.warn( + `Default role not found: ${process.env.DEFAULT_USER_ROLE || 'guest'}`, + ); + throw new Error( + `Default role not found: ${process.env.DEFAULT_USER_ROLE || 'guest'}`, + ); } - + // Define default user group const defaultUserGroup = process.env.DEFAULT_USER_GROUP || 'everyone'; const userGroupsData = await this.prisma.userGroup.findFirst({ @@ -98,7 +104,7 @@ export class UsersService { email, provider, image: imageData, - role: role.id, + role: role.id, userGroups: [userGroupsData.id], providerId: null, // Set providerId if needed providerData: null, // Set providerData if needed @@ -209,7 +215,7 @@ export class UsersService { this.logger.warn('Password is required for user creation.'); throw new Error('Password is required for user creation.'); } - + return this.prisma.user.create({ data: { ...cleanedData, @@ -314,7 +320,9 @@ export class UsersService { typeof newPassword !== 'string' || newPassword.length < 8 ) { - this.logger.warn('Invalid current or new password provided for password update.'); + this.logger.warn( + 'Invalid current or new password provided for password update.', + ); return undefined; } @@ -322,18 +330,25 @@ export class UsersService { // First, get the user with their current password const user = await this.prisma.user.findUnique({ where: { id: userId }, - select: { id: true, password: true } + select: { id: true, password: true }, }); if (!user) { - this.logger.warn(`User with ID ${userId} not found for password update.`); + this.logger.warn( + `User with ID ${userId} not found for password update.`, + ); return undefined; } // Verify current password - const isCurrentPasswordValid = await bcrypt.compare(currentPassword, user.password); + const isCurrentPasswordValid = await bcrypt.compare( + currentPassword, + user.password, + ); if (!isCurrentPasswordValid) { - this.logger.warn(`Invalid current password provided for user ${userId}.`); + this.logger.warn( + `Invalid current password provided for user ${userId}.`, + ); //throw new Error('Current password is incorrect'); throw new HttpException( `Error updating password: Current password is incorrect`, @@ -441,12 +456,12 @@ export class UsersService { if (!mimetype.startsWith('image/')) { throw new Error(`Invalid image MIME type: ${mimetype}`); } - + const buffer = Buffer.from(response.data, 'binary'); const base64Image = buffer.toString('base64'); return `data:${mimetype};base64,${base64Image}`; } -/* + /* async getPermissions(userId: string,): Promise<{ action: string; resource: string }[]> { const user = await this.prisma.user.findUnique({ where: { id: userId },