Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9a79d5e
feat(scripts): add tenant org data migration script
nevil-mathew Mar 10, 2026
60be852
add : AGENTS.md
nevil-mathew Mar 10, 2026
dafb430
fix: add condition to exclude deleted user roles in strict ID compati…
nevil-mathew Mar 17, 2026
70a2e8d
fix: add note on ID assignment and sequence reset in ensureStrictIdCo…
nevil-mathew Mar 17, 2026
1b628bd
Merge pull request #881 from ELEVATE-Project/user-tenant-migration
nevil-mathew Mar 17, 2026
92510e2
fix: update CircleCI configuration to use the latest Ubuntu image
nevil-mathew Apr 2, 2026
0d16de2
fix: update CircleCI configuration to use Ubuntu 22.04 image
nevil-mathew Apr 2, 2026
9b19397
fix: update SonarCloud orb to version 2.0.0 in CircleCI configuration
nevil-mathew Apr 2, 2026
e8246b5
Merge pull request #882 from ELEVATE-Project/circle-ci-update
nevil-mathew Apr 2, 2026
59e32f5
version-change-for-healthCheck
MallanagoudaB Apr 22, 2026
c1ba472
Merge pull request #885 from MallanagoudaBiradar/healthCheckVersionCh…
nevil-mathew Apr 22, 2026
5e071f9
Changes to add configuration colum to tenant table
Vinod-V3 Jun 21, 2026
c2b4f27
Changes done to handle otp flow and autoregister flow for login and s…
Vinod-V3 Jun 22, 2026
2a94ee3
update: Changes done for the PR comments added
Vinod-V3 Jun 24, 2026
8ff2044
feat: Migration script to add name in the entity_types
Vinod-V3 Jun 24, 2026
43c658d
update: Changes done for the PR comments added
Vinod-V3 Jun 24, 2026
1e49152
fix: Added phoneCode check for SMS notification
Vinod-V3 Jun 24, 2026
a2fe020
fix: Removed default value in the add-name-entity-type migration script
Vinod-V3 Jun 25, 2026
46df6cf
fix: Added materialized logic for script and added User key in commons
Vinod-V3 Jun 25, 2026
d9dc015
fix: Deleting redis data for otps generated via username, added confi…
Vinod-V3 Jun 26, 2026
2e3408d
fix: Code refactoring changes
Vinod-V3 Jun 26, 2026
29d62b7
Merge pull request #888 from Vinod-V3/login-enhancement
nevil-mathew Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
version: 2.1 # CircleCI version
orbs:
sonarcloud: sonarsource/sonarcloud@1.1.1
sonarcloud: sonarsource/sonarcloud@2.0.0
jobs:
build:
machine: #Linux machine instead of docker environment
image: ubuntu-2004:202111-01
image: ubuntu-2204:current
docker_layer_caching: true
working_directory: ~/user # Default working directory
steps:
Expand Down Expand Up @@ -52,4 +52,3 @@ workflows:
tags:
only:
- develop
#test commit
184 changes: 184 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# AGENTS.md

## Purpose

This repository contains the ELEVATE User Service (`com.shikshalokam.mentoring.userservice`), a Node.js/Express API for user, organization, role/permission, invite, and tenant workflows.

## Repository Layout

- `src/` - Main service codebase (this is where almost all work happens).
- `src/app.js` - Service entrypoint.
- `src/routes/index.js` - Dynamic API router and global error handling.
- `src/controllers/v1/` - Versioned controller endpoints.
- `src/services/` - Business logic layer.
- `src/database/` - Sequelize models, queries, migrations, seeders.
- `src/middlewares/` - Express middlewares: `authenticator.js` (JWT/auth), `pagination.js`, `validator.js`. All routes pass through these; touch with care.
- `src/validators/v1/` - Request validation schemas (one per controller). Every new endpoint should have a matching validator here.
- `src/generics/` - Shared infrastructure utilities: `utils.js` (large general helpers), `materializedViews.js`, `kafka-communication.js`, `redis-communication.js`, `RollbackStack.js`. Check here before writing new utility code.
- `src/dtos/` - Data transfer objects for user, org, tenant, and events. Update when adding or changing fields in API responses.
- `src/constants/` - App-wide constants: `common.js`, `blacklistConfig.js`, endpoint definitions. Don't define new constants inline in services/controllers.
- `src/locales/` - i18n string files (`en.json`, `hi.json`). All user-facing messages must be added here, not hardcoded.
- `src/configs/` - Kafka, Redis, cache, cloud-storage, queue worker setup.
- `src/scripts/` - Operational scripts and data migration utilities.
- `src/health-checks/` - Health check endpoints and config.
- `dev-ops/` - Dependency docker compose and reporting helpers.
- `README.md` - Full setup docs and dependency installation notes.

## Stack and Runtime

- Node.js 20 (recommended in `README.md`)
- Express 4
- PostgreSQL (with Citus in some deployments)
- Sequelize + `sequelize-cli`
- Redis
- Kafka (`kafkajs`)
- BullMQ worker (invites and bulk-user workflows)

## Critical Context

- Run service commands from `src/`, not repo root.
- Repo root has a minimal `package.json` used for tooling; the real app package is `src/package.json`.
- App startup validates env vars via `src/envVariables.js`; missing required vars will stop boot.
- Route format is dynamic:
- `${APPLICATION_BASE_URL}/:version/:controller/:method`
- `${APPLICATION_BASE_URL}/:version/:controller/:file/:method`

## Local Setup (Fast Path)

1. `cd src`
2. `cp .env.sample .env`
3. Fill required env values (see `src/envVariables.js` and `src/.env.sample`)
4. `npm install`
5. `npm run db:migrate`
6. `npm run db:seed:all` (optional but common for local)
7. `npm start`

Default local app URL is typically `http://localhost:3001` (or `APPLICATION_PORT` from `.env`).

## Core Commands

From `src/`:

- `npm start` - run in development with nodemon
- `npm run prod` - production mode
- `npm run qa` / `npm run stage` - environment-specific starts
- `npm run db:init` - create DB + migrate
- `npm run db:migrate` - run migrations
- `npm run db:seed:all` - run all seeders
> **Note:** Tests (unit and integration) are currently broken and should not be run. Omit any test steps until further notice.

## Important Env Groups

See full list in `src/envVariables.js`; key groups:

- App/Auth: `APPLICATION_*`, `ACCESS_TOKEN_*`, `REFRESH_TOKEN_*`, `API_DOC_URL`
- Database: `DEV_DATABASE_URL`, `TEST_DATABASE_URL`, `DATABASE_URL`, `DB_POOL_*`
- Kafka: `KAFKA_URL`, `KAFKA_GROUP_ID`, event topic toggles and topic names
- Redis/Cache: `REDIS_HOST`, `INTERNAL_CACHE_EXP_TIME`
- Storage: `CLOUD_STORAGE_PROVIDER`, `CLOUD_STORAGE_*`, `PUBLIC_ASSET_BUCKETNAME`
- Integrations: `MENTORING_SERVICE_URL`, `ENTITY_MANAGEMENT_SERVICE_BASE_URL`, scheduler and notification vars

## Health Endpoints

- `GET /health`
- `GET /healthCheckStatus`

Health config lives in `src/health-checks/health.config.js` and currently checks kafka, redis, postgres, plus dependent services.

## Operational Scripts

From `src/`:

- `npm run migrate:tenant-org-data` - tenant/org data move script
- `npm run check:user-in-account-search -- --auth-token=<token> ...` - paginated account search checker
- `node scripts/insertDefaultOrg.js` - bootstrap default org
- `node scripts/encryptDecryptEmails.js encrypt|decrypt`

More script notes: `src/scripts/readme.md`.

## Code Style and Tooling

- ESLint rules: `src/.eslintrc.json` (tabs, single quotes, no semicolons)
- Prettier: `.prettierrc.json` at repo root
- Husky pre-commit runs `lint-staged` from `src/`

## Module Aliases

Defined in `src/package.json` under `_moduleAliases`. Always use these instead of relative paths:

| Alias | Resolves to |
| ---------------- | -------------------- |
| `@root` | `src/` |
| `@configs` | `src/configs/` |
| `@constants` | `src/constants/` |
| `@controllers` | `src/controllers/` |
| `@database` | `src/database/` |
| `@generics` | `src/generics/` |
| `@health-checks` | `src/health-checks/` |
| `@middlewares` | `src/middlewares/` |
| `@routes` | `src/routes/` |
| `@services` | `src/services/` |
| `@validators` | `src/validators/` |
| `@utils` | `src/utils/` |
| `@helpers` | `src/helpers/` |
| `@scripts` | `src/scripts/` |
| `@dtos` | `src/dtos/` |
| `@public` | `src/public/` |

## Implementation Guardrails

- Keep controller-service-query layering intact.
- Preserve response shape used by router/error middleware (`statusCode`, `responseCode`, `message`, `result`, `meta`).
- For DB schema changes, add Sequelize migrations in `src/database/migrations/`.
- Prefer updating existing query/service modules instead of embedding raw SQL in controllers.
- If changing env requirements, update both `src/envVariables.js` and `src/.env.sample`.
- All user-facing strings must be added to `src/locales/en.json` (and `hi.json` if translatable). Never hardcode message strings in services or controllers.

## Pull Request Instructions

1. Keep PRs focused on one logical change (avoid mixing refactor + feature + migration unless required).
2. Rebase/sync with latest target branch before opening PR.
3. Run lint validation from `src/`:
- `npx eslint .` (if lint-sensitive files changed)
> **Note:** Unit and integration tests are currently broken — skip test steps.
4. Include migration/rollback notes in PR description when touching `src/database/migrations/`.
5. If env/config changes are introduced, update:
- `src/.env.sample`
- `src/envVariables.js`
- relevant README/notes
6. PR description should include:
- What changed
- Why it changed
- Risk/impact
- Test evidence (commands + summary, if applicable — tests currently broken)
- API contract changes (if any)

## Commit Message Format

Use Conventional Commit style:

- `<type>(<scope>): <subject>`

Example:

- `refactor(organization): optimize feature access logic for role mappings`

Allowed `type` values:

- `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `chore`, `build`, `ci`

Scope guidance:

- Use module/domain names such as `organization`, `user`, `tenant`, `roles`, `scripts`, `migrations`, `health-checks`, `configs`.

Subject guidance:

- Use imperative mood and keep it concise.
- Do not end subject with a period.

## Useful References

- Main setup and infra guidance: `README.md`
- Sequelize path mapping: `src/.sequelizerc`
- Citus distribution SQL helper: `src/distributionColumns.sql`
- Health check guide: `src/health-checks/README.md`
16 changes: 16 additions & 0 deletions src/constants/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,20 @@ module.exports = {
SIGNEDUP_STATUS: 'SIGNEDUP',
SEQUELIZE_UNIQUE_CONSTRAINT_ERROR: 'SequelizeUniqueConstraintError',
SEQUELIZE_UNIQUE_CONSTRAINT_ERROR_CODE: 'ER_DUP_ENTRY',
DEFAULT_TENANT_CONFIGURATION: {
allowed_auth_mode: process.env.DEFAULT_ALLOWED_AUTH_MODES.split(','),
auto_register: process.env.DEFAULT_AUTO_REGISTER === 'true',
},
AUTH_MODES: {
OTP: 'otp',
PASSWORD: 'password',
},
OTP_PURPOSES: {
SIGNUP: 'signup',
LOGIN: 'login',
},
EMAIL: 'email',
PHONE: 'phone',
USER_NAME: 'username',
USER: 'User',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict'

const common = require('@constants/common')

module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('tenants', 'configuration', {
type: Sequelize.JSONB,
allowNull: false,
defaultValue: common.DEFAULT_TENANT_CONFIGURATION,
})

await queryInterface.sequelize.query(
`UPDATE tenants
SET configuration = :configuration
WHERE configuration IS NULL`,
{
replacements: {
configuration: JSON.stringify(common.DEFAULT_TENANT_CONFIGURATION),
},
}
)
},

down: async (queryInterface) => {
await queryInterface.removeColumn('tenants', 'configuration')
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict'

module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.sequelize.query('DROP MATERIALIZED VIEW IF EXISTS m_users;')

await queryInterface.changeColumn('users', 'name', {
type: Sequelize.STRING,
allowNull: true,
})

await queryInterface.changeColumn('users', 'password', {
type: Sequelize.STRING,
allowNull: true,
})

await queryInterface.changeColumn('users_credentials', 'password', {
type: Sequelize.STRING,
allowNull: true,
})
},

down: async (queryInterface, Sequelize) => {
await queryInterface.sequelize.query('DROP MATERIALIZED VIEW IF EXISTS m_users;')

await queryInterface.sequelize.query(`UPDATE users SET name = '' WHERE name IS NULL`)
await queryInterface.sequelize.query(`UPDATE users SET password = '' WHERE password IS NULL`)
await queryInterface.sequelize.query(`UPDATE users_credentials SET password = '' WHERE password IS NULL`)
await queryInterface.changeColumn('users', 'name', {
type: Sequelize.STRING,
allowNull: false,
})

await queryInterface.changeColumn('users', 'password', {
type: Sequelize.STRING,
allowNull: false,
})

await queryInterface.changeColumn('users_credentials', 'password', {
type: Sequelize.STRING,
allowNull: false,
})
},
}
71 changes: 71 additions & 0 deletions src/database/migrations/20260623124000-add-name-entity-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict'

module.exports = {
async up(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction()
try {
const defaultOrgCode = process.env.DEFAULT_ORGANISATION_CODE

const defaultOrgsPerTenant = await queryInterface.sequelize.query(
'SELECT id, code, tenant_code FROM organizations WHERE code = :defaultOrgCode',
{
replacements: { defaultOrgCode },
type: queryInterface.sequelize.QueryTypes.SELECT,
transaction,
}
)

if (!defaultOrgsPerTenant.length) {
console.warn('No default organizations found across tenants. Skipping migration.')
await transaction.commit()
return
}

const now = new Date()
const entityTypesToInsert = defaultOrgsPerTenant.map((org) => ({
value: 'name',
label: 'Name',
status: 'ACTIVE',
created_by: null,
updated_by: null,
allow_filtering: false,
data_type: 'STRING',
organization_id: org.id,
organization_code: org.code,
tenant_code: org.tenant_code,
parent_id: null,
has_entities: false,
allow_custom_entities: false,
model_names: ['User'],
created_at: now,
updated_at: now,
}))

await queryInterface.bulkInsert('entity_types', entityTypesToInsert, { transaction })

await transaction.commit()
} catch (error) {
await transaction.rollback()
console.error('Migration up failed:', error)
throw error
}
},

async down(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction()
try {
await queryInterface.bulkDelete(
'entity_types',
{
value: 'name',
},
{ transaction }
)
await transaction.commit()
} catch (error) {
await transaction.rollback()
console.error('Migration down failed:', error)
throw error
}
},
}
7 changes: 7 additions & 0 deletions src/database/models/Tenant.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
'use strict'
const common = require('@constants/common')

module.exports = (sequelize, DataTypes) => {
const Tenant = sequelize.define(
'Tenant',
Expand Down Expand Up @@ -28,6 +30,11 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.JSONB,
allowNull: true,
},
configuration: {
type: DataTypes.JSONB,
allowNull: false,
defaultValue: common.DEFAULT_TENANT_CONFIGURATION,
},
meta: {
type: DataTypes.JSON,
allowNull: true,
Expand Down
Loading