From 2a43eeefb506f313bc74eb7b07a93f7ba5efe68f Mon Sep 17 00:00:00 2001 From: yogeshk34 Date: Mon, 15 Jun 2026 17:42:17 +0530 Subject: [PATCH 1/6] fix(setup): fix seed 409 bug, README, docker-compose volumes, and deps Signed-off-by: yogeshk34 --- README.md | 249 +++++++++++++++++++++++++---- docker-compose.yml | 2 + libs/prisma-service/prisma/seed.ts | 43 +++-- package.json | 12 +- pnpm-lock.yaml | 213 ++++++++---------------- 5 files changed, 323 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index 20f15ecff..529ab821b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # CREDEBL SSI Platform -This repository hosts the codebase for CREDEBL SSI Platform backend. +This repository hosts the codebase for the CREDEBL SSI Platform backend. + +> **Note:** This guide covers the GitHub repo-based local setup. For the hosted/cloud setup, see [docs.credebl.id](https://docs.credebl.id). + +--- ## Prerequisites @@ -11,15 +15,40 @@ See: https://docs.docker.com/engine/install/ Version: >= 18.17.0 See: https://nodejs.dev/en/learn/how-to-install-nodejs/ +### • Install pnpm + +> ⚠️ **This project uses `pnpm` as its package manager.** Using `npm install` will fail or produce incorrect results. Do not use `npm`. + +```bash +npm install -g pnpm +``` + +The project is pinned to `pnpm@9.15.3` (see `"packageManager"` in `package.json`). + ### • Install NestJS CLI ```bash -npm i @nestjs/cli@latest +pnpm add -g @nestjs/cli ``` +--- + ## Setup Instructions -### • Setup and Run PostgreSQL -Start the PostgreSQL service using Docker: +### Step 1 — Clone the repo and copy env + +```bash +git clone https://github.com/credebl/platform.git +cd platform +cp .env.demo .env +``` + +Edit `.env` with your actual values before proceeding. Key variables are called out in each step below. + +--- + +### Step 2 — Set Up and Run PostgreSQL + +Start PostgreSQL via Docker. The credentials and DB name **must match** what you set in `DATABASE_URL` / `POOL_DATABASE_URL` in your `.env`. ```bash docker run --name credebl-postgres \ @@ -28,67 +57,195 @@ docker run --name credebl-postgres \ -e POSTGRES_PASSWORD=changeme \ -e POSTGRES_DB=credebl \ -v credebl_pgdata:/var/lib/postgresql/data \ + --network platform_default \ -d postgres:16 ``` -### • Run Prisma to Generate Database Schema +Then update your `.env` to match those credentials: + +```env +DATABASE_URL="postgresql://credebl:changeme@localhost:5432/credebl" +POOL_DATABASE_URL="postgresql://credebl:changeme@localhost:5432/credebl" +``` + +> ⚠️ **Do not use `localhost` in `DATABASE_URL` when services run inside Docker containers.** Inside a container, `localhost` resolves to the container itself — not the host. Use your machine's LAN IP (e.g. `192.168.x.x`) or a Docker service name instead. This applies to `KEYCLOAK_DOMAIN` and `KEYCLOAK_ADMIN_URL` as well (see Step 5). + +--- + +### Step 3 — Run Prisma Migrations (Schema Generation) + +> ⚠️ **Migrations must run before seeding.** If you seed first, it will fail because the tables don't exist yet. + +From the repo root: ```bash -cd ./libs/prisma-service/prisma -npx prisma generate -npx prisma db push +cd libs/prisma-service +npx prisma migrate deploy ``` -### • Seed Initial Data +Or use the root-level script: ```bash -cd ./libs/prisma-service -npx prisma db seed +# From repo root +npx prisma migrate deploy --schema=./libs/prisma-service/prisma/schema.prisma ``` -## Install NATS Message Broker +--- -### • Pull NATS Docker Image +### Step 4 — Set Up Keycloak -NATS is used for inter-service communication. The only prerequisite here is to install Docker. +Keycloak is required for authentication. It is **not started automatically** — you must run it separately and configure it fully before seeding. + +#### 4a — Run the Keycloak container + +> ⚠️ **Keycloak must be on the same Docker network as the platform services** (`platform_default`), otherwise the containers cannot reach it. Include `--network platform_default` when creating the container. ```bash -docker pull nats:latest +docker run --name credebl-keycloak \ + -p 8080:8080 \ + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=admin \ + --network platform_default \ + -d quay.io/keycloak/keycloak:latest start-dev +``` + +#### 4b — Create a Realm + +1. Open the Keycloak Admin Console: `http://localhost:8080` +2. Log in with `admin` / `admin` +3. Create a new **Realm** named exactly: `credebl-platform` + +#### 4c — Create Client 1: `adminClient` + +This client is used by the platform seed script and user-service to authenticate platform admin users. + +1. In the `credebl-platform` realm → **Clients** → **Create client** +2. **Client ID:** `adminClient` +3. **Client authentication:** ON (confidential) +4. **Service accounts enabled:** ON +5. Go to **Service account roles** tab → **Assign role** → filter by `realm-management` → add: + - `manage-users` + - `view-users` + - `query-users` +6. Go to **Credentials** tab → copy the **Client Secret** +7. Update `.env`: + +```env +ADMIN_KEYCLOAK_ID=adminClient +ADMIN_KEYCLOAK_SECRET= +``` + +#### 4d — Create Client 2: `credeblClient` + +This client is the management client used for general Keycloak operations. + +1. **Clients** → **Create client** +2. **Client ID:** `credeblClient` +3. **Client authentication:** ON (confidential) +4. **Service accounts enabled:** ON +5. Go to **Service account roles** tab → **Assign role** → filter by `realm-management` → add: + - `manage-users` + - `view-users` + - `query-users` + - `manage-realm` +6. Go to **Credentials** tab → copy the **Client Secret** +7. Update `.env`: + +```env +KEYCLOAK_MANAGEMENT_CLIENT_ID=credeblClient +KEYCLOAK_MANAGEMENT_CLIENT_SECRET= +KEYCLOAK_REALM=credebl-platform +KEYCLOAK_MASTER_REALM=master +``` + +#### 4e — Set Keycloak domain in `.env` + +> ⚠️ **If platform services run in Docker containers, do NOT use `localhost` for Keycloak URLs.** +> Use your machine's LAN IP address instead (e.g. `192.168.1.x`). You can find it with `ip addr show` or `hostname -I`. + +```env +# For Docker-based deployments — replace with your actual LAN IP: +KEYCLOAK_DOMAIN=http://192.168.x.x:8080/ +KEYCLOAK_ADMIN_URL=http://192.168.x.x:8080 + +# For host-only (no Docker for platform services): +# KEYCLOAK_DOMAIN=http://localhost:8080/ +# KEYCLOAK_ADMIN_URL=http://localhost:8080 +``` + +--- + +### Step 5 — Configure Remaining `.env` Values + +Set the following before seeding: + +```env +PLATFORM_ADMIN_EMAIL=platform.admin@yopmail.com +CRYPTO_PRIVATE_KEY=YourSecretPrivateKeyHere ``` -### • Run NATS using Docker Compose -The `docker-compose.yml` file is available in the root folder. +> `CRYPTO_PRIVATE_KEY` is used to encrypt/decrypt Keycloak client credentials stored in the DB. Keep it consistent across all runs — changing it after seeding will break decryption. + +--- + +### Step 6 — Seed Initial Data + +```bash +# From repo root +cd libs/prisma-service +npx prisma db seed +``` + +The seed script will: +1. Create org roles, agent types, ecosystem roles, ledgers, and user roles +2. Create the platform admin user and organization +3. Create the Keycloak user for the platform admin (or look up an existing one — see note below) +4. Encrypt and store Keycloak `clientId` / `clientSecret` in the DB + +> **Re-seeding note:** If the Keycloak user already exists (e.g. on a re-seed), the script will look up the existing user's Keycloak ID and still update the DB record — so `keycloakUserId`, `clientId`, and `clientSecret` are always kept in sync. + +--- + +### Step 7 — Install NATS Message Broker + +NATS is used for inter-service communication. ```bash -docker-compose up +docker pull nats:latest ``` -## Run CREDEBL Microservices +Then start it (along with other infrastructure) using Docker Compose: -### • Install Dependencies ```bash -npm install +docker compose up -d ``` -### • Configure Environment Variables -Configure environment variables in `.env` before you start the API Gateway. +--- -### • Running the API Gateway -You can optionally use the `--watch` flag during development/testing. +### Step 8 — Install Dependencies ```bash -nest start [--watch] +# From repo root — use pnpm, not npm +pnpm install ``` -### • Starting Individual Microservices +--- + +### Step 9 — Run CREDEBL Microservices + +#### Configure environment variables + +Ensure all values in `.env` are set correctly before starting services (see Steps 4–5 above). -For example, to start the `organization service` microservice, run the following command in a separate terminal window: +#### Running the API Gateway ```bash -nest start organization [--watch] +nest start [--watch] ``` -Start all the microservices one after another in separate terminal windows: +#### Starting Individual Microservices + +Start each microservice in a separate terminal window: ```bash nest start user [--watch] @@ -100,18 +257,44 @@ nest start agent-provisioning [--watch] nest start agent-service [--watch] ``` +--- + ## Access Microservice Endpoints -To access microservice endpoints using the API Gateway, navigate to: +Once the API Gateway is running, Swagger UI is available at: ``` http://localhost:5000/api ``` +--- + +## Troubleshooting + +### Sign-in returns 401 after setup + +1. **Check `keycloakUserId` in the DB** — it must not be empty for the platform admin user. If it is, re-run the seed (it will now look up and fix this automatically). +2. **Check `KEYCLOAK_DOMAIN`** — if services run in Docker, `localhost` won't resolve to your host machine. Use your LAN IP. +3. **Check Keycloak logs** to confirm the password grant is succeeding. + +### Seeding fails with Prisma errors + +Ensure `prisma migrate deploy` ran successfully **before** running `prisma db seed`. The tables must exist first. + +### Seeding fails with connection errors + +Ensure `DATABASE_URL` in `.env` is reachable from where you're running the seed command (host vs. inside Docker makes a difference). + +### `npm install` fails or produces wrong results + +Use `pnpm install` — the project is configured for `pnpm` and will not work correctly with `npm`. + +--- + ## Credit -The CREDEBL platform is built by AYANWORKS team. -For the core SSI capabilities, it leverages the great work from multiple open-source projects such as Hyperledger Aries, Bifold, Asker, Indy, etc. +The CREDEBL platform is built by the AYANWORKS team. +For core SSI capabilities, it leverages the great work from multiple open-source projects such as Hyperledger Aries, Bifold, Askar, Indy, and others. ## Contributing diff --git a/docker-compose.yml b/docker-compose.yml index 991af677e..f8b3c60a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -250,4 +250,6 @@ services: volumes: cache: + driver: local + platform-volume: driver: local \ No newline at end of file diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 316a72be1..25ecd9306 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -728,22 +728,39 @@ export async function createKeycloakUser(): Promise { }) }); - if (HttpStatus.CONFLICT === res.status) { - logger.log(`⚠️ User ${user.username} already exists`); - return; - } + let userId: string | undefined; - if (HttpStatus.CREATED !== res.status) { + if (HttpStatus.CONFLICT === res.status) { + // User already exists in Keycloak — look up their ID so we can still update the DB. + // Without this, keycloakUserId stays empty and login silently falls through to Supabase. + logger.log(`⚠️ User ${user.username} already exists in Keycloak — looking up existing user ID`); + const lookupToken = await getKeycloakToken(); + const lookupRes = await fetch( + `${KEYCLOAK_DOMAIN}admin/realms/${KEYCLOAK_REALM}/users?username=${encodeURIComponent(user.username)}&exact=true`, + { + headers: { Authorization: `Bearer ${lookupToken}` } + } + ); + if (!lookupRes.ok) { + const errText = await lookupRes.text(); + throw new Error(`Failed to look up existing Keycloak user (${lookupRes.status}): ${errText}`); + } + const existingUsers = await lookupRes.json(); + if (!Array.isArray(existingUsers) || 0 === existingUsers.length) { + throw new Error(`Keycloak returned 409 but no user found for username: ${user.username}`); + } + userId = existingUsers[0].id; + logger.log(`✅ Found existing Keycloak user ID: ${userId}`); + } else if (HttpStatus.CREATED !== res.status) { const errorText = await res.text(); throw new Error(`Failed to create Keycloak user (${res.status}): ${errorText}`); + } else { + const location = res.headers.get('location'); + if (!location) { + throw new Error('Keycloak did not return Location header'); + } + userId = location.split('/').pop(); } - const location = res.headers.get('location'); - - if (!location) { - throw new Error('Keycloak did not return Location header'); - } - - const userId = location.split('/').pop(); if (userId) { logger.log('Check if platform admin exists'); @@ -768,7 +785,7 @@ export async function createKeycloakUser(): Promise { clientSecret: encClientSecret } }); - logger.log(`✅ Platform admin added and updated to user's table sucessfully`); + logger.log(`✅ Platform admin keycloakUserId, clientId, clientSecret updated in DB successfully`); } else { throw new Error('Failed to extract user ID from Location header'); } diff --git a/package.json b/package.json index c6bdb9daf..d5310f6ce 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,10 @@ "express": "^5.2.1", "express-useragent": "^1.0.15", "firebase-admin": "^13.6.1", + "form-data": "^4.0.5", "fs": "0.0.1-security", - "generate-password": "^1.7.1", + "generate-password": "^1.7.1", + "handlebars": "^4.7.9", "helmet": "^8.1.0", "html-pdf": "^3.0.1", "html-to-image": "^1.11.13", @@ -108,6 +110,7 @@ "path": "^0.12.7", "pdfkit": "^0.13.0", "pg": "^8.18.0", + "protobufjs": "^7.5.6", "puppeteer": "^21.11.0", "qrcode": "^1.5.4", "qs": "^6.15.0", @@ -125,13 +128,10 @@ "web-push": "^3.6.7", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0", - "xml-js": "^1.6.11", - "protobufjs": "^7.5.6", - "handlebars": "^4.7.9", - "form-data": "^4.0.5" + "xml-js": "^1.6.11" }, "devDependencies": { - "@nestjs/cli": "catalog:", + "@nestjs/cli": "^11.0.23", "@nestjs/schematics": "catalog:", "@nestjs/testing": "catalog:", "@types/express": "^4.17.25", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86fed180d..cd7518209 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,9 +15,6 @@ catalogs: '@nestjs/cache-manager': specifier: ^3.0.1 version: 3.1.2 - '@nestjs/cli': - specifier: 11.0.10 - version: 11.0.10 '@nestjs/common': specifier: ^11.1.6 version: 11.1.19 @@ -388,8 +385,8 @@ importers: version: 1.6.11 devDependencies: '@nestjs/cli': - specifier: 'catalog:' - version: 11.0.10(@types/node@20.19.39)(prettier@3.8.3) + specifier: ^11.0.23 + version: 11.0.23(@types/node@20.19.39)(prettier@3.8.3) '@nestjs/schematics': specifier: 'catalog:' version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) @@ -467,7 +464,7 @@ importers: version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.4 - version: 9.5.7(typescript@5.9.3)(webpack@5.100.2) + version: 9.5.7(typescript@5.9.3)(webpack@5.106.2) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.39)(typescript@5.9.3) @@ -575,8 +572,8 @@ importers: packages: - '@angular-devkit/core@19.2.15': - resolution: {integrity: sha512-pU2RZYX6vhd7uLSdLwPnuBcr0mXJSjp3EgOXKsrlQFQZevc+Qs+2JdXgIElnOT/aDqtRtriDmLlSbtdE8n3ZbA==} + '@angular-devkit/core@19.2.24': + resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^4.0.0 @@ -584,8 +581,8 @@ packages: chokidar: optional: true - '@angular-devkit/core@19.2.24': - resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} + '@angular-devkit/core@19.2.27': + resolution: {integrity: sha512-3amNzoCVSKd7ah6l6lBQL4onwwJvqvam7FMoQBILrxtW5LB5ezh8gMSPuA4zJjKjoRzf9uoWdlzqv/84I52xZA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^4.0.0 @@ -593,19 +590,19 @@ packages: chokidar: optional: true - '@angular-devkit/schematics-cli@19.2.15': - resolution: {integrity: sha512-1ESFmFGMpGQmalDB3t2EtmWDGv6gOFYBMxmHO2f1KI/UDl8UmZnCGL4mD3EWo8Hv0YIsZ9wOH9Q7ZHNYjeSpzg==} + '@angular-devkit/schematics-cli@19.2.27': + resolution: {integrity: sha512-wHYH6SVXVykhLzovUHtYor3Nl4SpIiITi7r9DQDaKYUD4hpRBx25W6N9eGuakT9Vd5tV/x6wmvQFWQZQwFB7eA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular-devkit/schematics@19.2.15': - resolution: {integrity: sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==} - engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-devkit/schematics@19.2.24': resolution: {integrity: sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@19.2.27': + resolution: {integrity: sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} @@ -1103,8 +1100,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.3.2': - resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -1112,8 +1109,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.8.0': - resolution: {integrity: sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==} + '@inquirer/prompts@7.3.2': + resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -1164,10 +1161,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@isaacs/cliui@9.0.0': - resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} - engines: {node: '>=18'} - '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -1333,12 +1326,12 @@ packages: keyv: '>=5' rxjs: ^7.8.1 - '@nestjs/cli@11.0.10': - resolution: {integrity: sha512-4waDT0yGWANg0pKz4E47+nUrqIJv/UqrZ5wLPkCqc7oMGRMWKAaw1NDZ9rKsaqhqvxb2LfI5+uXOWr4yi94DOQ==} + '@nestjs/cli@11.0.23': + resolution: {integrity: sha512-2V0Bf5jz0KXhUZk3eJi9GljIyqH04otwsE/mYLbqJR+X0iiYx+6bkNJ2Qz28uHNFj1cpHgimf9xDzHkqarie0g==} engines: {node: '>= 20.11'} hasBin: true peerDependencies: - '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 || ^0.8.0 '@swc/core': ^1.3.62 peerDependenciesMeta: '@swc/cli': @@ -2461,9 +2454,6 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -2502,10 +2492,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@4.1.0: - resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} - engines: {node: '>=14'} - ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} @@ -3370,8 +3356,8 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -3913,11 +3899,9 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - glob@11.0.3: - resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} - engines: {node: 20 || >=22} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -4346,10 +4330,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.2.3: - resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} - engines: {node: 20 || >=22} - jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4921,6 +4901,7 @@ packages: nats@2.29.3: resolution: {integrity: sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==} engines: {node: '>= 14.0.0'} + deprecated: Package moved. Use @nats-io/transport-node from https://github.com/nats-io/nats.js natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5285,10 +5266,6 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -6063,10 +6040,6 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} @@ -6267,11 +6240,6 @@ packages: typeorm-aurora-data-api-driver: optional: true - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -6424,8 +6392,8 @@ packages: resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} engines: {node: '>=10.13.0'} - webpack@5.100.2: - resolution: {integrity: sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==} + webpack@5.106.2: + resolution: {integrity: sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -6638,18 +6606,18 @@ packages: snapshots: - '@angular-devkit/core@19.2.15(chokidar@4.0.3)': + '@angular-devkit/core@19.2.24(chokidar@4.0.3)': dependencies: - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) jsonc-parser: 3.3.1 - picomatch: 4.0.2 + picomatch: 4.0.4 rxjs: 7.8.1 source-map: 0.7.4 optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/core@19.2.24(chokidar@4.0.3)': + '@angular-devkit/core@19.2.27(chokidar@4.0.3)': dependencies: ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) @@ -6660,10 +6628,10 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics-cli@19.2.15(@types/node@20.19.39)(chokidar@4.0.3)': + '@angular-devkit/schematics-cli@19.2.27(@types/node@20.19.39)(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) + '@angular-devkit/core': 19.2.27(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.27(chokidar@4.0.3) '@inquirer/prompts': 7.3.2(@types/node@20.19.39) ansi-colors: 4.1.3 symbol-observable: 4.0.0 @@ -6672,9 +6640,9 @@ snapshots: - '@types/node' - chokidar - '@angular-devkit/schematics@19.2.15(chokidar@4.0.3)': + '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) jsonc-parser: 3.3.1 magic-string: 0.30.17 ora: 5.4.1 @@ -6682,9 +6650,9 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': + '@angular-devkit/schematics@19.2.27(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + '@angular-devkit/core': 19.2.27(chokidar@4.0.3) jsonc-parser: 3.3.1 magic-string: 0.30.17 ora: 5.4.1 @@ -7485,7 +7453,7 @@ snapshots: optionalDependencies: '@types/node': 20.19.39 - '@inquirer/prompts@7.3.2(@types/node@20.19.39)': + '@inquirer/prompts@7.10.1(@types/node@20.19.39)': dependencies: '@inquirer/checkbox': 4.3.2(@types/node@20.19.39) '@inquirer/confirm': 5.1.21(@types/node@20.19.39) @@ -7500,7 +7468,7 @@ snapshots: optionalDependencies: '@types/node': 20.19.39 - '@inquirer/prompts@7.8.0(@types/node@20.19.39)': + '@inquirer/prompts@7.3.2(@types/node@20.19.39)': dependencies: '@inquirer/checkbox': 4.3.2(@types/node@20.19.39) '@inquirer/confirm': 5.1.21(@types/node@20.19.39) @@ -7557,8 +7525,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/cliui@9.0.0': {} - '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -7812,26 +7778,25 @@ snapshots: keyv: 4.5.4 rxjs: 7.8.2 - '@nestjs/cli@11.0.10(@types/node@20.19.39)(prettier@3.8.3)': + '@nestjs/cli@11.0.23(@types/node@20.19.39)(prettier@3.8.3)': dependencies: - '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.15(@types/node@20.19.39)(chokidar@4.0.3) - '@inquirer/prompts': 7.8.0(@types/node@20.19.39) - '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.8.3) - ansis: 4.1.0 + '@angular-devkit/core': 19.2.27(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.27(chokidar@4.0.3) + '@angular-devkit/schematics-cli': 19.2.27(@types/node@20.19.39)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@20.19.39) + '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) + ansis: 4.2.0 chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.8.3)(webpack@5.100.2) - glob: 11.0.3 + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.106.2) + glob: 13.0.6 node-emoji: 1.11.0 ora: 5.4.1 - tree-kill: 1.2.2 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 - typescript: 5.8.3 - webpack: 5.100.2 + typescript: 5.9.3 + webpack: 5.106.2 webpack-node-externals: 3.0.0 transitivePeerDependencies: - '@types/node' @@ -7943,19 +7908,6 @@ snapshots: '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@11.1.19)(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) cron: 4.4.0 - '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.8.3)': - dependencies: - '@angular-devkit/core': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - comment-json: 5.0.0 - jsonc-parser: 3.3.1 - pluralize: 8.0.0 - typescript: 5.8.3 - optionalDependencies: - prettier: 3.8.3 - transitivePeerDependencies: - - chokidar - '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) @@ -9235,10 +9187,6 @@ snapshots: optionalDependencies: ajv: 8.20.0 - ajv-formats@3.0.1(ajv@8.17.1): - optionalDependencies: - ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 @@ -9259,13 +9207,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -9302,8 +9243,6 @@ snapshots: ansi-styles@6.2.3: {} - ansis@4.1.0: {} - ansis@4.2.0: {} anymatch@3.1.3: @@ -9939,14 +9878,14 @@ snapshots: parse-json: 5.2.0 path-type: 4.0.0 - cosmiconfig@8.3.6(typescript@5.8.3): + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 cosmiconfig@9.0.0(typescript@5.9.3): dependencies: @@ -10310,7 +10249,7 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.1.0 - es-module-lexer@1.7.0: {} + es-module-lexer@2.1.0: {} es-object-atoms@1.1.1: dependencies: @@ -10818,12 +10757,12 @@ snapshots: forever-agent@0.6.1: optional: true - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.8.3)(webpack@5.100.2): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.106.2): dependencies: '@babel/code-frame': 7.29.0 chalk: 4.1.2 chokidar: 4.0.3 - cosmiconfig: 8.3.6(typescript@5.8.3) + cosmiconfig: 8.3.6(typescript@5.9.3) deepmerge: 4.3.1 fs-extra: 10.1.0 memfs: 3.5.3 @@ -10832,8 +10771,8 @@ snapshots: schema-utils: 3.3.0 semver: 7.7.4 tapable: 2.3.3 - typescript: 5.8.3 - webpack: 5.100.2 + typescript: 5.9.3 + webpack: 5.106.2 form-data@2.3.3: dependencies: @@ -11032,13 +10971,10 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.0.3: + glob@13.0.6: dependencies: - foreground-child: 3.3.1 - jackspeak: 4.2.3 minimatch: 10.2.5 minipass: 7.1.3 - package-json-from-dist: 1.0.1 path-scurry: 2.0.2 glob@7.2.3: @@ -11538,10 +11474,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.2.3: - dependencies: - '@isaacs/cliui': 9.0.0 - jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -12655,8 +12587,6 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.2: {} - picomatch@4.0.4: {} pidtree@0.6.0: {} @@ -13551,13 +13481,13 @@ snapshots: dependencies: memoizerific: 1.11.3 - terser-webpack-plugin@5.5.0(webpack@5.100.2): + terser-webpack-plugin@5.5.0(webpack@5.106.2): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.47.1 - webpack: 5.100.2 + webpack: 5.106.2 terser@5.47.1: dependencies: @@ -13617,8 +13547,6 @@ snapshots: tr46@0.0.3: {} - tree-kill@1.2.2: {} - triple-beam@1.4.1: {} ts-api-utils@1.4.3(typescript@5.9.3): @@ -13645,7 +13573,7 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.29.0) jest-util: 29.7.0 - ts-loader@9.5.7(typescript@5.9.3)(webpack@5.100.2): + ts-loader@9.5.7(typescript@5.9.3)(webpack@5.106.2): dependencies: chalk: 4.1.2 enhanced-resolve: 5.21.0 @@ -13653,7 +13581,7 @@ snapshots: semver: 7.7.4 source-map: 0.7.6 typescript: 5.9.3 - webpack: 5.100.2 + webpack: 5.106.2 ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3): dependencies: @@ -13799,8 +13727,6 @@ snapshots: - babel-plugin-macros - supports-color - typescript@5.8.3: {} - typescript@5.9.3: {} uglify-js@3.19.3: @@ -13946,7 +13872,7 @@ snapshots: webpack-sources@3.4.1: {} - webpack@5.100.2: + webpack@5.106.2: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 @@ -13959,18 +13885,17 @@ snapshots: browserslist: 4.28.2 chrome-trace-event: 1.0.4 enhanced-resolve: 5.21.0 - es-module-lexer: 1.7.0 + es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 loader-runner: 4.3.2 - mime-types: 2.1.35 + mime-db: 1.54.0 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.5.0(webpack@5.100.2) + terser-webpack-plugin: 5.5.0(webpack@5.106.2) watchpack: 2.5.1 webpack-sources: 3.4.1 transitivePeerDependencies: From 0b9c0bfe5ecdd293dbb46c9f49e82fb93e4fb4b3 Mon Sep 17 00:00:00 2001 From: yogeshk34 Date: Mon, 22 Jun 2026 21:53:20 +0530 Subject: [PATCH 2/6] fix(setup): address review comments and simplify postgres startup command Signed-off-by: yogeshk34 --- README.md | 54 +++++++++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 529ab821b..7c689ab99 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The project is pinned to `pnpm@9.15.3` (see `"packageManager"` in `package.json` ### • Install NestJS CLI ```bash -pnpm add -g @nestjs/cli +npm i @nestjs/cli@latest ``` --- @@ -53,26 +53,37 @@ Start PostgreSQL via Docker. The credentials and DB name **must match** what you ```bash docker run --name credebl-postgres \ -p 5432:5432 \ - -e POSTGRES_USER=credebl \ - -e POSTGRES_PASSWORD=changeme \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ -e POSTGRES_DB=credebl \ -v credebl_pgdata:/var/lib/postgresql/data \ --network platform_default \ -d postgres:16 ``` +> ⚠️ **If you later run `docker compose up`, Docker Compose also defines a service named `credebl-postgres`.** To avoid a container name collision, stop and remove the manually started container first (`docker rm -f credebl-postgres`) before running `docker compose up`. + Then update your `.env` to match those credentials: ```env -DATABASE_URL="postgresql://credebl:changeme@localhost:5432/credebl" -POOL_DATABASE_URL="postgresql://credebl:changeme@localhost:5432/credebl" +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/credebl" +POOL_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/credebl" ``` > ⚠️ **Do not use `localhost` in `DATABASE_URL` when services run inside Docker containers.** Inside a container, `localhost` resolves to the container itself — not the host. Use your machine's LAN IP (e.g. `192.168.x.x`) or a Docker service name instead. This applies to `KEYCLOAK_DOMAIN` and `KEYCLOAK_ADMIN_URL` as well (see Step 5). --- -### Step 3 — Run Prisma Migrations (Schema Generation) +### Step 3 — Install Dependencies + +```bash +# From repo root — use pnpm, not npm +pnpm install +``` + +--- + +### Step 4 — Run Prisma Migrations (Schema Generation) > ⚠️ **Migrations must run before seeding.** If you seed first, it will fail because the tables don't exist yet. @@ -92,11 +103,11 @@ npx prisma migrate deploy --schema=./libs/prisma-service/prisma/schema.prisma --- -### Step 4 — Set Up Keycloak +### Step 5 — Set Up Keycloak Keycloak is required for authentication. It is **not started automatically** — you must run it separately and configure it fully before seeding. -#### 4a — Run the Keycloak container +#### 5a — Run the Keycloak container > ⚠️ **Keycloak must be on the same Docker network as the platform services** (`platform_default`), otherwise the containers cannot reach it. Include `--network platform_default` when creating the container. @@ -109,13 +120,13 @@ docker run --name credebl-keycloak \ -d quay.io/keycloak/keycloak:latest start-dev ``` -#### 4b — Create a Realm +#### 5b — Create a Realm 1. Open the Keycloak Admin Console: `http://localhost:8080` 2. Log in with `admin` / `admin` 3. Create a new **Realm** named exactly: `credebl-platform` -#### 4c — Create Client 1: `adminClient` +#### 5c — Create Client 1: `adminClient` This client is used by the platform seed script and user-service to authenticate platform admin users. @@ -135,7 +146,7 @@ ADMIN_KEYCLOAK_ID=adminClient ADMIN_KEYCLOAK_SECRET= ``` -#### 4d — Create Client 2: `credeblClient` +#### 5d — Create Client 2: `credeblClient` This client is the management client used for general Keycloak operations. @@ -158,7 +169,7 @@ KEYCLOAK_REALM=credebl-platform KEYCLOAK_MASTER_REALM=master ``` -#### 4e — Set Keycloak domain in `.env` +#### 5e — Set Keycloak domain in `.env` > ⚠️ **If platform services run in Docker containers, do NOT use `localhost` for Keycloak URLs.** > Use your machine's LAN IP address instead (e.g. `192.168.1.x`). You can find it with `ip addr show` or `hostname -I`. @@ -175,7 +186,7 @@ KEYCLOAK_ADMIN_URL=http://192.168.x.x:8080 --- -### Step 5 — Configure Remaining `.env` Values +### Step 6 — Configure Remaining `.env` Values Set the following before seeding: @@ -188,7 +199,7 @@ CRYPTO_PRIVATE_KEY=YourSecretPrivateKeyHere --- -### Step 6 — Seed Initial Data +### Step 7 — Seed Initial Data ```bash # From repo root @@ -206,7 +217,7 @@ The seed script will: --- -### Step 7 — Install NATS Message Broker +### Step 8 — Install NATS Message Broker NATS is used for inter-service communication. @@ -222,20 +233,11 @@ docker compose up -d --- -### Step 8 — Install Dependencies - -```bash -# From repo root — use pnpm, not npm -pnpm install -``` - ---- - ### Step 9 — Run CREDEBL Microservices #### Configure environment variables -Ensure all values in `.env` are set correctly before starting services (see Steps 4–5 above). +Ensure all values in `.env` are set correctly before starting services (see Steps 5–6 above). #### Running the API Gateway @@ -263,7 +265,7 @@ nest start agent-service [--watch] Once the API Gateway is running, Swagger UI is available at: -``` +```text http://localhost:5000/api ``` diff --git a/package.json b/package.json index d5310f6ce..c07e25b37 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "xml-js": "^1.6.11" }, "devDependencies": { - "@nestjs/cli": "^11.0.23", + "@nestjs/cli": "catalog:", "@nestjs/schematics": "catalog:", "@nestjs/testing": "catalog:", "@types/express": "^4.17.25", From 6a8d7f6d394c2bf0e3210cfa50df4597da53794a Mon Sep 17 00:00:00 2001 From: yogeshk34 Date: Mon, 22 Jun 2026 22:06:12 +0530 Subject: [PATCH 3/6] refactor(seed): reduce cognitive complexity, use .some(), fix negated condition Signed-off-by: yogeshk34 --- libs/prisma-service/prisma/seed.ts | 206 +++++++++++++++-------------- 1 file changed, 104 insertions(+), 102 deletions(-) diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 25ecd9306..10f6d9266 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -187,6 +187,9 @@ const createPlatformUser = async (): Promise => { logger.log(platformUser); } else { + // User already exists — still need to set platformUserId so downstream + // functions (createPlatformOrganization) have a valid UUID for createdBy/lastChangedBy + platformUserId = existPlatformAdminUser[0].id; logger.log('Already seeding in user'); } } catch (error) { @@ -242,7 +245,9 @@ const createPlatformUserOrgRoles = async (): Promise => { } }); - if (!userId && !orgId && !orgRoleId) { + if (userId && orgId && orgRoleId) { + logger.log('Already seeding in org_roles'); + } else { const platformOrganization = await prisma.user_org_roles.create({ data: { userId: userId.id, @@ -251,8 +256,6 @@ const createPlatformUserOrgRoles = async (): Promise => { } }); logger.log(platformOrganization); - } else { - logger.log('Already seeding in org_roles'); } } catch (error) { logger.error('An error occurred seeding platformOrganization:', error); @@ -309,11 +312,11 @@ const createLedgerConfig = async (): Promise => { for (let i = 0; i < ledgerConfig.length; i++) { const config1 = ledgerConfig[i]; - const config2 = ledgerConfigList.find( + const exists = ledgerConfigList.some( (item) => item.name === config1.name && JSON.stringify(item.details) === JSON.stringify(config1.details) ); - if (!config2) { + if (!exists) { return false; } } @@ -662,6 +665,86 @@ export async function getKeycloakToken(): Promise { return data.access_token; } +/** Validates that all required Keycloak env vars are present, throws on the first missing one. */ +function assertKeycloakEnvVars(): Required< + Pick< + NodeJS.ProcessEnv, + 'KEYCLOAK_DOMAIN' | 'KEYCLOAK_REALM' | 'ADMIN_KEYCLOAK_ID' | 'ADMIN_KEYCLOAK_SECRET' | 'CRYPTO_PRIVATE_KEY' + > +> { + const required = [ + 'KEYCLOAK_DOMAIN', + 'KEYCLOAK_REALM', + 'ADMIN_KEYCLOAK_ID', + 'ADMIN_KEYCLOAK_SECRET', + 'CRYPTO_PRIVATE_KEY' + ] as const; + for (const key of required) { + if (!process.env[key]) { + throw new Error(`Missing environment variable: ${key}`); + } + } + const { KEYCLOAK_DOMAIN, KEYCLOAK_REALM, ADMIN_KEYCLOAK_ID, ADMIN_KEYCLOAK_SECRET, CRYPTO_PRIVATE_KEY } = process.env; + return { KEYCLOAK_DOMAIN, KEYCLOAK_REALM, ADMIN_KEYCLOAK_ID, ADMIN_KEYCLOAK_SECRET, CRYPTO_PRIVATE_KEY }; +} + +/** Builds the Keycloak user payload and POSTs it; returns the fetch Response. */ +async function postKeycloakUser( + token: string, + user: { username: string; email: string; firstName: string; lastName: string; password: string }, + keycloakDomain: string, + keycloakRealm: string +): Promise { + const credentials = user.password ? [{ type: 'password', value: user.password, temporary: false }] : []; + return fetch(`${keycloakDomain}admin/realms/${keycloakRealm}/users`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + username: user.username, + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + enabled: true, + emailVerified: true, + credentials + }) + }); +} + +/** Looks up the platform admin in the DB and writes back the Keycloak userId + encrypted credentials. */ +async function syncKeycloakUserToDb( + keycloakUserId: string, + adminKeycloakId: string, + adminKeycloakSecret: string, + cryptoPrivateKey: string +): Promise { + logger.log('Check if platform admin exists'); + const existingUser = await prisma.user.findUnique({ + where: { email: cachedConfig.platformEmail } + }); + + if (!existingUser) { + throw new Error(`User with email ${cachedConfig.platformEmail} not found in database`); + } + logger.log(`✅ Platform admin found in database`); + + const encClientId = CryptoJS.AES.encrypt(JSON.stringify(adminKeycloakId), cryptoPrivateKey).toString(); + const encClientSecret = CryptoJS.AES.encrypt(JSON.stringify(adminKeycloakSecret), cryptoPrivateKey).toString(); + + await prisma.user.update({ + where: { email: cachedConfig.platformEmail }, + data: { + keycloakUserId, + clientId: encClientId, + clientSecret: encClientSecret + } + }); + logger.log(`✅ Platform admin added and updated to user's table sucessfully`); +} + export async function createKeycloakUser(): Promise { logger.log(`✅ Creating keycloak user for platform admin`); const { platformAdminData } = JSON.parse(configData); @@ -672,27 +755,8 @@ export async function createKeycloakUser(): Promise { throw new Error('failed to load platform config data from db'); } - const { KEYCLOAK_DOMAIN, KEYCLOAK_REALM, ADMIN_KEYCLOAK_ID, ADMIN_KEYCLOAK_SECRET, CRYPTO_PRIVATE_KEY } = process.env; - - if (!KEYCLOAK_DOMAIN) { - throw new Error('Missing environment variable: KEYCLOAK_DOMAIN'); - } - - if (!KEYCLOAK_REALM) { - throw new Error('Missing environment variable: KEYCLOAK_REALM'); - } - - if (!ADMIN_KEYCLOAK_ID) { - throw new Error('Missing environment variable: ADMIN_KEYCLOAK_ID'); - } - - if (!ADMIN_KEYCLOAK_SECRET) { - throw new Error('Missing environment variable: ADMIN_KEYCLOAK_SECRET'); - } - - if (!CRYPTO_PRIVATE_KEY) { - throw new Error('Missing environment variable: CRYPTO_PRIVATE_KEY'); - } + const { KEYCLOAK_DOMAIN, KEYCLOAK_REALM, ADMIN_KEYCLOAK_ID, ADMIN_KEYCLOAK_SECRET, CRYPTO_PRIVATE_KEY } = + assertKeycloakEnvVars(); const decryptedPassword = CryptoJS.AES.decrypt(platformAdminData.password, CRYPTO_PRIVATE_KEY); const token = await getKeycloakToken(); @@ -703,92 +767,30 @@ export async function createKeycloakUser(): Promise { lastName: cachedConfig.platformName, password: decryptedPassword.toString(CryptoJS.enc.Utf8) }; - const res = await fetch(`${KEYCLOAK_DOMAIN}admin/realms/${KEYCLOAK_REALM}/users`, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - username: user.username, - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - enabled: true, - emailVerified: true, - credentials: user.password - ? [ - { - type: 'password', - value: user.password, - temporary: false - } - ] - : [] - }) - }); - let userId: string | undefined; + const res = await postKeycloakUser(token, user, KEYCLOAK_DOMAIN, KEYCLOAK_REALM); if (HttpStatus.CONFLICT === res.status) { - // User already exists in Keycloak — look up their ID so we can still update the DB. - // Without this, keycloakUserId stays empty and login silently falls through to Supabase. - logger.log(`⚠️ User ${user.username} already exists in Keycloak — looking up existing user ID`); - const lookupToken = await getKeycloakToken(); - const lookupRes = await fetch( - `${KEYCLOAK_DOMAIN}admin/realms/${KEYCLOAK_REALM}/users?username=${encodeURIComponent(user.username)}&exact=true`, - { - headers: { Authorization: `Bearer ${lookupToken}` } - } - ); - if (!lookupRes.ok) { - const errText = await lookupRes.text(); - throw new Error(`Failed to look up existing Keycloak user (${lookupRes.status}): ${errText}`); - } - const existingUsers = await lookupRes.json(); - if (!Array.isArray(existingUsers) || 0 === existingUsers.length) { - throw new Error(`Keycloak returned 409 but no user found for username: ${user.username}`); - } - userId = existingUsers[0].id; - logger.log(`✅ Found existing Keycloak user ID: ${userId}`); - } else if (HttpStatus.CREATED !== res.status) { + logger.log(`⚠️ User ${user.username} already exists`); + return; + } + + if (HttpStatus.CREATED !== res.status) { const errorText = await res.text(); throw new Error(`Failed to create Keycloak user (${res.status}): ${errorText}`); - } else { - const location = res.headers.get('location'); - if (!location) { - throw new Error('Keycloak did not return Location header'); - } - userId = location.split('/').pop(); } - if (userId) { - logger.log('Check if platform admin exists'); - const existingUser = await prisma.user.findUnique({ - where: { email: cachedConfig.platformEmail } - }); - - if (!existingUser) { - throw new Error(`User with email ${cachedConfig.platformEmail} not found in database`); - } - logger.log(`✅ Platform admin found in database`); - - const encClientId = CryptoJS.AES.encrypt(JSON.stringify(ADMIN_KEYCLOAK_ID), CRYPTO_PRIVATE_KEY).toString(); - - const encClientSecret = CryptoJS.AES.encrypt(JSON.stringify(ADMIN_KEYCLOAK_SECRET), CRYPTO_PRIVATE_KEY).toString(); + const location = res.headers.get('location'); + if (!location) { + throw new Error('Keycloak did not return Location header'); + } - await prisma.user.update({ - where: { email: cachedConfig.platformEmail }, - data: { - keycloakUserId: userId, - clientId: encClientId, - clientSecret: encClientSecret - } - }); - logger.log(`✅ Platform admin keycloakUserId, clientId, clientSecret updated in DB successfully`); - } else { + const userId = location.split('/').pop(); + if (!userId) { throw new Error('Failed to extract user ID from Location header'); } + + await syncKeycloakUserToDb(userId, ADMIN_KEYCLOAK_ID, ADMIN_KEYCLOAK_SECRET, CRYPTO_PRIVATE_KEY); } type PlatformConfig = { From 05b5c5f67af17aab24ca107b5082a15e9f36c77d Mon Sep 17 00:00:00 2001 From: yogeshk34 Date: Wed, 1 Jul 2026 13:56:20 +0530 Subject: [PATCH 4/6] fix(setup): restore Keycloak 409 sync, guard PLATFORM_ADMIN_EMAIL, update README Signed-off-by: yogeshk34 --- README.md | 96 +++++++----------------------- libs/prisma-service/prisma/seed.ts | 19 +++++- 2 files changed, 41 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 7c689ab99..f25e827da 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,7 @@ Edit `.env` with your actual values before proceeding. Key variables are called Start PostgreSQL via Docker. The credentials and DB name **must match** what you set in `DATABASE_URL` / `POOL_DATABASE_URL` in your `.env`. ```bash -docker run --name credebl-postgres \ - -p 5432:5432 \ - -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ - -e POSTGRES_DB=credebl \ - -v credebl_pgdata:/var/lib/postgresql/data \ - --network platform_default \ - -d postgres:16 +docker compose up -d postgres ``` > ⚠️ **If you later run `docker compose up`, Docker Compose also defines a service named `credebl-postgres`.** To avoid a container name collision, stop and remove the manually started container first (`docker rm -f credebl-postgres`) before running `docker compose up`. @@ -70,7 +63,7 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/credebl" POOL_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/credebl" ``` -> ⚠️ **Do not use `localhost` in `DATABASE_URL` when services run inside Docker containers.** Inside a container, `localhost` resolves to the container itself — not the host. Use your machine's LAN IP (e.g. `192.168.x.x`) or a Docker service name instead. This applies to `KEYCLOAK_DOMAIN` and `KEYCLOAK_ADMIN_URL` as well (see Step 5). +> ⚠️ **Do not use `localhost` in `DATABASE_URL` when services run inside Docker containers.** Inside a container, `localhost` resolves to the container itself not the host. Use your machine's LAN IP (e.g. `192.168.x.x`) or a Docker service name instead. This applies to `KEYCLOAK_DOMAIN` and `KEYCLOAK_ADMIN_URL` as well (see Step 5). --- @@ -105,11 +98,11 @@ npx prisma migrate deploy --schema=./libs/prisma-service/prisma/schema.prisma ### Step 5 — Set Up Keycloak -Keycloak is required for authentication. It is **not started automatically** — you must run it separately and configure it fully before seeding. +Keycloak is required for authentication and must be started separately before seeding. #### 5a — Run the Keycloak container -> ⚠️ **Keycloak must be on the same Docker network as the platform services** (`platform_default`), otherwise the containers cannot reach it. Include `--network platform_default` when creating the container. +> ⚠️ Keycloak must run on the same Docker network as the platform services (`platform_default`), otherwise containers cannot reach it. ```bash docker run --name credebl-keycloak \ @@ -117,87 +110,42 @@ docker run --name credebl-keycloak \ -e KEYCLOAK_ADMIN=admin \ -e KEYCLOAK_ADMIN_PASSWORD=admin \ --network platform_default \ - -d quay.io/keycloak/keycloak:latest start-dev + -d quay.io/keycloak/keycloak:25.0.6 start-dev ``` -#### 5b — Create a Realm - -1. Open the Keycloak Admin Console: `http://localhost:8080` -2. Log in with `admin` / `admin` -3. Create a new **Realm** named exactly: `credebl-platform` - -#### 5c — Create Client 1: `adminClient` - -This client is used by the platform seed script and user-service to authenticate platform admin users. - -1. In the `credebl-platform` realm → **Clients** → **Create client** -2. **Client ID:** `adminClient` -3. **Client authentication:** ON (confidential) -4. **Service accounts enabled:** ON -5. Go to **Service account roles** tab → **Assign role** → filter by `realm-management` → add: - - `manage-users` - - `view-users` - - `query-users` -6. Go to **Credentials** tab → copy the **Client Secret** -7. Update `.env`: - -```env -ADMIN_KEYCLOAK_ID=adminClient -ADMIN_KEYCLOAK_SECRET= -``` +#### 5b — Complete Realm & Client Setup -#### 5d — Create Client 2: `credeblClient` +For creating the `credebl-platform` realm, `adminClient`, and `credeblClient`, follow the official docs: +👉 https://docs.credebl.id/docs/contribute/setup/service-setup#keycloak -This client is the management client used for general Keycloak operations. +#### 5c — Set Keycloak domain in `.env` -1. **Clients** → **Create client** -2. **Client ID:** `credeblClient` -3. **Client authentication:** ON (confidential) -4. **Service accounts enabled:** ON -5. Go to **Service account roles** tab → **Assign role** → filter by `realm-management` → add: - - `manage-users` - - `view-users` - - `query-users` - - `manage-realm` -6. Go to **Credentials** tab → copy the **Client Secret** -7. Update `.env`: +> ⚠️ If platform services run in Docker, do **not** use `localhost` — use your machine's LAN IP instead. +> Find it with: `hostname -I` ```env -KEYCLOAK_MANAGEMENT_CLIENT_ID=credeblClient -KEYCLOAK_MANAGEMENT_CLIENT_SECRET= -KEYCLOAK_REALM=credebl-platform -KEYCLOAK_MASTER_REALM=master +KEYCLOAK_DOMAIN=http://:8080/ +KEYCLOAK_ADMIN_URL=http://:8080 ``` -#### 5e — Set Keycloak domain in `.env` - -> ⚠️ **If platform services run in Docker containers, do NOT use `localhost` for Keycloak URLs.** -> Use your machine's LAN IP address instead (e.g. `192.168.1.x`). You can find it with `ip addr show` or `hostname -I`. +> For remaining Keycloak env variables (`KEYCLOAK_REALM`, `KEYCLOAK_MASTER_REALM`, `KEYCLOAK_MANAGEMENT_CLIENT_ID`, `KEYCLOAK_MANAGEMENT_CLIENT_SECRET`, `ADMIN_KEYCLOAK_ID`, `ADMIN_KEYCLOAK_SECRET`), refer to the official docs linked above. -```env -# For Docker-based deployments — replace with your actual LAN IP: -KEYCLOAK_DOMAIN=http://192.168.x.x:8080/ -KEYCLOAK_ADMIN_URL=http://192.168.x.x:8080 +> **Note:** If you want to log into Studio UI, follow the optional user creation step in docs — use the **same email** as `PLATFORM_ADMIN_EMAIL` when adding the user in Keycloak, otherwise authentication will fail. -# For host-only (no Docker for platform services): -# KEYCLOAK_DOMAIN=http://localhost:8080/ -# KEYCLOAK_ADMIN_URL=http://localhost:8080 -``` +> ⚠️ All Keycloak env variables must be fully configured before running the seed — the seed script connects to Keycloak directly. --- ### Step 6 — Configure Remaining `.env` Values -Set the following before seeding: - ```env PLATFORM_ADMIN_EMAIL=platform.admin@yopmail.com CRYPTO_PRIVATE_KEY=YourSecretPrivateKeyHere ``` -> `CRYPTO_PRIVATE_KEY` is used to encrypt/decrypt Keycloak client credentials stored in the DB. Keep it consistent across all runs — changing it after seeding will break decryption. +> ⚠️ `CRYPTO_PRIVATE_KEY` encrypts Keycloak credentials stored in the DB. Keep it consistent across all runs changing it after seeding will break decryption. ---- +--- ### Step 7 — Seed Initial Data @@ -210,10 +158,12 @@ npx prisma db seed The seed script will: 1. Create org roles, agent types, ecosystem roles, ledgers, and user roles 2. Create the platform admin user and organization -3. Create the Keycloak user for the platform admin (or look up an existing one — see note below) +3. Create the Keycloak user for the platform admin (or look up an existing one) 4. Encrypt and store Keycloak `clientId` / `clientSecret` in the DB -> **Re-seeding note:** If the Keycloak user already exists (e.g. on a re-seed), the script will look up the existing user's Keycloak ID and still update the DB record — so `keycloakUserId`, `clientId`, and `clientSecret` are always kept in sync. +> **Re-seeding note:** Safe to run multiple times, existing users, +> roles, and Keycloak credentials are detected and skipped or synced automatically. + --- @@ -300,7 +250,7 @@ For core SSI capabilities, it leverages the great work from multiple open-source ## Contributing -Pull requests are welcome! Please read our [contributions guide](https://github.com/credebl/platform/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing — [guidance](https://github.com/apps/dco) on this is available. We also welcome issues submitted about problems you encounter in using CREDEBL. +Pull requests are welcome! Please read our [contributions guide](https://github.com/credebl/platform/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing [guidance](https://github.com/apps/dco) on this is available. We also welcome issues submitted about problems you encounter in using CREDEBL. ## License diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 10f6d9266..9caa9f257 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -168,6 +168,9 @@ const createEcosystemRoles = async (): Promise => { const createPlatformUser = async (): Promise => { try { + if (!process.env.PLATFORM_ADMIN_EMAIL) { + throw new Error('Missing required environment variable: PLATFORM_ADMIN_EMAIL'); + } const { platformAdminData } = JSON.parse(configData); platformAdminData.email = process.env.PLATFORM_ADMIN_EMAIL; platformAdminData.username = process.env.PLATFORM_ADMIN_EMAIL; @@ -771,7 +774,21 @@ export async function createKeycloakUser(): Promise { const res = await postKeycloakUser(token, user, KEYCLOAK_DOMAIN, KEYCLOAK_REALM); if (HttpStatus.CONFLICT === res.status) { - logger.log(`⚠️ User ${user.username} already exists`); + logger.log(`⚠️ User ${user.username} already exists in Keycloak — looking up existing user ID`); + const lookupToken = await getKeycloakToken(); + const lookupRes = await fetch( + `${KEYCLOAK_DOMAIN}admin/realms/${KEYCLOAK_REALM}/users?username=${encodeURIComponent(user.username)}&exact=true`, + { headers: { Authorization: `Bearer ${lookupToken}` } } + ); + if (!lookupRes.ok) { + const errText = await lookupRes.text(); + throw new Error(`Failed to look up existing Keycloak user (${lookupRes.status}): ${errText}`); + } + const existingUsers = (await lookupRes.json()) as { id: string }[]; + if (!Array.isArray(existingUsers) || 0 === existingUsers.length) { + throw new Error(`Keycloak returned 409 but no user found for username: ${user.username}`); + } + await syncKeycloakUserToDb(existingUsers[0].id, ADMIN_KEYCLOAK_ID, ADMIN_KEYCLOAK_SECRET, CRYPTO_PRIVATE_KEY); return; } From 61494d477ea5563c049b8067d5002471e4ed4d38 Mon Sep 17 00:00:00 2001 From: yogeshk34 Date: Wed, 1 Jul 2026 14:32:15 +0530 Subject: [PATCH 5/6] fix(seed): add prerequisite guard and idempotency check for user_org_roles Signed-off-by: yogeshk34 --- libs/prisma-service/prisma/seed.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 9caa9f257..fba94e8eb 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -248,7 +248,19 @@ const createPlatformUserOrgRoles = async (): Promise => { } }); - if (userId && orgId && orgRoleId) { + if (!userId || !orgId || !orgRoleId) { + throw new Error('Missing prerequisite: platform admin user, organization, or org role'); + } + + const existingUserOrgRole = await prisma.user_org_roles.findFirst({ + where: { + userId: userId.id, + orgRoleId: orgRoleId.id, + orgId: orgId.id + } + }); + + if (existingUserOrgRole) { logger.log('Already seeding in org_roles'); } else { const platformOrganization = await prisma.user_org_roles.create({ From 766e6771604480eaf8b7d91be5d90b85d8883867 Mon Sep 17 00:00:00 2001 From: yogeshk34 Date: Wed, 1 Jul 2026 14:41:51 +0530 Subject: [PATCH 6/6] fix(seed): fail fast when decrypted admin password is empty Signed-off-by: yogeshk34 --- libs/prisma-service/prisma/seed.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index fba94e8eb..072960222 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -710,7 +710,10 @@ async function postKeycloakUser( keycloakDomain: string, keycloakRealm: string ): Promise { - const credentials = user.password ? [{ type: 'password', value: user.password, temporary: false }] : []; + if (!user.password) { + throw new Error('Platform admin password could not be decrypted'); + } + const credentials = [{ type: 'password', value: user.password, temporary: false }]; return fetch(`${keycloakDomain}admin/realms/${keycloakRealm}/users`, { method: 'POST', headers: {