Skip to content

refactor:add pagination for required API's and fixes#1558

Merged
sujitaw merged 6 commits intomainfrom
refactor/ecosystem_apis_for_ui_requirements
Feb 11, 2026
Merged

refactor:add pagination for required API's and fixes#1558
sujitaw merged 6 commits intomainfrom
refactor/ecosystem_apis_for_ui_requirements

Conversation

@sujitaw
Copy link
Contributor

@sujitaw sujitaw commented Feb 9, 2026

What

  • added pagination to API's
  • fixes issues found during the process

Summary by CodeRabbit

  • New Features

    • Pagination added to ecosystem, organization, intent, template and invitation list endpoints.
    • New organization search & listing endpoint with pageNumber/pageSize/search.
  • API Changes

    • List endpoints now return paginated responses including totalPages metadata.
    • Invitations now include associated organization info.
    • Role- and status-based query parameters supported for invitations and org endpoints.
  • Documentation

    • API docs updated with a root server entry and new query parameter docs.

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>
@sujitaw sujitaw self-assigned this Feb 9, 2026
@sujitaw
Copy link
Contributor Author

sujitaw commented Feb 9, 2026

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds pagination types and threads paginated requests/responses through controllers, services, and repositories across ecosystem and organization modules; adjusts DTOs and enums for invitations and org relations; updates Prisma schema/migrations to link invitations to organisations; removes a seed JSON file and tweaks Swagger and JWT error handling.

Changes

Cohort / File(s) Summary
Common pagination & types
apps/api-gateway/common/interface.ts, libs/common/src/interfaces/interface.ts
Added IPageDetail/PaginatedResponse<T>, CommonTableColumns, and search? on pagination DTOs; public pagination types introduced.
Enums
libs/enum/src/enum.ts
Added InvitationViewRole enum (ECOSYSTEM_MEMBER, ECOSYSTEM_LEAD).
API-Gateway — Ecosystem DTOs
apps/api-gateway/src/ecosystem/dtos/ecosystem.ts, apps/api-gateway/src/ecosystem/dtos/send-ecosystem-invitation.ts
Removed several DTO fields (status, role, email, userId); replaced invitation status with orgId in update DTO.
API-Gateway — Ecosystem controller & service
apps/api-gateway/src/ecosystem/ecosystem.controller.ts, apps/api-gateway/src/ecosystem/ecosystem.service.ts
Added pagination query DTOs to many endpoints, ParseEnumPipe for enum queries, adjusted signatures to pass pagination and enum status/role to services; updated user typing.
API-Gateway — Organization & Swagger/JWT
apps/api-gateway/src/organization/organization.controller.ts, apps/api-gateway/src/main.ts, apps/api-gateway/src/authz/jwt.strategy.ts
Added platform organisations GET endpoint with pagination and Swagger root server entry; changed JWT strategy error handling to call done with UnauthorizedException.
Ecosystem app — Interfaces, controller, service, repository
apps/ecosystem/interfaces/ecosystem.interfaces.ts, apps/ecosystem/src/ecosystem.controller.ts, apps/ecosystem/src/ecosystem.service.ts, apps/ecosystem/repositories/ecosystem.repository.ts
Added/modified ecosystem interfaces (IEcosystemOrg, ICreateEcosystemOrg, expanded invitations), and converted many methods to accept IPaginationSortingDto and return PaginatedResponse<T>; repositories implement take/skip + count and compute totalPages; many signatures updated.
Organization app — controller/service/repository & common interfaces
apps/organization/src/organization.controller.ts, apps/organization/src/organization.service.ts, apps/organization/repositories/organization.repository.ts, libs/common/src/interfaces/organization.interface.ts
Added paginated organization listing flow: controller message handler, service wrapper, repository getAllOrganizations with transactional count, and new interfaces IGetAllOrgsPayload/IAllOrgsNameId; refactored organization-related interfaces.
Prisma schema & migrations
libs/prisma-service/prisma/schema.prisma, libs/prisma-service/prisma/migrations/.../migration.sql, libs/prisma-service/prisma/migrations/.../migration.sql
Added organisation <> ecosystem_invitations relation, loosened unique constraint to include invitedOrg via migration, and added FK constraint ON DELETE SET NULL ON UPDATE CASCADE.
Prisma seed removed
libs/prisma-service/prisma/data/credebl-master-table.json
Deleted large seed JSON file.
Ecosystem DTOs — pagination/sort DTO
apps/ecosystem/dtos/create-ecosystem-dto.ts
Added SortFields enum and PaginationGetAllEcosystem DTO extending pagination with sortBy and sortField.
Supabase client
libs/supabase/src/supabase.service.ts
Added lazy getClient() method with cached clientInstance and logging.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant APIGW as API-Gateway Controller
  participant GWService as API-Gateway Service
  participant EcosystemSvc as Ecosystem Service
  participant Repo as Repository
  participant DB as Database

  Client->>APIGW: GET /ecosystems?pageNumber=1&pageSize=20
  APIGW->>GWService: parse pagination & user
  GWService->>EcosystemSvc: getEcosystems(userId, pageDetail)
  EcosystemSvc->>Repo: fetch paged data (take/skip) + count
  Repo->>DB: SELECT ... LIMIT ... OFFSET ...
  DB-->>Repo: rows + totalCount
  Repo-->>EcosystemSvc: { totalPages, data }
  EcosystemSvc-->>GWService: PaginatedResponse
  GWService-->>APIGW: 200 { totalPages, data }
  APIGW-->>Client: 200 { totalPages, data }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

feature

Suggested reviewers

  • pranalidhanavade
  • shitrerohit

Poem

🐇 I hopped through modules, tidy and spry,

Pages now fetch where long lists used to lie,
Invitations snugly linked to orgs at last,
Enums and schema settled, migrations cast,
I twitch my nose — the APIs paginate, hooray!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: pagination addition to APIs and associated fixes. It is concise and specific enough for a teammate to understand the primary scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/ecosystem_apis_for_ui_requirements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/api-gateway/src/ecosystem/dtos/ecosystem.ts (1)

26-31: ⚠️ Potential issue | 🟡 Minor

GetEcosystemInvitationsQueryDto was simplified to only expose ecosystemId at the API level.

The DTO no longer exposes role and email as query parameters. However, role is still extracted separately via @Query('role') and userId from the authenticated user context, both passed to the service for filtering. Email filtering is no longer available—if the UI relied on server-side filtering by email, it will need to filter client-side or email support must be re-added to the query parameters.

apps/api-gateway/src/ecosystem/ecosystem.controller.ts (1)

348-374: ⚠️ Potential issue | 🟡 Minor

Returning HTTP 404 for an empty paginated page discards pagination metadata.

When ecosystemData.data.length === 0, the response is a 404 with no totalPages. A page with zero results (e.g., last page + 1, or an ecosystem with no orgs yet) is a valid paginated response, not "not found." Return 200 with { totalPages: 0, data: [] } instead.

♻️ Suggested fix
     const ecosystemData = await this.ecosystemService.getAllEcosystemOrgsByEcosystemId(ecosystemId, pageDto);
-    if (ecosystemData.data && 0 < ecosystemData.data.length) {
-      return res.status(HttpStatus.OK).json({
-        statusCode: HttpStatus.OK,
-        message: ResponseMessages.ecosystem.success.fetchOrgs,
-        data: ecosystemData
-      });
-    }
-
-    return res.status(HttpStatus.NOT_FOUND).json({
-      statusCode: HttpStatus.NOT_FOUND,
-      message: ResponseMessages.ecosystem.error.ecosystemOrgsFetchFailed
+    return res.status(HttpStatus.OK).json({
+      statusCode: HttpStatus.OK,
+      message: ResponseMessages.ecosystem.success.fetchOrgs,
+      data: ecosystemData
     });
apps/ecosystem/repositories/ecosystem.repository.ts (1)

299-345: ⚠️ Potential issue | 🔴 Critical

Division by zero when pageSize is 0, and negative skip when pageNumber < 1 across all paginated methods.

The repository methods receive IPageDetail which has no validation constraints. While PaginationDto (with @Min(1) validators) exists in the api-gateway, the ecosystem service controller accepts IPageDetail directly via @MessagePattern handlers, bypassing validation entirely. If pageSize === 0, Math.ceil(totalCount / 0) produces Infinity. If pageNumber < 1, (pageNumber - 1) * pageSize produces a negative skip, which Prisma rejects. This pattern repeats across at least 6 methods: getAllEcosystems, getAllEcosystemOrgsByEcosystemId, getEcosystemInvitations, getIntents, getTemplatesByOrgId, and getEcosystemsForEcosystemLead.

Add validation to IPageDetail interface or validate at the start of each repository method. Alternatively, enforce validation in the ecosystem service layer before passing to repository methods.

🤖 Fix all issues with AI agents
In `@apps/api-gateway/src/ecosystem/ecosystem.controller.ts`:
- Around line 140-142: The route handler currently defaults the query param to
Invitation.ACCEPTED which silently accepts invitations when clients omit
?status; remove the default so the signature becomes `@Query`('status') status:
Invitation (or better: `@Query`('status', new ParseEnumPipe(Invitation)) status:
Invitation) and import ParseEnumPipe from `@nestjs/common` to validate/require the
enum value; update the handler (the method taking `@Res`() res: Response and
`@Query`('status') status: Invitation) to return a 400 or throw
BadRequestException when status is missing/invalid so state-changing actions
must be explicit.

In `@apps/api-gateway/src/organization/organization.controller.ts`:
- Around line 829-832: getAllOrganisations currently returns the raw service
result via res.send and bypasses the controller's standard IResponse envelope;
change getAllOrganisations to build an IResponse object (with statusCode:
HttpStatus.OK, message: a short success string, and data: the value from
this.organizationService.getAllOrganizations(paginationDto)) and return it using
res.status(HttpStatus.OK).json(finalResponse) (matching other endpoints),
ensuring you still accept PaginationDto and keep the service call to
this.organizationService.getAllOrganizations.

In `@apps/ecosystem/repositories/ecosystem.repository.ts`:
- Around line 334-339: Several paginated repository methods are inconsistent:
when pageDetail.pageNumber > totalPages (and totalPages !== 0) some functions
return { totalPages, data: [] } while others let Prisma return stale/partial
results. For each paginated method (e.g., getEcosystemInvitations, getIntents,
getTemplatesByOrgId, getAllEcosystemOrgsByEcosystemId and the method around
lines 1339-1344), add the same out-of-range guard used in
getAllEcosystems/getEcosystemsForEcosystemLead — check if pageDetail.pageNumber
> totalPages && totalPages !== 0 and if so return { totalPages, data: [] }
immediately before calling Prisma — ensuring consistent behavior and using the
same result shape and variable names (totalPages, data) as the existing
implementations.
- Around line 1290-1301: In getEcosystemsForEcosystemLead update the role enum
used in the where clause: replace OrgRoles.ECOSYSTEM_LEAD with
EcosystemRoles.ECOSYSTEM_LEAD so the repository matches the service layer and
other methods; locate the usage inside the ecosystemOrgs.some.ecosystemRole.name
check in getEcosystemsForEcosystemLead and swap the enum reference accordingly.

In `@apps/organization/repositories/organization.repository.ts`:
- Around line 1227-1260: The repository method getAllOrganizations must guard
against invalid pagination inputs: ensure pageSize and pageNumber are normalized
before use (e.g., if pageSize < 1 set a sensible default like 10 or throw a
BadRequest, and set pageNumber = Math.max(1, pageNumber)) so skip = (pageNumber
- 1) * pageSize is never negative and totalPages = Math.ceil(totalCount /
pageSize) never divides by zero; implement this normalization/check at the start
of getAllOrganizations, then use the normalized values for prisma.findMany
skip/take and totalPages calculation.

In `@apps/organization/src/organization.service.ts`:
- Around line 2103-2110: The error log in getAllOrganisation incorrectly
references "In fetchUserInvitation"; update the logger call inside the catch of
getAllOrganisation to use the correct method name and context (e.g., "In
getAllOrganisation") and keep logging the serialized error; ensure the
RpcException throw remains unchanged and still uses error.response if present.
Reference: getAllOrganisation method and this.logger.error usage.

In `@libs/enum/src/enum.ts`:
- Around line 73-76: The string value for InvitationViewRole.ECOSYSTEM_LEAD
contains a typo ("Ecosysetm Lead"); update the enum in enum.ts so
InvitationViewRole.ECOSYSTEM_LEAD is set to "Ecosystem Lead" to match the
existing EcosystemRoles.ECOSYSTEM_LEAD value and avoid mismatches when comparing
these enums.
🧹 Nitpick comments (12)
libs/common/src/interfaces/organization.interface.ts (1)

111-120: IAllOrgsNameId lacks totalCount; IGetAllOrgsPayload.search should be optional.

  1. Other paginated responses in this codebase (e.g., IGetOrganization) return both totalCount and totalPages. IAllOrgsNameId only has totalPages, which may limit the consumer's ability to display "showing X of Y" style UI.
  2. search is declared as required in IGetAllOrgsPayload, but PaginationDto (used at the gateway) defaults it to ''. Consider marking it optional (search?: string) for consistency.
Suggested change
 export interface IAllOrgsNameId {
+  totalCount: number;
   totalPages: number;
   orgs: { id: string; name: string }[];
 }

 export interface IGetAllOrgsPayload {
   pageSize: number;
   pageNumber: number;
-  search: string;
+  search?: string;
 }
apps/organization/repositories/organization.repository.ts (1)

1229-1249: Duplicated where clause in transaction queries.

The where: { name: { contains: search, mode: 'insensitive' } } filter is repeated in both findMany and count. Extract it to a local variable to keep them in sync and reduce duplication, consistent with how getOrgInvitationsPagination (line 360) uses a shared queryObject.

Suggested refactor
 async getAllOrganizations(search: string, pageNumber: number, pageSize: number): Promise<IAllOrgsNameId> {
   try {
+    const where = { name: { contains: search, mode: 'insensitive' as const } };
     const result = await this.prisma.$transaction([
       this.prisma.organisation.findMany({
-        where: {
-          name: { contains: search, mode: 'insensitive' }
-        },
+        where,
         take: pageSize,
         select: {
           id: true,
           name: true
         },
         skip: (pageNumber - 1) * pageSize,
         orderBy: {
           createDateTime: 'desc'
         }
       }),
       this.prisma.organisation.count({
-        where: {
-          name: { contains: search, mode: 'insensitive' }
-        }
+        where
       })
     ]);
apps/api-gateway/common/interface.ts (1)

30-38: Consider adding totalCount to PaginatedResponse.

Most paginated APIs return both totalPages and totalCount (total number of records) so clients can display accurate pagination info (e.g., "showing 1–10 of 47 results"). Currently, clients can only derive an approximate total from totalPages * pageSize.

Proposed enhancement
 export interface PaginatedResponse<T> {
+  totalCount: number;
   totalPages: number;
   data: T[];
 }
apps/ecosystem/interfaces/ecosystem.interfaces.ts (1)

212-212: Remove unused type alias EcosystemInvitationRoles.

This type is defined but never referenced anywhere in the codebase. It can be safely removed.

apps/ecosystem/src/ecosystem.service.ts (3)

165-185: Remove commented-out code.

This large block of dead code clutters the method. The return on Line 164 makes this unreachable anyway. If this code is no longer needed, delete it; if it's kept for reference, track it in a separate issue or commit message instead.

🧹 Proposed cleanup
     try {
       return await this.ecosystemRepository.getInvitationsByUserId(userId);
-      // for (const val of invitationData) {
-      //   if (val.invitedOrg && val.ecosystemId) {
-      //     const orgDetails = await this.ecosystemRepository.getEcosystemOrg(val.ecosystemId, val.invitedOrg);
-      //     if (orgDetails) {
-      //       val.organization = orgDetails;
-      //     }
-      //   }
-      // }
-
-      // return invitationData;
-      // const includeOrg = invitationData.map(async (val) => {
-      //   if (val.invitedOrg && val.ecosystemId) {
-      //     const orgDetails = await this.ecosystemRepository.getEcosystemOrg(val.ecosystemId, val.invitedOrg);
-      //     if (orgDetails) {
-      //       val.organization = orgDetails;
-      //     }
-      //   } else {
-      //     return val;
-      //   }
-      // });
-      // return includeOrg;
     } catch (error) {

304-320: Potential null dereference when getEcosystemByRole returns null — and the role check is redundant.

getEcosystemByRole uses findFirst, which resolves to null when no match exists. The && guard on Line 310 protects against that, so there's no crash. However, the role-name comparison is redundant because the repository already filters by ecosystemRole.name === EcosystemRoles.ECOSYSTEM_LEAD — if a result is returned, it will always match. The else branch also silently returns all ecosystems for any non-lead user (including members), which may be unintended from an access-control perspective.

♻️ Simplify the condition
-      const ecosystem = await this.ecosystemRepository.getEcosystemByRole(userId, EcosystemRoles.ECOSYSTEM_LEAD);
-      if (ecosystem && ecosystem.ecosystemRole.name === EcosystemRoles.ECOSYSTEM_LEAD) {
-        const leadEcosystems = await this.ecosystemRepository.getEcosystemsForEcosystemLead(userId, pageDetail);
-        return leadEcosystems;
-      } else {
-        return this.ecosystemRepository.getAllEcosystems(pageDetail);
-      }
+      const isLead = await this.ecosystemRepository.getEcosystemByRole(userId, EcosystemRoles.ECOSYSTEM_LEAD);
+      if (isLead) {
+        return this.ecosystemRepository.getEcosystemsForEcosystemLead(userId, pageDetail);
+      }
+      return this.ecosystemRepository.getAllEcosystems(pageDetail);

44-44: Cross-layer import: microservice imports from API gateway.

The ecosystem microservice (apps/ecosystem) imports IPageDetail and PaginatedResponse from apps/api-gateway/common/interface. This creates a dependency from the microservice layer back into the API gateway layer, which inverts the expected dependency direction. Consider moving these shared interfaces to a common library (e.g., @credebl/common or libs/common) so both layers can depend on a shared module.

apps/ecosystem/src/ecosystem.controller.ts (2)

162-167: Confusing nested payload.payload naming.

The parameter name payload shadows the convention already used in every other handler. Having payload.payload (inner field also named payload) is easy to misread and a maintenance hazard. Consider renaming the inner field (e.g., params or invitationParams).

♻️ Suggested rename
   async getEcosystemMemberInvitations(payload: {
-    payload: IEcosystemMemberInvitations;
+    params: IEcosystemMemberInvitations;
     pageDetail: IPageDetail;
   }): Promise<PaginatedResponse<IEcosystemInvitation>> {
-    return this.ecosystemService.getEcosystemMemberInvitations(payload.payload, payload.pageDetail);
+    return this.ecosystemService.getEcosystemMemberInvitations(payload.params, payload.pageDetail);
   }

The same rename would need to be applied in the API gateway service where the NATS message is constructed.


279-285: Method name getTemplatesByIntentId is misleading — it fetches by orgId.

The handler accepts payload.orgId and delegates to getTemplatesByOrgId, yet the method is named getTemplatesByIntentId. This will confuse future readers.

♻️ Rename to match the actual behaviour
-  async getTemplatesByIntentId(payload: {
+  async getVerificationTemplatesByOrgId(payload: {
     orgId: string;
     pageDetail: IPageDetail;
   }): Promise<PaginatedResponse<object>> {
apps/ecosystem/repositories/ecosystem.repository.ts (1)

326-327: Duplicated pagination boilerplate across 6 methods.

The $transaction([findMany(..., take, skip), count()]) + Math.ceil + return { totalPages, data } pattern is copy-pasted in getAllEcosystems, getAllEcosystemOrgsByEcosystemId, getEcosystemInvitations, getIntents, getTemplatesByOrgId, and getEcosystemsForEcosystemLead. Consider extracting a generic helper, e.g.:

private async paginate<T>(
  model: { findMany: Function; count: Function },
  args: { where: any; orderBy?: any; select?: any; include?: any },
  pageDetail: IPageDetail
): Promise<PaginatedResponse<T>> { ... }

This would centralize the take/skip/totalPages logic, the division-by-zero guard, and the out-of-range page behavior in one place.

Also applies to: 880-881, 1123-1124, 1152-1153, 1191-1192, 1329-1330

apps/api-gateway/src/ecosystem/ecosystem.service.ts (1)

124-127: Parameter user shadows the imported Prisma user type.

Line 13 imports user (the Prisma type), and Line 124 declares a parameter also named user. While TypeScript allows this, it prevents using the user type elsewhere in this method if needed. A rename (e.g., currentUser or reqUser) would improve clarity.

apps/api-gateway/src/ecosystem/ecosystem.controller.ts (1)

302-313: Default status = EcosystemOrgStatus.INACTIVE for updateEcosystemOrgStatus may be surprising.

Similar to the invitation status default, this silently deactivates orgs when the status query param is omitted. Given the required: true in the @ApiQuery decorator (Line 305), Swagger will enforce it, but NestJS itself won't reject the request if the param is missing — it'll use the default. Consider using ParseEnumPipe without a default for consistency with the required: true declaration.

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/api-gateway/src/ecosystem/ecosystem.controller.ts (1)

386-415: ⚠️ Potential issue | 🟡 Minor

Swagger required: true contradicted by default value on role parameter.

Line 389 declares required: true in @ApiQuery, but line 409 provides a default (InvitationViewRole.ECOSYSTEM_LEAD). This means:

  • Swagger UI shows role as mandatory, yet the endpoint works without it (defaulting to ECOSYSTEM_LEAD).
  • A client omitting both role and ecosystemId gets a 400 — which is confusing since the API appears to accept the call.

Either remove the default to truly require the parameter (add ParseEnumPipe for validation), or set required: false in @ApiQuery to match the runtime behavior.

apps/ecosystem/src/ecosystem.service.ts (2)

283-299: ⚠️ Potential issue | 🔴 Critical

Null-pointer crash when user has no ecosystem role.

getEcosystemByRole (line 288) returns null when no matching ecosystem_orgs record is found (Prisma's findFirst returns null). Line 289 then dereferences ecosystem.ecosystemRole.name, which throws a TypeError: Cannot read properties of null.

This means any non-lead user hitting this endpoint will crash the service instead of falling through to getAllEcosystems.

Proposed fix
-      const ecosystem = await this.ecosystemRepository.getEcosystemByRole(userId, EcosystemRoles.ECOSYSTEM_LEAD);
-      if (ecosystem && ecosystem.ecosystemRole.name === EcosystemRoles.ECOSYSTEM_LEAD) {
-        const leadEcosystems = await this.ecosystemRepository.getEcosystemsForEcosystemLead(userId, pageDetail);
+      const ecosystemOrg = await this.ecosystemRepository.getEcosystemByRole(userId, EcosystemRoles.ECOSYSTEM_LEAD);
+      if (ecosystemOrg?.ecosystemRole?.name === EcosystemRoles.ECOSYSTEM_LEAD) {
+        const leadEcosystems = await this.ecosystemRepository.getEcosystemsForEcosystemLead(userId, pageDetail);
         return leadEcosystems;
       } else {
-        return this.ecosystemRepository.getAllEcosystems(pageDetail);
+        return this.ecosystemRepository.getAllEcosystems(pageDetail);
       }

Additionally, since getEcosystemByRole already filters by ecosystemRole.name = EcosystemRoles.ECOSYSTEM_LEAD, the if check on line 289 is redundant — a truthy result already guarantees the role matches. A simple if (ecosystemOrg) would suffice.


381-408: ⚠️ Potential issue | 🟠 Major

Empty OR array when both email and userId are undefined may return all pending invitations.

Lines 404–405 construct OR: [email ? {email} : undefined, userId ? {userId} : undefined].filter(Boolean). If both email and userId are falsy, the resulting OR is []. Prisma treats an empty OR array as vacuously true, so the query matches all pending member invitations across all users — a potential data leak.

Proposed fix — guard against empty OR
     } else {
+      const orConditions = [
+        email ? { email } : undefined,
+        userId ? { userId } : undefined
+      ].filter(Boolean);
+
+      if (0 === orConditions.length) {
+        throw new BadRequestException('Either email or userId is required for member invitations');
+      }
+
       where = {
         ...baseWhere,
         status: Invitation.PENDING,
-        OR: [email ? { email } : undefined, userId ? { userId } : undefined].filter(Boolean)
+        OR: orConditions
       };
     }
🧹 Nitpick comments (2)
apps/ecosystem/repositories/ecosystem.repository.ts (2)

1077-1124: Missing try/catch in getEcosystemInvitations unlike other paginated methods.

Other repository methods (e.g., getAllEcosystems, getAllEcosystemOrgsByEcosystemId) wrap their Prisma calls in try/catch with error logging. This method lets exceptions propagate without logging, making debugging harder.


42-42: Cross-application import: repository imports pagination types from the API gateway layer.

IPageDetail and PaginatedResponse are defined in apps/api-gateway/common/interface.ts but imported here in the ecosystem microservice's repository, service, and controller. This inverts the dependency direction — inner service layers should not depend on the gateway/presentation layer.

Move IPageDetail and PaginatedResponse to a shared library (e.g., @credebl/common) so both the gateway and ecosystem service can consume them without introducing cross-application coupling. This also affects apps/ecosystem/src/ecosystem.service.ts and apps/ecosystem/src/ecosystem.controller.ts.

@sujitaw sujitaw changed the title refactor/add pagination for required API's and fixes refactor:add pagination for required API's and fixes Feb 10, 2026
Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/ecosystem/src/ecosystem.service.ts (1)

394-408: ⚠️ Potential issue | 🟡 Minor

OR: [] returns an empty result set when both email and userId are falsy.

When getEcosystemMemberInvitations is called with a non-lead role and both email and userId are undefined, the .filter(Boolean) on line 405 yields OR: []. In Prisma, this is deterministic behavior that returns an empty result set (no rows). If this function is ever called without at least one of these identifiers, returning zero results may be unexpected. Consider guarding against this case for clarity.

apps/api-gateway/src/ecosystem/ecosystem.controller.ts (1)

386-409: ⚠️ Potential issue | 🟡 Minor

role param: no ParseEnumPipe, has a default, and @ApiQuery says required: true — all inconsistent.

Three issues with the role query parameter:

  1. @ApiQuery({ required: true }) (Line 389) vs. a default value on Line 409 — Swagger says "must provide" but the runtime silently falls back to ECOSYSTEM_LEAD.
  2. No ParseEnumPipe — an invalid value (e.g. ?role=foo) won't fail with 400; it'll use the default.
  3. GetEcosystemInvitationsQueryDto already declares a role field (validated with @IsEnum), so the separate @Query('role') extraction on Line 409 is redundant and potentially confusing.

Consider either relying solely on the DTO's role field (which already has @IsEnum validation) or using ParseEnumPipe on the standalone @Query('role') and dropping the DTO field.

🤖 Fix all issues with AI agents
In `@apps/api-gateway/src/ecosystem/ecosystem.controller.ts`:
- Around line 153-158: The JSON body (finalResponse.statusCode set to
HttpStatus.OK) and the HTTP response status (res.status(HttpStatus.CREATED)) are
inconsistent; update them to the same status—e.g., change
res.status(HttpStatus.CREATED) to res.status(HttpStatus.OK) or set
finalResponse.statusCode to HttpStatus.CREATED—so finalResponse and the call to
res.status(...) use the same HttpStatus constant (refer to finalResponse,
HttpStatus.OK/CREATED and res.status).
- Around line 303-314: updateEcosystemOrgStatus currently assigns a default
EcosystemOrgStatus.INACTIVE to the `@Query`('status') parameter which can silently
mutate state; remove the default value and validate the incoming value using
ParseEnumPipe instead (e.g., change the parameter signature for
updateEcosystemOrgStatus to accept `@Query`('status', new
ParseEnumPipe(EcosystemOrgStatus)) status: EcosystemOrgStatus) so the request
must supply an explicit enum value, and ensure the `@ApiQuery` metadata remains
consistent with required: true.

In `@apps/api-gateway/src/ecosystem/ecosystem.service.ts`:
- Around line 90-96: The return type of getAllEcosystemOrgsByEcosystemId is
wrong: change the method signature to return PaginatedResponse<IGetAllOrgs>
instead of PaginatedResponse<ecosystem_orgs>, import or reference the
IGetAllOrgs DTO/interface used by the ecosystem microservice, and update any
local typings/usages that assumed ecosystem_orgs; ensure the NATS call
this.natsClient.sendNatsMessage(this.serviceProxy, 'get-ecosystem-orgs', {
ecosystemId, pageDetail }) is kept as-is but typed to the new
PaginatedResponse<IGetAllOrgs> return type so the gateway proxy matches the
downstream shape.

In `@apps/ecosystem/dtos/create-ecosystem-dto.ts`:
- Around line 51-69: PaginationGetAllEcosystem currently inherits PaginationDto
(which defines search) but the service/repository interface
IPaginationSortingDto expects searchByText, so the controller passes a DTO with
the wrong field name and search is ignored; update PaginationGetAllEcosystem to
expose the expected field name (searchByText) instead of/alongside search so it
matches IPaginationSortingDto when sent to the service layer, e.g., rename or
add the property in PaginationGetAllEcosystem to searchByText (keeping
transforms/validators like `@Transform/`@IsOptional/@IsEnum as appropriate) so
consumers (controller -> service -> repository) receive the correct key.

In `@apps/ecosystem/repositories/ecosystem.repository.ts`:
- Around line 304-309: The current orderBy uses a computed key
[pageDetail.sortField] which can be undefined; change the
prisma.ecosystem.findMany call (and the analogous call in
getEcosystemsForEcosystemLead) to guard against undefined by deriving a
safeSortField (e.g., const safeSortField = pageDetail.sortField ?? 'createdAt')
and use that (orderBy: { [safeSortField]: SortValue.ASC === pageDetail.sortBy ?
'asc' : 'desc' }) or alternatively only include orderBy when
pageDetail.sortField is truthy; update both occurrences: pageDetail.sortField in
the shown transaction block and the one in getEcosystemsForEcosystemLead.

In `@apps/ecosystem/src/ecosystem.controller.ts`:
- Around line 282-288: Rename the misnamed controller method
getTemplatesByIntentId to match its behavior and message pattern (e.g.,
getTemplatesByOrgId or getTemplatesByOrgIdHandler) so it reflects that it
handles { cmd: 'get-verification-templates-by-org-id' }, accepts payload.orgId,
and delegates to this.ecosystemService.getTemplatesByOrgId; update any internal
references and tests to call the new method name to avoid confusion.

In `@libs/common/src/interfaces/organization.interface.ts`:
- Around line 79-94: Remove sensitive credential fields (clientSecret, clientId,
idpId) from the IDeleteOrganization interface so delete-response objects no
longer carry secrets; create a separate internal interface (e.g.,
IOrganizationCredentials or IOrgAuth) to hold those three fields for use only in
contexts that require them, and update any types that currently extend
IDeleteOrganization (notably IOrgData) to either extend the credential interface
explicitly where needed or reference it only in internal/non-response APIs to
avoid leaking credentials via delete-related types.
- Around line 116-121: Change the IGetAllOrgsPayload interface so that search is
optional while leaving orgId required: update the property declaration from
search: string to search?: string in the IGetAllOrgsPayload interface; ensure
any code that consumes this interface (e.g., PaginationDto-related logic or
service methods that default search to '') continues to handle undefined by
falling back to an empty string or the existing default behavior.
🧹 Nitpick comments (13)
apps/api-gateway/src/organization/organization.controller.ts (2)

807-807: Non-RESTful route path with mixed spelling convention.

The path get-all-platform-organisations uses a verb prefix (get-all-) which is redundant for a GET endpoint, and uses British spelling ("organisations") while the rest of the controller uses American spelling ("organizations"). Consider renaming to something like platform-organizations for consistency and RESTful convention.


813-814: Consider using the EcosystemRoles enum instead of OrgRoles for ecosystem-specific authorization.

While OrgRoles.ECOSYSTEM_LEAD exists and EcosystemRolesGuard correctly reads the ROLES_KEY metadata, using the OrgRoles enum here is architecturally inconsistent. The codebase defines a separate EcosystemRoles enum specifically for ecosystem authorization. For clarity and maintainability, use @Roles(EcosystemRoles.ECOSYSTEM_LEAD) or ensure consistency across all ecosystem-guarded endpoints.

libs/common/src/interfaces/organization.interface.ts (2)

25-32: Generic User export name risks collisions in a shared library.

Exporting a bare User interface from a shared libs/common module is likely to clash with other User types across the codebase (e.g., Prisma models, auth modules). Consider a more scoped name like IOrgUser or IOrganizationUser to stay consistent with the I-prefix convention used by the other interfaces in this file.


56-59: Pagination interfaces only carry totalPages — consider adding totalCount for consistency.

Both IOrganizationInvitations and IAllOrgsNameId include totalPages but omit totalCount (total number of records). Consumers often need the total item count for UI display (e.g., "Showing 1–10 of 47 results"). If the backend already computes the count to derive totalPages, surfacing it here costs nothing and improves the pagination contract.

Also applies to: 111-114

libs/common/src/interfaces/interface.ts (1)

74-77: Consider adding totalCount to PaginatedResponse<T>.

Clients often need the total number of items (not just total pages) for rendering pagination controls (e.g., "showing 1–10 of 57 results"). With only totalPages, the exact item count is lost due to Math.ceil. This is a minor API ergonomics gap.

Also, the naming convention in this file uses I-prefixed interfaces (IPaginationSortingDto, IAccessTokenData, etc.), but PaginatedResponse and CommonTableColumns break that convention.

Proposed change
-export interface PaginatedResponse<T> {
+export interface IPaginatedResponse<T> {
   totalPages: number;
+  totalCount: number;
   data: T[];
 }
apps/ecosystem/dtos/create-ecosystem-dto.ts (1)

46-49: SortFields enum is co-located with the DTO — verify it isn't needed elsewhere.

If SortFields is used by other modules (e.g., organization pagination), consider moving it to a shared location. If it's truly ecosystem-specific, the current placement is fine.

apps/ecosystem/src/ecosystem.controller.ts (1)

165-170: Confusing payload.payload naming.

The outer parameter is named payload and the inner member is also payload, resulting in payload.payload access at line 169. Consider renaming either the parameter or the member for clarity (e.g., msg: { payload: ...; pageDetail: ... } or payload: { memberInvitations: ...; pageDetail: ... }).

apps/ecosystem/src/ecosystem.service.ts (1)

283-299: Redundant role name check after role-filtered query.

Line 288 calls getEcosystemByRole(userId, EcosystemRoles.ECOSYSTEM_LEAD) which already filters by role name. The subsequent check on line 289 (ecosystem.ecosystemRole.name === EcosystemRoles.ECOSYSTEM_LEAD) is always true if ecosystem is non-null, making it redundant.

Simplified version
-      const ecosystem = await this.ecosystemRepository.getEcosystemByRole(userId, EcosystemRoles.ECOSYSTEM_LEAD);
-      if (ecosystem && ecosystem.ecosystemRole.name === EcosystemRoles.ECOSYSTEM_LEAD) {
-        const leadEcosystems = await this.ecosystemRepository.getEcosystemsForEcosystemLead(userId, pageDetail);
-        return leadEcosystems;
-      } else {
+      const ecosystem = await this.ecosystemRepository.getEcosystemByRole(userId, EcosystemRoles.ECOSYSTEM_LEAD);
+      if (ecosystem) {
+        return this.ecosystemRepository.getEcosystemsForEcosystemLead(userId, pageDetail);
+      } else {
         return this.ecosystemRepository.getAllEcosystems(pageDetail);
       }
apps/ecosystem/repositories/ecosystem.repository.ts (2)

1077-1124: getEcosystemInvitations ignores sortField/sortBy from pageDetail.

Sort is hardcoded to createDateTime: 'desc' while other paginated methods respect the caller's sort preferences. This may be intentional for invitations, but it's inconsistent.


1128-1153: Minor: where typed as any — use Prisma.intentsWhereInput for type safety.

Proposed fix
-    const where: any = {
+    const where: Prisma.intentsWhereInput = {
       ecosystemId
     };
apps/api-gateway/src/ecosystem/ecosystem.controller.ts (3)

362-374: Returning 404 for an empty page may confuse paginated clients.

With pagination in place, an empty data array on a valid page (e.g. page 2 with no more results) is a normal condition, not a "not found" scenario. Paginated endpoints conventionally return 200 OK with an empty array and total-count metadata, so consumers can distinguish "no more pages" from "resource doesn't exist."

Consider always returning 200 and letting the pagination metadata (e.g. totalItems: 0) convey emptiness instead of a 404.


411-415: Good: userId is sourced from the authenticated user, not the query string.

Overriding query.userId with reqUser.id is the right security pattern. The ecosystemId validation for the ECOSYSTEM_LEAD role is also appropriate.

One minor note: since userId in GetEcosystemInvitationsQueryDto is always overridden here, consider removing it from the DTO to avoid giving clients the impression they can control it.


757-762: Method name getTemplateByIntentId is misleading — it fetches templates by orgId.

The route is GET /:orgId/templates, the JSDoc says "Get template details by org ID", and the implementation calls getVerificationTemplates(orgId, pageDto). The method name should reflect this, e.g. getTemplatesByOrgId.

Suggested rename
-  async getTemplateByIntentId(
+  async getTemplatesByOrgId(

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api-gateway/src/ecosystem/ecosystem.controller.ts (1)

386-409: ⚠️ Potential issue | 🟡 Minor

role parameter: @ApiQuery({ required: true }) contradicts the default value.

Line 389 declares required: true, but line 409 provides a default InvitationViewRole.ECOSYSTEM_LEAD. This means the endpoint never actually requires the client to send role — the default silently kicks in. This is the same pattern that was deliberately fixed for status parameters with ParseEnumPipe.

If the default is intentional, change required: true to required: false in the @ApiQuery decorator. If it should be required, use ParseEnumPipe and remove the default, consistent with the other enum query params in this file.

Option A: Make it truly required (consistent with other enum params)
   `@ApiQuery`({
     name: 'role',
     enum: InvitationViewRole,
     required: true
   })
   ...
   async getEcosystemMemberInvitations(
     ...
-    `@Query`('role') role: InvitationViewRole = InvitationViewRole.ECOSYSTEM_LEAD
+    `@Query`('role', new ParseEnumPipe(InvitationViewRole)) role: InvitationViewRole
   ): Promise<Response> {
Option B: Keep the default, fix the docs
   `@ApiQuery`({
     name: 'role',
     enum: InvitationViewRole,
-    required: true
+    required: false
   })
🤖 Fix all issues with AI agents
In `@apps/ecosystem/repositories/ecosystem.repository.ts`:
- Around line 1077-1124: The getEcosystemInvitations method currently calls
this.prisma.$transaction and computes totalPages without error handling; wrap
the method body in a try/catch, catch any error thrown by
this.prisma.$transaction or Math.ceil, log the error using the repository logger
(e.g., this.logger.error or the same logger used by other paginated methods)
with context (method name and relevant identifiers like ecosystemId/pageDetail),
and re-throw the error so callers still receive it; ensure you reference the
existing symbols getEcosystemInvitations, this.prisma.$transaction,
this.prisma.ecosystem_invitations.count and pageDetail.pageSize when adding the
log message for consistency with other methods.
🧹 Nitpick comments (4)
libs/common/src/interfaces/organization.interface.ts (1)

1-42: New interface decomposition looks clean.

The reorganization of IOrganization into discrete top-level interfaces (UserOrgRole, User, IOrgRoles) improves clarity. One minor note: the User interface name (line 25) is very generic and could shadow or be confused with the Prisma user type that's used elsewhere in this codebase (e.g., import { user } from '@prisma/client'). Consider a more specific name like IOrgUser to avoid ambiguity.

apps/ecosystem/repositories/ecosystem.repository.ts (1)

1128-1153: getIntents pagination — orderBy is hardcoded, no sortField support.

Unlike getAllEcosystems which respects pageDetail.sortField, getIntents hardcodes orderBy: { createDateTime: 'desc' } and ignores sortField/sortBy from pageDetail. This is fine if intentional, but may confuse consumers who expect the pagination DTO's sort fields to take effect.

apps/ecosystem/src/ecosystem.controller.ts (1)

165-170: Confusing payload.payload nesting in getEcosystemMemberInvitations.

The method parameter is payload which contains a property also named payload (line 166, 169). This payload.payload pattern is hard to read and error-prone. Consider renaming the outer parameter or the inner property for clarity.

Proposed fix
   async getEcosystemMemberInvitations(payload: {
-    payload: IEcosystemMemberInvitations;
+    invitationFilter: IEcosystemMemberInvitations;
     pageDetail: IPaginationSortingDto;
   }): Promise<PaginatedResponse<IEcosystemInvitation>> {
-    return this.ecosystemService.getEcosystemMemberInvitations(payload.payload, payload.pageDetail);
+    return this.ecosystemService.getEcosystemMemberInvitations(payload.invitationFilter, payload.pageDetail);
   }

This would require a matching change in the API gateway service that sends this NATS message.

apps/api-gateway/src/ecosystem/ecosystem.service.ts (1)

125-128: createIntentTemplate signature change: parameter user shadows the imported Prisma user type.

The parameter name user (line 125) is the same as the imported type user from @prisma/client (line 14). While TypeScript distinguishes between type and value positions, this can be confusing to readers. Consider renaming the parameter to currentUser or reqUser for clarity.

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>
…1562)

* wip

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>

* fix/update to allow multiple orgs of lead as member in ecosystem

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>

* fix/code rabbit issues

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>

---------

Signed-off-by: sujitaw <sujit.sutar@ayanworks.com>
@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/ecosystem/repositories/ecosystem.repository.ts (1)

830-900: ⚠️ Potential issue | 🟡 Minor

getAllEcosystemOrgsByEcosystemId has no orderBy — non-deterministic pagination.

Paginated queries without a stable sort order can return duplicated or missing records across pages. Consider adding an orderBy clause (e.g., createDateTime: 'desc').

🐛 Proposed fix
         this.prisma.ecosystem_orgs.findMany({
           where: whereClause,
           select: {
             ...
           },
+          orderBy: {
+            createDateTime: 'desc'
+          },
           take: pageDetail.pageSize,
           skip: (pageDetail.pageNumber - 1) * pageDetail.pageSize
         }),
apps/api-gateway/src/ecosystem/ecosystem.controller.ts (2)

114-120: ⚠️ Potential issue | 🟠 Major

HTTP status mismatch: body says INTERNAL_SERVER_ERROR but response is sent as OK (200).

Line 115 sets statusCode: HttpStatus.INTERNAL_SERVER_ERROR in the JSON body, but line 119 sends the response with HttpStatus.OK (200). The client receives a 200 status code for what is actually a server error, making error detection impossible for HTTP-aware consumers.

🐛 Proposed fix
     const finalResponse: IResponse = {
       statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
       message: ResponseMessages.errorMessages.serverError
     };
 
-    return res.status(HttpStatus.OK).json(finalResponse);
+    return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json(finalResponse);

362-376: ⚠️ Potential issue | 🟡 Minor

Returning 404 for empty paginated results is unconventional.

For paginated endpoints, an empty page (e.g., page 5 when only 3 pages exist) is a normal condition — returning { totalPages, data: [] } with HTTP 200 is standard. Returning 404 forces clients to special-case empty results vs. actual not-found scenarios.

🐛 Proposed fix
   async getEcosystemOrgs(
     ...
   ): Promise<Response> {
     const ecosystemData = await this.ecosystemService.getAllEcosystemOrgsByEcosystemId(ecosystemId, pageDto);
-    if (ecosystemData.data && 0 < ecosystemData.data.length) {
-      return res.status(HttpStatus.OK).json({
-        statusCode: HttpStatus.OK,
-        message: ResponseMessages.ecosystem.success.fetchOrgs,
-        data: ecosystemData
-      });
-    }
-
-    return res.status(HttpStatus.NOT_FOUND).json({
-      statusCode: HttpStatus.NOT_FOUND,
-      message: ResponseMessages.ecosystem.error.ecosystemOrgsFetchFailed
+    return res.status(HttpStatus.OK).json({
+      statusCode: HttpStatus.OK,
+      message: ResponseMessages.ecosystem.success.fetchOrgs,
+      data: ecosystemData
     });
   }
apps/ecosystem/src/ecosystem.service.ts (1)

344-366: ⚠️ Potential issue | 🟠 Major

Correct the second parameter in the getEcosystemOrg call on line 361.

The method signature at line 527 of the repository expects getEcosystemOrg(ecosystemId, orgId), but line 361 passes result.userId as the second parameter. Since ecosystem_invitations.userId is a user ID and ecosystem_invitations.invitedOrg is the organization ID, the query will search for a record with the wrong field value and never find existing entries. This can result in duplicate ecosystem_orgs records.

🐛 Proposed fix
-        const existingOrg = await this.ecosystemRepository.getEcosystemOrg(result.ecosystemId, result.userId);
+        const existingOrg = await this.ecosystemRepository.getEcosystemOrg(result.ecosystemId, result.invitedOrg);
🤖 Fix all issues with AI agents
In `@libs/supabase/src/supabase.service.ts`:
- Line 21: Validate that process.env.SUPABASE_URL and process.env.SUPABASE_KEY
are present before calling createClient in the code that assigns
this.clientInstance; if either is missing, throw a clear Error (or call
process.exit with a message) describing the missing variable(s) so the service
fails fast instead of passing undefined into createClient (referencing
this.clientInstance and createClient).
🧹 Nitpick comments (7)
libs/supabase/src/supabase.service.ts (2)

12-23: Noisy and misleading log messages.

  • Line 12/15/19: Logging at log level on every getClient() call is excessive for a routine accessor — debug is more appropriate.
  • Line 23: "auth has been set!" is misleading — no authentication is configured here; the client is simply instantiated with a URL and key.
Suggested adjustments
-    this.logger.log('getting supabase client... ');
+    this.logger.debug('getting supabase client...');
 
     if (this.clientInstance) {
-      this.logger.log('client exists - returning for current Scope.REQUEST');
+      this.logger.debug('returning existing client for current Scope.REQUEST');
       return this.clientInstance;
     }
 
-    this.logger.log('initialising new supabase client for new Scope.REQUEST');
+    this.logger.debug('initialising new supabase client for Scope.REQUEST');
 
     ...
 
-    this.logger.log('auth has been set!');
+    this.logger.debug('supabase client initialised');

8-8: Remove empty constructor.

An empty constructor with no parameters or initialization logic is unnecessary boilerplate.

Proposed fix
-  constructor() {}
libs/prisma-service/prisma/schema.prisma (1)

805-808: Relations and constraint look correct; consider explicit onDelete for clarity.

The relations and unique constraint are consistent with the migration. One minor note: the organisation relation on Line 805 has no explicit onDelete directive. The corresponding migration uses ON DELETE SET NULL, which matches Prisma's default for optional relations — so behavior is correct. However, adding an explicit onDelete: SetNull would make the intent self-documenting, especially since this model's deletion behavior is partly application-managed.

Optional: make onDelete explicit
-  organisation        organisation? `@relation`(fields: [invitedOrg], references: [id])
+  organisation        organisation? `@relation`(fields: [invitedOrg], references: [id], onDelete: SetNull)

Based on learnings, deletions involving invitedOrg are handled at the application level with conditions matching both ecosystemId and orgId.

apps/ecosystem/src/ecosystem.controller.ts (1)

171-176: Confusing nested payload.payload naming.

The parameter is named payload and it contains a property also named payload, leading to payload.payload on line 175. This hurts readability. Consider renaming the inner field (e.g., invitationParams or params).

♻️ Proposed fix
   async getEcosystemMemberInvitations(payload: {
-    payload: IEcosystemMemberInvitations;
+    params: IEcosystemMemberInvitations;
     pageDetail: IPaginationSortingDto;
   }): Promise<PaginatedResponse<IEcosystemInvitation>> {
-    return this.ecosystemService.getEcosystemMemberInvitations(payload.payload, payload.pageDetail);
+    return this.ecosystemService.getEcosystemMemberInvitations(payload.params, payload.pageDetail);
   }

This would require a corresponding change in the gateway service where the NATS message payload is constructed.

apps/api-gateway/src/ecosystem/ecosystem.service.ts (1)

104-112: Root of the payload.payload naming — consider renaming the key here.

This is where the confusing { payload, pageDetail } is constructed, which becomes payload.payload in the microservice controller. Renaming the key (e.g., to params) here would fix the naming confusion end-to-end.

♻️ Proposed rename
   async getEcosystemMemberInvitations(
     payload: IEcosystemMemberInvitations,
     pageDetail: IPaginationSortingDto
   ): Promise<PaginatedResponse<IEcosystemInvitation>> {
     return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-ecosystem-member-invitations', {
-      payload,
+      params: payload,
       pageDetail
     });
   }
apps/api-gateway/src/ecosystem/ecosystem.controller.ts (2)

758-763: Misleading method name getTemplateByIntentId — it operates on orgId.

The route is /:orgId/templates, the parameter is orgId, and it calls getVerificationTemplates(orgId, ...). The method name getTemplateByIntentId is a leftover misnomer.

♻️ Proposed fix
-  async getTemplateByIntentId(
+  async getTemplatesByOrgId(
     `@Param`('orgId') orgId: string,
     `@Res`() res: Response,
     `@Query`() pageDto: PaginationDto
   ): Promise<Response> {

406-416: role query parameter defaults to ECOSYSTEM_LEAD without enum validation.

Unlike status in updateEcosystemInvitationStatus (which uses ParseEnumPipe), the role parameter accepts any string and falls back to ECOSYSTEM_LEAD. Since this is a read endpoint the risk is lower, but an invalid role value would silently produce incorrect query results. Consider adding ParseEnumPipe(InvitationViewRole).

🛡️ Proposed fix
-    `@Query`('role') role: InvitationViewRole = InvitationViewRole.ECOSYSTEM_LEAD
+    `@Query`('role', new ParseEnumPipe(InvitationViewRole)) role: InvitationViewRole

process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
);
this.clientInstance = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing validation for required environment variables.

If SUPABASE_URL or SUPABASE_KEY is undefined, createClient will receive undefined and either throw a cryptic error or silently create a non-functional client. Fail fast with a clear message.

Proposed fix
+    const supabaseUrl = process.env.SUPABASE_URL;
+    const supabaseKey = process.env.SUPABASE_KEY;
+
+    if (!supabaseUrl || !supabaseKey) {
+      throw new Error('SUPABASE_URL and SUPABASE_KEY environment variables must be set');
+    }
+
-    this.clientInstance = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY);
+    this.clientInstance = createClient(supabaseUrl, supabaseKey);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.clientInstance = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY);
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;
if (!supabaseUrl || !supabaseKey) {
throw new Error('SUPABASE_URL and SUPABASE_KEY environment variables must be set');
}
this.clientInstance = createClient(supabaseUrl, supabaseKey);
🤖 Prompt for AI Agents
In `@libs/supabase/src/supabase.service.ts` at line 21, Validate that
process.env.SUPABASE_URL and process.env.SUPABASE_KEY are present before calling
createClient in the code that assigns this.clientInstance; if either is missing,
throw a clear Error (or call process.exit with a message) describing the missing
variable(s) so the service fails fast instead of passing undefined into
createClient (referencing this.clientInstance and createClient).

@sujitaw sujitaw merged commit 115a21e into main Feb 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants