From e257553d98c97d3bd8deeb94a221c40876b16ec1 Mon Sep 17 00:00:00 2001 From: William Phetsinorath Date: Thu, 30 Apr 2026 17:08:51 +0200 Subject: [PATCH 1/2] chore: migrate duplicate database implementation in package Signed-off-by: William Phetsinorath --- .vscode/launch.json | 4 + AGENTS.md | 2 +- README.md | 6 - apps/client/package.json | 3 +- apps/server-nestjs/Dockerfile | 12 +- .../01-MODULARISATION-STRATEGIE.md | 2 +- .../mise-en-place-nginx-etrangleur/PLAN.md | 4 +- apps/server-nestjs/package.json | 20 +- apps/server-nestjs/prisma.config.ts | 23 -- apps/server-nestjs/src/__mocks__/prisma.ts | 2 +- .../argocd/argocd-datastore.service.ts | 2 +- .../gitlab/gitlab-datastore.service.ts | 2 +- .../infrastructure/database/prisma.service.ts | 2 +- .../keycloak/keycloak-datastore.service.ts | 2 +- .../modules/nexus/nexus-datastore.service.ts | 2 +- .../registry/registry-datastore.service.ts | 2 +- .../modules/vault/vault-datastore.service.ts | 2 +- .../src/prisma/migrations/migration_lock.toml | 3 - apps/server/Dockerfile | 8 +- apps/server/package.json | 29 +-- apps/server/prisma.config.ts | 23 -- apps/server/src/__mocks__/prisma.ts | 2 +- apps/server/src/connect.spec.ts | 2 +- apps/server/src/init/db/dump.ts | 2 +- apps/server/src/init/db/utils.ts | 2 +- apps/server/src/mocks/prisma.ts | 2 +- apps/server/src/prisma.ts | 2 +- .../20230706084346_dso/migration.sql | 151 ------------- .../20230710181052_dso/migration.sql | 85 -------- .../20230711132934_dso/migration.sql | 11 - .../20230802143822_dso/migration.sql | 10 - .../20230912084459_dso/migration.sql | 2 - .../20231010111515_dso/migration.sql | 8 - .../20231011125838_dso/migration.sql | 81 ------- .../20231011125839_dso/migration.sql | 35 ---- .../20231011125841_dso/migration.sql | 36 ---- .../20231012105520_dso/migration.sql | 11 - .../20231024155020_dso/migration.sql | 3 - .../20231026150220_dso/migration.sql | 3 - .../20240112135751_dso/migration.sql | 2 - .../20240321123436_dso/migration.sql | 12 -- .../20240329172938_dso/migration.sql | 46 ---- .../20240424093852_dso/migration.sql | 23 -- .../20240427181037_dso/migration.sql | 19 -- .../20240605135052_dso/migration.sql | 9 - .../20240612123132_dso/migration.sql | 8 - .../20240614222908_dso/migration.sql | 11 - .../20240618112205_dso/migration.sql | 58 ----- .../20240717084709_dso/migration.sql | 9 - .../20240723135420_dso/migration.sql | 198 ------------------ .../20240725162050_dso/migration.sql | 5 - .../20240726210139_dso/migration.sql | 14 -- .../20240808082632_dso/migration.sql | 17 -- .../20240826143230_dso/migration.sql | 3 - .../20240829085548_dso/migration.sql | 12 -- .../20240916141253_token/migration.sql | 23 -- .../migration.sql | 8 - .../20240923142722_dso/migration.sql | 2 - .../20240923155416_dso/migration.sql | 2 - .../20240928002900_dso/migration.sql | 2 - .../migration.sql | 12 -- .../20241104232540_add_usertype/migration.sql | 12 -- .../20241104232541_add_pat/migration.sql | 84 -------- .../migration.sql | 2 - .../20241112101945_add_slug/migration.sql | 14 -- .../migration.sql | 2 - .../20241216131342_dso/migration.sql | 17 -- .../20250107104749_dso/migration.sql | 2 - .../migration.sql | 25 --- .../migration.sql | 15 -- .../20250723141246_dso/migration.sql | 2 - .../20250818095032_remove_quota/migration.sql | 44 ---- .../migration.sql | 5 - .../migration.sql | 9 - .../migration.sql | 4 - .../migration.sql | 4 - apps/server/src/prisma/schema/admin.prisma | 21 -- apps/server/src/prisma/schema/project.prisma | 109 ---------- apps/server/src/prisma/schema/schema.prisma | 21 -- apps/server/src/prisma/schema/token.prisma | 30 --- .../src/prisma/schema/topography.prisma | 52 ----- apps/server/src/prisma/schema/user.prisma | 23 -- .../src/resources/admin-role/business.spec.ts | 2 +- .../src/resources/admin-role/business.ts | 2 +- .../src/resources/admin-role/queries.ts | 2 +- .../src/resources/admin-token/business.ts | 2 +- .../src/resources/admin-token/router.spec.ts | 2 +- .../src/resources/cluster/business.spec.ts | 2 +- apps/server/src/resources/cluster/business.ts | 2 +- apps/server/src/resources/cluster/queries.ts | 2 +- .../resources/environment/business.spec.ts | 2 +- .../src/resources/environment/business.ts | 2 +- .../src/resources/environment/queries.ts | 2 +- apps/server/src/resources/log/queries.ts | 2 +- .../src/resources/project-member/business.ts | 2 +- .../src/resources/project-member/queries.ts | 2 +- .../resources/project-role/business.spec.ts | 2 +- .../src/resources/project-role/business.ts | 2 +- .../src/resources/project-role/queries.ts | 2 +- .../src/resources/project-service/business.ts | 2 +- .../src/resources/project-service/queries.ts | 2 +- .../src/resources/project/business.spec.ts | 2 +- apps/server/src/resources/project/business.ts | 2 +- apps/server/src/resources/project/queries.ts | 8 +- .../src/resources/repository/business.ts | 2 +- .../src/resources/repository/queries.ts | 2 +- .../src/resources/stage/business.spec.ts | 2 +- apps/server/src/resources/stage/business.ts | 2 +- apps/server/src/resources/stage/queries.ts | 2 +- .../src/resources/system/settings/queries.ts | 2 +- apps/server/src/resources/user/business.ts | 2 +- apps/server/src/resources/user/queries.ts | 2 +- .../src/resources/user/tokens/business.ts | 2 +- .../src/resources/zone/business.spec.ts | 2 +- apps/server/src/resources/zone/queries.ts | 2 +- apps/server/src/utils/controller.ts | 2 +- apps/server/src/utils/hook-wrapper.ts | 2 +- apps/server/src/utils/mocks.ts | 2 +- ci/scripts/setup.sh | 6 - package.json | 5 +- packages/database/eslint.config.js | 3 + packages/database/package.json | 46 ++++ packages/database/prisma.config.ts | 9 + .../20230706084346_dso/migration.sql | 0 .../20230710181052_dso/migration.sql | 0 .../20230711132934_dso/migration.sql | 0 .../20230802143822_dso/migration.sql | 0 .../20230912084459_dso/migration.sql | 0 .../20231010111515_dso/migration.sql | 0 .../20231011125838_dso/migration.sql | 0 .../20231011125839_dso/migration.sql | 0 .../20231011125841_dso/migration.sql | 0 .../20231012105520_dso/migration.sql | 0 .../20231024155020_dso/migration.sql | 0 .../20231026150220_dso/migration.sql | 0 .../20240112135751_dso/migration.sql | 0 .../20240321123436_dso/migration.sql | 0 .../20240329172938_dso/migration.sql | 0 .../20240424093852_dso/migration.sql | 0 .../20240427181037_dso/migration.sql | 0 .../20240605135052_dso/migration.sql | 0 .../20240612123132_dso/migration.sql | 0 .../20240614222908_dso/migration.sql | 0 .../20240618112205_dso/migration.sql | 0 .../20240717084709_dso/migration.sql | 0 .../20240723135420_dso/migration.sql | 0 .../20240725162050_dso/migration.sql | 0 .../20240726210139_dso/migration.sql | 0 .../20240808082632_dso/migration.sql | 0 .../20240826143230_dso/migration.sql | 0 .../20240829085548_dso/migration.sql | 0 .../20240916141253_token/migration.sql | 0 .../migration.sql | 0 .../20240923142722_dso/migration.sql | 0 .../20240923155416_dso/migration.sql | 0 .../20240928002900_dso/migration.sql | 0 .../migration.sql | 0 .../20241104232540_add_usertype/migration.sql | 0 .../20241104232541_add_pat/migration.sql | 0 .../migration.sql | 0 .../20241112101945_add_slug/migration.sql | 0 .../migration.sql | 0 .../20241216131342_dso/migration.sql | 0 .../20250107104749_dso/migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../20250723141246_dso/migration.sql | 0 .../20250818095032_remove_quota/migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../20260206105522_dso/migration.sql | 0 .../20260211143048_dso/migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../20260226141201_role_type/migration.sql | 0 .../migration.sql | 0 .../20260402095325_dso/migration.sql | 0 .../migration.sql | 0 .../prisma/migrations/migration_lock.toml | 0 .../database}/prisma/schema/admin.prisma | 0 .../database}/prisma/schema/project.prisma | 0 .../database}/prisma/schema/schema.prisma | 0 .../database}/prisma/schema/token.prisma | 0 .../database}/prisma/schema/topography.prisma | 0 .../database}/prisma/schema/user.prisma | 0 packages/database/src/index.ts | 2 + packages/database/tsconfig.eslint.json | 16 ++ packages/database/tsconfig.json | 18 ++ packages/hooks/package.json | 5 +- packages/shared/package.json | 5 +- packages/test-utils/package.json | 3 +- playwright/package.json | 8 +- pnpm-lock.yaml | 43 ++-- pnpm-workspace.yaml | 35 ++-- 202 files changed, 247 insertions(+), 1630 deletions(-) delete mode 100644 apps/server-nestjs/prisma.config.ts delete mode 100644 apps/server-nestjs/src/prisma/migrations/migration_lock.toml delete mode 100644 apps/server/prisma.config.ts delete mode 100644 apps/server/src/prisma/migrations/20230706084346_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20230710181052_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20230711132934_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20230802143822_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20230912084459_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20231010111515_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20231011125838_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20231011125839_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20231011125841_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20231012105520_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20231024155020_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20231026150220_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240112135751_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240321123436_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240329172938_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240424093852_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240427181037_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240605135052_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240612123132_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240614222908_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240618112205_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240717084709_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240723135420_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240725162050_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240726210139_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240808082632_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240826143230_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240829085548_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240916141253_token/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240919122331_optional_user_id/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240923142722_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240923155416_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20240928002900_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20241008125724_enabling_maven/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20241104232540_add_usertype/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20241104232541_add_pat/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20241107142721_user_last_login/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20241112101945_add_slug/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20241112102015_add_provisionning_version/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20241216131342_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20250107104749_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20250121222953_prevent_upgrade/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20250121222954_drop_organization/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20250723141246_dso/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20250818095032_remove_quota/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20250825150622_add_cluster_resources/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20250916134454_add_project_resources/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20251028150522_rename_default_zone/migration.sql delete mode 100644 apps/server/src/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql delete mode 100644 apps/server/src/prisma/schema/admin.prisma delete mode 100644 apps/server/src/prisma/schema/project.prisma delete mode 100644 apps/server/src/prisma/schema/schema.prisma delete mode 100644 apps/server/src/prisma/schema/token.prisma delete mode 100644 apps/server/src/prisma/schema/topography.prisma delete mode 100644 apps/server/src/prisma/schema/user.prisma create mode 100644 packages/database/eslint.config.js create mode 100644 packages/database/package.json create mode 100644 packages/database/prisma.config.ts rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20230706084346_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20230710181052_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20230711132934_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20230802143822_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20230912084459_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20231010111515_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20231011125838_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20231011125839_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20231011125841_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20231012105520_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20231024155020_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20231026150220_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240112135751_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240321123436_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240329172938_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240424093852_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240427181037_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240605135052_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240612123132_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240614222908_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240618112205_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240717084709_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240723135420_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240725162050_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240726210139_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240808082632_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240826143230_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240829085548_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240916141253_token/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240919122331_optional_user_id/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240923142722_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240923155416_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20240928002900_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20241008125724_enabling_maven/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20241104232540_add_usertype/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20241104232541_add_pat/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20241107142721_user_last_login/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20241112101945_add_slug/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20241112102015_add_provisionning_version/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20241216131342_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20250107104749_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20250121222953_prevent_upgrade/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20250121222954_drop_organization/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20250723141246_dso/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20250818095032_remove_quota/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20250825150622_add_cluster_resources/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20250916134454_add_project_resources/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20251028150522_rename_default_zone/migration.sql (100%) rename {apps/server-nestjs/src => packages/database}/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20251218103526_alter_repository_defaults/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20251219144931_add_environment_autosync/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260127154602_add_oidc_group_to_project_role/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260127164002_add_type_to_roles/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260204150335_add_system_roles/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260206105522_dso/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260211143048_dso/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260217144930_enable_legacy_permissions/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260226130000_remove_refined_permissions_setting/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260226140001_add_tout_le_monde/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260226141201_role_type/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260330122500_migrate_project_system_roles_type_system_managed/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260402095325_dso/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/20260415103000_set_root_admin_to_system_external/migration.sql (100%) rename {apps/server/src => packages/database}/prisma/migrations/migration_lock.toml (100%) rename {apps/server-nestjs/src => packages/database}/prisma/schema/admin.prisma (100%) rename {apps/server-nestjs/src => packages/database}/prisma/schema/project.prisma (100%) rename {apps/server-nestjs/src => packages/database}/prisma/schema/schema.prisma (100%) rename {apps/server-nestjs/src => packages/database}/prisma/schema/token.prisma (100%) rename {apps/server-nestjs/src => packages/database}/prisma/schema/topography.prisma (100%) rename {apps/server-nestjs/src => packages/database}/prisma/schema/user.prisma (100%) create mode 100644 packages/database/src/index.ts create mode 100644 packages/database/tsconfig.eslint.json create mode 100644 packages/database/tsconfig.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 8205221d49..e1bce2c790 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,7 @@ "integ" ], "console": "integratedTerminal", + "envFile": "${workspaceFolder}/apps/server/.env.integ", "restart": true, "presentation": { "hidden": true @@ -39,6 +40,7 @@ "dev" ], "console": "integratedTerminal", + "envFile": "${workspaceFolder}/apps/server/.env", "restart": true, "presentation": { "hidden": true @@ -100,6 +102,7 @@ "run", "start:dev" ], + "envFile": "${workspaceFolder}/apps/server-nestjs/.env.integ", "console": "integratedTerminal", "restart": true, "presentation": { @@ -123,6 +126,7 @@ "start:dev" ], "console": "integratedTerminal", + "envFile": "${workspaceFolder}/apps/server-nestjs/.env", "restart": true, "presentation": { "hidden": true diff --git a/AGENTS.md b/AGENTS.md index 1602cf9d07..45f4c60512 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,7 @@ Plugins use TS module augmentation to extend `ProjectStore` and `Config` interfa ## Database (Prisma) -Multi-file schema in `apps/server/src/prisma/schema/*.prisma` (project, user, token, admin, topography). +Multi-file schema in `packages/database/prisma/schema/*.prisma` (project, user, token, admin, topography). Migrations: standard Prisma Migrate. Major version data migrations in `migrations/v9/`. ## Environment config diff --git a/README.md b/README.md index 3a89503a2b..1318c43a07 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,6 @@ Maintenant que vous avez les prérequis projets (prérequis techniques et de con # Installer toutes les dépendances $ pnpm install -# Initialiser la base de données PostgreSQL lancée localement -$ pnpm db:generate - # Construire les applications client et server $ pnpm build @@ -117,9 +114,6 @@ pnpm install # Créer les fichiers d'environnement exemples ./ci/scripts/init-env.sh -# Générer le client Prisma côté serveur -pnpm --filter @cpn-console/server run db:generate - # Construire tous les paquets applicatifs pnpm build ``` diff --git a/apps/client/package.json b/apps/client/package.json index 3c23cf69d9..a720614eb4 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -22,7 +22,8 @@ "lint:ts": "NODE_OPTIONS='--no-warnings=ExperimentalWarning' eslint ./", "preview": "vite preview --port 8080", "test": "vitest run", - "test:cov": "vitest run --coverage", + "test:cov": "pnpm run test --coverage", + "test:watch": "pnpm run test --watch", "type-check": "vue-tsc --noEmit -p tsconfig.json" }, "dependencies": { diff --git a/apps/server-nestjs/Dockerfile b/apps/server-nestjs/Dockerfile index abcef15d31..93e7c976bf 100644 --- a/apps/server-nestjs/Dockerfile +++ b/apps/server-nestjs/Dockerfile @@ -18,6 +18,7 @@ COPY --chown=node:root apps/server-nestjs/package.json ./apps/server-n COPY --chown=node:root packages/eslintconfig/package.json ./packages/eslintconfig/package.json COPY --chown=node:root packages/shared/package.json ./packages/shared/package.json COPY --chown=node:root packages/logger/package.json ./packages/logger/package.json +COPY --chown=node:root packages/database/package.json ./packages/database/package.json COPY --chown=node:root packages/hooks/package.json ./packages/hooks/package.json COPY --chown=node:root packages/test-utils/package.json ./packages/test-utils/package.json COPY --chown=node:root packages/tsconfig/package.json ./packages/tsconfig/package.json @@ -48,11 +49,10 @@ RUN pnpm --filter @cpn-console/hooks run build # Réinjecter les libs buildées dans node_modules (injectWorkspacePackages copie au moment de l'install) RUN pnpm --filter @cpn-console/server-nestjs install --frozen-lockfile -# Générer le client Prisma (schéma multi-fichiers : pointer sur le dossier) -RUN pnpm --filter @cpn-console/server-nestjs exec prisma generate \ - --schema=src/prisma/schema +# Générer le client Prisma depuis le package partagé +RUN pnpm --filter @cpn-console/database run generate -ENTRYPOINT ["pnpm", "--filter", "server-nestjs", "run"] +ENTRYPOINT ["pnpm", "--filter", "@cpn-console/server-nestjs", "run"] CMD ["start:dev"] # Build stage ---------------------------------------------------------------------- @@ -60,6 +60,7 @@ FROM dev AS build # Build shared (génère dist/ et types/) RUN pnpm --filter @cpn-console/logger run build +RUN pnpm --filter @cpn-console/database run build RUN pnpm --filter @cpn-console/shared run build # Build hooks (génère dist/ et types/ nécessaires aux imports) @@ -90,11 +91,8 @@ RUN mkdir -p /home/node/logs && chmod 770 -R /home/node/logs \ # Copier l'artefact de build et les node_modules de production COPY --chown=node:root --from=build /app/build . COPY --chown=node:root --from=build /app/apps/server-nestjs/dist ./dist -# COPY --chown=node:root --from=build /app/build/node_modules ./node_modules # Regénérer le client Prisma dans l'image de production -RUN npm run db:generate - USER node EXPOSE 3001 diff --git a/apps/server-nestjs/documentation/Modularisation-de-console-server/01-MODULARISATION-STRATEGIE.md b/apps/server-nestjs/documentation/Modularisation-de-console-server/01-MODULARISATION-STRATEGIE.md index 10c05e2525..5454ba3485 100644 --- a/apps/server-nestjs/documentation/Modularisation-de-console-server/01-MODULARISATION-STRATEGIE.md +++ b/apps/server-nestjs/documentation/Modularisation-de-console-server/01-MODULARISATION-STRATEGIE.md @@ -114,7 +114,7 @@ module.exports = { findById, create }; import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateUserDto } from './dto/create-user.dto'; -import { User } from '@prisma/client'; +import { User } from '@cpn-console/database'; @Injectable() export class UsersService { diff --git a/apps/server-nestjs/documentation/mise-en-place-nginx-etrangleur/PLAN.md b/apps/server-nestjs/documentation/mise-en-place-nginx-etrangleur/PLAN.md index b7ba2c4258..85bbf75839 100644 --- a/apps/server-nestjs/documentation/mise-en-place-nginx-etrangleur/PLAN.md +++ b/apps/server-nestjs/documentation/mise-en-place-nginx-etrangleur/PLAN.md @@ -62,7 +62,7 @@ - Le nginx existant dans l'image `client` proxifiait directement vers `server:8080` — upstream changé vers `nginx-strangler:8080` - `server-nestjs/src/main.ts` utilisait `process.env.PORT ?? 0` au lieu de `ConfigurationService.port` — corrigé -- Le schéma Prisma de `server-nestjs` est multi-fichiers dans `src/prisma/schema/` — `prisma generate` doit pointer sur le dossier, pas sur `schema.prisma` +- Le schéma Prisma est multi-fichiers dans `packages/database/prisma/schema/` — `prisma generate` doit pointer sur le dossier, pas sur `schema.prisma` - En CI (`job-lint.yml`), `nginx -t` échoue si `apt-get update -qq` n'est pas exécuté avant l'install, et si les chemins `pid`/`error_log`/`access_log` ne sont pas patchés vers `/tmp/` (runner non-root) - En CI (`job-playwright.yml`), les images `server-nestjs:ci` et `nginx-strangler:ci` doivent être buildées localement avant le `docker compose up --no-build` (même pattern que `opencds-mockoon`) @@ -134,7 +134,7 @@ location = /api/v1/system/health { ### Tâche 2.2 : Créer `apps/server-nestjs/Dockerfile` ✅ -Multi-stage : `base` → `deps` → `build` → `prod`. `prisma generate --schema=src/prisma/schema` (dossier multi-fichiers). `USER node`, `HEALTHCHECK`, `EXPOSE 3001`. +Multi-stage : `base` → `deps` → `build` → `prod`. `pnpm --filter @cpn-console/database run generate` (schéma multi-fichiers). `USER node`, `HEALTHCHECK`, `EXPOSE 3001`. ### Tâche 2.3 : `apps/server-nestjs/.env.docker-example` ✅ diff --git a/apps/server-nestjs/package.json b/apps/server-nestjs/package.json index 275e671c55..e54ef4617a 100644 --- a/apps/server-nestjs/package.json +++ b/apps/server-nestjs/package.json @@ -10,25 +10,23 @@ "url": "https://github.com/cloud-pi-native/console" }, "scripts": { + "prebuild": "pnpm --filter @cpn-console/database run generate", "build": "nest build", - "db:generate": "prisma generate", - "db:migrate": "prisma migrate dev --name dso", - "db:reset": "prisma migrate reset", "format": "eslint ./ --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "prestart": "pnpm --filter @cpn-console/database run migrate", "start": "nest start", - "start:debug": "nest start --debug --watch", - "start:dev": "nest start --watch", + "start:debug": "pnpm run start --debug --watch", + "start:dev": "pnpm run start --watch", "start:prod": "node dist/main", - "pretest": "pnpm run db:generate", + "pretest": "pnpm --filter @cpn-console/database run generate", "test": "vitest run", - "test:watch": "vitest", - "pretest:cov": "pnpm run db:generate", - "test:cov": "vitest run --coverage", - "test:debug": "vitest --inspect" + "test:cov": "pnpm run test --coverage", + "test:watch": "pnpm run test --watch" }, "dependencies": { "@cpn-console/argocd-plugin": "workspace:^", + "@cpn-console/database": "workspace:^", "@cpn-console/gitlab-plugin": "workspace:^", "@cpn-console/harbor-plugin": "workspace:^", "@cpn-console/hooks": "workspace:^", @@ -65,7 +63,6 @@ "@opentelemetry/sdk-metrics": "^2.5.1", "@opentelemetry/sdk-node": "^0.212.0", "@opentelemetry/sdk-trace-node": "^2.5.1", - "@prisma/client": "^6.19.2", "@ts-rest/core": "^3.52.1", "@ts-rest/fastify": "^3.52.1", "@ts-rest/open-api": "^3.52.1", @@ -76,7 +73,6 @@ "mustache": "^4.2.0", "nestjs-pino": "^4.6.0", "pino-http": "^11.0.0", - "prisma": "^6.19.2", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "undici": "^7.24.0", diff --git a/apps/server-nestjs/prisma.config.ts b/apps/server-nestjs/prisma.config.ts deleted file mode 100644 index ff5aaf9186..0000000000 --- a/apps/server-nestjs/prisma.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' -import { parseEnv } from 'node:util' -import { defineConfig } from 'prisma/config' - -if (process.env.DOCKER !== 'true' && fs.existsSync('.env')) { - Object.assign(process.env, Object.fromEntries( - Object.entries(parseEnv(fs.readFileSync(path.resolve('.env'), 'utf-8'))).filter(([_k, v]) => Boolean(v)), - )) -} - -if (process.env.INTEGRATION === 'true' && fs.existsSync('.env.integ')) { - Object.assign(process.env, Object.fromEntries( - Object.entries(parseEnv(fs.readFileSync(path.resolve('.env.integ'), 'utf-8'))).filter(([_k, v]) => Boolean(v)), - )) -} - -export default defineConfig({ - schema: path.join('src', 'prisma', 'schema'), - migrations: { - path: path.join('src', 'prisma', 'migrations'), - }, -}) diff --git a/apps/server-nestjs/src/__mocks__/prisma.ts b/apps/server-nestjs/src/__mocks__/prisma.ts index 075578c96c..47305cede3 100644 --- a/apps/server-nestjs/src/__mocks__/prisma.ts +++ b/apps/server-nestjs/src/__mocks__/prisma.ts @@ -1,4 +1,4 @@ -import type { PrismaClient } from '@prisma/client' +import type { PrismaClient } from '@cpn-console/database' import { beforeEach, vi } from 'vitest' import { mockDeep, mockReset } from 'vitest-mock-extended' diff --git a/apps/server-nestjs/src/modules/argocd/argocd-datastore.service.ts b/apps/server-nestjs/src/modules/argocd/argocd-datastore.service.ts index 5ccb2f6b10..2200796099 100644 --- a/apps/server-nestjs/src/modules/argocd/argocd-datastore.service.ts +++ b/apps/server-nestjs/src/modules/argocd/argocd-datastore.service.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '@prisma/client' +import type { Prisma } from '@cpn-console/database' import { Inject, Injectable } from '@nestjs/common' import { PrismaService } from '../infrastructure/database/prisma.service' diff --git a/apps/server-nestjs/src/modules/gitlab/gitlab-datastore.service.ts b/apps/server-nestjs/src/modules/gitlab/gitlab-datastore.service.ts index 55b9ba4392..20c8b415fe 100644 --- a/apps/server-nestjs/src/modules/gitlab/gitlab-datastore.service.ts +++ b/apps/server-nestjs/src/modules/gitlab/gitlab-datastore.service.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '@prisma/client' +import type { Prisma } from '@cpn-console/database' import { Inject, Injectable } from '@nestjs/common' import { PrismaService } from '../infrastructure/database/prisma.service' diff --git a/apps/server-nestjs/src/modules/infrastructure/database/prisma.service.ts b/apps/server-nestjs/src/modules/infrastructure/database/prisma.service.ts index f10a31e573..70b63b5049 100644 --- a/apps/server-nestjs/src/modules/infrastructure/database/prisma.service.ts +++ b/apps/server-nestjs/src/modules/infrastructure/database/prisma.service.ts @@ -1,6 +1,6 @@ import type { OnModuleDestroy, OnModuleInit } from '@nestjs/common' +import { PrismaClient } from '@cpn-console/database' import { Injectable } from '@nestjs/common' -import { PrismaClient } from '@prisma/client' @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { diff --git a/apps/server-nestjs/src/modules/keycloak/keycloak-datastore.service.ts b/apps/server-nestjs/src/modules/keycloak/keycloak-datastore.service.ts index 192259f58f..d70ac0cd47 100644 --- a/apps/server-nestjs/src/modules/keycloak/keycloak-datastore.service.ts +++ b/apps/server-nestjs/src/modules/keycloak/keycloak-datastore.service.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '@prisma/client' +import type { Prisma } from '@cpn-console/database' import { Inject, Injectable } from '@nestjs/common' import { PrismaService } from '../infrastructure/database/prisma.service' diff --git a/apps/server-nestjs/src/modules/nexus/nexus-datastore.service.ts b/apps/server-nestjs/src/modules/nexus/nexus-datastore.service.ts index dcecc8efa9..a7594b7296 100644 --- a/apps/server-nestjs/src/modules/nexus/nexus-datastore.service.ts +++ b/apps/server-nestjs/src/modules/nexus/nexus-datastore.service.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '@prisma/client' +import type { Prisma } from '@cpn-console/database' import { Inject, Injectable } from '@nestjs/common' import { PrismaService } from '../infrastructure/database/prisma.service' import { NEXUS_PLUGIN_NAME } from './nexus.constants' diff --git a/apps/server-nestjs/src/modules/registry/registry-datastore.service.ts b/apps/server-nestjs/src/modules/registry/registry-datastore.service.ts index 24cfbf6f2e..5a125f23d8 100644 --- a/apps/server-nestjs/src/modules/registry/registry-datastore.service.ts +++ b/apps/server-nestjs/src/modules/registry/registry-datastore.service.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '@prisma/client' +import type { Prisma } from '@cpn-console/database' import { Inject, Injectable } from '@nestjs/common' import { PrismaService } from '../infrastructure/database/prisma.service' import { REGISTRY_PLUGIN_NAME } from './registry.constants' diff --git a/apps/server-nestjs/src/modules/vault/vault-datastore.service.ts b/apps/server-nestjs/src/modules/vault/vault-datastore.service.ts index 6cfae8d9af..95865e8351 100644 --- a/apps/server-nestjs/src/modules/vault/vault-datastore.service.ts +++ b/apps/server-nestjs/src/modules/vault/vault-datastore.service.ts @@ -1,4 +1,4 @@ -import type { Prisma } from '@prisma/client' +import type { Prisma } from '@cpn-console/database' import { Inject, Injectable } from '@nestjs/common' import { PrismaService } from '../infrastructure/database/prisma.service' diff --git a/apps/server-nestjs/src/prisma/migrations/migration_lock.toml b/apps/server-nestjs/src/prisma/migrations/migration_lock.toml deleted file mode 100644 index 648c57fd59..0000000000 --- a/apps/server-nestjs/src/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (e.g., Git) -provider = "postgresql" \ No newline at end of file diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 6e27b95fd9..1675aa3ee2 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -15,6 +15,7 @@ COPY --chown=node:root apps/server/package.json ./apps/server/package.json COPY --chown=node:root packages/eslintconfig/package.json ./packages/eslintconfig/package.json COPY --chown=node:root packages/shared/package.json ./packages/shared/package.json COPY --chown=node:root packages/logger/package.json ./packages/logger/package.json +COPY --chown=node:root packages/database/package.json ./packages/database/package.json COPY --chown=node:root packages/hooks/package.json ./packages/hooks/package.json COPY --chown=node:root packages/test-utils/package.json ./packages/test-utils/package.json COPY --chown=node:root packages/tsconfig/package.json ./packages/tsconfig/package.json @@ -33,8 +34,8 @@ COPY --chown=node:root packages/ ./packages/ COPY --chown=node:root apps/server/ ./apps/server/ # Generate Prisma client -RUN pnpm --filter server run db:generate -ENTRYPOINT [ "pnpm", "--filter", "server", "run" ] +RUN pnpm --filter @cpn-console/database run generate +ENTRYPOINT [ "pnpm", "--filter", "@cpn-console/server", "run" ] CMD [ "dev" ] @@ -48,7 +49,6 @@ RUN pnpm --filter @cpn-console/server run build # Export @cpn-console/server to target build directory RUN pnpm --filter @cpn-console/server --prod deploy build - # Prod stage ----------------------------------------------------------------------- FROM docker.io/node:24.13.1-bullseye-slim AS prod @@ -61,7 +61,7 @@ RUN mkdir -p /home/node/logs && chmod 770 -R /home/node/logs \ && mkdir -p /home/node/.npm && chmod 770 -R /home/node/.npm \ && chown node:root /app COPY --chown=node:root --from=build /app/build . -RUN npm run db:generate + USER node EXPOSE 8080 ENTRYPOINT ["npm", "start"] diff --git a/apps/server/package.json b/apps/server/package.json index e48fe73ec4..2ae81031c0 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -12,37 +12,30 @@ "types": "types/bootstrap.d.ts", "files": [ "dist", - "prisma.config.ts", "src" ], "scripts": { + "prebuild": "pnpm --filter @cpn-console/database run generate", "build": "tspc", "build:clean": "rimraf ./dist ./types ./tsconfig.tsbuildinfo", - "db:deploy": "prisma migrate deploy", - "db:diff": "prisma migrate diff", - "db:generate": "prisma generate", - "db:migrate": "prisma migrate dev --name dso", - "db:reset": "prisma migrate reset", - "predev": "pnpm run db:deploy", + "predev": "pnpm --filter @cpn-console/database run deploy", "dev": "nodemon --exec 'vite-node src/bootstrap.ts'", - "predebug": "pnpm run db:migrate", + "predebug": "pnpm --filter @cpn-console/database run migrate", "debug": "nodemon --exec 'NODE_OPTIONS=\"--inspect-brk=0.0.0.0\" vite-node src/bootstrap.ts'", "format": "eslint ./ --fix", - "preinteg": "INTEGRATION=true pnpm run db:migrate", - "integ": "INTEGRATION=true nodemon --exec 'vite-node src/bootstrap.ts'", "lint": "eslint ./", - "prepublishOnly": "pnpm run db:generate", - "prestart": "npm run db:deploy", + "preinteg": "pnpm --filter @cpn-console/database run migrate", + "integ": "INTEGRATION=true nodemon --exec 'vite-node src/bootstrap.ts'", + "prestart": "pnpm --filter @cpn-console/database run deploy", "start": "node --enable-source-maps dist/bootstrap.js", - "pretest": "pnpm run db:generate", + "pretest": "pnpm --filter @cpn-console/database run generate", "test": "vitest run", - "pretest:cov": "pnpm run db:generate", - "test:cov": "vitest run --coverage", - "test:e2e": "pnpm run dev", - "test:e2e-ci": "pnpm run start" + "test:cov": "pnpm run test --coverage", + "test:watch": "pnpm run test --watch" }, "dependencies": { "@cpn-console/argocd-plugin": "workspace:^", + "@cpn-console/database": "workspace:^", "@cpn-console/gitlab-plugin": "workspace:^", "@cpn-console/harbor-plugin": "workspace:^", "@cpn-console/hooks": "workspace:^", @@ -66,7 +59,6 @@ "@opentelemetry/exporter-trace-otlp-proto": "^0.213.0", "@opentelemetry/sdk-metrics": "^2.5.1", "@opentelemetry/sdk-node": "^0.212.0", - "@prisma/client": "^6.19.2", "@ts-rest/core": "^3.52.1", "@ts-rest/fastify": "^3.52.1", "@ts-rest/open-api": "^3.52.1", @@ -76,7 +68,6 @@ "fastify-keycloak-adapter": "2.3.2", "json-2-csv": "^5.5.10", "mustache": "^4.2.0", - "prisma": "^6.19.2", "undici": "^7.24.0", "vitest-mock-extended": "^2.0.2" }, diff --git a/apps/server/prisma.config.ts b/apps/server/prisma.config.ts deleted file mode 100644 index ff5aaf9186..0000000000 --- a/apps/server/prisma.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' -import { parseEnv } from 'node:util' -import { defineConfig } from 'prisma/config' - -if (process.env.DOCKER !== 'true' && fs.existsSync('.env')) { - Object.assign(process.env, Object.fromEntries( - Object.entries(parseEnv(fs.readFileSync(path.resolve('.env'), 'utf-8'))).filter(([_k, v]) => Boolean(v)), - )) -} - -if (process.env.INTEGRATION === 'true' && fs.existsSync('.env.integ')) { - Object.assign(process.env, Object.fromEntries( - Object.entries(parseEnv(fs.readFileSync(path.resolve('.env.integ'), 'utf-8'))).filter(([_k, v]) => Boolean(v)), - )) -} - -export default defineConfig({ - schema: path.join('src', 'prisma', 'schema'), - migrations: { - path: path.join('src', 'prisma', 'migrations'), - }, -}) diff --git a/apps/server/src/__mocks__/prisma.ts b/apps/server/src/__mocks__/prisma.ts index 075578c96c..47305cede3 100644 --- a/apps/server/src/__mocks__/prisma.ts +++ b/apps/server/src/__mocks__/prisma.ts @@ -1,4 +1,4 @@ -import type { PrismaClient } from '@prisma/client' +import type { PrismaClient } from '@cpn-console/database' import { beforeEach, vi } from 'vitest' import { mockDeep, mockReset } from 'vitest-mock-extended' diff --git a/apps/server/src/connect.spec.ts b/apps/server/src/connect.spec.ts index e64228a561..d6fb113aef 100644 --- a/apps/server/src/connect.spec.ts +++ b/apps/server/src/connect.spec.ts @@ -1,5 +1,5 @@ +import { PrismaClientInitializationError } from '@cpn-console/database' import { logger } from '@cpn-console/logger' -import { PrismaClientInitializationError } from '@prisma/client/runtime/library.js' import { beforeEach, describe, expect, it, vi } from 'vitest' import prisma from './__mocks__/prisma.js' import app from './app.js' diff --git a/apps/server/src/init/db/dump.ts b/apps/server/src/init/db/dump.ts index 6709946605..38d37ad9a7 100644 --- a/apps/server/src/init/db/dump.ts +++ b/apps/server/src/init/db/dump.ts @@ -8,7 +8,7 @@ */ import { writeFileSync } from 'node:fs' -import { Prisma } from '@prisma/client' +import { Prisma } from '@cpn-console/database' import prisma from '@/prisma.js' import { associations, manyToManyRelation, modelKeys, models, resourceListToDict } from './utils.js' diff --git a/apps/server/src/init/db/utils.ts b/apps/server/src/init/db/utils.ts index e4b941089f..593c123eaf 100644 --- a/apps/server/src/init/db/utils.ts +++ b/apps/server/src/init/db/utils.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import { Prisma } from '@prisma/client' +import { Prisma } from '@cpn-console/database' // eslint-disable-next-line no-extend-native BigInt.prototype.toJSON = function () { diff --git a/apps/server/src/mocks/prisma.ts b/apps/server/src/mocks/prisma.ts index 075578c96c..47305cede3 100644 --- a/apps/server/src/mocks/prisma.ts +++ b/apps/server/src/mocks/prisma.ts @@ -1,4 +1,4 @@ -import type { PrismaClient } from '@prisma/client' +import type { PrismaClient } from '@cpn-console/database' import { beforeEach, vi } from 'vitest' import { mockDeep, mockReset } from 'vitest-mock-extended' diff --git a/apps/server/src/prisma.ts b/apps/server/src/prisma.ts index 4590932b68..ebdfbc113d 100644 --- a/apps/server/src/prisma.ts +++ b/apps/server/src/prisma.ts @@ -1,4 +1,4 @@ -import { PrismaClient } from '@prisma/client' +import { PrismaClient } from '@cpn-console/database' const prisma = new PrismaClient() diff --git a/apps/server/src/prisma/migrations/20230706084346_dso/migration.sql b/apps/server/src/prisma/migrations/20230706084346_dso/migration.sql deleted file mode 100644 index f2f4e7b0b7..0000000000 --- a/apps/server/src/prisma/migrations/20230706084346_dso/migration.sql +++ /dev/null @@ -1,151 +0,0 @@ --- CreateTable -CREATE TABLE "Environment" ( - "id" UUID NOT NULL, - "name" TEXT NOT NULL, - "projectId" UUID NOT NULL, - "status" TEXT NOT NULL DEFAULT 'initializing', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Environment_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Log" ( - "id" UUID NOT NULL, - "data" JSONB NOT NULL, - "action" TEXT NOT NULL DEFAULT '', - "userId" UUID NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Log_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Organization" ( - "id" UUID NOT NULL, - "source" TEXT NOT NULL, - "name" TEXT NOT NULL, - "label" TEXT NOT NULL, - "active" BOOLEAN NOT NULL DEFAULT true, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Organization_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Permission" ( - "id" UUID NOT NULL, - "userId" UUID NOT NULL, - "environmentId" UUID NOT NULL, - "level" INTEGER NOT NULL DEFAULT 0, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Permission_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Project" ( - "id" UUID NOT NULL, - "name" TEXT NOT NULL, - "organizationId" UUID NOT NULL, - "description" TEXT, - "status" TEXT NOT NULL, - "locked" BOOLEAN NOT NULL DEFAULT false, - "services" JSONB NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Project_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Repository" ( - "id" UUID NOT NULL, - "projectId" UUID NOT NULL, - "internalRepoName" TEXT NOT NULL, - "externalRepoUrl" TEXT NOT NULL, - "externalUserName" TEXT, - "externalToken" TEXT, - "isInfra" BOOLEAN NOT NULL DEFAULT false, - "isPrivate" BOOLEAN NOT NULL DEFAULT false, - "status" TEXT NOT NULL DEFAULT 'initializing', - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Repository_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "User" ( - "id" UUID NOT NULL, - "firstName" TEXT NOT NULL, - "lastName" TEXT NOT NULL, - "email" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Role" ( - "userId" UUID NOT NULL, - "projectId" UUID NOT NULL, - "role" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Role_pkey" PRIMARY KEY ("userId","projectId") -); - --- CreateIndex -CREATE UNIQUE INDEX "Organization_id_key" ON "Organization"("id"); - --- CreateIndex -CREATE UNIQUE INDEX "Organization_name_key" ON "Organization"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "Organization_label_key" ON "Organization"("label"); - --- CreateIndex -CREATE UNIQUE INDEX "Permission_id_key" ON "Permission"("id"); - --- CreateIndex -CREATE UNIQUE INDEX "Permission_userId_environmentId_key" ON "Permission"("userId", "environmentId"); - --- CreateIndex -CREATE UNIQUE INDEX "Project_id_key" ON "Project"("id"); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "Role_userId_projectId_key" ON "Role"("userId", "projectId"); - --- AddForeignKey -ALTER TABLE "Environment" ADD CONSTRAINT "Environment_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Log" ADD CONSTRAINT "Log_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Permission" ADD CONSTRAINT "Permission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Permission" ADD CONSTRAINT "Permission_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "Environment"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Project" ADD CONSTRAINT "Project_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Repository" ADD CONSTRAINT "Repository_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Role" ADD CONSTRAINT "Role_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Role" ADD CONSTRAINT "Role_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/server/src/prisma/migrations/20230710181052_dso/migration.sql b/apps/server/src/prisma/migrations/20230710181052_dso/migration.sql deleted file mode 100644 index 26e1ade3f2..0000000000 --- a/apps/server/src/prisma/migrations/20230710181052_dso/migration.sql +++ /dev/null @@ -1,85 +0,0 @@ --- CreateEnum -CREATE TYPE "ClusterPrivacy" AS ENUM ('public', 'dedicated'); - --- CreateTable -CREATE TABLE "Cluster" ( - "id" UUID NOT NULL, - "label" VARCHAR(50) NOT NULL, - "privacy" "ClusterPrivacy" NOT NULL DEFAULT 'dedicated', - "secretName" VARCHAR(50) NOT NULL, - "clusterResources" BOOLEAN NOT NULL DEFAULT false, - "kubeConfigId" UUID NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - - CONSTRAINT "Cluster_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Kubeconfig" ( - "id" UUID NOT NULL, - "user" JSONB NOT NULL, - "cluster" JSONB NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL, - "parentClusterId" UUID, - - CONSTRAINT "Kubeconfig_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "_ClusterToEnvironment" ( - "A" UUID NOT NULL, - "B" UUID NOT NULL -); - --- CreateTable -CREATE TABLE "_ClusterToProject" ( - "A" UUID NOT NULL, - "B" UUID NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "Cluster_id_key" ON "Cluster"("id"); - --- CreateIndex -CREATE UNIQUE INDEX "Cluster_label_key" ON "Cluster"("label"); - --- CreateIndex -CREATE UNIQUE INDEX "Cluster_secretName_key" ON "Cluster"("secretName"); - --- CreateIndex -CREATE UNIQUE INDEX "Cluster_kubeConfigId_key" ON "Cluster"("kubeConfigId"); - --- CreateIndex -CREATE UNIQUE INDEX "Kubeconfig_id_key" ON "Kubeconfig"("id"); - --- CreateIndex -CREATE UNIQUE INDEX "Kubeconfig_parentClusterId_key" ON "Kubeconfig"("parentClusterId"); - --- CreateIndex -CREATE UNIQUE INDEX "_ClusterToEnvironment_AB_unique" ON "_ClusterToEnvironment"("A", "B"); - --- CreateIndex -CREATE INDEX "_ClusterToEnvironment_B_index" ON "_ClusterToEnvironment"("B"); - --- CreateIndex -CREATE UNIQUE INDEX "_ClusterToProject_AB_unique" ON "_ClusterToProject"("A", "B"); - --- CreateIndex -CREATE INDEX "_ClusterToProject_B_index" ON "_ClusterToProject"("B"); - --- AddForeignKey -ALTER TABLE "Cluster" ADD CONSTRAINT "Cluster_kubeConfigId_fkey" FOREIGN KEY ("kubeConfigId") REFERENCES "Kubeconfig"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ClusterToEnvironment" ADD CONSTRAINT "_ClusterToEnvironment_A_fkey" FOREIGN KEY ("A") REFERENCES "Cluster"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ClusterToEnvironment" ADD CONSTRAINT "_ClusterToEnvironment_B_fkey" FOREIGN KEY ("B") REFERENCES "Environment"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ClusterToProject" ADD CONSTRAINT "_ClusterToProject_A_fkey" FOREIGN KEY ("A") REFERENCES "Cluster"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ClusterToProject" ADD CONSTRAINT "_ClusterToProject_B_fkey" FOREIGN KEY ("B") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/server/src/prisma/migrations/20230711132934_dso/migration.sql b/apps/server/src/prisma/migrations/20230711132934_dso/migration.sql deleted file mode 100644 index 8f3fb5ff9f..0000000000 --- a/apps/server/src/prisma/migrations/20230711132934_dso/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `parentClusterId` on the `Kubeconfig` table. All the data in the column will be lost. - -*/ --- DropIndex -DROP INDEX "Kubeconfig_parentClusterId_key"; - --- AlterTable -ALTER TABLE "Kubeconfig" DROP COLUMN "parentClusterId"; diff --git a/apps/server/src/prisma/migrations/20230802143822_dso/migration.sql b/apps/server/src/prisma/migrations/20230802143822_dso/migration.sql deleted file mode 100644 index 4eb37edc53..0000000000 --- a/apps/server/src/prisma/migrations/20230802143822_dso/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TYPE "ProjectStatus" AS ENUM ('initializing', 'created', 'failed', 'archived'); - -ALTER TABLE public."Project" ALTER COLUMN status TYPE "ProjectStatus" USING - case - when status = 'created' then 'created'::"ProjectStatus" - when status = 'failed' then 'failed'::"ProjectStatus" - when status = 'archived' then 'archived'::"ProjectStatus" - else 'initializing'::"ProjectStatus" - end; -ALTER TABLE public."Project" ALTER COLUMN status SET DEFAULT 'initializing'; diff --git a/apps/server/src/prisma/migrations/20230912084459_dso/migration.sql b/apps/server/src/prisma/migrations/20230912084459_dso/migration.sql deleted file mode 100644 index f402a0e3d4..0000000000 --- a/apps/server/src/prisma/migrations/20230912084459_dso/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Cluster" ADD COLUMN "infos" VARCHAR(200); diff --git a/apps/server/src/prisma/migrations/20231010111515_dso/migration.sql b/apps/server/src/prisma/migrations/20231010111515_dso/migration.sql deleted file mode 100644 index f553e7880e..0000000000 --- a/apps/server/src/prisma/migrations/20231010111515_dso/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `externalToken` on the `Repository` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "Repository" DROP COLUMN "externalToken"; diff --git a/apps/server/src/prisma/migrations/20231011125838_dso/migration.sql b/apps/server/src/prisma/migrations/20231011125838_dso/migration.sql deleted file mode 100644 index 394b650a51..0000000000 --- a/apps/server/src/prisma/migrations/20231011125838_dso/migration.sql +++ /dev/null @@ -1,81 +0,0 @@ --- CreateEnum -CREATE TYPE "QuotaStageStatus" AS ENUM -('active', 'pendingDelete'); - --- Create new tables --- CreateTable -CREATE TABLE "Quota" -( - "id" UUID NOT NULL, - "memory" VARCHAR NOT NULL, - "cpu" REAL NOT NULL, - "name" VARCHAR NOT NULL, - "isPrivate" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "Quota_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Stage" -( - "id" UUID NOT NULL, - "name" VARCHAR NOT NULL, - - CONSTRAINT "Stage_pkey" PRIMARY KEY ("id") -); - --- Associate Quotas and Stages --- CreateTable -CREATE TABLE "QuotaStage" -( - "id" UUID NOT NULL, - "quotaId" UUID NOT NULL, - "stageId" UUID NOT NULL, - "status" "QuotaStageStatus" NOT NULL DEFAULT 'active', - - CONSTRAINT "QuotaStage_pkey" PRIMARY KEY ("id") -); -CREATE UNIQUE INDEX "Quota_id_key" ON "Quota"("id"); -CREATE UNIQUE INDEX "Quota_name_key" ON "Quota"("name"); -CREATE UNIQUE INDEX "Stage_id_key" ON "Stage"("id"); -CREATE UNIQUE INDEX "Stage_name_key" ON "Stage"("name"); -CREATE UNIQUE INDEX "QuotaStage_id_key" ON "QuotaStage"("id"); -CREATE UNIQUE INDEX "QuotaStage_quotaId_stageId_key" ON "QuotaStage"("quotaId", "stageId"); -ALTER TABLE "QuotaStage" ADD CONSTRAINT "QuotaStage_quotaId_fkey" FOREIGN KEY ("quotaId") REFERENCES "Quota"("id") ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE "QuotaStage" ADD CONSTRAINT "QuotaStage_stageId_fkey" FOREIGN KEY ("stageId") REFERENCES "Stage"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- Create default values for Quotas and Stages --- Quota -INSERT INTO "Quota" - (id, cpu, memory, "name", "isPrivate") -VALUES - ('5a57b62f-2465-4fb6-a853-5a751d099199', 2, '4Gi', 'small', false), - ('08770663-3b76-4af6-8978-9f75eda4faa7', 4, '8Gi', 'medium', false), - ('b7b4d9bd-7a8f-4287-bb12-5ce2dadb4ff2', 6, '12Gi', 'large', false), - ('97b851e8-9067-4a3d-a0e8-c3a6820c49be', 8, '16Gi', 'xlarge', false); - --- Stage -INSERT INTO "Stage" - (id, "name") -VALUES - ('4a9ad694-4c54-4a3c-9579-548bf4b7b1b9', 'dev'), - ('38fa869d-6267-441d-af7f-e0548fd06b7e', 'staging'), - ('d434310e-7850-4d59-b47f-0772edf50582', 'integration'), - ('9b3e9991-896d-4d90-bdc5-a34be8c06b8f', 'prod'); - --- QuotaStage -INSERT INTO "QuotaStage" - (id, "quotaId", "stageId") -VALUES - ('0cb0c549-560e-4f26-8f4e-832dd722f68a', '5a57b62f-2465-4fb6-a853-5a751d099199', '4a9ad694-4c54-4a3c-9579-548bf4b7b1b9'), - ('0530e9c9-b37d-4dec-93e6-1895f700e61c', '5a57b62f-2465-4fb6-a853-5a751d099199', '38fa869d-6267-441d-af7f-e0548fd06b7e'), - ('8a99db49-b7b1-44bf-865d-5e709e8aa0fc', '5a57b62f-2465-4fb6-a853-5a751d099199', 'd434310e-7850-4d59-b47f-0772edf50582'), - ('67561f00-d219-4ca6-b94a-3ee83f09d2d6', '5a57b62f-2465-4fb6-a853-5a751d099199', '9b3e9991-896d-4d90-bdc5-a34be8c06b8f'), - ('8b3c201e-7518-4254-a94a-16c404e46936', '08770663-3b76-4af6-8978-9f75eda4faa7', '4a9ad694-4c54-4a3c-9579-548bf4b7b1b9'), - ('9157ae12-3e39-43f8-a24f-ae5d9c6b69b7', '08770663-3b76-4af6-8978-9f75eda4faa7', '38fa869d-6267-441d-af7f-e0548fd06b7e'), - ('c733a1dd-c9fd-4def-b29e-df49ef7b6698', '08770663-3b76-4af6-8978-9f75eda4faa7', 'd434310e-7850-4d59-b47f-0772edf50582'), - ('15a51f47-0ab2-4a94-a808-722639d8c092', '08770663-3b76-4af6-8978-9f75eda4faa7', '9b3e9991-896d-4d90-bdc5-a34be8c06b8f'), - ('cb66e80c-2304-472d-bc19-a411011674ca', 'b7b4d9bd-7a8f-4287-bb12-5ce2dadb4ff2', 'd434310e-7850-4d59-b47f-0772edf50582'), - ('59fb0e79-3a76-4b96-81d4-63f4caa98cfa', 'b7b4d9bd-7a8f-4287-bb12-5ce2dadb4ff2', '9b3e9991-896d-4d90-bdc5-a34be8c06b8f'), - ('4174b22c-2bee-4f4a-9d85-da7b5463f214', '97b851e8-9067-4a3d-a0e8-c3a6820c49be', 'd434310e-7850-4d59-b47f-0772edf50582'), - ('de0589b6-7cf5-4f1e-ab44-53e71a6cdb7a', '97b851e8-9067-4a3d-a0e8-c3a6820c49be', '9b3e9991-896d-4d90-bdc5-a34be8c06b8f'); diff --git a/apps/server/src/prisma/migrations/20231011125839_dso/migration.sql b/apps/server/src/prisma/migrations/20231011125839_dso/migration.sql deleted file mode 100644 index 8c98a7f74e..0000000000 --- a/apps/server/src/prisma/migrations/20231011125839_dso/migration.sql +++ /dev/null @@ -1,35 +0,0 @@ --- Multiplication des environnements par clusteurs -ALTER TABLE "Environment" ADD COLUMN "clusterId" UUID; - -DO -$$ -DECLARE - perm record; - cte record; - env_uuid UUID; -BEGIN - FOR cte IN SELECT "B" AS environmentId, "A" AS "clusterId", "name", "projectId", status, "updatedAt", "createdAt" - FROM public."_ClusterToEnvironment", public."Environment" - WHERE public."Environment".id = "B" - LOOP - env_uuid := gen_random_uuid(); - INSERT INTO public."Environment" (id, "name", "projectId", "clusterId", status, "createdAt", "updatedAt") VALUES - (env_uuid, cte."name", cte."projectId", cte."clusterId", cte.status, cte."createdAt", cte."updatedAt"); - - FOR perm in SELECT * FROM public."Permission" WHERE "environmentId" = cte.environmentId - LOOP - INSERT INTO public."Permission" (id, "level", "createdAt", "updatedAt", "environmentId", "userId") VALUES - (gen_random_uuid(), perm."level", perm."createdAt", perm."updatedAt", env_uuid, perm."userId"); - END LOOP; - END LOOP; -END; -$$ -; -DELETE FROM public."Environment" WHERE "clusterId" is null; -ALTER TABLE public."Environment" ALTER COLUMN "clusterId" SET NOT NULL; -ALTER TABLE "Environment" ADD CONSTRAINT "Environment_clusterId_fkey" FOREIGN KEY ("clusterId") REFERENCES "Cluster"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- Delete old _ClusterToEnvironment -ALTER TABLE "_ClusterToEnvironment" DROP CONSTRAINT "_ClusterToEnvironment_A_fkey"; -ALTER TABLE "_ClusterToEnvironment" DROP CONSTRAINT "_ClusterToEnvironment_B_fkey"; -DROP TABLE "_ClusterToEnvironment"; diff --git a/apps/server/src/prisma/migrations/20231011125841_dso/migration.sql b/apps/server/src/prisma/migrations/20231011125841_dso/migration.sql deleted file mode 100644 index 035cd1c855..0000000000 --- a/apps/server/src/prisma/migrations/20231011125841_dso/migration.sql +++ /dev/null @@ -1,36 +0,0 @@ --- Associate cluster to Stages --- CreateTable -CREATE TABLE "_ClusterToStage" ( - "A" UUID NOT NULL, - "B" UUID NOT NULL -); --- AddForeignKey -ALTER TABLE "_ClusterToStage" ADD CONSTRAINT "_ClusterToStage_A_fkey" FOREIGN KEY ("A") REFERENCES "Cluster"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_ClusterToStage" ADD CONSTRAINT "_ClusterToStage_B_fkey" FOREIGN KEY ("B") REFERENCES "Stage"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- CreateIndex -CREATE UNIQUE INDEX "_ClusterToStage_AB_unique" ON "_ClusterToStage"("A", "B"); - --- CreateIndex -CREATE INDEX "_ClusterToStage_B_index" ON "_ClusterToStage"("B"); - -DO -$$ -DECLARE - cluster record; - cte record; - env_uuid UUID; -BEGIN - FOR cluster IN SELECT id - FROM public."Cluster" - LOOP - INSERT INTO public."_ClusterToStage" ("A", "B") VALUES - (cluster.id, '4a9ad694-4c54-4a3c-9579-548bf4b7b1b9'), - (cluster.id, '38fa869d-6267-441d-af7f-e0548fd06b7e'), - (cluster.id, 'd434310e-7850-4d59-b47f-0772edf50582'), - (cluster.id, '9b3e9991-896d-4d90-bdc5-a34be8c06b8f'); - END LOOP; -END; -$$ diff --git a/apps/server/src/prisma/migrations/20231012105520_dso/migration.sql b/apps/server/src/prisma/migrations/20231012105520_dso/migration.sql deleted file mode 100644 index 43793bdb49..0000000000 --- a/apps/server/src/prisma/migrations/20231012105520_dso/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ --- AlterTable -ALTER TABLE "Environment" ADD COLUMN "quotaStageId" UUID; -UPDATE "Environment" SET "quotaStageId" = '8b3c201e-7518-4254-a94a-16c404e46936' WHERE "name" = 'dev'; -UPDATE "Environment" SET "quotaStageId" = '9157ae12-3e39-43f8-a24f-ae5d9c6b69b7' WHERE "name" = 'staging'; -UPDATE "Environment" SET "quotaStageId" = '4174b22c-2bee-4f4a-9d85-da7b5463f214' WHERE "name" = 'integration'; -UPDATE "Environment" SET "quotaStageId" = 'de0589b6-7cf5-4f1e-ab44-53e71a6cdb7a' WHERE "name" = 'prod'; -ALTER TABLE "Environment" ALTER COLUMN "name" SET DATA TYPE VARCHAR(11); -ALTER TABLE "Environment" ALTER COLUMN "quotaStageId" SET NOT NULL; - --- AddForeignKey -ALTER TABLE "Environment" ADD CONSTRAINT "Environment_quotaStageId_fkey" FOREIGN KEY ("quotaStageId") REFERENCES "QuotaStage"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/server/src/prisma/migrations/20231024155020_dso/migration.sql b/apps/server/src/prisma/migrations/20231024155020_dso/migration.sql deleted file mode 100644 index 9af004b6a3..0000000000 --- a/apps/server/src/prisma/migrations/20231024155020_dso/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Please read 6.0.0 Release notes ! --- lock all projects -UPDATE public."Project" SET "locked"=true \ No newline at end of file diff --git a/apps/server/src/prisma/migrations/20231026150220_dso/migration.sql b/apps/server/src/prisma/migrations/20231026150220_dso/migration.sql deleted file mode 100644 index d970d3965a..0000000000 --- a/apps/server/src/prisma/migrations/20231026150220_dso/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Please read 6.0.0 Release notes ! --- set all projects to failed to avoid unlock them -UPDATE public."Project" SET "status" = 'failed' WHERE "status" != 'archived' \ No newline at end of file diff --git a/apps/server/src/prisma/migrations/20240112135751_dso/migration.sql b/apps/server/src/prisma/migrations/20240112135751_dso/migration.sql deleted file mode 100644 index c387d9885c..0000000000 --- a/apps/server/src/prisma/migrations/20240112135751_dso/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Log" ADD COLUMN "requestId" VARCHAR(21); diff --git a/apps/server/src/prisma/migrations/20240321123436_dso/migration.sql b/apps/server/src/prisma/migrations/20240321123436_dso/migration.sql deleted file mode 100644 index 18e20262c8..0000000000 --- a/apps/server/src/prisma/migrations/20240321123436_dso/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `status` on the `Environment` table. All the data in the column will be lost. - - You are about to drop the column `status` on the `Repository` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "Environment" DROP COLUMN "status"; - --- AlterTable -ALTER TABLE "Repository" DROP COLUMN "status"; diff --git a/apps/server/src/prisma/migrations/20240329172938_dso/migration.sql b/apps/server/src/prisma/migrations/20240329172938_dso/migration.sql deleted file mode 100644 index f784b21567..0000000000 --- a/apps/server/src/prisma/migrations/20240329172938_dso/migration.sql +++ /dev/null @@ -1,46 +0,0 @@ --- AlterTable -ALTER TABLE "Cluster" ADD COLUMN "zoneId" UUID; - --- CreateTable -CREATE TABLE "Zone" -( - "id" UUID NOT NULL, - "slug" VARCHAR(10) NOT NULL, - "label" VARCHAR(50) NOT NULL, - "description" VARCHAR(200), - "createdAt" TIMESTAMP -(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP -(3) NOT NULL, - - CONSTRAINT "Zone_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Zone_id_key" ON "Zone"("id"); - --- CreateIndex -CREATE UNIQUE INDEX "Zone_slug_key" ON "Zone"("slug"); - --- Create default zone -INSERT INTO "Zone" - (id, "slug", "label", "description", "updatedAt") -VALUES - ('a66c4230-eba6-41f1-aae5-bb1e4f90cce0', 'default', 'Zone Défaut', 'Zone par défaut, à changer', CURRENT_TIMESTAMP); - --- Set default zoneId for current clusters -UPDATE "Cluster" -SET "zoneId" -= 'a66c4230-eba6-41f1-aae5-bb1e4f90cce0' -WHERE "zoneId" -IS NULL; - --- AlterTable -ALTER TABLE "Cluster" ALTER COLUMN "zoneId" -SET -NOT NULL; - --- AddForeignKey -ALTER TABLE "Cluster" ADD CONSTRAINT "Cluster_zoneId_fkey" FOREIGN KEY ("zoneId") REFERENCES "Zone"("id") -ON DELETE RESTRICT ON -UPDATE CASCADE; diff --git a/apps/server/src/prisma/migrations/20240424093852_dso/migration.sql b/apps/server/src/prisma/migrations/20240424093852_dso/migration.sql deleted file mode 100644 index cb4f7ad968..0000000000 --- a/apps/server/src/prisma/migrations/20240424093852_dso/migration.sql +++ /dev/null @@ -1,23 +0,0 @@ --- CreateTable -CREATE TABLE "ProjectPlugin" ( - "pluginName" TEXT NOT NULL, - "projectId" UUID NOT NULL, - "key" TEXT NOT NULL, - "value" TEXT NOT NULL -); - --- CreateTable -CREATE TABLE "AdminPlugin" ( - "pluginName" TEXT NOT NULL, - "key" TEXT NOT NULL, - "value" TEXT NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "ProjectPlugin_projectId_pluginName_key_key" ON "ProjectPlugin"("projectId", "pluginName", "key"); - --- CreateIndex -CREATE UNIQUE INDEX "AdminPlugin_pluginName_key_key" ON "AdminPlugin"("pluginName", "key"); - --- AddForeignKey -ALTER TABLE "ProjectPlugin" ADD CONSTRAINT "ProjectPlugin_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/server/src/prisma/migrations/20240427181037_dso/migration.sql b/apps/server/src/prisma/migrations/20240427181037_dso/migration.sql deleted file mode 100644 index 11672324f8..0000000000 --- a/apps/server/src/prisma/migrations/20240427181037_dso/migration.sql +++ /dev/null @@ -1,19 +0,0 @@ -DO $$ -DECLARE - project_row RECORD; - registry_id INT; -BEGIN - -- Début de la boucle sur chaque ligne de la table 'Project' - FOR project_row IN SELECT id, services FROM public."Project" LOOP - -- Extrait 'registry.id' de la colonne JSON 'services' - registry_id := (SELECT (project_row.services -> 'registry' ->> 'id')::TEXT); - -- Si 'registry.id' existe, insérer dans la table 'config' - IF registry_id IS NOT NULL THEN - INSERT INTO public."ProjectPlugin" ("projectId", "pluginName", "key", "value") - VALUES (project_row.id, 'registry', 'projectId', registry_id::TEXT); - END IF; - END LOOP; -END $$; - --- AlterTable -ALTER TABLE "Project" DROP COLUMN "services"; diff --git a/apps/server/src/prisma/migrations/20240605135052_dso/migration.sql b/apps/server/src/prisma/migrations/20240605135052_dso/migration.sql deleted file mode 100644 index 9c7d5a8f8f..0000000000 --- a/apps/server/src/prisma/migrations/20240605135052_dso/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ --- CreateEnum -CREATE TYPE "RoleList" AS ENUM ('owner', 'user'); - --- AlterTable -ALTER TABLE public."Role" ALTER COLUMN "role" TYPE "RoleList" USING - case - when role = 'owner' then 'owner'::"RoleList" - else 'user'::"RoleList" - end; \ No newline at end of file diff --git a/apps/server/src/prisma/migrations/20240612123132_dso/migration.sql b/apps/server/src/prisma/migrations/20240612123132_dso/migration.sql deleted file mode 100644 index 45a4a5d1ea..0000000000 --- a/apps/server/src/prisma/migrations/20240612123132_dso/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ --- CreateTable -CREATE TABLE "ProjectClusterHistory" ( - "projectId" UUID NOT NULL, - "clusterId" UUID NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "ProjectClusterHistory_projectId_clusterId_key" ON "ProjectClusterHistory"("projectId", "clusterId"); diff --git a/apps/server/src/prisma/migrations/20240614222908_dso/migration.sql b/apps/server/src/prisma/migrations/20240614222908_dso/migration.sql deleted file mode 100644 index 2b1641a657..0000000000 --- a/apps/server/src/prisma/migrations/20240614222908_dso/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ -DO $$ -DECLARE - env_row RECORD; -BEGIN - -- Début de la boucle sur chaque ligne de la table 'Project' - FOR env_row IN SELECT "projectId", "clusterId" FROM public."Environment" LOOP - INSERT INTO public."ProjectClusterHistory" ("projectId", "clusterId") - VALUES (env_row."projectId", env_row."clusterId") - ON CONFLICT DO NOTHING; - END LOOP; -END $$; \ No newline at end of file diff --git a/apps/server/src/prisma/migrations/20240618112205_dso/migration.sql b/apps/server/src/prisma/migrations/20240618112205_dso/migration.sql deleted file mode 100644 index 5e7ff03d45..0000000000 --- a/apps/server/src/prisma/migrations/20240618112205_dso/migration.sql +++ /dev/null @@ -1,58 +0,0 @@ --- AlterTable -ALTER TABLE "Environment" ADD COLUMN "quotaId" UUID, -ADD COLUMN "stageId" UUID; - --- AddForeignKey -ALTER TABLE "Environment" ADD CONSTRAINT "Environment_quotaId_fkey" FOREIGN KEY ("quotaId") REFERENCES "Quota"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Environment" ADD CONSTRAINT "Environment_stageId_fkey" FOREIGN KEY ("stageId") REFERENCES "Stage"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- CreateTable -CREATE TABLE "_QuotaToStage" ( - "A" UUID NOT NULL, - "B" UUID NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "_QuotaToStage_AB_unique" ON "_QuotaToStage"("A", "B"); - --- CreateIndex -CREATE INDEX "_QuotaToStage_B_index" ON "_QuotaToStage"("B"); - --- AddForeignKey -ALTER TABLE "_QuotaToStage" ADD CONSTRAINT "_QuotaToStage_A_fkey" FOREIGN KEY ("A") REFERENCES "Quota"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_QuotaToStage" ADD CONSTRAINT "_QuotaToStage_B_fkey" FOREIGN KEY ("B") REFERENCES "Stage"("id") ON DELETE CASCADE ON UPDATE CASCADE; - -DO $$ -DECLARE - quota_stage_row RECORD; -BEGIN - FOR quota_stage_row IN SELECT * FROM public."QuotaStage" loop - UPDATE public."Environment" SET "stageId" = quota_stage_row."stageId" WHERE "Environment"."quotaStageId" = quota_stage_row.id; - UPDATE public."Environment" SET "quotaId" = quota_stage_row."quotaId" WHERE "Environment"."quotaStageId" = quota_stage_row.id; - insert into public."_QuotaToStage" values (quota_stage_row."quotaId", quota_stage_row."stageId"); - END LOOP; -END $$; - --- DropForeignKey -ALTER TABLE "Environment" DROP CONSTRAINT "Environment_quotaStageId_fkey"; - --- AlterTable -ALTER TABLE "Environment" ALTER COLUMN "quotaId" SET NOT NULL, -ALTER COLUMN "stageId" SET NOT NULL, -DROP COLUMN "quotaStageId"; - --- DropForeignKey -ALTER TABLE "QuotaStage" DROP CONSTRAINT "QuotaStage_quotaId_fkey"; - --- DropForeignKey -ALTER TABLE "QuotaStage" DROP CONSTRAINT "QuotaStage_stageId_fkey"; - --- DropTable -DROP TABLE "QuotaStage"; - --- DropEnum -DROP TYPE "QuotaStageStatus"; diff --git a/apps/server/src/prisma/migrations/20240717084709_dso/migration.sql b/apps/server/src/prisma/migrations/20240717084709_dso/migration.sql deleted file mode 100644 index 0036da8a12..0000000000 --- a/apps/server/src/prisma/migrations/20240717084709_dso/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ -/* - Warnings: - - - Made the column `description` on table `Project` required. This step will fail if there are existing NULL values in that column. - -*/ --- AlterTable -ALTER TABLE "Project" ALTER COLUMN "description" SET NOT NULL, -ALTER COLUMN "description" SET DEFAULT ''; diff --git a/apps/server/src/prisma/migrations/20240723135420_dso/migration.sql b/apps/server/src/prisma/migrations/20240723135420_dso/migration.sql deleted file mode 100644 index ed6ae9b84e..0000000000 --- a/apps/server/src/prisma/migrations/20240723135420_dso/migration.sql +++ /dev/null @@ -1,198 +0,0 @@ --- DropForeignKey if exists -DO $$ BEGIN - IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'Permission_environmentId_fkey') THEN - ALTER TABLE "Permission" DROP CONSTRAINT "Permission_environmentId_fkey"; - END IF; -END $$; - --- DropForeignKey if exists -DO $$ BEGIN - IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'Permission_userId_fkey') THEN - ALTER TABLE "Permission" DROP CONSTRAINT "Permission_userId_fkey"; - END IF; -END $$; - --- DropForeignKey if exists -DO $$ BEGIN - IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'Role_projectId_fkey') THEN - ALTER TABLE "Role" DROP CONSTRAINT "Role_projectId_fkey"; - END IF; -END $$; - --- DropForeignKey if exists -DO $$ BEGIN - IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'Role_userId_fkey') THEN - ALTER TABLE "Role" DROP CONSTRAINT "Role_userId_fkey"; - END IF; -END $$; - --- CreateTable if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ProjectMembers') THEN - CREATE TABLE "ProjectMembers" ( - "projectId" UUID NOT NULL, - "userId" UUID NOT NULL, - "roleIds" TEXT[] - ); - END IF; -END $$; - --- AlterTable -ALTER TABLE "Log" ADD COLUMN IF NOT EXISTS "projectId" UUID; - -INSERT INTO public."User" (id, "firstName", "lastName", email, "createdAt", "updatedAt") -VALUES('04ac168a-2c4f-4816-9cce-af6c612e5912'::uuid, 'Anonymous', 'User', 'anon@user', '2023-07-03 14:46:56.770', '2023-07-03 14:46:56.770') -ON CONFLICT (id) DO NOTHING; - --- AlterTable -ALTER TABLE "Project" ADD COLUMN IF NOT EXISTS "everyonePerms" BIGINT NOT NULL DEFAULT 896, -ADD COLUMN IF NOT EXISTS "ownerId" UUID; - -DO $$ -DECLARE - role_row RECORD; -BEGIN - IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'Role') THEN - -- Début de la boucle sur chaque ligne de la table 'Project' - FOR role_row IN SELECT "userId", "projectId", "role" FROM public."Role" LOOP - INSERT INTO public."ProjectMembers" ("userId", "projectId", "roleIds") VALUES (role_row."userId", role_row."projectId", '{}'); - IF role_row."role" = 'owner'::public."RoleList" THEN - UPDATE public."Project" - SET "ownerId"=role_row."userId" - WHERE id=role_row."projectId"::uuid; - END IF; - END LOOP; - END IF; -END $$; - -UPDATE public."Project" -SET "ownerId"='04ac168a-2c4f-4816-9cce-af6c612e5912' -WHERE "ownerId" IS NULL; - -ALTER TABLE public."Project" ALTER COLUMN "ownerId" SET NOT NULL; - -DELETE FROM public."ProjectMembers" pm -USING public."Project" p -WHERE pm."userId" = p."ownerId" -AND pm."projectId" = p."id"; - --- DropTable if exists -DO $$ BEGIN - IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'Permission') THEN - DROP TABLE "Permission"; - END IF; -END $$; - --- DropTable if exists -DO $$ BEGIN - IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'Role') THEN - DROP TABLE "Role"; - END IF; -END $$; - --- DropEnum if exists -DO $$ BEGIN - IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'RoleList') THEN - DROP TYPE "RoleList"; - END IF; -END $$; - --- CreateTable if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'AdminRole') THEN - CREATE TABLE "AdminRole" ( - "id" UUID NOT NULL, - "name" TEXT NOT NULL, - "permissions" BIGINT NOT NULL, - "position" SMALLINT NOT NULL, - CONSTRAINT "AdminRole_pkey" PRIMARY KEY ("id") - ); - END IF; -END $$; - --- CreateTable if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ProjectRole') THEN - CREATE TABLE "ProjectRole" ( - "id" UUID NOT NULL, - "name" TEXT NOT NULL, - "permissions" BIGINT NOT NULL, - "projectId" UUID NOT NULL, - "position" SMALLINT NOT NULL, - CONSTRAINT "ProjectRole_pkey" PRIMARY KEY ("id") - ); - END IF; -END $$; - --- CreateIndex if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'AdminRole_id_key') THEN - CREATE UNIQUE INDEX "AdminRole_id_key" ON "AdminRole"("id"); - END IF; -END $$; - --- CreateIndex if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'AdminRole_name_key') THEN - CREATE UNIQUE INDEX "AdminRole_name_key" ON "AdminRole"("name"); - END IF; -END $$; - --- CreateIndex if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'ProjectMembers_projectId_userId_key') THEN - CREATE UNIQUE INDEX "ProjectMembers_projectId_userId_key" ON "ProjectMembers"("projectId", "userId"); - END IF; -END $$; - --- CreateIndex if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'ProjectRole_id_key') THEN - CREATE UNIQUE INDEX "ProjectRole_id_key" ON "ProjectRole"("id"); - END IF; -END $$; - --- CreateIndex if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'Environment_projectId_name_key') THEN - CREATE UNIQUE INDEX "Environment_projectId_name_key" ON "Environment"("projectId", "name"); - END IF; -END $$; - --- AddForeignKey if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'Log_projectId_fkey') THEN - ALTER TABLE "Log" ADD CONSTRAINT "Log_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE; - END IF; -END $$; - --- AddForeignKey if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'Project_ownerId_fkey') THEN - ALTER TABLE "Project" ADD CONSTRAINT "Project_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - END IF; -END $$; - --- AddForeignKey if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'ProjectMembers_projectId_fkey') THEN - ALTER TABLE "ProjectMembers" ADD CONSTRAINT "ProjectMembers_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - END IF; -END $$; - --- AddForeignKey if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'ProjectMembers_userId_fkey') THEN - ALTER TABLE "ProjectMembers" ADD CONSTRAINT "ProjectMembers_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - END IF; -END $$; - --- AddForeignKey if not exists -DO $$ BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'ProjectRole_projectId_fkey') THEN - ALTER TABLE "ProjectRole" ADD CONSTRAINT "ProjectRole_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - END IF; -END $$; - --- AlterTable -ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "adminRoleIds" TEXT[]; diff --git a/apps/server/src/prisma/migrations/20240725162050_dso/migration.sql b/apps/server/src/prisma/migrations/20240725162050_dso/migration.sql deleted file mode 100644 index c9b41827bf..0000000000 --- a/apps/server/src/prisma/migrations/20240725162050_dso/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- DropIndex -DROP INDEX "AdminRole_name_key"; - --- AlterTable -ALTER TABLE "AdminRole" ADD COLUMN "oidcGroup" TEXT; diff --git a/apps/server/src/prisma/migrations/20240726210139_dso/migration.sql b/apps/server/src/prisma/migrations/20240726210139_dso/migration.sql deleted file mode 100644 index 265f262abb..0000000000 --- a/apps/server/src/prisma/migrations/20240726210139_dso/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ -/* - Warnings: - - - Made the column `oidcGroup` on table `AdminRole` required. This step will fail if there are existing NULL values in that column. - -*/ --- AlterTable - -UPDATE public."AdminRole" -SET "oidcGroup"='' -WHERE "oidcGroup" IS NULL; - -ALTER TABLE "AdminRole" ALTER COLUMN "oidcGroup" SET NOT NULL, -ALTER COLUMN "oidcGroup" SET DEFAULT ''; diff --git a/apps/server/src/prisma/migrations/20240808082632_dso/migration.sql b/apps/server/src/prisma/migrations/20240808082632_dso/migration.sql deleted file mode 100644 index 4fc2768605..0000000000 --- a/apps/server/src/prisma/migrations/20240808082632_dso/migration.sql +++ /dev/null @@ -1,17 +0,0 @@ --- CreateTable -CREATE TABLE "SystemSetting" -( - "key" TEXT NOT NULL, - "value" TEXT NOT NULL, - - CONSTRAINT "SystemSetting_pkey" PRIMARY KEY ("key") -); - --- CreateIndex -CREATE UNIQUE INDEX "SystemSetting_key_key" ON "SystemSetting"("key"); - --- Create maintenance setting -INSERT INTO "SystemSetting" - ("key", "value") -VALUES - ('maintenance', 'off'); diff --git a/apps/server/src/prisma/migrations/20240826143230_dso/migration.sql b/apps/server/src/prisma/migrations/20240826143230_dso/migration.sql deleted file mode 100644 index 95ab54869d..0000000000 --- a/apps/server/src/prisma/migrations/20240826143230_dso/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO public."AdminRole" -(id, "name", permissions, "position", "oidcGroup") -VALUES('76229c96-4716-45bc-99da-00498ec9018c'::uuid, 'Admin', 2, 0, '/admin'); \ No newline at end of file diff --git a/apps/server/src/prisma/migrations/20240829085548_dso/migration.sql b/apps/server/src/prisma/migrations/20240829085548_dso/migration.sql deleted file mode 100644 index c116482186..0000000000 --- a/apps/server/src/prisma/migrations/20240829085548_dso/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - Warnings: - - - Made the column `externalUserName` on table `Repository` required. This step will fail if there are existing NULL values in that column. - -*/ --- AlterTable -UPDATE "Repository" SET "externalUserName" = '' WHERE "externalUserName" IS NULL; - -ALTER TABLE "Repository" ALTER COLUMN "externalUserName" SET NOT NULL, -ALTER COLUMN "externalUserName" SET DEFAULT '', -ALTER COLUMN "externalRepoUrl" SET DEFAULT ''; diff --git a/apps/server/src/prisma/migrations/20240916141253_token/migration.sql b/apps/server/src/prisma/migrations/20240916141253_token/migration.sql deleted file mode 100644 index b0472cd806..0000000000 --- a/apps/server/src/prisma/migrations/20240916141253_token/migration.sql +++ /dev/null @@ -1,23 +0,0 @@ --- CreateEnum -CREATE TYPE "TokenStatus" AS ENUM ('active', 'revoked'); - --- CreateTable -CREATE TABLE "AdminToken" ( - "id" UUID NOT NULL, - "name" TEXT NOT NULL, - "permissions" BIGINT NOT NULL, - "userId" UUID, - "expirationDate" TIMESTAMP(3), - "lastUse" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "status" "TokenStatus" NOT NULL DEFAULT 'active', - "hash" TEXT NOT NULL, - - CONSTRAINT "AdminToken_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "AdminToken_id_key" ON "AdminToken"("id"); - --- AddForeignKey -ALTER TABLE "AdminToken" ADD CONSTRAINT "AdminToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/server/src/prisma/migrations/20240919122331_optional_user_id/migration.sql b/apps/server/src/prisma/migrations/20240919122331_optional_user_id/migration.sql deleted file mode 100644 index 47488b00c5..0000000000 --- a/apps/server/src/prisma/migrations/20240919122331_optional_user_id/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ --- DropForeignKey -ALTER TABLE "Log" DROP CONSTRAINT "Log_userId_fkey"; - --- AlterTable -ALTER TABLE "Log" ALTER COLUMN "userId" DROP NOT NULL; - --- AddForeignKey -ALTER TABLE "Log" ADD CONSTRAINT "Log_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/apps/server/src/prisma/migrations/20240923142722_dso/migration.sql b/apps/server/src/prisma/migrations/20240923142722_dso/migration.sql deleted file mode 100644 index 18eca3eadf..0000000000 --- a/apps/server/src/prisma/migrations/20240923142722_dso/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Log" ALTER COLUMN "requestId" SET DATA TYPE VARCHAR(36); diff --git a/apps/server/src/prisma/migrations/20240923155416_dso/migration.sql b/apps/server/src/prisma/migrations/20240923155416_dso/migration.sql deleted file mode 100644 index 74e0946f0a..0000000000 --- a/apps/server/src/prisma/migrations/20240923155416_dso/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Zone" ADD COLUMN "argocdUrl" TEXT NOT NULL DEFAULT 'https://example.com'; diff --git a/apps/server/src/prisma/migrations/20240928002900_dso/migration.sql b/apps/server/src/prisma/migrations/20240928002900_dso/migration.sql deleted file mode 100644 index 41dac75359..0000000000 --- a/apps/server/src/prisma/migrations/20240928002900_dso/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterEnum -ALTER TYPE "ProjectStatus" ADD VALUE 'warning'; diff --git a/apps/server/src/prisma/migrations/20241008125724_enabling_maven/migration.sql b/apps/server/src/prisma/migrations/20241008125724_enabling_maven/migration.sql deleted file mode 100644 index ef888d5e50..0000000000 --- a/apps/server/src/prisma/migrations/20241008125724_enabling_maven/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -DO $$ -DECLARE - project_row RECORD; - registry_id INT; -BEGIN - -- Début de la boucle sur chaque ligne de la table 'Project' - FOR project_row IN SELECT id FROM public."Project" WHERE status <> 'archived'::public."ProjectStatus" LOOP - INSERT INTO public."ProjectPlugin" ("projectId", "pluginName", "key", "value") - VALUES (project_row.id, 'nexus', 'activateMavenRepo', 'enabled') - ON CONFLICT DO NOTHING; - END LOOP; -END $$; \ No newline at end of file diff --git a/apps/server/src/prisma/migrations/20241104232540_add_usertype/migration.sql b/apps/server/src/prisma/migrations/20241104232540_add_usertype/migration.sql deleted file mode 100644 index a57e5956c3..0000000000 --- a/apps/server/src/prisma/migrations/20241104232540_add_usertype/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ --- CreateEnum -CREATE TYPE "UserType" AS ENUM ('human', 'bot', 'ghost'); - --- AlterEnum -ALTER TYPE "TokenStatus" ADD VALUE 'inactive'; - --- AlterTable -ALTER TABLE "User" ADD COLUMN "type" "UserType" NOT NULL DEFAULT 'human'; -UPDATE "User" SET type = 'ghost' WHERE id = '04ac168a-2c4f-4816-9cce-af6c612e5912'; - --- AlterTable -ALTER TABLE "User" ALTER COLUMN "type" DROP DEFAULT; diff --git a/apps/server/src/prisma/migrations/20241104232541_add_pat/migration.sql b/apps/server/src/prisma/migrations/20241104232541_add_pat/migration.sql deleted file mode 100644 index 71e15a312c..0000000000 --- a/apps/server/src/prisma/migrations/20241104232541_add_pat/migration.sql +++ /dev/null @@ -1,84 +0,0 @@ --- CreateTable (idempotent) -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'PersonalAccessToken') THEN - CREATE TABLE "PersonalAccessToken" ( - "id" UUID NOT NULL, - "name" TEXT NOT NULL, - "userId" UUID NOT NULL, - "expirationDate" TIMESTAMP(3) NOT NULL, - "lastUse" TIMESTAMP(3), - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "status" "TokenStatus" NOT NULL DEFAULT 'active', - "hash" TEXT NOT NULL, - - CONSTRAINT "PersonalAccessToken_pkey" PRIMARY KEY ("id") - ); - END IF; -END $$; - --- CreateIndex (idempotent) -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'PersonalAccessToken_id_key') THEN - CREATE UNIQUE INDEX "PersonalAccessToken_id_key" ON "PersonalAccessToken"("id"); - END IF; -END $$; - --- AddForeignKey (idempotent) -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'PersonalAccessToken_userId_fkey') THEN - ALTER TABLE "PersonalAccessToken" ADD CONSTRAINT "PersonalAccessToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - END IF; -END $$; - --- Process AdminToken (idempotent) -DO $$ -DECLARE - admin_token record; - user_uuid UUID; -BEGIN - FOR admin_token IN SELECT "name", "id" - FROM public."AdminToken" - LOOP - -- Generate new UUID if user does not exist - user_uuid := COALESCE( - (SELECT id FROM public."User" WHERE email = concat(admin_token.name, '@bot.id')), - gen_random_uuid() - ); - - -- Insert user if not already exists - INSERT INTO public."User" (id, "firstName", "lastName", email, "createdAt", "updatedAt", "type") - VALUES(user_uuid, 'Bot Admin', admin_token.name, concat(admin_token.name, '@bot.id'), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'bot') - ON CONFLICT (id) DO NOTHING; - - -- Update AdminToken with the new user ID - UPDATE public."AdminToken" SET "userId" = user_uuid WHERE id = admin_token.id; - END LOOP; -END $$; - --- Alter AdminToken userId column to NOT NULL (idempotent) -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.columns - WHERE table_name = 'AdminToken' AND column_name = 'userId' AND is_nullable = 'NO') THEN - ALTER TABLE public."AdminToken" ALTER COLUMN "userId" SET NOT NULL; - END IF; -END $$; - --- DropForeignKey if exists (idempotent) -DO $$ -BEGIN - IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'AdminToken_userId_fkey') THEN - ALTER TABLE "AdminToken" DROP CONSTRAINT "AdminToken_userId_fkey"; - END IF; -END $$; - --- AddForeignKey (idempotent) -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = 'AdminToken_userId_fkey') THEN - ALTER TABLE "AdminToken" ADD CONSTRAINT "AdminToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - END IF; -END $$; diff --git a/apps/server/src/prisma/migrations/20241107142721_user_last_login/migration.sql b/apps/server/src/prisma/migrations/20241107142721_user_last_login/migration.sql deleted file mode 100644 index 521b2b10ac..0000000000 --- a/apps/server/src/prisma/migrations/20241107142721_user_last_login/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "lastLogin" TIMESTAMP(3); diff --git a/apps/server/src/prisma/migrations/20241112101945_add_slug/migration.sql b/apps/server/src/prisma/migrations/20241112101945_add_slug/migration.sql deleted file mode 100644 index a7500833b9..0000000000 --- a/apps/server/src/prisma/migrations/20241112101945_add_slug/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ --- AlterTable -ALTER TABLE "Project" ADD COLUMN "slug" TEXT; - -UPDATE public."Project" p -SET slug = ( - SELECT concat(org.name, '-', subp.name) FROM public."Project" subp - LEFT JOIN public."Organization" org on org."id" = subp."organizationId" - WHERE subp.id = p.id -); - -ALTER TABLE public."Project" ALTER COLUMN "slug" SET NOT NULL; - --- CreateIndex -CREATE UNIQUE INDEX "Project_slug_key" ON "Project"("slug"); diff --git a/apps/server/src/prisma/migrations/20241112102015_add_provisionning_version/migration.sql b/apps/server/src/prisma/migrations/20241112102015_add_provisionning_version/migration.sql deleted file mode 100644 index b143cbeb9b..0000000000 --- a/apps/server/src/prisma/migrations/20241112102015_add_provisionning_version/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Project" ADD COLUMN "lastSuccessProvisionningVersion" TEXT; diff --git a/apps/server/src/prisma/migrations/20241216131342_dso/migration.sql b/apps/server/src/prisma/migrations/20241216131342_dso/migration.sql deleted file mode 100644 index 7a88681901..0000000000 --- a/apps/server/src/prisma/migrations/20241216131342_dso/migration.sql +++ /dev/null @@ -1,17 +0,0 @@ --- AlterTable -ALTER TABLE "_ClusterToProject" ADD CONSTRAINT "_ClusterToProject_AB_pkey" PRIMARY KEY ("A", "B"); - --- DropIndex -DROP INDEX "_ClusterToProject_AB_unique"; - --- AlterTable -ALTER TABLE "_ClusterToStage" ADD CONSTRAINT "_ClusterToStage_AB_pkey" PRIMARY KEY ("A", "B"); - --- DropIndex -DROP INDEX "_ClusterToStage_AB_unique"; - --- AlterTable -ALTER TABLE "_QuotaToStage" ADD CONSTRAINT "_QuotaToStage_AB_pkey" PRIMARY KEY ("A", "B"); - --- DropIndex -DROP INDEX "_QuotaToStage_AB_unique"; diff --git a/apps/server/src/prisma/migrations/20250107104749_dso/migration.sql b/apps/server/src/prisma/migrations/20250107104749_dso/migration.sql deleted file mode 100644 index 21ce77b8d5..0000000000 --- a/apps/server/src/prisma/migrations/20250107104749_dso/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Cluster" ADD COLUMN "external" BOOLEAN NOT NULL DEFAULT false; diff --git a/apps/server/src/prisma/migrations/20250121222953_prevent_upgrade/migration.sql b/apps/server/src/prisma/migrations/20250121222953_prevent_upgrade/migration.sql deleted file mode 100644 index ac63cd639d..0000000000 --- a/apps/server/src/prisma/migrations/20250121222953_prevent_upgrade/migration.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Vérifie les versions dans la table Project -DO $$ -DECLARE - project_id TEXT; - project_name TEXT; - last_version TEXT; -BEGIN - -- Boucle sur les projets non archivés - FOR project_id, project_name, last_version IN ( - SELECT id, name, "lastSuccessProvisionningVersion" - FROM "Project" - WHERE "status" != 'archived' - ) - LOOP - -- Vérifie si la version est NULL - IF last_version IS NULL THEN - RAISE EXCEPTION 'Le projet % (ID: %) a une version NULL.', project_name, project_id; - END IF; - - -- Vérifie si la version est inférieure à 8.23.0 selon SemVer - IF (string_to_array(last_version, '.')::int[] < ARRAY[8,23,0]) THEN - RAISE EXCEPTION 'Le projet % (ID: %) a une version (%), inférieure à 8.23.0.', project_name, project_id, last_version; - END IF; - END LOOP; -END $$; diff --git a/apps/server/src/prisma/migrations/20250121222954_drop_organization/migration.sql b/apps/server/src/prisma/migrations/20250121222954_drop_organization/migration.sql deleted file mode 100644 index 54871c9017..0000000000 --- a/apps/server/src/prisma/migrations/20250121222954_drop_organization/migration.sql +++ /dev/null @@ -1,15 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `organizationId` on the `Project` table. All the data in the column will be lost. - - You are about to drop the `Organization` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "Project" DROP CONSTRAINT "Project_organizationId_fkey"; - --- AlterTable -ALTER TABLE "Project" DROP COLUMN "organizationId"; - --- DropTable -DROP TABLE "Organization"; diff --git a/apps/server/src/prisma/migrations/20250723141246_dso/migration.sql b/apps/server/src/prisma/migrations/20250723141246_dso/migration.sql deleted file mode 100644 index 68ca0df2fa..0000000000 --- a/apps/server/src/prisma/migrations/20250723141246_dso/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Cluster" ALTER COLUMN "infos" SET DATA TYPE VARCHAR(1000); diff --git a/apps/server/src/prisma/migrations/20250818095032_remove_quota/migration.sql b/apps/server/src/prisma/migrations/20250818095032_remove_quota/migration.sql deleted file mode 100644 index 8364090d8d..0000000000 --- a/apps/server/src/prisma/migrations/20250818095032_remove_quota/migration.sql +++ /dev/null @@ -1,44 +0,0 @@ --- AlterTable -ALTER TABLE "Environment" -ADD COLUMN "cpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "gpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "memory" REAL NOT NULL DEFAULT 0; - -COMMENT ON COLUMN "Environment".cpu IS 'CPU share as float (1 and 0.01 are valid values)'; -COMMENT ON COLUMN "Environment".gpu IS 'GPU share as float (1 and 0.01 are valid values)'; -COMMENT ON COLUMN "Environment".memory IS 'Memory value as GigaBytes (1 and 0.01 are valid values)'; - --- Use values from Quota. Memory is an extract of q.memory numeric value as it contains a unit (e.g. '2Gi'). -UPDATE "Environment" -SET cpu = q.cpu, memory = COALESCE(NULLIF(regexp_replace(q.memory, '\D', '','g'), ''), '0')::numeric -FROM "Quota" q -WHERE "quotaId" = q."id"; - -/* - Warnings: - - - You are about to drop the column `quotaId` on the `Environment` table. All the data in the column will be lost. - - You are about to drop the `Quota` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `_QuotaToStage` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "Environment" DROP CONSTRAINT "Environment_quotaId_fkey"; - --- DropForeignKey -ALTER TABLE "_QuotaToStage" DROP CONSTRAINT "_QuotaToStage_A_fkey"; - --- DropForeignKey -ALTER TABLE "_QuotaToStage" DROP CONSTRAINT "_QuotaToStage_B_fkey"; - --- AlterTable -ALTER TABLE "Environment" DROP COLUMN "quotaId", -ALTER COLUMN "cpu" DROP DEFAULT, -ALTER COLUMN "gpu" DROP DEFAULT, -ALTER COLUMN "memory" DROP DEFAULT; - --- DropTable -DROP TABLE "Quota"; - --- DropTable -DROP TABLE "_QuotaToStage"; diff --git a/apps/server/src/prisma/migrations/20250825150622_add_cluster_resources/migration.sql b/apps/server/src/prisma/migrations/20250825150622_add_cluster_resources/migration.sql deleted file mode 100644 index 77f32b5ab5..0000000000 --- a/apps/server/src/prisma/migrations/20250825150622_add_cluster_resources/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- AlterTable -ALTER TABLE "Cluster" -ADD COLUMN "cpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "gpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "memory" REAL NOT NULL DEFAULT 0; diff --git a/apps/server/src/prisma/migrations/20250916134454_add_project_resources/migration.sql b/apps/server/src/prisma/migrations/20250916134454_add_project_resources/migration.sql deleted file mode 100644 index decca804aa..0000000000 --- a/apps/server/src/prisma/migrations/20250916134454_add_project_resources/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ --- AlterTable -ALTER TABLE "Project" -ADD COLUMN "limitless" BOOLEAN NOT NULL DEFAULT true, -ADD COLUMN "hprodCpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "hprodGpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "hprodMemory" REAL NOT NULL DEFAULT 0, -ADD COLUMN "prodCpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "prodGpu" REAL NOT NULL DEFAULT 0, -ADD COLUMN "prodMemory" REAL NOT NULL DEFAULT 0; diff --git a/apps/server/src/prisma/migrations/20251028150522_rename_default_zone/migration.sql b/apps/server/src/prisma/migrations/20251028150522_rename_default_zone/migration.sql deleted file mode 100644 index 95f3a689db..0000000000 --- a/apps/server/src/prisma/migrations/20251028150522_rename_default_zone/migration.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Rename default zone -UPDATE "Zone" -SET ("label", "description") = ('DSO', 'Zone par défaut') -WHERE slug = 'default'; diff --git a/apps/server/src/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql b/apps/server/src/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql deleted file mode 100644 index aadb6cdba9..0000000000 --- a/apps/server/src/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql +++ /dev/null @@ -1,4 +0,0 @@ --- AlterTable -ALTER TABLE "Repository" ADD COLUMN "deployRevision" TEXT, -ADD COLUMN "deployPath" TEXT, -ADD COLUMN "helmValuesFiles" TEXT; diff --git a/apps/server/src/prisma/schema/admin.prisma b/apps/server/src/prisma/schema/admin.prisma deleted file mode 100644 index c4ca38ccb1..0000000000 --- a/apps/server/src/prisma/schema/admin.prisma +++ /dev/null @@ -1,21 +0,0 @@ -model AdminPlugin { - pluginName String - key String - value String - - @@unique([pluginName, key]) -} - -model AdminRole { - id String @id @unique @default(uuid()) @db.Uuid - name String - permissions BigInt - position Int @db.SmallInt - oidcGroup String @default("") - type String @default("managed") -} - -model SystemSetting { - key String @id @unique - value String -} diff --git a/apps/server/src/prisma/schema/project.prisma b/apps/server/src/prisma/schema/project.prisma deleted file mode 100644 index d45ccf451e..0000000000 --- a/apps/server/src/prisma/schema/project.prisma +++ /dev/null @@ -1,109 +0,0 @@ -model Environment { - id String @id @default(uuid()) @db.Uuid - name String @db.VarChar(11) - projectId String @db.Uuid - memory Float @db.Real - cpu Float @db.Real - gpu Float @db.Real - autosync Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - clusterId String @db.Uuid - stageId String @db.Uuid - cluster Cluster @relation(fields: [clusterId], references: [id]) - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) - stage Stage @relation(fields: [stageId], references: [id]) - - @@unique([projectId, name]) -} - -model Repository { - id String @id @default(uuid()) @db.Uuid - projectId String @db.Uuid - internalRepoName String - externalRepoUrl String @default("") - externalUserName String @default("") - isInfra Boolean @default(false) - isPrivate Boolean @default(false) - deployRevision String @default("") - deployPath String @default("") - helmValuesFiles String @default("") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) -} - -model ProjectClusterHistory { - projectId String @db.Uuid - clusterId String @db.Uuid - - @@unique([projectId, clusterId]) -} - -model ProjectMembers { - projectId String @db.Uuid - userId String @db.Uuid - roleIds String[] - project Project @relation(fields: [projectId], references: [id]) - user User @relation(fields: [userId], references: [id]) - - @@unique([projectId, userId]) -} - -model ProjectPlugin { - pluginName String - projectId String @db.Uuid - key String - value String - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) - - @@unique([projectId, pluginName, key]) -} - -model ProjectRole { - id String @id @unique @default(uuid()) @db.Uuid - name String - permissions BigInt - projectId String @db.Uuid - position Int @db.SmallInt - oidcGroup String @default("") - type String @default("managed") - project Project @relation(fields: [projectId], references: [id]) -} - -model Project { - id String @id @unique @default(uuid()) @db.Uuid - name String - description String @default("") - status ProjectStatus @default(initializing) - locked Boolean @default(false) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - everyonePerms BigInt @default(896) - ownerId String @db.Uuid - environments Environment[] - logs Log[] - owner User @relation(fields: [ownerId], references: [id]) - members ProjectMembers[] - plugins ProjectPlugin[] - roles ProjectRole[] - repositories Repository[] - clusters Cluster[] @relation("ClusterToProject") - slug String @unique - limitless Boolean @default(true) - hprodCpu Float @db.Real - hprodGpu Float @db.Real - hprodMemory Float @db.Real - prodCpu Float @db.Real - prodGpu Float @db.Real - prodMemory Float @db.Real - lastSuccessProvisionningVersion String? -} - -enum ProjectStatus { - initializing - created - failed - archived - warning -} diff --git a/apps/server/src/prisma/schema/schema.prisma b/apps/server/src/prisma/schema/schema.prisma deleted file mode 100644 index aadf7fea13..0000000000 --- a/apps/server/src/prisma/schema/schema.prisma +++ /dev/null @@ -1,21 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DB_URL") -} - -model Log { - id String @id @default(uuid()) @db.Uuid - data Json - action String @default("") - userId String? @db.Uuid - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - requestId String? @db.VarChar(36) - projectId String? @db.Uuid - project Project? @relation(fields: [projectId], references: [id]) - user User? @relation(fields: [userId], references: [id]) -} diff --git a/apps/server/src/prisma/schema/token.prisma b/apps/server/src/prisma/schema/token.prisma deleted file mode 100644 index c0c55751c5..0000000000 --- a/apps/server/src/prisma/schema/token.prisma +++ /dev/null @@ -1,30 +0,0 @@ -model AdminToken { - id String @id @unique @default(uuid()) @db.Uuid - name String - permissions BigInt - userId String @db.Uuid - expirationDate DateTime? - lastUse DateTime? - createdAt DateTime @default(now()) - status TokenStatus @default(active) - hash String - owner User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) -} - -model PersonalAccessToken { - id String @id @unique @default(uuid()) @db.Uuid - name String - userId String @db.Uuid - expirationDate DateTime - lastUse DateTime? - createdAt DateTime @default(now()) - status TokenStatus @default(active) - hash String - owner User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) -} - -enum TokenStatus { - active - revoked - inactive -} diff --git a/apps/server/src/prisma/schema/topography.prisma b/apps/server/src/prisma/schema/topography.prisma deleted file mode 100644 index 63d90abb8a..0000000000 --- a/apps/server/src/prisma/schema/topography.prisma +++ /dev/null @@ -1,52 +0,0 @@ -model Cluster { - id String @id @unique @default(uuid()) @db.Uuid - label String @unique @db.VarChar(50) - privacy ClusterPrivacy @default(dedicated) - secretName String @unique @default(uuid()) @db.VarChar(50) - clusterResources Boolean @default(false) - kubeConfigId String @unique @db.Uuid - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - infos String? @db.VarChar(1000) - memory Float @db.Real - cpu Float @db.Real - gpu Float @db.Real - zoneId String @db.Uuid - kubeconfig Kubeconfig @relation(fields: [kubeConfigId], references: [id], onDelete: Cascade) - zone Zone @relation(fields: [zoneId], references: [id]) - environments Environment[] - projects Project[] @relation("ClusterToProject") - stages Stage[] @relation("ClusterToStage") -} - -model Kubeconfig { - id String @id @unique @default(uuid()) @db.Uuid - user Json - cluster Json - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - parentCluster Cluster? -} - -model Stage { - id String @id @unique @default(uuid()) @db.Uuid - name String @unique @db.VarChar - environments Environment[] - clusters Cluster[] @relation("ClusterToStage") -} - -model Zone { - id String @id @unique @default(uuid()) @db.Uuid - slug String @unique @db.VarChar(10) - label String @db.VarChar(50) - description String? @db.VarChar(200) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - argocdUrl String @default("https://example.com") - clusters Cluster[] -} - -enum ClusterPrivacy { - public - dedicated -} diff --git a/apps/server/src/prisma/schema/user.prisma b/apps/server/src/prisma/schema/user.prisma deleted file mode 100644 index e90fb69f83..0000000000 --- a/apps/server/src/prisma/schema/user.prisma +++ /dev/null @@ -1,23 +0,0 @@ -model User { - id String @id @db.Uuid - firstName String - lastName String - email String @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - lastLogin DateTime? - adminRoleIds String[] - type UserType - - logs Log[] - projectsOwned Project[] - adminTokens AdminToken[] - projectMembers ProjectMembers[] - personalAccessTokens PersonalAccessToken[] -} - -enum UserType { - human - bot - ghost -} diff --git a/apps/server/src/resources/admin-role/business.spec.ts b/apps/server/src/resources/admin-role/business.spec.ts index 9f9a7051ea..9cd0e983e1 100644 --- a/apps/server/src/resources/admin-role/business.spec.ts +++ b/apps/server/src/resources/admin-role/business.spec.ts @@ -1,4 +1,4 @@ -import type { AdminRole, User } from '@prisma/client' +import type { AdminRole, User } from '@cpn-console/database' import { faker } from '@faker-js/faker' import { describe, expect, it, vi } from 'vitest' import prisma from '../../__mocks__/prisma.js' diff --git a/apps/server/src/resources/admin-role/business.ts b/apps/server/src/resources/admin-role/business.ts index f81fd65b75..f69a8c3f72 100644 --- a/apps/server/src/resources/admin-role/business.ts +++ b/apps/server/src/resources/admin-role/business.ts @@ -1,5 +1,5 @@ +import type { Project, ProjectRole } from '@cpn-console/database' import type { AdminRole, adminRoleContract } from '@cpn-console/shared' -import type { Project, ProjectRole } from '@prisma/client' import type { ErrorResType } from '@/utils/errors.js' import prisma from '@/prisma.js' import { addLogs, getAdminRoleById, listAdminRoles } from '@/resources/queries-index.js' diff --git a/apps/server/src/resources/admin-role/queries.ts b/apps/server/src/resources/admin-role/queries.ts index 9c29eb0b36..23bcfaefc8 100644 --- a/apps/server/src/resources/admin-role/queries.ts +++ b/apps/server/src/resources/admin-role/queries.ts @@ -1,7 +1,7 @@ import type { AdminRole, Prisma, -} from '@prisma/client' +} from '@cpn-console/database' import prisma from '@/prisma.js' export const listAdminRoles = () => prisma.adminRole.findMany({ orderBy: { position: 'asc' } }) diff --git a/apps/server/src/resources/admin-token/business.ts b/apps/server/src/resources/admin-token/business.ts index 0702fde2b9..a4b843dc94 100644 --- a/apps/server/src/resources/admin-token/business.ts +++ b/apps/server/src/resources/admin-token/business.ts @@ -1,5 +1,5 @@ +import type { $Enums, AdminToken, Prisma } from '@cpn-console/database' import type { adminTokenContract } from '@cpn-console/shared' -import type { $Enums, AdminToken, Prisma } from '@prisma/client' import { createHash, randomUUID } from 'node:crypto' import { logger as baseLogger } from '@cpn-console/logger' import { generateRandomPassword, isAtLeastTomorrow } from '@cpn-console/shared' diff --git a/apps/server/src/resources/admin-token/router.spec.ts b/apps/server/src/resources/admin-token/router.spec.ts index 61efec35fc..4eac9cb648 100644 --- a/apps/server/src/resources/admin-token/router.spec.ts +++ b/apps/server/src/resources/admin-token/router.spec.ts @@ -1,5 +1,5 @@ +import type { AdminToken } from '@cpn-console/database' import type { ExposedAdminToken } from '@cpn-console/shared' -import type { AdminToken } from '@prisma/client' import { ADMIN_PERMS, adminTokenContract } from '@cpn-console/shared' import { faker } from '@faker-js/faker' import { beforeEach, describe, expect, it, vi } from 'vitest' diff --git a/apps/server/src/resources/cluster/business.spec.ts b/apps/server/src/resources/cluster/business.spec.ts index c003e66ae1..6bec30aa38 100644 --- a/apps/server/src/resources/cluster/business.spec.ts +++ b/apps/server/src/resources/cluster/business.spec.ts @@ -1,4 +1,4 @@ -import type { Cluster, Environment } from '@prisma/client' +import type { Cluster, Environment } from '@cpn-console/database' import { faker } from '@faker-js/faker' import { describe, expect, it, vi } from 'vitest' import prisma from '../../__mocks__/prisma.js' diff --git a/apps/server/src/resources/cluster/business.ts b/apps/server/src/resources/cluster/business.ts index ef542ce33a..e1a8686f97 100644 --- a/apps/server/src/resources/cluster/business.ts +++ b/apps/server/src/resources/cluster/business.ts @@ -1,5 +1,5 @@ +import type { Prisma, Project, User } from '@cpn-console/database' import type { Cluster, clusterContract, ClusterDetails, Kubeconfig } from '@cpn-console/shared' -import type { Prisma, Project, User } from '@prisma/client' import type { Resources } from '@/types/index.js' import { ClusterDetailsSchema, ClusterPrivacy } from '@cpn-console/shared' import prisma from '@/prisma.js' diff --git a/apps/server/src/resources/cluster/queries.ts b/apps/server/src/resources/cluster/queries.ts index f66e1fc0bc..e797b11186 100644 --- a/apps/server/src/resources/cluster/queries.ts +++ b/apps/server/src/resources/cluster/queries.ts @@ -1,4 +1,4 @@ -import type { Cluster, Environment, Kubeconfig, Prisma, Project, Stage } from '@prisma/client' +import type { Cluster, Environment, Kubeconfig, Prisma, Project, Stage } from '@cpn-console/database' import prisma from '@/prisma.js' export async function getClustersAssociatedWithProject(projectId: Project['id']) { diff --git a/apps/server/src/resources/environment/business.spec.ts b/apps/server/src/resources/environment/business.spec.ts index 7bfebb5b2e..bff8e14df0 100644 --- a/apps/server/src/resources/environment/business.spec.ts +++ b/apps/server/src/resources/environment/business.spec.ts @@ -1,4 +1,4 @@ -import type { Cluster, Environment, Project, ProjectMembers, ProjectRole, Stage, User } from '@prisma/client' +import type { Cluster, Environment, Project, ProjectMembers, ProjectRole, Stage, User } from '@cpn-console/database' import { faker } from '@faker-js/faker' import { beforeEach, describe, expect, it, vi } from 'vitest' import prisma from '../../__mocks__/prisma.js' diff --git a/apps/server/src/resources/environment/business.ts b/apps/server/src/resources/environment/business.ts index 2f068dedf8..a2a2c95eb3 100644 --- a/apps/server/src/resources/environment/business.ts +++ b/apps/server/src/resources/environment/business.ts @@ -1,4 +1,4 @@ -import type { Cluster, Environment, Project, Stage, User } from '@prisma/client' +import type { Cluster, Environment, Project, Stage, User } from '@cpn-console/database' import type { Resources, UserDetails } from '@/types/index.js' import prisma from '@/prisma.js' import { diff --git a/apps/server/src/resources/environment/queries.ts b/apps/server/src/resources/environment/queries.ts index 6b228e2687..d51df6bbc4 100644 --- a/apps/server/src/resources/environment/queries.ts +++ b/apps/server/src/resources/environment/queries.ts @@ -1,4 +1,4 @@ -import type { Environment, Prisma, Project } from '@prisma/client' +import type { Environment, Prisma, Project } from '@cpn-console/database' import prisma from '@/prisma.js' // SELECT diff --git a/apps/server/src/resources/log/queries.ts b/apps/server/src/resources/log/queries.ts index 3851a8f13f..fceb3b99f7 100644 --- a/apps/server/src/resources/log/queries.ts +++ b/apps/server/src/resources/log/queries.ts @@ -1,4 +1,4 @@ -import type { Log, Prisma, Project, User } from '@prisma/client' +import type { Log, Prisma, Project, User } from '@cpn-console/database' import { exclude } from '@cpn-console/shared' import prisma from '@/prisma.js' diff --git a/apps/server/src/resources/project-member/business.ts b/apps/server/src/resources/project-member/business.ts index 4144b81813..620425db28 100644 --- a/apps/server/src/resources/project-member/business.ts +++ b/apps/server/src/resources/project-member/business.ts @@ -1,5 +1,5 @@ +import type { Project, User } from '@cpn-console/database' import type { projectMemberContract, XOR } from '@cpn-console/shared' -import type { Project, User } from '@prisma/client' import { logger as baseLogger } from '@cpn-console/logger' import { UserSchema } from '@cpn-console/shared' import prisma from '@/prisma.js' diff --git a/apps/server/src/resources/project-member/queries.ts b/apps/server/src/resources/project-member/queries.ts index a4ceb00df1..0f173e88f2 100644 --- a/apps/server/src/resources/project-member/queries.ts +++ b/apps/server/src/resources/project-member/queries.ts @@ -2,7 +2,7 @@ import type { Prisma, Project, -} from '@prisma/client' +} from '@cpn-console/database' import prisma from '@/prisma.js' diff --git a/apps/server/src/resources/project-role/business.spec.ts b/apps/server/src/resources/project-role/business.spec.ts index 89a70147fd..20bc517978 100644 --- a/apps/server/src/resources/project-role/business.spec.ts +++ b/apps/server/src/resources/project-role/business.spec.ts @@ -1,4 +1,4 @@ -import type { Project, ProjectMembers, ProjectRole } from '@prisma/client' +import type { Project, ProjectMembers, ProjectRole } from '@cpn-console/database' import { faker } from '@faker-js/faker' import { describe, expect, it, vi } from 'vitest' import { BadRequest400 } from '@/utils/errors.js' diff --git a/apps/server/src/resources/project-role/business.ts b/apps/server/src/resources/project-role/business.ts index 52bd8966b3..d59531785e 100644 --- a/apps/server/src/resources/project-role/business.ts +++ b/apps/server/src/resources/project-role/business.ts @@ -1,5 +1,5 @@ +import type { Project, ProjectRole } from '@cpn-console/database' import type { projectRoleContract } from '@cpn-console/shared' -import type { Project, ProjectRole } from '@prisma/client' import { isSystemRoleType } from '@cpn-console/shared' import prisma from '@/prisma.js' import { diff --git a/apps/server/src/resources/project-role/queries.ts b/apps/server/src/resources/project-role/queries.ts index 3e2aa4a0fa..b96cf47b98 100644 --- a/apps/server/src/resources/project-role/queries.ts +++ b/apps/server/src/resources/project-role/queries.ts @@ -2,7 +2,7 @@ import type { Prisma, Project, ProjectRole, -} from '@prisma/client' +} from '@cpn-console/database' import prisma from '@/prisma.js' diff --git a/apps/server/src/resources/project-service/business.ts b/apps/server/src/resources/project-service/business.ts index bce70064ef..4dd48c1f02 100644 --- a/apps/server/src/resources/project-service/business.ts +++ b/apps/server/src/resources/project-service/business.ts @@ -1,10 +1,10 @@ +import type { Project, ProjectPlugin } from '@cpn-console/database' import type { ZoneObject } from '@cpn-console/hooks' import type { PermissionTarget, PluginsUpdateBody, ServiceUrl, } from '@cpn-console/shared' -import type { Project, ProjectPlugin } from '@prisma/client' import { editStrippers, populatePluginManifests, servicesInfos } from '@cpn-console/hooks' import { logger as baseLogger } from '@cpn-console/logger' import { diff --git a/apps/server/src/resources/project-service/queries.ts b/apps/server/src/resources/project-service/queries.ts index cf353614b2..8a25de6332 100644 --- a/apps/server/src/resources/project-service/queries.ts +++ b/apps/server/src/resources/project-service/queries.ts @@ -1,4 +1,4 @@ -import type { Project } from '@prisma/client' +import type { Project } from '@cpn-console/database' import type { ConfigRecords } from './business.js' import prisma from '@/prisma.js' diff --git a/apps/server/src/resources/project/business.spec.ts b/apps/server/src/resources/project/business.spec.ts index a9f6c4fa62..e5e2b3de64 100644 --- a/apps/server/src/resources/project/business.spec.ts +++ b/apps/server/src/resources/project/business.spec.ts @@ -1,4 +1,4 @@ -import type { Cluster, Project, ProjectMembers, ProjectRole, User } from '@prisma/client' +import type { Cluster, Project, ProjectMembers, ProjectRole, User } from '@cpn-console/database' import { faker } from '@faker-js/faker' import { beforeEach, describe, expect, it, vi } from 'vitest' import prisma from '../../__mocks__/prisma.js' diff --git a/apps/server/src/resources/project/business.ts b/apps/server/src/resources/project/business.ts index 6396d359ec..49caec9519 100644 --- a/apps/server/src/resources/project/business.ts +++ b/apps/server/src/resources/project/business.ts @@ -1,5 +1,5 @@ +import type { Project, User } from '@cpn-console/database' import type { projectContract } from '@cpn-console/shared' -import type { Project, User } from '@prisma/client' import type { UserDetails } from '@/types/index.js' import type { ErrorResType } from '@/utils/errors.js' import { servicesInfos } from '@cpn-console/hooks' diff --git a/apps/server/src/resources/project/queries.ts b/apps/server/src/resources/project/queries.ts index a796373414..eda440f6f7 100644 --- a/apps/server/src/resources/project/queries.ts +++ b/apps/server/src/resources/project/queries.ts @@ -1,13 +1,13 @@ -import type { projectContract, XOR } from '@cpn-console/shared' import type { Prisma, Project, User, -} from '@prisma/client' -import { PROJECT_PERMS } from '@cpn-console/shared' +} from '@cpn-console/database' +import type { projectContract, XOR } from '@cpn-console/shared' import { ProjectStatus, -} from '@prisma/client' +} from '@cpn-console/database' +import { PROJECT_PERMS } from '@cpn-console/shared' import prisma from '@/prisma.js' import { appVersion } from '@/utils/env.js' import { uuid } from '@/utils/queries-tools.js' diff --git a/apps/server/src/resources/repository/business.ts b/apps/server/src/resources/repository/business.ts index 473c9c73a7..572f24047a 100644 --- a/apps/server/src/resources/repository/business.ts +++ b/apps/server/src/resources/repository/business.ts @@ -1,5 +1,5 @@ +import type { Project, Repository, User } from '@cpn-console/database' import type { CreateRepositoryBody, UpdateRepositoryBody } from '@cpn-console/shared' -import type { Project, Repository, User } from '@prisma/client' import { addLogs, deleteRepository as deleteRepositoryQuery, getProjectInfosAndRepos, getProjectRepositories as getProjectRepositoriesQuery, initializeRepository, updateRepository as updateRepositoryQuery } from '@/resources/queries-index.js' import { BadRequest400, Unprocessable422 } from '@/utils/errors.js' import { hook } from '@/utils/hook-wrapper.js' diff --git a/apps/server/src/resources/repository/queries.ts b/apps/server/src/resources/repository/queries.ts index 277e7af602..97b2948d73 100644 --- a/apps/server/src/resources/repository/queries.ts +++ b/apps/server/src/resources/repository/queries.ts @@ -1,4 +1,4 @@ -import type { Project, Repository } from '@prisma/client' +import type { Project, Repository } from '@cpn-console/database' import prisma from '@/prisma.js' // SELECT diff --git a/apps/server/src/resources/stage/business.spec.ts b/apps/server/src/resources/stage/business.spec.ts index ba43c2cb75..16df9b7ca5 100644 --- a/apps/server/src/resources/stage/business.spec.ts +++ b/apps/server/src/resources/stage/business.spec.ts @@ -1,4 +1,4 @@ -import type { Environment, Stage } from '@prisma/client' +import type { Environment, Stage } from '@cpn-console/database' import { faker } from '@faker-js/faker' import { beforeEach, describe, expect, it, vi } from 'vitest' import prisma from '../../__mocks__/prisma.js' diff --git a/apps/server/src/resources/stage/business.ts b/apps/server/src/resources/stage/business.ts index db6faf686e..1cf0a8cbb0 100644 --- a/apps/server/src/resources/stage/business.ts +++ b/apps/server/src/resources/stage/business.ts @@ -1,5 +1,5 @@ +import type { Cluster, Stage } from '@cpn-console/database' import type { CreateStageBody, UpdateStageBody } from '@cpn-console/shared' -import type { Cluster, Stage } from '@prisma/client' import prisma from '@/prisma.js' import { createStage as createStageQuery, diff --git a/apps/server/src/resources/stage/queries.ts b/apps/server/src/resources/stage/queries.ts index 98d526600a..fab60d3144 100644 --- a/apps/server/src/resources/stage/queries.ts +++ b/apps/server/src/resources/stage/queries.ts @@ -1,4 +1,4 @@ -import type { Cluster, Stage } from '@prisma/client' +import type { Cluster, Stage } from '@cpn-console/database' import prisma from '@/prisma.js' export function listStages() { diff --git a/apps/server/src/resources/system/settings/queries.ts b/apps/server/src/resources/system/settings/queries.ts index c64cb3b74c..86b675af13 100644 --- a/apps/server/src/resources/system/settings/queries.ts +++ b/apps/server/src/resources/system/settings/queries.ts @@ -1,4 +1,4 @@ -import type { Prisma, SystemSetting } from '@prisma/client' +import type { Prisma, SystemSetting } from '@cpn-console/database' import prisma from '@/prisma.js' export function upsertSystemSetting(newSystemSetting: SystemSetting) { diff --git a/apps/server/src/resources/user/business.ts b/apps/server/src/resources/user/business.ts index e20c9c0230..b397bcb11f 100644 --- a/apps/server/src/resources/user/business.ts +++ b/apps/server/src/resources/user/business.ts @@ -1,5 +1,5 @@ +import type { AdminRole, AdminToken, PersonalAccessToken, Prisma, User } from '@cpn-console/database' import type { userContract, XOR } from '@cpn-console/shared' -import type { AdminRole, AdminToken, PersonalAccessToken, Prisma, User } from '@prisma/client' import type { UserDetails } from '@/types/index.js' import { createHash } from 'node:crypto' import prisma from '@/prisma.js' diff --git a/apps/server/src/resources/user/queries.ts b/apps/server/src/resources/user/queries.ts index 1e33a8128b..e5be4e73a1 100644 --- a/apps/server/src/resources/user/queries.ts +++ b/apps/server/src/resources/user/queries.ts @@ -1,4 +1,4 @@ -import type { Prisma, User } from '@prisma/client' +import type { Prisma, User } from '@cpn-console/database' import prisma from '@/prisma.js' type UserCreate = Omit diff --git a/apps/server/src/resources/user/tokens/business.ts b/apps/server/src/resources/user/tokens/business.ts index e378d3b79b..4f649d8df8 100644 --- a/apps/server/src/resources/user/tokens/business.ts +++ b/apps/server/src/resources/user/tokens/business.ts @@ -1,5 +1,5 @@ +import type { AdminToken, User } from '@cpn-console/database' import type { personalAccessTokenContract } from '@cpn-console/shared' -import type { AdminToken, User } from '@prisma/client' import { createHash } from 'node:crypto' import { generateRandomPassword, isAtLeastTomorrow } from '@cpn-console/shared' import { BadRequest400 } from '@/utils/errors.js' diff --git a/apps/server/src/resources/zone/business.spec.ts b/apps/server/src/resources/zone/business.spec.ts index 2b42464e26..ef4c8cc7c5 100644 --- a/apps/server/src/resources/zone/business.spec.ts +++ b/apps/server/src/resources/zone/business.spec.ts @@ -1,4 +1,4 @@ -import type { Cluster, Zone } from '@prisma/client' +import type { Cluster, Zone } from '@cpn-console/database' import { faker } from '@faker-js/faker' import { beforeEach, describe, expect, it, vi } from 'vitest' import prisma from '../../__mocks__/prisma.js' diff --git a/apps/server/src/resources/zone/queries.ts b/apps/server/src/resources/zone/queries.ts index 1390bb153a..7ed201c32e 100644 --- a/apps/server/src/resources/zone/queries.ts +++ b/apps/server/src/resources/zone/queries.ts @@ -1,4 +1,4 @@ -import type { Cluster, Zone } from '@prisma/client' +import type { Cluster, Zone } from '@cpn-console/database' import prisma from '@/prisma.js' export function getZoneByIdOrThrow(id: Zone['id']) { diff --git a/apps/server/src/utils/controller.ts b/apps/server/src/utils/controller.ts index df231272c7..2192d2f9bd 100644 --- a/apps/server/src/utils/controller.ts +++ b/apps/server/src/utils/controller.ts @@ -1,5 +1,5 @@ +import type { Cluster, Prisma, Project, ProjectMembers, ProjectRole } from '@cpn-console/database' import type { XOR } from '@cpn-console/shared' -import type { Cluster, Prisma, Project, ProjectMembers, ProjectRole } from '@prisma/client' import type { FastifyRequest } from 'fastify' import type { UserDetails } from '@/types/index.js' import { PROJECT_PERMS as PP, PROJECT_PERMS, projectIsLockedInfo, tokenHeaderName } from '@cpn-console/shared' diff --git a/apps/server/src/utils/hook-wrapper.ts b/apps/server/src/utils/hook-wrapper.ts index 0df3d89a4b..256a8cc7bf 100644 --- a/apps/server/src/utils/hook-wrapper.ts +++ b/apps/server/src/utils/hook-wrapper.ts @@ -1,6 +1,6 @@ +import type { Cluster, Kubeconfig, Project, ProjectMembers, ProjectRole, Zone } from '@cpn-console/database' import type { ClusterObject, HookResult, KubeCluster, KubeUser, Project as ProjectPayload, RepoCreds, Repository, Store, ZoneObject } from '@cpn-console/hooks' import type { AsyncReturnType } from '@cpn-console/shared' -import type { Cluster, Kubeconfig, Project, ProjectMembers, ProjectRole, Zone } from '@prisma/client' import type { ConfigRecords } from '@/resources/project-service/business.js' import { hooks } from '@cpn-console/hooks' import { logger as baseLogger } from '@cpn-console/logger' diff --git a/apps/server/src/utils/mocks.ts b/apps/server/src/utils/mocks.ts index 16169f4d31..dc21c04a7f 100644 --- a/apps/server/src/utils/mocks.ts +++ b/apps/server/src/utils/mocks.ts @@ -1,5 +1,5 @@ +import type { Repository } from '@cpn-console/database' import type { PluginsManifests, RepoCreds, ServiceInfos } from '@cpn-console/hooks' -import type { Repository } from '@prisma/client' import type { UserDetails } from '../types/index.js' import type * as utilsController from '../utils/controller.js' import { editStrippers, populatePluginManifests } from '@cpn-console/hooks' diff --git a/ci/scripts/setup.sh b/ci/scripts/setup.sh index 4917b17cf8..36310022f8 100755 --- a/ci/scripts/setup.sh +++ b/ci/scripts/setup.sh @@ -20,12 +20,6 @@ i=$(($i + 1)) pnpm install -printf "\n${red}${i}.${no_color} Generate Prisma client\n" -i=$(($i + 1)) - -pnpm --filter=server run db:generate - - printf "\n${red}${i}.${no_color} Build packges\n" i=$(($i + 1)) diff --git a/package.json b/package.json index e8f02ffeaa..c99484e96c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "scripts": { "build": "pnpm -r run build", "build:clean": "pnpm -r run build:clean", - "db:generate": "pnpm -r run db:generate", "dev": "docker compose -f ./docker/docker-compose.local.yml up -d --remove-orphans && echo '\nStart applications with commands:\n\n pnpm --filter @cpn-console/server run dev\n pnpm --filter @cpn-console/client run dev\n pnpm --filter @cpn-console/server-nestjs run dev\n\nMake sure you provide env variables in `.env` files.'", "dev:clean": "docker compose -f ./docker/docker-compose.local.yml down --remove-orphans", "dev:delete": "docker compose -f ./docker/docker-compose.local.yml down -v --remove-orphans", @@ -47,9 +46,7 @@ "playwright:test:integration": "pnpm --dir playwright run playwright:test:integration", "prepare": "husky", "test": "pnpm -r run test", - "test:cov": "pnpm -r run test:cov", - "test:e2e": "pnpm kube:e2e", - "test:e2e-ci": "pnpm kube:prod; pnpm kube:e2e-ci" + "test:cov": "pnpm -r run test:cov" }, "devDependencies": { "@commitlint/cli": "^20.5.0", diff --git a/packages/database/eslint.config.js b/packages/database/eslint.config.js new file mode 100644 index 0000000000..5a664d2b58 --- /dev/null +++ b/packages/database/eslint.config.js @@ -0,0 +1,3 @@ +import eslintConfigBase from '@cpn-console/eslint-config' + +export default eslintConfigBase diff --git a/packages/database/package.json b/packages/database/package.json new file mode 100644 index 0000000000..2e3690089e --- /dev/null +++ b/packages/database/package.json @@ -0,0 +1,46 @@ +{ + "name": "@cpn-console/database", + "type": "module", + "version": "0.0.0", + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/cloud-pi-native/console" + }, + "exports": { + ".": { + "types": "./types/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "prisma", + "prisma.config.ts", + "types" + ], + "scripts": { + "build": "tsc", + "build:clean": "rimraf ./dist ./types ./tsconfig.tsbuildinfo", + "deploy": "prisma migrate deploy", + "diff": "prisma migrate diff", + "prepublishOnly": "prisma generate", + "generate": "prisma generate", + "migrate": "prisma migrate dev --name dso", + "reset": "prisma migrate reset", + "studio": "prisma studio", + "format": "eslint ./ --fix", + "lint": "eslint ./" + }, + "dependencies": { + "@prisma/client": "^6.19.2" + }, + "devDependencies": { + "@cpn-console/eslint-config": "workspace:^", + "@cpn-console/ts-config": "workspace:^", + "@types/node": "^24.12.0", + "prisma": "^6.19.2", + "rimraf": "^6.1.3", + "typescript": "^5.9.3" + } +} diff --git a/packages/database/prisma.config.ts b/packages/database/prisma.config.ts new file mode 100644 index 0000000000..c78e77bbf5 --- /dev/null +++ b/packages/database/prisma.config.ts @@ -0,0 +1,9 @@ +import path from 'node:path' +import { defineConfig } from 'prisma/config' + +export default defineConfig({ + schema: path.join('prisma', 'schema'), + migrations: { + path: path.join('prisma', 'migrations'), + }, +}) diff --git a/apps/server-nestjs/src/prisma/migrations/20230706084346_dso/migration.sql b/packages/database/prisma/migrations/20230706084346_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20230706084346_dso/migration.sql rename to packages/database/prisma/migrations/20230706084346_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20230710181052_dso/migration.sql b/packages/database/prisma/migrations/20230710181052_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20230710181052_dso/migration.sql rename to packages/database/prisma/migrations/20230710181052_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20230711132934_dso/migration.sql b/packages/database/prisma/migrations/20230711132934_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20230711132934_dso/migration.sql rename to packages/database/prisma/migrations/20230711132934_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20230802143822_dso/migration.sql b/packages/database/prisma/migrations/20230802143822_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20230802143822_dso/migration.sql rename to packages/database/prisma/migrations/20230802143822_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20230912084459_dso/migration.sql b/packages/database/prisma/migrations/20230912084459_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20230912084459_dso/migration.sql rename to packages/database/prisma/migrations/20230912084459_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20231010111515_dso/migration.sql b/packages/database/prisma/migrations/20231010111515_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20231010111515_dso/migration.sql rename to packages/database/prisma/migrations/20231010111515_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20231011125838_dso/migration.sql b/packages/database/prisma/migrations/20231011125838_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20231011125838_dso/migration.sql rename to packages/database/prisma/migrations/20231011125838_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20231011125839_dso/migration.sql b/packages/database/prisma/migrations/20231011125839_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20231011125839_dso/migration.sql rename to packages/database/prisma/migrations/20231011125839_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20231011125841_dso/migration.sql b/packages/database/prisma/migrations/20231011125841_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20231011125841_dso/migration.sql rename to packages/database/prisma/migrations/20231011125841_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20231012105520_dso/migration.sql b/packages/database/prisma/migrations/20231012105520_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20231012105520_dso/migration.sql rename to packages/database/prisma/migrations/20231012105520_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20231024155020_dso/migration.sql b/packages/database/prisma/migrations/20231024155020_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20231024155020_dso/migration.sql rename to packages/database/prisma/migrations/20231024155020_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20231026150220_dso/migration.sql b/packages/database/prisma/migrations/20231026150220_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20231026150220_dso/migration.sql rename to packages/database/prisma/migrations/20231026150220_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240112135751_dso/migration.sql b/packages/database/prisma/migrations/20240112135751_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240112135751_dso/migration.sql rename to packages/database/prisma/migrations/20240112135751_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240321123436_dso/migration.sql b/packages/database/prisma/migrations/20240321123436_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240321123436_dso/migration.sql rename to packages/database/prisma/migrations/20240321123436_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240329172938_dso/migration.sql b/packages/database/prisma/migrations/20240329172938_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240329172938_dso/migration.sql rename to packages/database/prisma/migrations/20240329172938_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240424093852_dso/migration.sql b/packages/database/prisma/migrations/20240424093852_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240424093852_dso/migration.sql rename to packages/database/prisma/migrations/20240424093852_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240427181037_dso/migration.sql b/packages/database/prisma/migrations/20240427181037_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240427181037_dso/migration.sql rename to packages/database/prisma/migrations/20240427181037_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240605135052_dso/migration.sql b/packages/database/prisma/migrations/20240605135052_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240605135052_dso/migration.sql rename to packages/database/prisma/migrations/20240605135052_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240612123132_dso/migration.sql b/packages/database/prisma/migrations/20240612123132_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240612123132_dso/migration.sql rename to packages/database/prisma/migrations/20240612123132_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240614222908_dso/migration.sql b/packages/database/prisma/migrations/20240614222908_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240614222908_dso/migration.sql rename to packages/database/prisma/migrations/20240614222908_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240618112205_dso/migration.sql b/packages/database/prisma/migrations/20240618112205_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240618112205_dso/migration.sql rename to packages/database/prisma/migrations/20240618112205_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240717084709_dso/migration.sql b/packages/database/prisma/migrations/20240717084709_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240717084709_dso/migration.sql rename to packages/database/prisma/migrations/20240717084709_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240723135420_dso/migration.sql b/packages/database/prisma/migrations/20240723135420_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240723135420_dso/migration.sql rename to packages/database/prisma/migrations/20240723135420_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240725162050_dso/migration.sql b/packages/database/prisma/migrations/20240725162050_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240725162050_dso/migration.sql rename to packages/database/prisma/migrations/20240725162050_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240726210139_dso/migration.sql b/packages/database/prisma/migrations/20240726210139_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240726210139_dso/migration.sql rename to packages/database/prisma/migrations/20240726210139_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240808082632_dso/migration.sql b/packages/database/prisma/migrations/20240808082632_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240808082632_dso/migration.sql rename to packages/database/prisma/migrations/20240808082632_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240826143230_dso/migration.sql b/packages/database/prisma/migrations/20240826143230_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240826143230_dso/migration.sql rename to packages/database/prisma/migrations/20240826143230_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240829085548_dso/migration.sql b/packages/database/prisma/migrations/20240829085548_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240829085548_dso/migration.sql rename to packages/database/prisma/migrations/20240829085548_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240916141253_token/migration.sql b/packages/database/prisma/migrations/20240916141253_token/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240916141253_token/migration.sql rename to packages/database/prisma/migrations/20240916141253_token/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240919122331_optional_user_id/migration.sql b/packages/database/prisma/migrations/20240919122331_optional_user_id/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240919122331_optional_user_id/migration.sql rename to packages/database/prisma/migrations/20240919122331_optional_user_id/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240923142722_dso/migration.sql b/packages/database/prisma/migrations/20240923142722_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240923142722_dso/migration.sql rename to packages/database/prisma/migrations/20240923142722_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240923155416_dso/migration.sql b/packages/database/prisma/migrations/20240923155416_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240923155416_dso/migration.sql rename to packages/database/prisma/migrations/20240923155416_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20240928002900_dso/migration.sql b/packages/database/prisma/migrations/20240928002900_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20240928002900_dso/migration.sql rename to packages/database/prisma/migrations/20240928002900_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20241008125724_enabling_maven/migration.sql b/packages/database/prisma/migrations/20241008125724_enabling_maven/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20241008125724_enabling_maven/migration.sql rename to packages/database/prisma/migrations/20241008125724_enabling_maven/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20241104232540_add_usertype/migration.sql b/packages/database/prisma/migrations/20241104232540_add_usertype/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20241104232540_add_usertype/migration.sql rename to packages/database/prisma/migrations/20241104232540_add_usertype/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20241104232541_add_pat/migration.sql b/packages/database/prisma/migrations/20241104232541_add_pat/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20241104232541_add_pat/migration.sql rename to packages/database/prisma/migrations/20241104232541_add_pat/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20241107142721_user_last_login/migration.sql b/packages/database/prisma/migrations/20241107142721_user_last_login/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20241107142721_user_last_login/migration.sql rename to packages/database/prisma/migrations/20241107142721_user_last_login/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20241112101945_add_slug/migration.sql b/packages/database/prisma/migrations/20241112101945_add_slug/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20241112101945_add_slug/migration.sql rename to packages/database/prisma/migrations/20241112101945_add_slug/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20241112102015_add_provisionning_version/migration.sql b/packages/database/prisma/migrations/20241112102015_add_provisionning_version/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20241112102015_add_provisionning_version/migration.sql rename to packages/database/prisma/migrations/20241112102015_add_provisionning_version/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20241216131342_dso/migration.sql b/packages/database/prisma/migrations/20241216131342_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20241216131342_dso/migration.sql rename to packages/database/prisma/migrations/20241216131342_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20250107104749_dso/migration.sql b/packages/database/prisma/migrations/20250107104749_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20250107104749_dso/migration.sql rename to packages/database/prisma/migrations/20250107104749_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20250121222953_prevent_upgrade/migration.sql b/packages/database/prisma/migrations/20250121222953_prevent_upgrade/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20250121222953_prevent_upgrade/migration.sql rename to packages/database/prisma/migrations/20250121222953_prevent_upgrade/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20250121222954_drop_organization/migration.sql b/packages/database/prisma/migrations/20250121222954_drop_organization/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20250121222954_drop_organization/migration.sql rename to packages/database/prisma/migrations/20250121222954_drop_organization/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20250723141246_dso/migration.sql b/packages/database/prisma/migrations/20250723141246_dso/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20250723141246_dso/migration.sql rename to packages/database/prisma/migrations/20250723141246_dso/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20250818095032_remove_quota/migration.sql b/packages/database/prisma/migrations/20250818095032_remove_quota/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20250818095032_remove_quota/migration.sql rename to packages/database/prisma/migrations/20250818095032_remove_quota/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20250825150622_add_cluster_resources/migration.sql b/packages/database/prisma/migrations/20250825150622_add_cluster_resources/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20250825150622_add_cluster_resources/migration.sql rename to packages/database/prisma/migrations/20250825150622_add_cluster_resources/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20250916134454_add_project_resources/migration.sql b/packages/database/prisma/migrations/20250916134454_add_project_resources/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20250916134454_add_project_resources/migration.sql rename to packages/database/prisma/migrations/20250916134454_add_project_resources/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20251028150522_rename_default_zone/migration.sql b/packages/database/prisma/migrations/20251028150522_rename_default_zone/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20251028150522_rename_default_zone/migration.sql rename to packages/database/prisma/migrations/20251028150522_rename_default_zone/migration.sql diff --git a/apps/server-nestjs/src/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql b/packages/database/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql similarity index 100% rename from apps/server-nestjs/src/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql rename to packages/database/prisma/migrations/20251208140951_add_argocd_inputs/migration.sql diff --git a/apps/server/src/prisma/migrations/20251218103526_alter_repository_defaults/migration.sql b/packages/database/prisma/migrations/20251218103526_alter_repository_defaults/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20251218103526_alter_repository_defaults/migration.sql rename to packages/database/prisma/migrations/20251218103526_alter_repository_defaults/migration.sql diff --git a/apps/server/src/prisma/migrations/20251219144931_add_environment_autosync/migration.sql b/packages/database/prisma/migrations/20251219144931_add_environment_autosync/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20251219144931_add_environment_autosync/migration.sql rename to packages/database/prisma/migrations/20251219144931_add_environment_autosync/migration.sql diff --git a/apps/server/src/prisma/migrations/20260127154602_add_oidc_group_to_project_role/migration.sql b/packages/database/prisma/migrations/20260127154602_add_oidc_group_to_project_role/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260127154602_add_oidc_group_to_project_role/migration.sql rename to packages/database/prisma/migrations/20260127154602_add_oidc_group_to_project_role/migration.sql diff --git a/apps/server/src/prisma/migrations/20260127164002_add_type_to_roles/migration.sql b/packages/database/prisma/migrations/20260127164002_add_type_to_roles/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260127164002_add_type_to_roles/migration.sql rename to packages/database/prisma/migrations/20260127164002_add_type_to_roles/migration.sql diff --git a/apps/server/src/prisma/migrations/20260204150335_add_system_roles/migration.sql b/packages/database/prisma/migrations/20260204150335_add_system_roles/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260204150335_add_system_roles/migration.sql rename to packages/database/prisma/migrations/20260204150335_add_system_roles/migration.sql diff --git a/apps/server/src/prisma/migrations/20260206105522_dso/migration.sql b/packages/database/prisma/migrations/20260206105522_dso/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260206105522_dso/migration.sql rename to packages/database/prisma/migrations/20260206105522_dso/migration.sql diff --git a/apps/server/src/prisma/migrations/20260211143048_dso/migration.sql b/packages/database/prisma/migrations/20260211143048_dso/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260211143048_dso/migration.sql rename to packages/database/prisma/migrations/20260211143048_dso/migration.sql diff --git a/apps/server/src/prisma/migrations/20260217144930_enable_legacy_permissions/migration.sql b/packages/database/prisma/migrations/20260217144930_enable_legacy_permissions/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260217144930_enable_legacy_permissions/migration.sql rename to packages/database/prisma/migrations/20260217144930_enable_legacy_permissions/migration.sql diff --git a/apps/server/src/prisma/migrations/20260226130000_remove_refined_permissions_setting/migration.sql b/packages/database/prisma/migrations/20260226130000_remove_refined_permissions_setting/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260226130000_remove_refined_permissions_setting/migration.sql rename to packages/database/prisma/migrations/20260226130000_remove_refined_permissions_setting/migration.sql diff --git a/apps/server/src/prisma/migrations/20260226140001_add_tout_le_monde/migration.sql b/packages/database/prisma/migrations/20260226140001_add_tout_le_monde/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260226140001_add_tout_le_monde/migration.sql rename to packages/database/prisma/migrations/20260226140001_add_tout_le_monde/migration.sql diff --git a/apps/server/src/prisma/migrations/20260226141201_role_type/migration.sql b/packages/database/prisma/migrations/20260226141201_role_type/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260226141201_role_type/migration.sql rename to packages/database/prisma/migrations/20260226141201_role_type/migration.sql diff --git a/apps/server/src/prisma/migrations/20260330122500_migrate_project_system_roles_type_system_managed/migration.sql b/packages/database/prisma/migrations/20260330122500_migrate_project_system_roles_type_system_managed/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260330122500_migrate_project_system_roles_type_system_managed/migration.sql rename to packages/database/prisma/migrations/20260330122500_migrate_project_system_roles_type_system_managed/migration.sql diff --git a/apps/server/src/prisma/migrations/20260402095325_dso/migration.sql b/packages/database/prisma/migrations/20260402095325_dso/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260402095325_dso/migration.sql rename to packages/database/prisma/migrations/20260402095325_dso/migration.sql diff --git a/apps/server/src/prisma/migrations/20260415103000_set_root_admin_to_system_external/migration.sql b/packages/database/prisma/migrations/20260415103000_set_root_admin_to_system_external/migration.sql similarity index 100% rename from apps/server/src/prisma/migrations/20260415103000_set_root_admin_to_system_external/migration.sql rename to packages/database/prisma/migrations/20260415103000_set_root_admin_to_system_external/migration.sql diff --git a/apps/server/src/prisma/migrations/migration_lock.toml b/packages/database/prisma/migrations/migration_lock.toml similarity index 100% rename from apps/server/src/prisma/migrations/migration_lock.toml rename to packages/database/prisma/migrations/migration_lock.toml diff --git a/apps/server-nestjs/src/prisma/schema/admin.prisma b/packages/database/prisma/schema/admin.prisma similarity index 100% rename from apps/server-nestjs/src/prisma/schema/admin.prisma rename to packages/database/prisma/schema/admin.prisma diff --git a/apps/server-nestjs/src/prisma/schema/project.prisma b/packages/database/prisma/schema/project.prisma similarity index 100% rename from apps/server-nestjs/src/prisma/schema/project.prisma rename to packages/database/prisma/schema/project.prisma diff --git a/apps/server-nestjs/src/prisma/schema/schema.prisma b/packages/database/prisma/schema/schema.prisma similarity index 100% rename from apps/server-nestjs/src/prisma/schema/schema.prisma rename to packages/database/prisma/schema/schema.prisma diff --git a/apps/server-nestjs/src/prisma/schema/token.prisma b/packages/database/prisma/schema/token.prisma similarity index 100% rename from apps/server-nestjs/src/prisma/schema/token.prisma rename to packages/database/prisma/schema/token.prisma diff --git a/apps/server-nestjs/src/prisma/schema/topography.prisma b/packages/database/prisma/schema/topography.prisma similarity index 100% rename from apps/server-nestjs/src/prisma/schema/topography.prisma rename to packages/database/prisma/schema/topography.prisma diff --git a/apps/server-nestjs/src/prisma/schema/user.prisma b/packages/database/prisma/schema/user.prisma similarity index 100% rename from apps/server-nestjs/src/prisma/schema/user.prisma rename to packages/database/prisma/schema/user.prisma diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts new file mode 100644 index 0000000000..acd6254db6 --- /dev/null +++ b/packages/database/src/index.ts @@ -0,0 +1,2 @@ +export * from '@prisma/client' +export { PrismaClientInitializationError } from '@prisma/client/runtime/library.js' diff --git a/packages/database/tsconfig.eslint.json b/packages/database/tsconfig.eslint.json new file mode 100644 index 0000000000..982c60cf77 --- /dev/null +++ b/packages/database/tsconfig.eslint.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "./**/*.ts", + "./**/*.js", + "./**/*.cjs", + "./**/*.mjs" + ], + "exclude": [ + "./node_modules", + "./dist", + "./types", + "./coverage", + "./**/*.d.ts" + ] +} diff --git a/packages/database/tsconfig.json b/packages/database/tsconfig.json new file mode 100644 index 0000000000..23d08090a5 --- /dev/null +++ b/packages/database/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": [ + "@cpn-console/ts-config/tsconfig.base.json" + ], + "compilerOptions": { + "baseUrl": "./", + "rootDir": "./src", + "declarationDir": "./types", + "outDir": "./dist" + }, + "include": [ + "./src/**/*.ts" + ], + "exclude": [ + "./src/**/*.spec.ts", + "./src/**/__mocks__" + ] +} diff --git a/packages/hooks/package.json b/packages/hooks/package.json index c14988aeb1..1e72e57e23 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -26,9 +26,8 @@ "kube:e2e-ci": "echo 'check cache'", "lint": "eslint ./", "test": "vitest run", - "test:cov": "vitest run --coverage", - "test:e2e-ci": "echo 'check cache'", - "test:watch": "vitest" + "test:cov": "pnpm run test --coverage", + "test:watch": "pnpm run test --watch" }, "dependencies": { "@cpn-console/logger": "workspace:^", diff --git a/packages/shared/package.json b/packages/shared/package.json index 13963ee5ae..62618bf30e 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -26,9 +26,8 @@ "kube:e2e-ci": "echo 'check cache'", "lint": "eslint ./", "test": "vitest run", - "test:cov": "vitest run --coverage", - "test:e2e-ci": "echo 'check cache'", - "test:watch": "vitest" + "test:cov": "pnpm run test --coverage", + "test:watch": "pnpm run test --watch" }, "dependencies": { "@cpn-console/logger": "workspace:*", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 83a00b3e38..29ba454ad6 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -20,8 +20,7 @@ "build:clean": "rimraf ./dist ./types ./tsconfig.tsbuildinfo", "format": "eslint ./ --fix", "kube:e2e-ci": "echo 'check cache'", - "lint": "eslint ./", - "test:e2e-ci": "echo 'check cache'" + "lint": "eslint ./" }, "dependencies": { "@cpn-console/shared": "workspace:^", diff --git a/playwright/package.json b/playwright/package.json index e9b28aae13..4a6f9b67ba 100644 --- a/playwright/package.json +++ b/playwright/package.json @@ -11,10 +11,10 @@ ], "main": "playwright.config.ts", "scripts": { - "playwright:test": "pnpm exec playwright test", - "playwright:test:ui": "pnpm exec playwright test --ui", - "integ:playwright:test": "INTEGRATION=true pnpm exec playwright test", - "integ:playwright:test:ui": "INTEGRATION=true pnpm exec playwright test --ui", + "playwright:test": "playwright test", + "playwright:test:ui": "playwright test --ui", + "integ:playwright:test": "INTEGRATION=true playwright test", + "integ:playwright:test:ui": "INTEGRATION=true playwright test --ui", "format": "eslint ./ --fix" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 108c2b4a3b..1462b952cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,6 +195,9 @@ importers: '@cpn-console/argocd-plugin': specifier: workspace:^ version: link:../../plugins/argocd + '@cpn-console/database': + specifier: workspace:^ + version: link:../../packages/database '@cpn-console/gitlab-plugin': specifier: workspace:^ version: link:../../plugins/gitlab @@ -264,9 +267,6 @@ importers: '@opentelemetry/sdk-node': specifier: ^0.212.0 version: 0.212.0(@opentelemetry/api@1.9.0) - '@prisma/client': - specifier: ^6.19.2 - version: 6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) '@ts-rest/core': specifier: ^3.52.1 version: 3.52.1(@types/node@24.12.0)(zod@3.25.76) @@ -294,9 +294,6 @@ importers: mustache: specifier: ^4.2.0 version: 4.2.0 - prisma: - specifier: ^6.19.2 - version: 6.19.2(magicast@0.3.5)(typescript@5.9.3) undici: specifier: ^7.24.0 version: 7.24.5 @@ -358,6 +355,9 @@ importers: '@cpn-console/argocd-plugin': specifier: workspace:^ version: file:plugins/argocd(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) + '@cpn-console/database': + specifier: workspace:^ + version: link:../../packages/database '@cpn-console/gitlab-plugin': specifier: workspace:^ version: file:plugins/gitlab(@types/node@22.19.15)(typescript@5.9.3)(vitest@2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0)) @@ -466,9 +466,6 @@ importers: '@opentelemetry/sdk-trace-node': specifier: ^2.5.1 version: 2.6.0(@opentelemetry/api@1.9.0) - '@prisma/client': - specifier: ^6.19.2 - version: 6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) '@ts-rest/core': specifier: ^3.52.1 version: 3.52.1(@types/node@22.19.15)(zod@3.25.76) @@ -499,9 +496,6 @@ importers: pino-http: specifier: ^11.0.0 version: 11.0.0 - prisma: - specifier: ^6.19.2 - version: 6.19.2(magicast@0.3.5)(typescript@5.9.3) reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -618,6 +612,31 @@ importers: specifier: ^2.1.9 version: 2.1.9(@types/node@22.19.15)(jsdom@25.0.1)(msw@2.12.10(@types/node@22.19.15)(typescript@5.9.3))(terser@5.46.0) + packages/database: + dependencies: + '@prisma/client': + specifier: ^6.19.2 + version: 6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) + devDependencies: + '@cpn-console/eslint-config': + specifier: workspace:^ + version: link:../eslintconfig + '@cpn-console/ts-config': + specifier: workspace:^ + version: link:../tsconfig + '@types/node': + specifier: ^24.12.0 + version: 24.12.0 + prisma: + specifier: ^6.19.2 + version: 6.19.2(magicast@0.3.5)(typescript@5.9.3) + rimraf: + specifier: ^6.1.3 + version: 6.1.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages/eslintconfig: devDependencies: '@antfu/eslint-config': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6c8011a60c..44d941c710 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,28 @@ +packages: + - "packages/**" + - "plugins/**" + - "apps/**" + - "!apps/server/src/plugins/external/**" + - "playwright/**" +allowBuilds: + '@nestjs/core': true + '@prisma/client': true + '@prisma/engines': true + esbuild: true + msw: true + prisma: true + protobufjs: true + vue-demi: true +allowUnusedPatches: true + +autoInstallPeers: true +injectWorkspacePackages: true # minimum number of minutes that must pass after a version is published before # pnpm will install it. This applies to all dependencies, including transitive ones. minimumReleaseAge: 1440 # 1 day +patchedDependencies: + "@gouvminint/vue-dsfr": patches/@gouvminint__vue-dsfr.patch + fastify-keycloak-adapter@2.3.2: patches/fastify-keycloak-adapter@2.3.2.patch shellEmulator: true @@ -12,16 +34,3 @@ trustPolicyExclude: - semver@6.3.1 - chokidar@4.0.3 - pino@9.14.0 - -autoInstallPeers: true -injectWorkspacePackages: true -packages: - - "packages/**" - - "plugins/**" - - "apps/**" - - "!apps/server/src/plugins/external/**" - - "playwright/**" -patchedDependencies: - "@gouvminint/vue-dsfr": patches/@gouvminint__vue-dsfr.patch - fastify-keycloak-adapter@2.3.2: patches/fastify-keycloak-adapter@2.3.2.patch -allowUnusedPatches: true From aae0c068b70d0c399a702be2bbf07610e5f83e5f Mon Sep 17 00:00:00 2001 From: William Phetsinorath Date: Thu, 30 Apr 2026 15:58:56 +0200 Subject: [PATCH 2/2] refactor(server-nestjs): migrate Sonarqube to NestJS Signed-off-by: William Phetsinorath --- .../configuration/configuration.service.ts | 9 + .../sonarqube-client.service.spec.ts | 205 ++++++++ .../sonarqube/sonarqube-client.service.ts | 267 +++++++++++ .../sonarqube/sonarqube-datastore.service.ts | 67 +++ .../sonarqube/sonarqube-health.service.ts | 33 ++ .../sonarqube-http-client.service.ts | 94 ++++ .../sonarqube/sonarqube-testing.utils.ts | 90 ++++ .../modules/sonarqube/sonarqube.constants.ts | 40 ++ .../src/modules/sonarqube/sonarqube.module.ts | 24 + .../sonarqube/sonarqube.service.spec.ts | 299 ++++++++++++ .../modules/sonarqube/sonarqube.service.ts | 441 ++++++++++++++++++ .../src/modules/sonarqube/sonarqube.utils.ts | 6 + .../src/modules/vault/vault-client.service.ts | 50 +- .../src/modules/vault/vault.utils.ts | 6 + apps/server-nestjs/test/sonarqube.e2e-spec.ts | 176 +++++++ 15 files changed, 1806 insertions(+), 1 deletion(-) create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.spec.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube-datastore.service.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube-health.service.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube-http-client.service.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube-testing.utils.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube.constants.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube.module.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts create mode 100644 apps/server-nestjs/src/modules/sonarqube/sonarqube.utils.ts create mode 100644 apps/server-nestjs/test/sonarqube.e2e-spec.ts diff --git a/apps/server-nestjs/src/modules/infrastructure/configuration/configuration.service.ts b/apps/server-nestjs/src/modules/infrastructure/configuration/configuration.service.ts index 67155ecfb6..9e11d41c04 100644 --- a/apps/server-nestjs/src/modules/infrastructure/configuration/configuration.service.ts +++ b/apps/server-nestjs/src/modules/infrastructure/configuration/configuration.service.ts @@ -90,6 +90,11 @@ export class ConfigurationService { ? process.env.NEXUS_INTERNAL_URL : process.env.NEXUS_URL + // sonarqube + sonarqubeUrl = process.env.SONARQUBE_URL + sonarqubeInternalUrl = process.env.SONARQUBE_INTERNAL_URL + sonarApiToken = process.env.SONAR_API_TOKEN + getInternalOrPublicGitlabUrl() { return this.gitlabInternalUrl ?? this.gitlabUrl } @@ -106,6 +111,10 @@ export class ConfigurationService { return this.nexusInternalUrl ?? this.nexusUrl } + getInternalOrPublicSonarqubeUrl() { + return this.sonarqubeInternalUrl ?? this.sonarqubeUrl + } + NODE_ENV = process.env.NODE_ENV === 'test' ? 'test' diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.spec.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.spec.ts new file mode 100644 index 0000000000..66689ec44b --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.spec.ts @@ -0,0 +1,205 @@ +import type { AddPermissionGroupParams, CreateUserParams, DeactivateUserParams, RevokeUserTokenParams } from './sonarqube-client.service' +import { faker } from '@faker-js/faker' +import { Test } from '@nestjs/testing' +import { http, HttpResponse } from 'msw' +import { setupServer } from 'msw/node' +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest' +import { ConfigurationService } from '../infrastructure/configuration/configuration.service' +import { SonarqubeClientService } from './sonarqube-client.service' +import { SonarqubeHttpClientService } from './sonarqube-http-client.service' +import { makeSonarqubeGeneratedToken, makeSonarqubeGroup, makeSonarqubePaging, makeSonarqubeProject, makeSonarqubeUser } from './sonarqube-testing.utils' + +const sonarUrl = 'https://sonarqube.internal' +const sonarToken = 'my-token' +const sonarBearerToken = Buffer.from(`${sonarToken}:`, 'utf8').toString('base64') +const sonarAuthHeader = `Bearer ${sonarBearerToken}` + +const server = setupServer() + +function createTestingModule() { + return Test.createTestingModule({ + providers: [ + SonarqubeClientService, + SonarqubeHttpClientService, + { + provide: ConfigurationService, + useValue: { + sonarApiToken: sonarToken, + getInternalOrPublicSonarqubeUrl: () => sonarUrl, + } satisfies Partial, + }, + ], + }) +} + +describe('sonarqubeClientService', () => { + let service: SonarqubeClientService + + beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) + beforeEach(async () => { + const module = await createTestingModule().compile() + service = module.get(SonarqubeClientService) + }) + afterEach(() => server.resetHandlers()) + afterAll(() => server.close()) + + it('should be defined', () => { + expect(service).toBeDefined() + }) + + describe('userGroupsSearch', () => { + it('should GET user_groups/search with auth', async () => { + const group = makeSonarqubeGroup({ name: 'my-group' }) + server.use( + http.get(`${sonarUrl}/api/user_groups/search`, ({ request }) => { + expect(request.headers.get('authorization')).toBe(sonarAuthHeader) + expect(new URL(request.url).searchParams.get('q')).toBe('my-group') + return HttpResponse.json({ paging: makeSonarqubePaging({ total: 1 }), groups: [group] }) + }), + ) + const result = await service.searchUserGroup({ q: 'my-group' }) + expect(result.groups).toEqual([group]) + }) + }) + + describe('userGroupsCreate', () => { + it('should POST user_groups/create', async () => { + server.use( + http.post(`${sonarUrl}/api/user_groups/create`, ({ request }) => { + expect(new URL(request.url).searchParams.get('name')).toBe('new-group') + return HttpResponse.json({}) + }), + ) + await expect(service.createUserGroup({ name: 'new-group' })).resolves.not.toThrow() + }) + }) + + describe('usersSearch', () => { + it('should GET users/search', async () => { + const user = makeSonarqubeUser({ login: 'my-user' }) + server.use( + http.get(`${sonarUrl}/api/users/search`, () => + HttpResponse.json({ paging: makeSonarqubePaging({ total: 1 }), users: [user] })), + ) + const result = await service.searchUsers({ q: 'my-user' }) + expect(result.users).toEqual([user]) + }) + }) + + describe('usersCreate', () => { + it('should POST users/create with all params as query string', async () => { + const user = { + email: faker.internet.email(), + local: 'true', + login: faker.internet.username(), + name: faker.internet.username(), + password: faker.internet.password(), + } satisfies CreateUserParams + server.use( + http.post(`${sonarUrl}/api/users/create`, ({ request }) => { + const params = new URL(request.url).searchParams + expect(params.get('login')).toBe(user.login) + expect(params.get('email')).toBe(user.email) + expect(params.get('local')).toBe(user.local) + return HttpResponse.json({}) + }), + ) + await service.createUser(user) + }) + }) + + describe('usersDeactivate', () => { + it('should POST users/deactivate with anonymize param', async () => { + const user = { + login: faker.internet.username(), + anonymize: true, + } satisfies DeactivateUserParams + server.use( + http.post(`${sonarUrl}/api/users/deactivate`, ({ request }) => { + const params = new URL(request.url).searchParams + expect(params.get('login')).toBe(user.login) + expect(params.get('anonymize')).toBe(user.anonymize) + return HttpResponse.json({}) + }), + ) + await service.deactivateUser(user) + }) + }) + + describe('userTokensRevoke / userTokensGenerate', () => { + it('should POST user_tokens/revoke', async () => { + const token = makeSonarqubeGeneratedToken() + const revoke = { + login: token.login, + name: token.name, + } satisfies RevokeUserTokenParams + server.use( + http.post(`${sonarUrl}/api/user_tokens/revoke`, () => HttpResponse.json({})), + ) + await expect(service.revokeUserToken(revoke)).resolves.not.toThrow() + }) + + it('should POST user_tokens/generate and return the token', async () => { + const generated = makeSonarqubeGeneratedToken() + server.use( + http.post(`${sonarUrl}/api/user_tokens/generate`, () => HttpResponse.json(generated)), + ) + const result = await service.generateUserToken({ login: generated.login, name: generated.name }) + expect(result.token).toBe(generated.token) + }) + }) + + describe('projectsSearch', () => { + it('should GET projects/search', async () => { + const project = makeSonarqubeProject() + server.use( + http.get(`${sonarUrl}/api/projects/search`, () => + HttpResponse.json({ paging: makeSonarqubePaging({ total: 1 }), components: [project] })), + ) + const result = await service.searchProject({ q: project.name }) + expect(result.components).toEqual([project]) + }) + }) + + describe('projectsDelete', () => { + it('should POST projects/delete with project key as query param', async () => { + const project = makeSonarqubeProject() + server.use( + http.post(`${sonarUrl}/api/projects/delete`, ({ request }) => { + expect(new URL(request.url).searchParams.get('project')).toBe(project.key) + return HttpResponse.json({}) + }), + ) + await service.deleteProject({ project: project.key }) + }) + }) + + describe('permissionsAddGroup', () => { + it('should POST permissions/add_group with global params', async () => { + const group = { + groupName: '/admin', + permission: 'admin', + } satisfies AddPermissionGroupParams + server.use( + http.post(`${sonarUrl}/api/permissions/add_group`, ({ request }) => { + const params = new URL(request.url).searchParams + expect(params.get('groupName')).toBe(group.groupName) + expect(params.get('permission')).toBe(group.permission) + expect(params.has('projectKey')).toBe(false) + return HttpResponse.json({}) + }), + ) + await service.addPermissionGroup(group) + }) + + it('should POST permissions/add_group with projectKey for project-scoped call', async () => { + server.use( + http.post(`${sonarUrl}/api/permissions/add_group`, ({ request }) => { + expect(new URL(request.url).searchParams.get('projectKey')).toBe('proj-key') + return HttpResponse.json({}) + }), + ) + await service.addPermissionGroup({ groupName: '/proj', permission: 'scan', projectKey: 'proj-key' }) + }) + }) +}) diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts new file mode 100644 index 0000000000..07f4445937 --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube-client.service.ts @@ -0,0 +1,267 @@ +import { Inject, Injectable } from '@nestjs/common' +import { StartActiveSpan } from '../infrastructure/telemetry/telemetry.decorator' +import { SonarqubeHttpClientService } from './sonarqube-http-client.service' + +export interface SonarqubePaging { + pageIndex: number + pageSize: number + total: number +} + +export interface SonarqubeGroup { + id: string + name: string + description: string + membersCount: number + default: boolean +} + +export interface SonarqubeUser { + login: string + name: string + active: boolean + email: string + groups: string[] + tokensCount: number + local: boolean + externalIdentity: string + externalProvider: string + managed: boolean +} + +export const SONARQUBE_PROJECT_QUALIFIER_APPLICATION = 'APP' +export const SONARQUBE_PROJECT_QUALIFIER_BRANCH = 'BRC' +export const SONARQUBE_PROJECT_QUALIFIER_DIRECTORY = 'DIR' +export const SONARQUBE_PROJECT_QUALIFIER_FILE = 'FIL' +export const SONARQUBE_PROJECT_QUALIFIER_LIBRARY = 'LIB' +export const SONARQUBE_PROJECT_QUALIFIER_PROJECT = 'TRK' +export const SONARQUBE_PROJECT_QUALIFIER_UNIT_TEST = 'UTS' +export const SONARQUBE_PROJECT_QUALIFIER_VIEW = 'VW' +export const SONARQUBE_PROJECT_QUALIFIER_SUB_VIEW = 'SVW' + +export type SonarqubeProjectQualifier + = | typeof SONARQUBE_PROJECT_QUALIFIER_APPLICATION + | typeof SONARQUBE_PROJECT_QUALIFIER_BRANCH + | typeof SONARQUBE_PROJECT_QUALIFIER_DIRECTORY + | typeof SONARQUBE_PROJECT_QUALIFIER_FILE + | typeof SONARQUBE_PROJECT_QUALIFIER_LIBRARY + | typeof SONARQUBE_PROJECT_QUALIFIER_PROJECT + | typeof SONARQUBE_PROJECT_QUALIFIER_UNIT_TEST + | typeof SONARQUBE_PROJECT_QUALIFIER_VIEW + | typeof SONARQUBE_PROJECT_QUALIFIER_SUB_VIEW + +export interface SonarqubeProject { + key: string + name: string + qualifier: SonarqubeProjectQualifier + visibility: 'private' | 'public' + lastAnalysisDate?: string + revision?: string +} + +export interface SonarqubeProjectResult { + projectSlug: string + repository: string + key: string +} + +export interface SonarqubeGeneratedToken { + token: string + login: string + name: string +} + +type BaseParams = Record + +export interface SearchUserGroupParams extends BaseParams { + q?: string + p?: number + ps?: number +} + +export interface CreateUserGroupParams extends BaseParams { + name: string + description?: string +} + +export interface CreatePermissionTemplateParams extends BaseParams { + name: string + description?: string + projectKeyPattern?: string +} + +export interface SetPermissionDefaultTemplateParams extends BaseParams { + templateName: string + projectKeyPattern?: string +} + +export interface AddPermissionProjectCreatorToTemplateParams extends BaseParams { + templateName: string + permission: string +} + +export interface AddPermissionGroupToTemplateParams extends BaseParams { + groupName: string + templateName: string + permission: string +} + +export interface AddPermissionGroupParams extends BaseParams { + groupName: string + permission: string + projectKey?: string +} + +export interface AddPermissionUserParams extends BaseParams { + projectKey: string + permission: string + login: string +} + +export interface SearchUsersParams extends BaseParams { + q?: string + p?: number + ps?: number +} + +export interface CreateUserParams extends BaseParams { + email: string + local: string + login: string + name: string + password: string +} + +export interface DeactivateUserParams extends BaseParams { + login: string + anonymize: boolean +} + +export interface RevokeUserTokenParams extends BaseParams { + login: string + name: string +} + +export interface GenerateUserTokenParams extends BaseParams { + login: string + name: string +} + +export interface SearchProjectParams extends BaseParams { + q?: string + p?: number + ps?: number +} + +export interface CreateProjectParams extends BaseParams { + project: string + visibility: string + name: string + mainbranch: string +} + +export interface DeleteProjectParams extends BaseParams { + project: string +} + +export interface SearchUserGroupResponse { + paging: SonarqubePaging + groups: SonarqubeGroup[] +} + +export interface SearchUsersResponse { + paging: SonarqubePaging + users: SonarqubeUser[] +} + +export interface SearchProjectResponse { + paging: SonarqubePaging + components: SonarqubeProject[] +} + +@Injectable() +export class SonarqubeClientService { + constructor( + @Inject(SonarqubeHttpClientService) private readonly http: SonarqubeHttpClientService, + ) {} + + @StartActiveSpan() + searchUserGroup(params: SearchUserGroupParams) { + return this.http.fetch('user_groups/search', { params }).then(res => res.data!) + } + + @StartActiveSpan() + async createUserGroup(params: CreateUserGroupParams) { + await this.http.fetch('user_groups/create', { method: 'POST', params }) + } + + @StartActiveSpan() + async createPermissionTemplate(params: CreatePermissionTemplateParams) { + await this.http.fetch('permissions/create_template', { method: 'POST', params }) + } + + @StartActiveSpan() + async setPermissionDefaultTemplate(params: SetPermissionDefaultTemplateParams) { + await this.http.fetch('permissions/set_default_template', { method: 'POST', params }) + } + + @StartActiveSpan() + async addPermissionProjectCreatorToTemplate(params: AddPermissionProjectCreatorToTemplateParams) { + await this.http.fetch('permissions/add_project_creator_to_template', { method: 'POST', params }) + } + + @StartActiveSpan() + async addPermissionGroupToTemplate(params: AddPermissionGroupToTemplateParams) { + await this.http.fetch('permissions/add_group_to_template', { method: 'POST', params }) + } + + @StartActiveSpan() + async addPermissionGroup(params: AddPermissionGroupParams) { + await this.http.fetch('permissions/add_group', { method: 'POST', params }) + } + + @StartActiveSpan() + async addPermissionUser(params: AddPermissionUserParams) { + await this.http.fetch('permissions/add_user', { method: 'POST', params }) + } + + @StartActiveSpan() + searchUsers(params: SearchUsersParams) { + return this.http.fetch('users/search', { params }).then(res => res.data!) + } + + @StartActiveSpan() + async createUser(params: CreateUserParams) { + await this.http.fetch('users/create', { method: 'POST', params }) + } + + @StartActiveSpan() + async deactivateUser(params: DeactivateUserParams) { + await this.http.fetch('users/deactivate', { method: 'POST', params }) + } + + @StartActiveSpan() + async revokeUserToken(params: RevokeUserTokenParams) { + await this.http.fetch('user_tokens/revoke', { method: 'POST', params }) + } + + @StartActiveSpan() + generateUserToken(params: GenerateUserTokenParams) { + return this.http.fetch('user_tokens/generate', { method: 'POST', params }).then(res => res.data!) + } + + @StartActiveSpan() + searchProject(params: SearchProjectParams) { + return this.http.fetch('projects/search', { params }).then(res => res.data!) + } + + @StartActiveSpan() + async createProject(params: CreateProjectParams) { + await this.http.fetch('projects/create', { method: 'POST', params }) + } + + @StartActiveSpan() + async deleteProject(params: DeleteProjectParams) { + await this.http.fetch('projects/delete', { method: 'POST', params }) + } +} diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube-datastore.service.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube-datastore.service.ts new file mode 100644 index 0000000000..77fdb38c3a --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube-datastore.service.ts @@ -0,0 +1,67 @@ +import type { Prisma } from '@cpn-console/database' +import { Inject, Injectable } from '@nestjs/common' +import { PrismaService } from '../infrastructure/database/prisma.service' +import { SONARQUBE_PLUGIN_NAME } from './sonarqube.constants' + +export const projectSelect = { + id: true, + slug: true, + repositories: { + select: { + internalRepoName: true, + }, + }, + plugins: { + where: { + pluginName: SONARQUBE_PLUGIN_NAME, + }, + select: { + key: true, + value: true, + }, + }, +} satisfies Prisma.ProjectSelect + +export type ProjectWithDetails = Prisma.ProjectGetPayload<{ + select: typeof projectSelect +}> + +@Injectable() +export class SonarqubeDatastoreService { + constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {} + + async getAllProjects(): Promise { + return this.prisma.project.findMany({ + select: projectSelect, + where: { + plugins: { + some: { + pluginName: SONARQUBE_PLUGIN_NAME, + }, + }, + }, + }) + } + + async getProject(id: string): Promise { + return this.prisma.project.findUnique({ + where: { id }, + select: projectSelect, + }) + } + + async getAdminPluginConfig(pluginName: string, key: string): Promise { + const result = await this.prisma.adminPlugin.findUnique({ + where: { + pluginName_key: { + pluginName, + key, + }, + }, + select: { + value: true, + }, + }) + return result?.value ?? null + } +} diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube-health.service.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube-health.service.ts new file mode 100644 index 0000000000..870f53eebc --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube-health.service.ts @@ -0,0 +1,33 @@ +import { Inject, Injectable } from '@nestjs/common' +import { HealthIndicatorService } from '@nestjs/terminus' +import { ConfigurationService } from '../infrastructure/configuration/configuration.service' + +@Injectable() +export class SonarqubeHealthService { + constructor( + @Inject(ConfigurationService) private readonly config: ConfigurationService, + @Inject(HealthIndicatorService) private readonly healthIndicator: HealthIndicatorService, + ) {} + + async check(key: string) { + const indicator = this.healthIndicator.check(key) + const urlBase = this.config.getInternalOrPublicSonarqubeUrl() + if (!urlBase) return indicator.down('Not configured') + + const url = new URL('/api/system/health', urlBase).toString() + const token = this.config.sonarApiToken + const headers: Record = {} + if (token) { + const bearerToken = Buffer.from(`${token}:`, 'utf8').toString('base64') + headers.Authorization = `Bearer ${bearerToken}` + } + + try { + const response = await fetch(url, { headers }) + if (response.status < 500) return indicator.up({ httpStatus: response.status }) + return indicator.down({ httpStatus: response.status }) + } catch (error) { + return indicator.down(error instanceof Error ? error.message : String(error)) + } + } +} diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube-http-client.service.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube-http-client.service.ts new file mode 100644 index 0000000000..678b059990 --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube-http-client.service.ts @@ -0,0 +1,94 @@ +import { Inject, Injectable } from '@nestjs/common' +import { trace } from '@opentelemetry/api' +import { ConfigurationService } from '../infrastructure/configuration/configuration.service' + +export interface SonarqubeFetchOptions { + method?: string + params?: Record +} + +export interface SonarqubeResponse { + status: number + data: T | null +} + +export type SonarqubeErrorKind = 'NotConfigured' | 'Unexpected' + +export class SonarqubeError extends Error { + readonly kind: SonarqubeErrorKind + readonly status?: number + readonly method?: string + readonly path?: string + + constructor( + kind: SonarqubeErrorKind, + message: string, + details: { status?: number, method?: string, path?: string } = {}, + ) { + super(message) + this.name = 'SonarqubeError' + this.kind = kind + this.status = details.status + this.method = details.method + this.path = details.path + } +} + +@Injectable() +export class SonarqubeHttpClientService { + constructor( + @Inject(ConfigurationService) private readonly config: ConfigurationService, + ) {} + + private get baseUrl(): string { + const url = this.config.getInternalOrPublicSonarqubeUrl() + if (!url) throw new SonarqubeError('NotConfigured', 'SONARQUBE_URL or SONARQUBE_INTERNAL_URL is required') + return url + } + + private get apiBaseUrl(): string { + return new URL('api/', this.baseUrl).toString() + } + + private get defaultHeaders(): Record { + if (!this.config.sonarApiToken) throw new SonarqubeError('NotConfigured', 'SONAR_API_TOKEN is required') + const bearerToken = Buffer.from(`${this.config.sonarApiToken}:`, 'utf8').toString('base64') + return { + Authorization: `Bearer ${bearerToken}`, + } + } + + async fetch(path: string, options: SonarqubeFetchOptions = {}): Promise> { + const span = trace.getActiveSpan() + const method = (options.method ?? 'GET').toUpperCase() + span?.setAttribute('sonarqube.method', method) + span?.setAttribute('sonarqube.path', path) + + const request = this.createRequest(path, method, options.params) + const response = await fetch(request).catch((error) => { + throw new SonarqubeError('Unexpected', error instanceof Error ? error.message : String(error), { method, path }) + }) + + span?.setAttribute('sonarqube.http.status', response.status) + return handleResponse(response) + } + + private createRequest(path: string, method: string, params?: Record): Request { + const url = new URL(path, this.apiBaseUrl) + if (params) { + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null) url.searchParams.append(key, String(value)) + } + } + return new Request(url.toString(), { method, headers: this.defaultHeaders }) + } +} + +async function handleResponse(response: Response): Promise> { + if (response.status === 204) return { status: response.status, data: null } + const contentType = response.headers.get('content-type') ?? '' + const parsed = contentType.includes('application/json') + ? await response.json() + : await response.text() + return { status: response.status, data: parsed as T } +} diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube-testing.utils.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube-testing.utils.ts new file mode 100644 index 0000000000..d1e78ebea4 --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube-testing.utils.ts @@ -0,0 +1,90 @@ +import type { SonarqubeGeneratedToken, SonarqubeGroup, SonarqubePaging, SonarqubeProject, SonarqubeUser } from './sonarqube-client.service' +import type { ProjectWithDetails } from './sonarqube-datastore.service' +import { faker } from '@faker-js/faker' +import { SONARQUBE_PROJECT_QUALIFIER_PROJECT } from './sonarqube-client.service' + +export function makeUserToken(overrides: Partial = {}) { + return { + token: faker.string.uuid(), + login: faker.internet.username(), + name: faker.person.fullName(), + ...overrides, + } satisfies SonarqubeGeneratedToken +} + +export function makeEmptyGroupsResponse() { + return { paging: makeSonarqubePaging(), groups: [] } +} + +export function makeEmptyUsersResponse() { + return { paging: makeSonarqubePaging(), users: [] } +} + +export function makeEmptyProjectsResponse() { + return { paging: makeSonarqubePaging(), components: [] } +} + +export function makeProjectWithDetails(overrides: Partial = {}): ProjectWithDetails { + return { + id: faker.string.uuid(), + slug: faker.internet.domainWord(), + repositories: [], + plugins: [], + ...overrides, + } satisfies ProjectWithDetails +} + +export function makeSonarqubeGroup(overrides: Partial = {}): SonarqubeGroup { + return { + id: faker.string.uuid(), + name: faker.internet.domainWord(), + description: '', + membersCount: 0, + default: false, + ...overrides, + } satisfies SonarqubeGroup +} + +export function makeSonarqubeUser(overrides: Partial = {}): SonarqubeUser { + return { + login: faker.internet.username(), + name: faker.person.fullName(), + active: true, + email: faker.internet.email(), + groups: [], + tokensCount: 0, + local: true, + externalIdentity: '', + externalProvider: '', + managed: false, + ...overrides, + } satisfies SonarqubeUser +} + +export function makeSonarqubeProject(overrides: Partial = {}): SonarqubeProject { + return { + key: faker.string.alphanumeric(20), + name: faker.internet.domainWord(), + qualifier: SONARQUBE_PROJECT_QUALIFIER_PROJECT, + visibility: 'private', + ...overrides, + } satisfies SonarqubeProject +} + +export function makeSonarqubePaging(overrides: Partial = {}): SonarqubePaging { + return { + pageIndex: 1, + pageSize: 100, + total: 0, + ...overrides, + } satisfies SonarqubePaging +} + +export function makeSonarqubeGeneratedToken(overrides: Partial = {}): SonarqubeGeneratedToken { + return { + token: faker.string.alphanumeric(40), + login: faker.internet.username(), + name: `Sonar Token for ${faker.internet.username()}`, + ...overrides, + } satisfies SonarqubeGeneratedToken +} diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube.constants.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube.constants.ts new file mode 100644 index 0000000000..e6744737fc --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube.constants.ts @@ -0,0 +1,40 @@ +export const SONARQUBE_PLUGIN_NAME = 'sonarqube' +export const DEFAULT_PERMISSION_TEMPLATE_NAME = 'Forge Default' + +// SonarQube global permission names +export const GLOBAL_ADMIN_PERMISSIONS = ['admin', 'profileadmin', 'gateadmin', 'provisioning'] as const + +// Permission template — grants to project creator and sonar-administrators on new projects +export const DEFAULT_TEMPLATE_PERMISSIONS = ['admin', 'codeviewer', 'issueadmin', 'securityhotspotadmin', 'scan', 'user'] as const + +// Project-level permission sets per role (SonarQube permission API names) +export const PROJECT_ADMIN_PERMISSIONS = ['admin', 'scan', 'user', 'codeviewer', 'issueadmin', 'securityhotspotadmin'] as const +export const PROJECT_DEVOPS_PERMISSIONS = ['scan', 'user', 'codeviewer', 'issueadmin', 'securityhotspotadmin'] as const +export const PROJECT_DEVELOPER_PERMISSIONS = ['scan', 'user', 'codeviewer'] as const +export const PROJECT_SECURITY_PERMISSIONS = ['scan', 'user', 'codeviewer', 'issueadmin', 'securityhotspotadmin'] as const +export const PROJECT_READONLY_PERMISSIONS = ['user', 'codeviewer'] as const + +// CI robot/service account — needs Execute Analysis + Browse + See Source Code +export const ROBOT_PROJECT_PERMISSIONS = ['scan', 'user', 'codeviewer'] as const + +// Default platform-wide Keycloak group paths (following gitlab /console/* naming) +export const DEFAULT_ADMIN_GROUP_PATH = '/console/admin' +export const DEFAULT_READONLY_GROUP_PATH = '/console/readonly' +export const DEFAULT_SECURITY_GROUP_PATH = '/console/security' + +// Default project role group path suffixes (appended to /{projectSlug}) +export const DEFAULT_PROJECT_ADMIN_SUFFIX = '/console/admin' +export const DEFAULT_PROJECT_DEVOPS_SUFFIX = '/console/devops' +export const DEFAULT_PROJECT_DEVELOPER_SUFFIX = '/console/developer' +export const DEFAULT_PROJECT_SECURITY_SUFFIX = '/console/security' +export const DEFAULT_PROJECT_READONLY_SUFFIX = '/console/readonly' + +// Admin plugin config keys for overriding defaults +export const ADMIN_GROUP_PATH_PLUGIN_KEY = 'adminGroupPath' +export const READONLY_GROUP_PATH_PLUGIN_KEY = 'readonlyGroupPath' +export const SECURITY_GROUP_PATH_PLUGIN_KEY = 'securityGroupPath' +export const PROJECT_ADMIN_SUFFIX_PLUGIN_KEY = 'projectAdminSuffix' +export const PROJECT_DEVOPS_SUFFIX_PLUGIN_KEY = 'projectDevopsSuffix' +export const PROJECT_DEVELOPER_SUFFIX_PLUGIN_KEY = 'projectDeveloperSuffix' +export const PROJECT_SECURITY_SUFFIX_PLUGIN_KEY = 'projectSecuritySuffix' +export const PROJECT_READONLY_SUFFIX_PLUGIN_KEY = 'projectReadonlySuffix' diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube.module.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube.module.ts new file mode 100644 index 0000000000..89bd0857b8 --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common' +import { HealthIndicatorService } from '@nestjs/terminus' +import { ConfigurationModule } from '../infrastructure/configuration/configuration.module' +import { InfrastructureModule } from '../infrastructure/infrastructure.module' +import { VaultModule } from '../vault/vault.module' +import { SonarqubeClientService } from './sonarqube-client.service' +import { SonarqubeDatastoreService } from './sonarqube-datastore.service' +import { SonarqubeHealthService } from './sonarqube-health.service' +import { SonarqubeHttpClientService } from './sonarqube-http-client.service' +import { SonarqubeService } from './sonarqube.service' + +@Module({ + imports: [ConfigurationModule, InfrastructureModule, VaultModule], + providers: [ + HealthIndicatorService, + SonarqubeHttpClientService, + SonarqubeClientService, + SonarqubeDatastoreService, + SonarqubeHealthService, + SonarqubeService, + ], + exports: [SonarqubeClientService, SonarqubeHealthService], +}) +export class SonarqubeModule {} diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts new file mode 100644 index 0000000000..5412665c61 --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.spec.ts @@ -0,0 +1,299 @@ +import type { Mocked } from 'vitest' +import { Test } from '@nestjs/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { ConfigurationService } from '../infrastructure/configuration/configuration.service' +import { VaultClientService } from '../vault/vault-client.service' +import { makeVaultSecret } from '../vault/vault-testing.utils.js' +import { SONARQUBE_PROJECT_QUALIFIER_PROJECT, SonarqubeClientService } from './sonarqube-client.service' +import { SonarqubeDatastoreService } from './sonarqube-datastore.service' +import { + makeEmptyGroupsResponse, + makeEmptyProjectsResponse, + makeEmptyUsersResponse, + makeProjectWithDetails, + makeSonarqubePaging, + makeSonarqubeUser, + makeUserToken, +} from './sonarqube-testing.utils' +import { SonarqubeService } from './sonarqube.service' + +function createTestingModule() { + return Test.createTestingModule({ + providers: [ + SonarqubeService, + { + provide: SonarqubeClientService, + useValue: { + searchUserGroup: vi.fn(), + createUserGroup: vi.fn(), + createPermissionTemplate: vi.fn(), + setPermissionDefaultTemplate: vi.fn(), + addPermissionProjectCreatorToTemplate: vi.fn(), + addPermissionGroupToTemplate: vi.fn(), + addPermissionGroup: vi.fn(), + addPermissionUser: vi.fn(), + searchUsers: vi.fn(), + createUser: vi.fn(), + deactivateUser: vi.fn(), + revokeUserToken: vi.fn(), + generateUserToken: vi.fn(), + searchProject: vi.fn(), + createProject: vi.fn(), + deleteProject: vi.fn(), + } satisfies Partial, + }, + { + provide: SonarqubeDatastoreService, + useValue: { + getAllProjects: vi.fn(), + getProject: vi.fn(), + getAdminPluginConfig: vi.fn(), + } satisfies Partial, + }, + { + provide: VaultClientService, + useValue: { + readSonarqubeUser: vi.fn(), + writeSonarqubeUser: vi.fn(), + deleteSonarqubeUser: vi.fn(), + } satisfies Partial, + }, + { + provide: ConfigurationService, + useValue: { + projectRootDir: 'forge', + getInternalOrPublicSonarqubeUrl: () => 'https://sonarqube.internal', + } satisfies Partial, + }, + ], + }) +} + +describe('sonarqubeService', () => { + let service: SonarqubeService + let client: Mocked + let datastore: Mocked + let vault: Mocked + + beforeEach(async () => { + const module = await createTestingModule().compile() + service = module.get(SonarqubeService) + client = module.get(SonarqubeClientService) + datastore = module.get(SonarqubeDatastoreService) + vault = module.get(VaultClientService) + + datastore.getAdminPluginConfig.mockResolvedValue(null) + client.searchUserGroup.mockResolvedValue(makeEmptyGroupsResponse()) + client.createUserGroup.mockResolvedValue(undefined) + client.createPermissionTemplate.mockResolvedValue(undefined) + client.setPermissionDefaultTemplate.mockResolvedValue(undefined) + client.addPermissionProjectCreatorToTemplate.mockResolvedValue(undefined) + client.addPermissionGroupToTemplate.mockResolvedValue(undefined) + client.addPermissionGroup.mockResolvedValue(undefined) + client.addPermissionUser.mockResolvedValue(undefined) + client.searchUsers.mockResolvedValue(makeEmptyUsersResponse()) + client.createUser.mockResolvedValue(undefined) + client.deactivateUser.mockResolvedValue(undefined) + client.revokeUserToken.mockResolvedValue(undefined) + client.searchProject.mockResolvedValue(makeEmptyProjectsResponse()) + client.createProject.mockResolvedValue(undefined) + client.deleteProject.mockResolvedValue(undefined) + vault.readSonarqubeUser.mockResolvedValue(null) + vault.writeSonarqubeUser.mockResolvedValue(undefined) + vault.deleteSonarqubeUser.mockResolvedValue(undefined) + }) + + describe('init', () => { + it('should set up the permission template', async () => { + await service.init() + expect(client.createPermissionTemplate).toHaveBeenCalledWith({ name: 'Forge Default' }) + expect(client.setPermissionDefaultTemplate).toHaveBeenCalledWith({ templateName: 'Forge Default' }) + }) + + it('should create /console/admin group with global permissions when it does not exist', async () => { + await service.init() + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: '/console/admin' })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: '/console/admin' })) + }) + + it('should create /console/readonly and /console/security platform groups', async () => { + await service.init() + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: '/console/readonly' })) + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: '/console/security' })) + }) + + it('should not create groups that already exist', async () => { + client.searchUserGroup.mockResolvedValue({ + paging: makeSonarqubePaging({ total: 1 }), + groups: [{ id: '1', name: '/console/admin', description: '', membersCount: 1, default: false }], + }) + await service.init() + expect(client.createUserGroup).not.toHaveBeenCalledWith(expect.objectContaining({ name: '/console/admin' })) + }) + + it('should skip initialization when URL is not configured', async () => { + const module = await Test.createTestingModule({ + providers: [ + SonarqubeService, + { provide: SonarqubeClientService, useValue: client }, + { provide: SonarqubeDatastoreService, useValue: datastore }, + { provide: VaultClientService, useValue: vault }, + { provide: ConfigurationService, useValue: { getInternalOrPublicSonarqubeUrl: () => undefined } }, + ], + }).compile() + await module.get(SonarqubeService).init() + expect(client.createPermissionTemplate).not.toHaveBeenCalled() + }) + + it('should use custom group paths from admin plugin config', async () => { + datastore.getAdminPluginConfig.mockImplementation((_plugin, key) => { + if (key === 'adminGroupPath') return Promise.resolve('/custom/admin') + return Promise.resolve(null) + }) + await service.init() + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: '/custom/admin' })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: '/custom/admin' })) + }) + }) + + describe('handleUpsert', () => { + it('should create the 5 project role groups in SonarQube', async () => { + const project = makeProjectWithDetails() + client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug })) + + await service.handleUpsert(project) + + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: `/${project.slug}/console/admin` })) + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: `/${project.slug}/console/devops` })) + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: `/${project.slug}/console/developer` })) + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: `/${project.slug}/console/security` })) + expect(client.createUserGroup).toHaveBeenCalledWith(expect.objectContaining({ name: `/${project.slug}/console/readonly` })) + }) + + it('should create a new user and write vault credentials', async () => { + const project = makeProjectWithDetails() + const userToken = makeUserToken({ login: project.slug }) + client.generateUserToken.mockResolvedValue(userToken) + + await service.handleUpsert(project) + + expect(client.createUser).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug })) + expect(client.generateUserToken).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug })) + expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.objectContaining({ SONAR_USERNAME: project.slug, SONAR_TOKEN: userToken.token })) + }) + + it('should set role-based permissions on new repositories', async () => { + const project = makeProjectWithDetails({ repositories: [{ internalRepoName: 'repo' }] }) + client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug })) + + await service.handleUpsert(project) + + expect(client.createProject).toHaveBeenCalledWith(expect.objectContaining({ visibility: 'private', name: `${project.slug}-repo` })) + expect(client.addPermissionUser).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: `/${project.slug}/console/admin` })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: `/${project.slug}/console/devops` })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: `/${project.slug}/console/developer` })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: '/console/readonly' })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: '/console/security' })) + }) + + it('should not recreate user or write vault when both user and secret exist', async () => { + const project = makeProjectWithDetails({ slug: 'existing', repositories: [] }) + client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug })) + client.searchUsers.mockResolvedValue({ paging: makeSonarqubePaging({ total: 1 }), users: [makeSonarqubeUser({ login: project.slug })] }) + vault.readSonarqubeUser.mockResolvedValue(makeVaultSecret({ data: { SONAR_USERNAME: project.slug, SONAR_PASSWORD: 'pw', SONAR_TOKEN: 'tok' } })) + + await service.handleUpsert(project) + + expect(client.createUser).not.toHaveBeenCalled() + expect(client.generateUserToken).not.toHaveBeenCalled() + expect(vault.writeSonarqubeUser).not.toHaveBeenCalled() + }) + + it('should rotate token when user exists but vault secret is missing', async () => { + const project = makeProjectWithDetails({ repositories: [] }) + client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug })) + client.searchUsers.mockResolvedValue({ paging: makeSonarqubePaging({ total: 1 }), users: [makeSonarqubeUser({ login: project.slug })] }) + + await service.handleUpsert(project) + + expect(client.createUser).not.toHaveBeenCalled() + expect(client.generateUserToken).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug })) + expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.objectContaining({ SONAR_PASSWORD: 'not initialized' })) + }) + + it('should delete sonarqube projects for removed repositories', async () => { + const project = makeProjectWithDetails({ repositories: [{ internalRepoName: 'kept' }] }) + client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug })) + client.searchProject.mockResolvedValue({ + paging: makeSonarqubePaging({ total: 2 }), + components: [ + { key: `${project.slug}-kept-aabb`, name: '', qualifier: SONARQUBE_PROJECT_QUALIFIER_PROJECT, visibility: 'private' }, + { key: `${project.slug}-removed-ccdd`, name: '', qualifier: SONARQUBE_PROJECT_QUALIFIER_PROJECT, visibility: 'private' }, + ], + }) + client.searchUsers.mockResolvedValue({ paging: makeSonarqubePaging({ total: 1 }), users: [makeSonarqubeUser({ login: project.slug })] }) + vault.readSonarqubeUser.mockResolvedValue(makeVaultSecret({ data: { SONAR_USERNAME: project.slug, SONAR_PASSWORD: 'pw', SONAR_TOKEN: 'tok' } })) + + await service.handleUpsert(project) + + expect(client.deleteProject).toHaveBeenCalledWith({ project: `${project.slug}-removed-ccdd` }) + expect(client.deleteProject).not.toHaveBeenCalledWith({ project: `${project.slug}-kept-aabb` }) + }) + + it('should use comma-separated group path suffixes from project plugin config', async () => { + const project = makeProjectWithDetails({ + repositories: [{ internalRepoName: 'repo' }], + plugins: [{ key: 'projectAdminSuffix', value: '/console/admin,/console/owner' }], + }) + client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug })) + + await service.handleUpsert(project) + + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: `/${project.slug}/console/admin` })) + expect(client.addPermissionGroup).toHaveBeenCalledWith(expect.objectContaining({ groupName: `/${project.slug}/console/owner` })) + }) + }) + + describe('handleDelete', () => { + it('should delete sonarqube projects, anonymize user and remove vault entry', async () => { + const project = makeProjectWithDetails({ slug: 'doomed' }) + client.searchProject.mockResolvedValue({ + paging: makeSonarqubePaging({ total: 1 }), + components: [{ key: 'doomed-repo-aabb', name: '', qualifier: SONARQUBE_PROJECT_QUALIFIER_PROJECT, visibility: 'private' }], + }) + client.searchUsers.mockResolvedValue({ paging: makeSonarqubePaging({ total: 1 }), users: [makeSonarqubeUser({ login: 'doomed' })] }) + + await service.handleDelete(project) + + expect(client.deleteProject).toHaveBeenCalledWith({ project: 'doomed-repo-aabb' }) + expect(client.deactivateUser).toHaveBeenCalledWith({ login: 'doomed', anonymize: true }) + expect(vault.deleteSonarqubeUser).toHaveBeenCalledWith('doomed') + }) + + it('should skip anonymization when the user does not exist', async () => { + const project = makeProjectWithDetails({ slug: 'no-user' }) + + await service.handleDelete(project) + + expect(client.deactivateUser).not.toHaveBeenCalled() + expect(vault.deleteSonarqubeUser).toHaveBeenCalledWith('no-user') + }) + }) + + describe('handleCron', () => { + it('should reconcile all projects and run init', async () => { + const projects = [ + makeProjectWithDetails({ repositories: [] }), + makeProjectWithDetails({ repositories: [] }), + ] + datastore.getAllProjects.mockResolvedValue(projects) + client.generateUserToken.mockImplementation(({ login }) => Promise.resolve(makeUserToken({ login }))) + + await service.handleCron() + + expect(client.searchProject).toHaveBeenCalledTimes(2) + expect(client.createPermissionTemplate).toHaveBeenCalledOnce() + }) + }) +}) diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts new file mode 100644 index 0000000000..1a6eec948f --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts @@ -0,0 +1,441 @@ +import type { OnModuleInit } from '@nestjs/common' +import type { SonarqubeUserSecret } from '../vault/vault-client.service' +import type { SonarqubeProjectResult, SonarqubeUser } from './sonarqube-client.service' +import type { ProjectWithDetails } from './sonarqube-datastore.service' +import { generateProjectKey, generateRandomPassword } from '@cpn-console/hooks' +import { Inject, Injectable, Logger } from '@nestjs/common' +import { OnEvent } from '@nestjs/event-emitter' +import { Cron, CronExpression } from '@nestjs/schedule' +import { trace } from '@opentelemetry/api' +import { ConfigurationService } from '../infrastructure/configuration/configuration.service' +import { StartActiveSpan } from '../infrastructure/telemetry/telemetry.decorator' +import { VaultClientService } from '../vault/vault-client.service' +import { SonarqubeClientService } from './sonarqube-client.service' +import { SonarqubeDatastoreService } from './sonarqube-datastore.service' +import { + ADMIN_GROUP_PATH_PLUGIN_KEY, + DEFAULT_ADMIN_GROUP_PATH, + DEFAULT_PERMISSION_TEMPLATE_NAME, + DEFAULT_PROJECT_ADMIN_SUFFIX, + DEFAULT_PROJECT_DEVELOPER_SUFFIX, + DEFAULT_PROJECT_DEVOPS_SUFFIX, + DEFAULT_PROJECT_READONLY_SUFFIX, + DEFAULT_PROJECT_SECURITY_SUFFIX, + DEFAULT_READONLY_GROUP_PATH, + DEFAULT_SECURITY_GROUP_PATH, + DEFAULT_TEMPLATE_PERMISSIONS, + GLOBAL_ADMIN_PERMISSIONS, + PROJECT_ADMIN_PERMISSIONS, + PROJECT_ADMIN_SUFFIX_PLUGIN_KEY, + PROJECT_DEVELOPER_PERMISSIONS, + PROJECT_DEVELOPER_SUFFIX_PLUGIN_KEY, + PROJECT_DEVOPS_PERMISSIONS, + PROJECT_DEVOPS_SUFFIX_PLUGIN_KEY, + PROJECT_READONLY_PERMISSIONS, + PROJECT_READONLY_SUFFIX_PLUGIN_KEY, + PROJECT_SECURITY_PERMISSIONS, + PROJECT_SECURITY_SUFFIX_PLUGIN_KEY, + READONLY_GROUP_PATH_PLUGIN_KEY, + ROBOT_PROJECT_PERMISSIONS, + SECURITY_GROUP_PATH_PLUGIN_KEY, + SONARQUBE_PLUGIN_NAME, +} from './sonarqube.constants' + +interface SonarqubeRolePaths { + admin: string[] + devops: string[] + developer: string[] + security: string[] + readonly: string[] +} + +@Injectable() +export class SonarqubeService implements OnModuleInit { + private readonly logger = new Logger(SonarqubeService.name) + + constructor( + @Inject(SonarqubeDatastoreService) private readonly datastore: SonarqubeDatastoreService, + @Inject(SonarqubeClientService) private readonly client: SonarqubeClientService, + @Inject(ConfigurationService) private readonly config: ConfigurationService, + @Inject(VaultClientService) private readonly vault: VaultClientService, + ) { + this.logger.log('SonarqubeService initialized') + } + + async onModuleInit() { + await this.init().catch(error => this.logger.error('SonarQube initialization failed', error)) + } + + @StartActiveSpan() + async init(): Promise { + if (!this.config.getInternalOrPublicSonarqubeUrl()) { + this.logger.warn('SonarQube URL not configured — skipping initialization') + return + } + this.logger.log('Initializing SonarQube platform configuration') + const adminGroupPath = await this.getAdminGroupPath() + const [readonlyGroupPath, securityGroupPath] = await Promise.all([ + this.getReadonlyGroupPath(), + this.getSecurityGroupPath(), + ]) + await this.ensureDefaultPermissionTemplate() + await Promise.all([ + this.ensureGroupWithGlobalPermissions(adminGroupPath, GLOBAL_ADMIN_PERMISSIONS), + this.ensureGroup(readonlyGroupPath), + this.ensureGroup(securityGroupPath), + ]) + this.logger.log('SonarQube platform configuration initialized') + } + + @OnEvent('project.upsert') + @StartActiveSpan() + async handleUpsert(project: ProjectWithDetails) { + const span = trace.getActiveSpan() + span?.setAttribute('project.slug', project.slug) + this.logger.log(`Handling a project upsert event for ${project.slug}`) + await this.ensureProjectGroup(project) + this.logger.log(`SonarQube sync completed for project ${project.slug}`) + } + + @OnEvent('project.delete') + @StartActiveSpan() + async handleDelete(project: ProjectWithDetails) { + const span = trace.getActiveSpan() + span?.setAttribute('project.slug', project.slug) + this.logger.log(`Handling a project delete event for ${project.slug}`) + await this.deleteProjectGroup(project) + this.logger.log(`SonarQube deletion completed for project ${project.slug}`) + } + + @Cron(CronExpression.EVERY_HOUR) + @StartActiveSpan() + async handleCron() { + const span = trace.getActiveSpan() + this.logger.log('Starting SonarQube reconciliation') + await this.init().catch(error => this.logger.error('SonarQube init during cron failed', error)) + const projects = await this.datastore.getAllProjects() + span?.setAttribute('sonarqube.projects.count', projects.length) + this.logger.log(`Loaded ${projects.length} projects for SonarQube reconciliation`) + await this.ensureProjectGroups(projects) + this.logger.log(`SonarQube reconciliation completed (${projects.length})`) + } + + @StartActiveSpan() + private async ensureProjectGroups(projects: ProjectWithDetails[]) { + const span = trace.getActiveSpan() + span?.setAttribute('sonarqube.projects.count', projects.length) + await Promise.all(projects.map(p => + this.ensureProjectGroup(p).catch(error => + this.logger.error(`Failed to reconcile SonarQube project (slug=${p.slug})`, error), + ), + )) + } + + @StartActiveSpan() + private async ensureProjectGroup(project: ProjectWithDetails) { + const span = trace.getActiveSpan() + span?.setAttribute('project.slug', project.slug) + const rolePaths = await this.getProjectRoleGroupPaths(project) + await Promise.all([ + this.ensureUser(project.slug, project.slug), + this.ensureProjectSonarGroups(rolePaths), + this.ensureProjectRepositories(project, rolePaths), + ]) + } + + @StartActiveSpan() + private async deleteProjectGroup(project: ProjectWithDetails) { + const span = trace.getActiveSpan() + span?.setAttribute('project.slug', project.slug) + + const sonarProjects = await this.findProjectsForSlug(project.slug) + span?.setAttribute('sonarqube.projects.count', sonarProjects.length) + this.logger.log(`Deleting ${sonarProjects.length} SonarQube repositories for project ${project.slug}`) + + await Promise.all(sonarProjects.map(async (sp) => { + await this.client.deleteProject({ project: sp.key }) + this.logger.verbose(`Deleted SonarQube repository (key=${sp.key})`) + })) + + const user = await this.findUser(project.slug) + if (user) { + await this.client.deactivateUser({ login: project.slug, anonymize: true }) + this.logger.log(`Anonymized SonarQube user (login=${project.slug})`) + } else { + this.logger.verbose(`SonarQube user not found, skipping anonymization (login=${project.slug})`) + } + + await this.vault.deleteSonarqubeUser(project.slug) + this.logger.verbose(`Deleted SonarQube vault credentials (slug=${project.slug})`) + } + + @StartActiveSpan() + private async ensureDefaultPermissionTemplate(): Promise { + this.logger.verbose(`Ensuring SonarQube permission template (name=${DEFAULT_PERMISSION_TEMPLATE_NAME})`) + await this.client.createPermissionTemplate({ name: DEFAULT_PERMISSION_TEMPLATE_NAME }) + await Promise.all(DEFAULT_TEMPLATE_PERMISSIONS.map(permission => + this.client.addPermissionProjectCreatorToTemplate({ templateName: DEFAULT_PERMISSION_TEMPLATE_NAME, permission }), + )) + await Promise.all(DEFAULT_TEMPLATE_PERMISSIONS.map(permission => + this.client.addPermissionGroupToTemplate({ groupName: 'sonar-administrators', templateName: DEFAULT_PERMISSION_TEMPLATE_NAME, permission }), + )) + await this.client.setPermissionDefaultTemplate({ templateName: DEFAULT_PERMISSION_TEMPLATE_NAME }) + this.logger.log(`SonarQube permission template ensured (name=${DEFAULT_PERMISSION_TEMPLATE_NAME})`) + } + + @StartActiveSpan() + private async ensureUser(username: string, projectSlug: string): Promise { + const existingSecret = await this.vault.readSonarqubeUser(projectSlug) + const user = await this.findUser(username) + let newSecret: SonarqubeUserSecret | undefined + + if (!user) { + this.logger.log(`Creating SonarQube user (login=${username})`) + const password = generateRandomPassword(30) + await this.client.createUser({ email: `${projectSlug}@${projectSlug}`, local: 'true', login: username, name: username, password }) + const token = await this.rotateToken(username) + newSecret = { SONAR_USERNAME: username, SONAR_PASSWORD: password, SONAR_TOKEN: token } + } else if (existingSecret) { + this.logger.verbose(`SonarQube user already exists with vault credentials (login=${username})`) + } else { + this.logger.warn(`SonarQube user exists but vault secret is missing, rotating token (login=${username})`) + const token = await this.rotateToken(username) + newSecret = { SONAR_USERNAME: username, SONAR_PASSWORD: 'not initialized', SONAR_TOKEN: token } + } + + if (newSecret) { + await this.vault.writeSonarqubeUser(projectSlug, newSecret) + this.logger.log(`Stored SonarQube credentials in vault (slug=${projectSlug})`) + } + } + + private async ensureProjectSonarGroups(rolePaths: SonarqubeRolePaths): Promise { + const allGroups = [ + ...rolePaths.admin, + ...rolePaths.devops, + ...rolePaths.developer, + ...rolePaths.security, + ...rolePaths.readonly, + ] + await Promise.all(allGroups.map(group => this.ensureGroup(group))) + } + + @StartActiveSpan() + private async ensureProjectRepositories(project: ProjectWithDetails, rolePaths: SonarqubeRolePaths): Promise { + const span = trace.getActiveSpan() + span?.setAttribute('project.slug', project.slug) + span?.setAttribute('repositories.count', project.repositories.length) + + const [readonlyGroupPath, securityGroupPath, existingSonarProjects] = await Promise.all([ + this.getReadonlyGroupPath(), + this.getSecurityGroupPath(), + this.findProjectsForSlug(project.slug), + ]) + + const orphans = existingSonarProjects.filter(sp => !project.repositories.some(r => r.internalRepoName === sp.repository)) + if (orphans.length) this.logger.log(`Removing ${orphans.length} orphan SonarQube repositories for project ${project.slug}`) + + await Promise.all([ + ...orphans.map(async (sp) => { + await this.client.deleteProject({ project: sp.key }) + this.logger.verbose(`Deleted orphan SonarQube repository (key=${sp.key})`) + }), + ...project.repositories.map(async (repository) => { + const projectKey = generateProjectKey(project.slug, repository.internalRepoName) + if (!existingSonarProjects.some(sp => sp.repository === repository.internalRepoName)) { + await this.client.createProject({ + project: projectKey, + visibility: 'private', + name: `${project.slug}-${repository.internalRepoName}`, + mainbranch: 'main', + }) + this.logger.log(`Created SonarQube repository (key=${projectKey})`) + } + await this.ensureProjectPermissions(projectKey, project.slug, rolePaths, readonlyGroupPath, securityGroupPath) + this.logger.verbose(`Ensured permissions on SonarQube repository (key=${projectKey})`) + }), + ]) + } + + private async ensureProjectPermissions( + projectKey: string, + login: string, + rolePaths: SonarqubeRolePaths, + readonlyGroupPath: string, + securityGroupPath: string, + ): Promise { + await Promise.all([ + ...ROBOT_PROJECT_PERMISSIONS.map(permission => + this.client.addPermissionUser({ projectKey, permission, login }), + ), + ...buildGroupPermissions(rolePaths, readonlyGroupPath, securityGroupPath).flatMap(({ groupName, permissions }) => + permissions.map(permission => this.client.addPermissionGroup({ projectKey, permission, groupName })), + ), + ]) + } + + private async ensureGroupWithGlobalPermissions(groupName: string, permissions: readonly string[]): Promise { + await this.ensureGroup(groupName) + await Promise.all(permissions.map(permission => + this.client.addPermissionGroup({ groupName, permission }), + )) + } + + private async ensureGroup(groupName: string): Promise { + const result = await this.client.searchUserGroup({ q: groupName }) + if (result.groups.some(g => g.name === groupName)) { + this.logger.verbose(`SonarQube group already exists (name=${groupName})`) + } else { + await this.client.createUserGroup({ name: groupName }) + this.logger.log(`Created SonarQube group (name=${groupName})`) + } + } + + private async rotateToken(login: string): Promise { + const name = `Sonar Token for ${login}` + await this.client.revokeUserToken({ login, name }).catch(() => {}) + const { token } = await this.client.generateUserToken({ login, name }) + this.logger.log(`Rotated SonarQube token (login=${login})`) + return token + } + + private async getAdminGroupPath(): Promise { + const config = await this.datastore.getAdminPluginConfig(SONARQUBE_PLUGIN_NAME, ADMIN_GROUP_PATH_PLUGIN_KEY) + return config ?? DEFAULT_ADMIN_GROUP_PATH + } + + private async getReadonlyGroupPath(): Promise { + const config = await this.datastore.getAdminPluginConfig(SONARQUBE_PLUGIN_NAME, READONLY_GROUP_PATH_PLUGIN_KEY) + return config ?? DEFAULT_READONLY_GROUP_PATH + } + + private async getSecurityGroupPath(): Promise { + const config = await this.datastore.getAdminPluginConfig(SONARQUBE_PLUGIN_NAME, SECURITY_GROUP_PATH_PLUGIN_KEY) + return config ?? DEFAULT_SECURITY_GROUP_PATH + } + + private async getAdminOrProjectPluginConfig(project: ProjectWithDetails, key: string): Promise { + const adminPluginConfig = await this.datastore.getAdminPluginConfig(SONARQUBE_PLUGIN_NAME, key) + if (adminPluginConfig) return adminPluginConfig + return getProjectPluginConfig(project, key) ?? undefined + } + + private async getProjectRoleGroupPaths(project: ProjectWithDetails): Promise { + const [admin, devops, developer, security, readonly] = await Promise.all([ + this.getProjectAdminGroupPaths(project), + this.getProjectDevopsGroupPaths(project), + this.getProjectDeveloperGroupPaths(project), + this.getProjectSecurityGroupPaths(project), + this.getProjectReadonlyGroupPaths(project), + ]) + return { admin, devops, developer, security, readonly } + } + + private async getProjectAdminGroupPaths(project: ProjectWithDetails): Promise { + const projectConfig = getProjectPluginConfig(project, PROJECT_ADMIN_SUFFIX_PLUGIN_KEY) + const globalConfig = await this.getAdminOrProjectPluginConfig(project, PROJECT_ADMIN_SUFFIX_PLUGIN_KEY) + const raw = projectConfig ?? globalConfig ?? DEFAULT_PROJECT_ADMIN_SUFFIX + return generateProjectRoleGroupPath(project.slug, raw) + } + + private async getProjectDevopsGroupPaths(project: ProjectWithDetails): Promise { + const projectConfig = getProjectPluginConfig(project, PROJECT_DEVOPS_SUFFIX_PLUGIN_KEY) + const globalConfig = await this.getAdminOrProjectPluginConfig(project, PROJECT_DEVOPS_SUFFIX_PLUGIN_KEY) + const raw = projectConfig ?? globalConfig ?? DEFAULT_PROJECT_DEVOPS_SUFFIX + return generateProjectRoleGroupPath(project.slug, raw) + } + + private async getProjectDeveloperGroupPaths(project: ProjectWithDetails): Promise { + const projectConfig = getProjectPluginConfig(project, PROJECT_DEVELOPER_SUFFIX_PLUGIN_KEY) + const globalConfig = await this.getAdminOrProjectPluginConfig(project, PROJECT_DEVELOPER_SUFFIX_PLUGIN_KEY) + const raw = projectConfig ?? globalConfig ?? DEFAULT_PROJECT_DEVELOPER_SUFFIX + return generateProjectRoleGroupPath(project.slug, raw) + } + + private async getProjectSecurityGroupPaths(project: ProjectWithDetails): Promise { + const projectConfig = getProjectPluginConfig(project, PROJECT_SECURITY_SUFFIX_PLUGIN_KEY) + const globalConfig = await this.getAdminOrProjectPluginConfig(project, PROJECT_SECURITY_SUFFIX_PLUGIN_KEY) + const raw = projectConfig ?? globalConfig ?? DEFAULT_PROJECT_SECURITY_SUFFIX + return generateProjectRoleGroupPath(project.slug, raw) + } + + private async getProjectReadonlyGroupPaths(project: ProjectWithDetails): Promise { + const projectConfig = getProjectPluginConfig(project, PROJECT_READONLY_SUFFIX_PLUGIN_KEY) + const globalConfig = await this.getAdminOrProjectPluginConfig(project, PROJECT_READONLY_SUFFIX_PLUGIN_KEY) + const raw = projectConfig ?? globalConfig ?? DEFAULT_PROJECT_READONLY_SUFFIX + return generateProjectRoleGroupPath(project.slug, raw) + } + + private async findUser(login: string): Promise { + let page = 1 + const pageSize = 100 + while (true) { + const response = await this.client.searchUsers({ q: login, ps: pageSize, p: page }) + const found = response.users.find(u => u.login === login) + if (found) return found + if (!response.users.length || response.paging.pageIndex * response.paging.pageSize >= response.paging.total) return undefined + page++ + } + } + + private async findProjectsForSlug(projectSlug: string): Promise { + let found: SonarqubeProjectResult[] = [] + let page = 0 + const pageSize = 100 + let total = 0 + do { + page++ + const result = await this.client.searchProject({ q: projectSlug, p: page, ps: pageSize }) + total = result.paging.total + found = [...found, ...filterProjectsOwningSlug(result.components, projectSlug)] + } while (page * pageSize < total) + return found + } +} + +function getProjectPluginConfig(project: ProjectWithDetails, key: string) { + return project.plugins?.find(p => p.key === key)?.value +} + +function generateProjectRoleGroupPath(projectSlug: string, rawGroupPathSuffixes: string): string[] { + return rawGroupPathSuffixes + .split(',') + .map(path => path.trim()) + .filter(Boolean) + .map(suffix => `/${projectSlug}${suffix}`) +} + +function buildGroupPermissions( + rolePaths: SonarqubeRolePaths, + readonlyGroupPath: string, + securityGroupPath: string, +): { groupName: string, permissions: readonly string[] }[] { + return [ + ...rolePaths.admin.map(groupName => ({ groupName, permissions: PROJECT_ADMIN_PERMISSIONS })), + ...rolePaths.devops.map(groupName => ({ groupName, permissions: PROJECT_DEVOPS_PERMISSIONS })), + ...rolePaths.developer.map(groupName => ({ groupName, permissions: PROJECT_DEVELOPER_PERMISSIONS })), + ...rolePaths.security.map(groupName => ({ groupName, permissions: PROJECT_SECURITY_PERMISSIONS })), + ...rolePaths.readonly.map(groupName => ({ groupName, permissions: PROJECT_READONLY_PERMISSIONS })), + { groupName: securityGroupPath, permissions: PROJECT_SECURITY_PERMISSIONS }, + { groupName: readonlyGroupPath, permissions: PROJECT_READONLY_PERMISSIONS }, + ] +} + +function filterProjectsOwningSlug( + components: { key: string }[], + projectSlug: string, +): SonarqubeProjectResult[] { + return components.reduce((acc, { key: sonarKey }) => { + const parts = sonarKey.split('-') + parts.pop() + for (let i = parts.length - 1; i > 0; i--) { + const project = parts.slice(0, i).join('-') + const repository = parts.slice(i).join('-') + if (sonarKey.startsWith(`${project}-${repository}-`) && project === projectSlug) { + acc.push({ projectSlug, repository, key: sonarKey }) + break + } + } + return acc + }, []) +} diff --git a/apps/server-nestjs/src/modules/sonarqube/sonarqube.utils.ts b/apps/server-nestjs/src/modules/sonarqube/sonarqube.utils.ts new file mode 100644 index 0000000000..8b8fc8adff --- /dev/null +++ b/apps/server-nestjs/src/modules/sonarqube/sonarqube.utils.ts @@ -0,0 +1,6 @@ +export function sonarProjectPropertiesFile(projectKey: string) { + return [ + `sonar.projectKey=${projectKey}`, + 'sonar.qualitygate.wait=true', + ] +} diff --git a/apps/server-nestjs/src/modules/vault/vault-client.service.ts b/apps/server-nestjs/src/modules/vault/vault-client.service.ts index 1de60fe04a..2ea913b61d 100644 --- a/apps/server-nestjs/src/modules/vault/vault-client.service.ts +++ b/apps/server-nestjs/src/modules/vault/vault-client.service.ts @@ -3,7 +3,7 @@ import { trace } from '@opentelemetry/api' import { ConfigurationService } from '../infrastructure/configuration/configuration.service' import { StartActiveSpan } from '../infrastructure/telemetry/telemetry.decorator' import { VaultError, VaultHttpClientService } from './vault-http-client.service' -import { generateGitlabMirrorCredPath, generateProjectPath, generateTechReadOnlyCredPath } from './vault.utils' +import { generateGitlabMirrorCredPath, generateProjectPath, generateSonarqubeCredPath, generateTechReadOnlyCredPath } from './vault.utils' export interface VaultSysPoliciesAclUpsertRequest { policy: string @@ -68,6 +68,12 @@ export interface VaultIdentityGroupResponse { } } +export interface SonarqubeUserSecret { + SONAR_USERNAME: string + SONAR_PASSWORD: string + SONAR_TOKEN: string +} + export interface VaultMetadata { created_time: string custom_metadata: Record | null @@ -237,6 +243,48 @@ export class VaultClientService { await this.write(creds, vaultPath) } + @StartActiveSpan() + async readSonarqubeUser(projectSlug: string): Promise | null> { + const vaultPath = generateSonarqubeCredPath(this.config.projectRootDir, projectSlug) + const span = trace.getActiveSpan() + span?.setAttributes({ + 'project.slug': projectSlug, + 'vault.kv.path': vaultPath, + }) + this.logger.verbose(`Reading Vault SonarQube user credentials (projectSlug=${projectSlug})`) + return await this.read(vaultPath).catch((error) => { + if (error instanceof VaultError && error.kind === 'NotFound') return null + throw error + }) + } + + @StartActiveSpan() + async writeSonarqubeUser(projectSlug: string, secret: SonarqubeUserSecret): Promise { + const vaultPath = generateSonarqubeCredPath(this.config.projectRootDir, projectSlug) + const span = trace.getActiveSpan() + span?.setAttributes({ + 'project.slug': projectSlug, + 'vault.kv.path': vaultPath, + }) + this.logger.verbose(`Writing Vault SonarQube user credentials (projectSlug=${projectSlug})`) + await this.write(secret, vaultPath) + } + + @StartActiveSpan() + async deleteSonarqubeUser(projectSlug: string): Promise { + const vaultPath = generateSonarqubeCredPath(this.config.projectRootDir, projectSlug) + const span = trace.getActiveSpan() + span?.setAttributes({ + 'project.slug': projectSlug, + 'vault.kv.path': vaultPath, + }) + this.logger.verbose(`Deleting Vault SonarQube user credentials (projectSlug=${projectSlug})`) + await this.delete(vaultPath).catch((error) => { + if (error instanceof VaultError && error.kind === 'NotFound') return + throw error + }) + } + @StartActiveSpan() async writeMirrorTriggerToken(secret: Record): Promise { const span = trace.getActiveSpan() diff --git a/apps/server-nestjs/src/modules/vault/vault.utils.ts b/apps/server-nestjs/src/modules/vault/vault.utils.ts index 0606286188..576da08e24 100644 --- a/apps/server-nestjs/src/modules/vault/vault.utils.ts +++ b/apps/server-nestjs/src/modules/vault/vault.utils.ts @@ -15,3 +15,9 @@ export function generateTechReadOnlyCredPath(projectRootDir: string | undefined, ? `${generateProjectPath(projectRootDir, projectSlug)}/tech/GITLAB_MIRROR` : `${projectSlug}/tech/GITLAB_MIRROR` } + +export function generateSonarqubeCredPath(projectRootDir: string | undefined, projectSlug: string) { + return projectRootDir + ? `${generateProjectPath(projectRootDir, projectSlug)}/SONAR` + : `${projectSlug}/SONAR` +} diff --git a/apps/server-nestjs/test/sonarqube.e2e-spec.ts b/apps/server-nestjs/test/sonarqube.e2e-spec.ts new file mode 100644 index 0000000000..ea061ad740 --- /dev/null +++ b/apps/server-nestjs/test/sonarqube.e2e-spec.ts @@ -0,0 +1,176 @@ +import type { TestingModule } from '@nestjs/testing' +import { generateProjectKey } from '@cpn-console/hooks' +import { faker } from '@faker-js/faker' +import { Test } from '@nestjs/testing' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { ConfigurationModule } from '../src/modules/infrastructure/configuration/configuration.module' +import { PrismaService } from '../src/modules/infrastructure/database/prisma.service' +import { InfrastructureModule } from '../src/modules/infrastructure/infrastructure.module' +import { SonarqubeClientService } from '../src/modules/sonarqube/sonarqube-client.service' +import { projectSelect } from '../src/modules/sonarqube/sonarqube-datastore.service' +import { makeProjectWithDetails } from '../src/modules/sonarqube/sonarqube-testing.utils' +import { SonarqubeModule } from '../src/modules/sonarqube/sonarqube.module' +import { SonarqubeService } from '../src/modules/sonarqube/sonarqube.service' +import { VaultClientService } from '../src/modules/vault/vault-client.service' +import { VaultModule } from '../src/modules/vault/vault.module' + +const canRunSonarqubeE2E + = Boolean(process.env.E2E) + && Boolean(process.env.SONARQUBE_URL) + && Boolean(process.env.SONAR_API_TOKEN) + && Boolean(process.env.VAULT_URL) + && Boolean(process.env.VAULT_TOKEN) + && Boolean(process.env.DB_URL) + +const describeWithSonarqube = describe.runIf(canRunSonarqubeE2E) + +describeWithSonarqube('SonarqubeService (e2e)', () => { + let moduleRef: TestingModule + let sonarqubeService: SonarqubeService + let sonarqubeClient: SonarqubeClientService + let vaultService: VaultClientService + let prisma: PrismaService + + let ownerId: string + let testProjectId: string + let testProjectSlug: string + let testRepoName: string + + beforeAll(async () => { + moduleRef = await Test.createTestingModule({ + imports: [SonarqubeModule, VaultModule, ConfigurationModule, InfrastructureModule], + }).compile() + + await moduleRef.init() + + sonarqubeService = moduleRef.get(SonarqubeService) + sonarqubeClient = moduleRef.get(SonarqubeClientService) + vaultService = moduleRef.get(VaultClientService) + prisma = moduleRef.get(PrismaService) + + ownerId = faker.string.uuid() + testProjectId = faker.string.uuid() + testProjectSlug = faker.helpers.slugify(`test-sonar-${faker.string.alphanumeric(8).toLowerCase()}`) + testRepoName = faker.helpers.slugify(`test-sonar-${faker.string.alphanumeric(8).toLowerCase()}`) + + await prisma.user.create({ + data: { + id: ownerId, + email: faker.internet.email().toLowerCase(), + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + type: 'human', + }, + }) + }) + + afterAll(async () => { + if (sonarqubeService && testProjectSlug) { + await sonarqubeService.handleDelete( + makeProjectWithDetails({ slug: testProjectSlug, repositories: [] }), + ).catch(() => {}) + } + + if (prisma) { + await prisma.repository.deleteMany({ where: { projectId: testProjectId } }).catch(() => {}) + await prisma.project.deleteMany({ where: { id: testProjectId } }).catch(() => {}) + await prisma.user.deleteMany({ where: { id: ownerId } }).catch(() => {}) + } + + await moduleRef?.close() + vi.restoreAllMocks() + vi.unstubAllEnvs() + }) + + it('should create platform groups during initialization', async () => { + // init() is triggered by moduleRef.init() via onModuleInit — groups must already exist + const [adminResult, readonlyResult, securityResult] = await Promise.all([ + sonarqubeClient.searchUserGroup({ q: '/console/admin' }), + sonarqubeClient.searchUserGroup({ q: '/console/readonly' }), + sonarqubeClient.searchUserGroup({ q: '/console/security' }), + ]) + + expect(adminResult.groups.some(g => g.name === '/console/admin')).toBe(true) + expect(readonlyResult.groups.some(g => g.name === '/console/readonly')).toBe(true) + expect(securityResult.groups.some(g => g.name === '/console/security')).toBe(true) + }) + + it('should reconcile project in SonarQube (groups, user, repository, vault secret)', async () => { + await prisma.project.create({ + data: { + id: testProjectId, + slug: testProjectSlug, + name: testProjectSlug, + ownerId, + description: 'E2E SonarQube Test Project', + hprodCpu: 0, + hprodGpu: 0, + hprodMemory: 0, + prodCpu: 0, + prodGpu: 0, + prodMemory: 0, + }, + }) + + await prisma.repository.create({ + data: { + projectId: testProjectId, + internalRepoName: testRepoName, + isPrivate: false, + }, + }) + + const project = await prisma.project.findUniqueOrThrow({ + where: { id: testProjectId }, + select: projectSelect, + }) + + await sonarqubeService.handleUpsert(project) + + // All 5 project role groups should exist in SonarQube + const projectGroupNames = [ + `/${testProjectSlug}/console/admin`, + `/${testProjectSlug}/console/devops`, + `/${testProjectSlug}/console/developer`, + `/${testProjectSlug}/console/security`, + `/${testProjectSlug}/console/readonly`, + ] + for (const groupName of projectGroupNames) { + const result = await sonarqubeClient.searchUserGroup({ q: groupName }) + expect(result.groups.some(g => g.name === groupName), `group ${groupName} should exist`).toBe(true) + } + + // Robot/CI user should exist + const usersResult = await sonarqubeClient.searchUsers({ q: testProjectSlug }) + expect(usersResult.users.some(u => u.login === testProjectSlug)).toBe(true) + + // SonarQube analysis project for the repository should exist + const projectKey = generateProjectKey(testProjectSlug, testRepoName) + const projectsResult = await sonarqubeClient.searchProject({ q: testProjectSlug }) + expect(projectsResult.components.some(p => p.key === projectKey)).toBe(true) + + // Vault credentials should be written with correct username and token + const vaultSecret = await vaultService.readSonarqubeUser(testProjectSlug) + expect(vaultSecret?.data?.SONAR_USERNAME).toBe(testProjectSlug) + expect(vaultSecret?.data?.SONAR_TOKEN).toBeTruthy() + expect(vaultSecret?.data?.SONAR_PASSWORD).toBeTruthy() + }, 30000) + + it('should delete the project from SonarQube and remove vault credentials', async () => { + const project = await prisma.project.findUniqueOrThrow({ + where: { id: testProjectId }, + select: projectSelect, + }) + + await sonarqubeService.handleDelete(project) + + // SonarQube analysis project should be removed + const projectKey = generateProjectKey(testProjectSlug, testRepoName) + const projectsResult = await sonarqubeClient.searchProject({ q: testProjectSlug }) + expect(projectsResult.components.some(p => p.key === projectKey)).toBe(false) + + // Vault credentials should be removed + const vaultSecret = await vaultService.readSonarqubeUser(testProjectSlug) + expect(vaultSecret).toBeNull() + }, 30000) +})