1. Overview

This document provides a comprehensive technical overview of the API-Issues service, which handles issue tracking, notifications, and related functionality. The document traces function flows from endpoints to implementations, detailing key components and their interactions.

The API-Issues service is part of the Nimbly Technologies platform, designed for issue management and tracking. It integrates with other services in the ecosystem to provide comprehensive issue management capabilities from creation to resolution, with support for notifications, approvals, and status tracking.

2. Libraries and Dependencies

The service relies on the following key libraries and frameworks:

2.1. Core Dependencies

  • express: Web framework for Node.js
  • mongoose: MongoDB object modeling tool
  • firebase-admin: Firebase Admin SDK
  • @google-cloud/functions-framework: Framework for Cloud Functions
  • @google-cloud/pubsub: Google Cloud PubSub client
  • @google-cloud/storage: Google Cloud Storage client
  • @google-cloud/trace-agent: Google Cloud Trace agent for monitoring
  • @hapi/joi: Object schema validation
  • @nimbly-technologies/entity-node: Custom entity module for Nimbly
  • @nimbly-technologies/nimbly-access: [Access control](../Settings/Access control/AccessControlOverview.md) module
  • @nimbly-technologies/nimbly-backend-utils: Backend utilities
  • @nimbly-technologies/nimbly-common: Common types and utilities
  • @sendgrid/mail: SendGrid mail service
  • axios: HTTP client
  • cors: CORS middleware
  • crypto: Cryptographic functions
  • dotenv: Environment variable management
  • jsdom: DOM implementation for Node.js
  • jszip: ZIP file handling
  • lodash: Utility library
  • moment: Date manipulation
  • moment-timezone: Timezone support for moment
  • node-cron: Cron jobs
  • ramda: Functional programming utilities
  • redis: Redis client
  • uuid: UUID generation

2.2. Development Dependencies

  • typescript: TypeScript
  • jest: Testing framework
  • prettier: Code formatter
  • nodemon: Development server with auto-reload

3. API Endpoints

EndpointMethodDescription
/issuesGETGet filtered issue data based on parameters
/issues/v2GETGet filtered issue data (version 2)
/issues/v3POSTGet filtered issue data (version 3)
/issues/v2POSTGet filtered issue data (version 1 POST method)
/issues/countGETCount total issues
/issues/countPOSTCount total issues (v1)
/issues/zapierGETGet issue data for Zapier integration
/issues/:issueIDGETGet single issue data
/issues/v2/:issueIDGETGet single extended issue data
/issues/create/effectPOSTCreate issue with side effects
/issues/createPOSTCreate new issue
/issues/create/v2POSTCreate new issue (version 2)
/issues/report-issuePOSTCreate issue from report
/issues/cf-issuePOSTCreate issue from custom form
/issues/custom-deadlinePOSTSet custom deadline for issue
/issues/update/effectPOSTUpdate issue with side effects
/issues/updatePUTUpdate issue properties
/issues/bulkUpdatePUTBulk update multiple issues
/issues/quickResolvePUTQuick resolve issue
/issues/:issueID/notificationPOSTSend issue notification to auditors
/issues/:issueID/statusHistoryGETGet issue status history
/issues/:issueID/notification/:auditorIDPOSTSend issue notification to specific auditor
/issues/internal/updatePUTInternal update for issues
/issues/readStatusPOSTSend read status
/issues/readStatus/upsertPOSTUpsert read status
/issues/readStatus/syncPOSTRe-sync issue status
/issues/readAllDELETEDelete all read status by user
/issues/readStatus/:issueIDGETGet issue user read status
/issues/readStatus/bulk-updatePOSTBulk update issue user read status
/issueMessagesGETGet issue messages
/issueMessages/:messageIDGETGet specific issue message
/issueMessages/createPOSTCreate issue message
/issueMessages/create/bulkPOSTCreate bulk issue messages
/issueMessages/resolveAllRequiredMessagePOSTResolve all required messages
/issueMessages/checkAllRequiredMessagePOSTCheck all required messages
/issueMessages/:messageIDPUTUpdate issue message
/issueMessages/:messageIDDELETEDelete issue message
/issueApproval/issueBulkApprovePOSTBulk approve issues
/issueApproval/issueBulkRejectPOSTBulk reject issues
/issueApproval/issueApprovePOSTApprove single issue
/issueApproval/issueRejectPOSTReject single issue
/issueApproval/validateMediaApprovalTokenPOSTValidate media approval token
/auto-due-datesGETGet auto due dates
/auto-due-dates/:idGETGet specific auto due date
/auto-due-datesPOSTCreate auto due date
/auto-due-dates/:idPUTUpdate auto due date
/auto-due-dates/:idDELETEDelete auto due date
/healthIndicatorsGETGet health indicators
/issue-tracker-filtersGETGet issue tracker filters
/issue-tracker-filters/:idGETGet specific issue tracker filter
/issue-tracker-filtersPOSTCreate issue tracker filter
/issue-tracker-filters/:idPUTUpdate issue tracker filter
/issue-tracker-filters/:idDELETEDelete issue tracker filter

4. Data Validation and Models

4.1. Validation

The API-Issues service uses Joi for data validation. Validation schemas are defined for each entity and applied at the controller level using middleware:

router.post(
  '/create',
  authMiddleware.expressHandler(),
  validatorHandler(issueValidator, 'create'),
  expressMiddlewareHandler(issueController.create.bind(issueController)),
);

Validation schemas are defined in domain-specific validator files. For example, issue validation is defined in src/domains/issue/issue.validator.ts:

export const IssueValidator = {
  filtered: Joi.object({
    query: Joi.object({
      'resolvedAt[startAt]': Joi.string(),
      'resolvedAt[endAt]': Joi.string(),
      'dueDate[startAt]': Joi.string(),
      'dueDate[endAt]': Joi.string(),
      'createdAt[startAt]': Joi.string(),
      'createdAt[endAt]': Joi.string(),
      qDepartment: Joi.string(),
      qSite: Joi.string(),
      qQuestionnaire: Joi.string(),
      qFlag: Joi.string().valid('yellow', 'red'),
      qStatus: Joi.string().valid('open', 'resolved'),
      qPriority: Joi.string().valid('normal', 'high'),
      qUser: Joi.string(),
      sortBy: Joi.string().valid('createdAt', 'resolvedAt', 'dueDate', 'updatedAt'),
      sortType: Joi.string().valid('asc', 'desc'),
      search: Joi.string(),
    }),
  }),
  create: Joi.object({
    assignedDepartments: Joi.array().required(),
    assignedTo: Joi.string().allow(''),
    category: Joi.string().required(),
    createdBy: Joi.string().required(),
    dueDate: Joi.date(),
    members: Joi.object(),
    photoLimit: Joi.number().required(),
    priority: Joi.string().valid('normal', 'high').required(),
    questionIndex: Joi.number().required(),
    questionText: Joi.string().required(),
    questionnaireID: Joi.string().allow(''),
    resolvedAt: Joi.date().allow(null),
    resolvedBy: Joi.string().allow(''),
    section: Joi.number().required(),
    severity: Joi.string().valid('yellow', 'red').required(),
    siteID: Joi.string().required(),
    status: Joi.string().valid('open', 'resolved').required(),
    triggerNotification: Joi.boolean(),
  }),
  update: Joi.object({
    issueID: Joi.string().required(),
    assignedDepartments: Joi.array(),
    assignedTo: Joi.string().allow(''),
    dueDate: Joi.date().allow(null),
    members: Joi.object(),
    photoLimit: Joi.number(),
    priority: Joi.string().valid('normal', 'high'),
    resolvedAt: Joi.date().allow(null),
    resolvedBy: Joi.string().allow(''),
    severity: Joi.string().valid('yellow', 'red'),
    status: Joi.string().valid('open', 'resolved'),
    triggerNotification: Joi.boolean(),
  }),
  // Other validation schemas...
}

4.2. Data Models

The service uses TypeScript interfaces for type checking and Mongoose schemas for database operations. Key data models include:

4.2.1. Issue Model

The Issue interface is defined in @nimbly-technologies/nimbly-common:

export interface Issue {
  issueID: string;
  adminIsReading: boolean;
  adminUnreadCount: number;
  appIsReading: boolean;
  appUnreadCount: number;
  assignedDepartments: string[];
  assignedTo: {
    key: string;
    name: string;
  };
  category: string;
  createdAt: string;
  createdBy: {
    key: string;
    name: string;
  };
  dueDate: string | null;
  members: {
    [userID: string]: boolean;
  };
  photoLimit: number;
  priority: 'normal' | 'high';
  questionIndex: number;
  questionText: string;
  questionnaire: {
    key: string;
    name: string;
  };
  resolvedAt: string | null;
  resolvedBy: {
    key: string;
    name: string;
  };
  severity: 'yellow' | 'red';
  status: 'open' | 'resolved';
  site: {
    key: string;
    name: string;
  };
  section: number;
  updatedAt: string;
}

The MongoDB schema is implemented in the IssueMongoRepository class from @nimbly-technologies/entity-node.

4.2.2. Issue Message Model

export interface IssueMessage {
  messageID: string;
  issueID: string;
  message: string;
  required: boolean;
  type: MessageType;
  createdAt: string;
  createdBy: {
    key: string;
    name: string;
  };
  attachments: string[];
  reactions: Reaction[];
}

4.2.3. Issue Approval Model

export interface IssueApproval {
  approvalID: string;
  issueID: string;
  status: 'pending' | 'approved' | 'rejected';
  createdAt: string;
  updatedAt: string;
  approvedBy: {
    key: string;
    name: string;
  } | null;
  rejectedBy: {
    key: string;
    name: string;
  } | null;
  comments: string;
}

5. Architecture and Flow

The API-Issues service follows a layered architecture pattern with the following components:

  1. Routers: Define API endpoints and HTTP method handlers
  2. Controllers: Handle HTTP requests and responses
  3. Use Cases: Implement business logic
  4. Repositories: Provide data access abstractions
  5. Helpers/Utils: Utility functions and helpers

The general flow of a request through the system is:

Client Request → Router → Controller → Use Case → Repository → Database/External Service

And for the response:

Database/External Service → Repository → Use Case → Controller → Router → Client Response

6. Domain Models

The service is organized around several domain models:

6.1. Issue

The core entity representing a problem, task, or action item. Key properties include:

  • issueID: Unique identifier
  • assignedDepartments: Departments assigned to the issue
  • assignedTo: User assigned to the issue
  • category: Issue category
  • createdAt: Creation timestamp
  • createdBy: User who created the issue
  • dueDate: Due date for resolution
  • priority: Issue priority (normal/high)
  • questionText: Description of the issue
  • resolvedAt: Resolution timestamp
  • resolvedBy: User who resolved the issue
  • severity: Issue severity (yellow/red)
  • status: Issue status (open/resolved)
  • [site](../Sites/SitesOverview.md): Site related to the issue

6.2. Issue Message

Represents communications related to an issue:

  • messageID: Unique identifier
  • issueID: Related issue
  • message: Message content
  • createdAt: Creation timestamp
  • createdBy: User who created the message
  • attachments: Attached files

6.3. Issue Approval

Handles approval workflows for issues:

  • approvalID: Unique identifier
  • issueID: Related issue
  • status: Approval status
  • approvedBy: User who approved
  • rejectedBy: User who rejected
  • comments: Approval comments

6.4. Auto Due Date

Configuration for automatic due date assignment:

  • id: Unique identifier
  • siteID: Related site
  • period: Time period
  • unit: Time unit (days, weeks, etc.)

6.5. Issue Tracker Filter

Custom filters for issue tracking:

  • id: Unique identifier
  • name: Filter name
  • criteria: Filter criteria
  • createdBy: User who created the filter
  • isDefault: Whether it’s a default filter

7. Controllers

Controllers handle HTTP requests and responses, invoking the appropriate use cases based on the request:

7.1. Issue Controller

Located in src/controller/issue.controller.ts, handles issue-related endpoints:

  • filtered: Get filtered issues
  • singleIssue: Get a single issue
  • create: Create new issue
  • update: Update issue
  • bulkUpdate: Bulk update issues
  • quickResolve: Quick resolve an issue
  • pushNotification: Send issue notifications
  • statusHistory: Get issue status history

7.2. Issue Message Controller

Located in src/controller/issueMessage.controller.ts, handles issue message endpoints:

  • create: Create new message
  • createBulk: Create multiple messages
  • getMessage: Get a specific message
  • getMessages: Get all messages for an issue
  • update: Update a message
  • delete: Delete a message

7.3. Issue Approval Controller

Located in src/controller/issueApproval.controller.ts, handles approval workflows:

  • issueApprove: Approve an issue
  • issueReject: Reject an issue
  • issueBulkApprove: Bulk approve issues
  • issueBulkReject: Bulk reject issues

7.4. Auto Due Date Controller

Located in src/controller/autoDueDate.controller.ts, manages auto due date configurations:

  • getAll: Get all auto due dates
  • getById: Get a specific auto due date
  • create: Create new auto due date
  • update: Update auto due date
  • delete: Delete auto due date

7.5. Issue Tracker Filter Controller

Located in src/controller/issueTrackerFilter.controller.ts, manages filter configurations:

  • getAll: Get all filters
  • getById: Get a specific filter
  • create: Create new filter
  • update: Update filter
  • delete: Delete filter

8. Use Cases

Use cases implement the core business logic of the application:

8.1. Issue Use Case

Located in src/domains/issue/usecase/issue.usecase.ts, implements issue management logic:

  • addIssues: Create new issues
  • updateIssues: Update existing issues
  • bulkUpdateIssues: Update multiple issues
  • getSingleIssue: Get a specific issue
  • getFilteredIssuesInOrganizationPaginate: Get filtered issues with pagination
  • createIssueFromReport: Create an issue from a report
  • statusHistory: Get issue status history

8.2. Issue Message Use Case

Located in src/domains/issueMessage/usecase/issueMessage.usecase.ts, handles message operations:

  • createIssueMessage: Create a new message
  • createBulkIssueMessage: Create multiple messages
  • updateIssueMessage: Update a message
  • deleteIssueMessage: Delete a message
  • getIssueMessages: Get all messages for an issue

8.3. Issue User Read Status Use Case

Located in src/domains/issueUserReadStatus/usecase/issueUserReadStatus.usecase.ts:

  • sendReadStatus: Update user’s read status for an issue
  • upsertReadStatus: Create or update read status
  • reSyncIssueStatus: Synchronize issue read status
  • getIssueUserReadStatus: Get user’s read status for an issue

8.4. Issue Approval Use Case

Located in src/domains/issueApproval/usecase/issueApproval.usecase.ts:

  • issueApprove: Approve an issue
  • issueReject: Reject an issue
  • issueBulkApprove: Bulk approve issues
  • issueBulkReject: Bulk reject issues

8.5. Auto Due Date Use Case

Located in src/domains/autoDueDate/usecase/autoDueDate.usecase.ts:

  • create: Create auto due date configuration
  • update: Update configuration
  • delete: Delete configuration
  • getAll: Get all configurations
  • getById: Get specific configuration

8.6. Issue Tracker Filter Use Case

Located in src/domains/issueTrackerFilter/usecase/issueTrackerFilter.usecase.ts:

  • create: Create filter
  • update: Update filter
  • delete: Delete filter
  • getAll: Get all filters
  • getById: Get specific filter

9. Repositories

Repositories provide data access abstractions for various persistence mechanisms:

9.1. Issue Repository

Located in src/domains/issue/repository/issue.repository.ts, handles issue data operations:

  • getIssue: Get a specific issue
  • getFilteredIssuesInOrganization: Get filtered issues
  • createIssue: Create a new issue
  • updateIssue: Update an issue
  • deleteIssue: Delete an issue

9.2. Issue Message Repository

Located in src/domains/issueMessage/repository/issueMessage.repository.ts:

  • createIssueMessage: Create a message
  • updateIssueMessage: Update a message
  • deleteIssueMessage: Delete a message
  • getIssueMessages: Get all messages for an issue

9.3. Issue User Read Status Repository

Located in src/domains/issueUserReadStatus/repository/issueUserReadStatus.repository.ts:

  • sendReadStatus: Update read status
  • upsertReadStatus: Create or update read status
  • getIssueUserReadStatus: Get read status for an issue

9.4. Issue Approval Repository

Located in src/domains/issueApproval/repository/issueApproval.repository.ts:

  • approveIssue: Approve an issue
  • rejectIssue: Reject an issue
  • getIssueApproval: Get approval status

9.5. Auto Due Date Repository

Located in src/domains/autoDueDate/repository/autoDueDate.mongo.repository.ts:

  • create: Create configuration
  • update: Update configuration
  • delete: Delete configuration
  • getAll: Get all configurations
  • getById: Get specific configuration

9.6. Issue Tracker Filter Repository

Located in src/routes/issueTrackerFilter.router.ts:

  • create: Create filter
  • update: Update filter
  • delete: Delete filter
  • getAll: Get all filters
  • getById: Get specific filter

10. Implementation Details

10.1. Issue Creation Flow

The issue creation process follows these steps:

  1. Client sends a POST request to /issues/create
  2. The request is routed through issue.router.ts to issueController.create
  3. issueController.create extracts the request payload and calls issueUsecase.addIssues
  4. issueUsecase.addIssues performs validation and creates the issue
  5. The issue is saved to MongoDB through the issueRepository.createIssue method
  6. If notification is enabled, notifications are sent using the NotificationPubsubRepository
  7. The controller returns a success response with the created issue ID

10.2. Issue Update Flow

The issue update process follows these steps:

  1. Client sends a PUT request to /issues/update
  2. The request is routed through issue.router.ts to issueController.update
  3. issueController.update extracts the request payload and calls issueUsecase.updateIssues
  4. issueUsecase.updateIssues retrieves the existing issue, validates the changes, and updates it
  5. The issue is updated in MongoDB through the issueRepository.updateIssue method
  6. Issue history is recorded using the issueHistoryRepository.create method
  7. If notification is enabled, notifications are sent using the NotificationPubsubRepository
  8. The controller returns a success response with the updated issue properties

10.3. Issue Notification Flow

The notification process follows these steps:

  1. When an issue is created, updated, or requires notification
  2. The appropriate use case calls pubsubNotificationRepository.publish
  3. A message is published to Google Cloud PubSub
  4. The Notification service receives the message and processes it
  5. Notifications are sent to relevant users via email, mobile push notification, or other channels

10.4. Issue Read Status Flow

The read status tracking process follows these steps:

  1. Client sends a POST request to /issues/readStatus
  2. The request is routed to issueUserReadStatusController.sendReadStatus
  3. issueUserReadStatusController.sendReadStatus calls issueUserReadStatusUsecase.sendReadStatus
  4. The read status is updated in Firebase through issueUserReadStatusRepository.sendReadStatus
  5. The controller returns a success response

11. Authentication and Authorization

The API-Issues service uses JWT-based authentication and provides role-based access control:

11.1. Authentication Middleware

Located in src/routes/issue.router.ts, the authMiddleware checks for valid JWT tokens:

const authMiddleware = new AuthMiddleware<UserAuth>(process.env.JWT_SECRET!, jwt);

This middleware is applied to all protected routes:

router.get(
  '/',
  authMiddleware.expressHandler(),
  validatorHandler(issueValidator, 'filtered'),
  expressMiddlewareHandler(issueController.filtered.bind(issueController)),
);

11.2. Authorization Logic

Authorization logic is implemented in the use cases:

  1. User roles and permissions are checked for specific operations
  2. Department/site access is validated based on user context
  3. Operations are restricted based on user’s role in the organization

12. Error Handling

The service implements a consistent error handling pattern:

  1. Errors are caught at the use case level
  2. HTTP-appropriate error codes and messages are generated
  3. Error responses are standardized for client consumption

The service uses a standardized error response format:

// Helper function for creating responses
export default function response(data: any, error: string | null) {
  if (error) {
    return {
      message: error,
      data: null,
    };
  }
 
  return {
    message: 'SUCCESS',
    data,
  };
}

Error handling in controllers typically follows this pattern:

try {
  const issues = await this.issueUsecase.getFilteredIssuesInOrganizationPaginate(
    context,
    QueryOptionsV2.convertFromQueryOptionsV1(queryParam),
  );
  return response(issues.docs, null);
} catch (error) {
  log.error(`[ERROR]: ${error.message}`);
  return response(null, error.code || errors.INTERNAL);
}

The service uses predefined error codes from the @nimbly-technologies/nimbly-common package:

  • errors.INTERNAL: Internal server error
  • errors.INVALID_DATA: Invalid data format
  • errors.NOT_FOUND: Resource not found
  • errors.UNAUTHORIZED: Unauthorized access
  • errors.FORBIDDEN: Forbidden access
  • errors.CONFLICT: Resource conflict

These error codes are mapped to appropriate HTTP status codes in the Express middleware handlers.


13. Flow Visualizations

13.1. Issue Creation Flow

graph TD
    A[Client] -->|POST /issues/create| B[issue.router.ts]
    B -->|Routes to| C[issueController.create]
    C -->|Extracts payload| D[issueUsecase.addIssues]
    D -->|Validates request| E{Valid?}
    E -->|Yes| F[issueRepository.createIssue]
    E -->|No| G[Return Error]
    F -->|Saves to| H[(MongoDB)]
    F -->|Trigger notification?| I{Notify?}
    I -->|Yes| J[NotificationPubsubRepository.publish]
    I -->|No| K[Skip notification]
    J -->|Publishes to| L[(Google Cloud PubSub)]
    J --> M[Return Success]
    K --> M[Return Success]
    G --> N[Return Error Response]
    M --> O[Return Success Response]

13.2. Issue Update Flow

graph TD
    A[Client] -->|PUT /issues/update| B[issue.router.ts]
    B -->|Routes to| C[issueController.update]
    C -->|Extracts payload| D[issueUsecase.updateIssues]
    D -->|Retrieves current issue| E[issueRepository.getIssue]
    E -->|Gets from| F[(MongoDB)]
    E -->|Returns issue| G[Validate changes]
    G -->|Valid?| H{Valid?}
    H -->|Yes| I[issueRepository.updateIssue]
    H -->|No| J[Return Error]
    I -->|Updates in| K[(MongoDB)]
    I -->|Record history| L[issueHistoryRepository.create]
    L -->|Saves to| M[(MongoDB)]
    I -->|Trigger notification?| N{Notify?}
    N -->|Yes| O[NotificationPubsubRepository.publish]
    N -->|No| P[Skip notification]
    O -->|Publishes to| Q[(Google Cloud PubSub)]
    O --> R[Return Success]
    P --> R[Return Success]
    J --> S[Return Error Response]
    R --> T[Return Success Response]

13.3. Issue Filtering Flow

graph TD
    A[Client] -->|GET /issues with query params| B[issue.router.ts]
    B -->|Routes to| C[issueController.filtered]
    C -->|Creates QueryOptions| D[QueryOptions]
    D -->|Passes to| E[issueUsecase.getFilteredIssuesInOrganizationPaginate]
    E -->|Calls| F[issueRepository.getFilteredIssuesInOrganization]
    F -->|Queries| G[(MongoDB)]
    G -->|Returns raw issues| H[Process issues]
    H -->|Apply filters| I[Filter by user role]
    I -->|Filter by departments| J[Filter by sites]
    J -->|Paginate results| K[Return filtered issues]
    K -->|Return to| L[Controller]
    L -->|Format response| M[Return JSON response]
    M --> N[Client receives response]

14. Notification System

The API-Issues service includes a comprehensive notification system to alert users about issue events. Notifications are triggered by various actions including issue creation, updates, status changes, and messages.

14.1. Notification Types

The service supports several notification triggers:

  • Issue Creation: When a new issue is created
  • Issue Assignment: When an issue is assigned to a user
  • Issue Status Change: When an issue’s status changes (e.g., from open to resolved)
  • Due Date Change: When an issue’s due date is modified
  • Severity Change: When an issue’s severity level changes
  • Priority Change: When an issue’s priority changes
  • Department Assignment Change: When departments assigned to an issue change
  • Message Creation: When a new message is added to an issue
  • Issue Approval: When an issue is approved or rejected

14.2. Notification Channels

Notifications can be delivered through multiple channels:

  • Email: Using SendGrid integration
  • Push Notification: Using Firebase Cloud Messaging
  • In-App Notification: Using the Notification service

14.3. Notification Configuration

Notification settings are stored in MongoDB and managed by the NotificationSettingsRepository. Users can configure which notifications they receive and through which channels.

14.4. Notification Process

The notification process is handled by the NotificationPubsubRepository, which publishes messages to Google Cloud PubSub. These messages are then processed by a separate Notification service.

Example: Issue Update Notification

When an issue is updated, the system:

  1. Identifies the changes (e.g., status, assigned user, etc.)
  2. Determines which users should be notified based on their roles and the notification settings
  3. Generates appropriate notification content for each channel
  4. Publishes notification messages to PubSub
// Example from issueUsecase.ts
private async notifyIssueUpdate(
  ctx: Context<UserAuth>,
  issue: Issue,
  oldIssue: Issue,
  triggerNotification: boolean
): Promise<void> {
  if (!triggerNotification) return;
  
  const statusChanged = issue.status !== oldIssue.status;
  const assignedUserChanged = issue.assignedTo.key !== oldIssue.assignedTo.key;
  const dueDateChanged = issue.dueDate !== oldIssue.dueDate;
  // More change checks...
  
  // Get notification recipients
  const notificationRecipients = await this.getNotificationRecipients(
    ctx,
    issue,
    {
      statusChanged,
      assignedUserChanged,
      dueDateChanged,
      // More change flags...
    }
  );
  
  // For each type of change, notify relevant recipients
  if (statusChanged && notificationRecipients.statusChangeRecipients.length > 0) {
    await this.pubsubNotificationRepository.publish({
      recipients: notificationRecipients.statusChangeRecipients.map(user => user.uid),
      notificationType: 'ISSUE_STATUS_CHANGE',
      title: 'Issue Status Change',
      body: `Issue #${issue.issueID} status changed from ${oldIssue.status} to ${issue.status}`,
      data: {
        issueID: issue.issueID,
        oldStatus: oldIssue.status,
        newStatus: issue.status,
        // More data...
      }
    });
  }
  
  // Similar blocks for other change types...
}

14.5. Issue Notification Flow

graph TD
    A[Issue Created/Updated] -->|Triggers| B[issueUsecase methods]
    B -->|Determines| C{Notification needed?}
    C -->|Yes| D[Identify recipients]
    C -->|No| E[Skip notification]
    D -->|Based on issue properties| F[Get notification settings]
    F -->|Uses| G[notificationSettingsRepository.getSettings]
    G -->|Retrieves from| H[(MongoDB)]
    G -->|Returns settings| I[Determine notification channels]
    I -->|Email?| J[Generate email content]
    I -->|Push?| K[Generate push notification]
    J & K -->|Send using| L[pubsubNotificationRepository.publish]
    L -->|Publishes to| M[(Google Cloud PubSub)]
    M -->|Triggers| N[Notification Service]
    N -->|Sends| O[Notifications to recipients]

14.6. Issue Approval Flow

graph TD
    A[Client] -->|POST /issueApproval/issueApprove| B[issueApproval.router.ts]
    B -->|Routes to| C[issueApprovalController.issueApprove]
    C -->|Extracts payload| D[issueApprovalUsecase.issueApprove]
    D -->|Validates user permissions| E{Authorized?}
    E -->|Yes| F[issueApprovalRepository.approveIssue]
    E -->|No| G[Return Error]
    F -->|Updates approval in| H[(MongoDB)]
    F -->|Update issue status| I[issueRepository.updateIssue]
    I -->|Updates in| J[(MongoDB)]
    F -->|Create history record| K[issueHistoryRepository.create]
    K -->|Saves to| L[(MongoDB)]
    F -->|Send notification| M[pubsubNotificationRepository.publish]
    M -->|Publishes to| N[(Google Cloud PubSub)]
    F & I & K & M --> O[Return Success]
    G --> P[Return Error Response]
    O --> Q[Return Success Response]

15. Application Initialization and Entry Points

The API-Issues service follows a structured initialization process to set up dependencies, establish connections, and start the HTTP server. Understanding this process helps with troubleshooting and maintenance.

15.1. Entry Point

The main entry point for the service is in src/app.ts, which initializes the application and exports a Cloud Function handler:

import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import mongoose from 'mongoose';
import httpContext from 'express-http-context';
import * as traceAgent from '@google-cloud/trace-agent';
 
import routes from './routes';
import { log } from '@nimbly-technologies/nimbly-backend-utils';
 
// Initialize trace agent for Google Cloud monitoring
export const tracer = traceAgent.start({
  serviceContext: {
    service: process.env.SERVICE_NAME,
    version: process.env.npm_package_version,
  },
});
 
// Initialize MongoDB connection
const mongoUri = process.env.MONGODB_URI;
mongoose.connect(mongoUri!, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true,
  useFindAndModify: false,
});
 
const db = mongoose.connection;
db.on('error', err => {
  log.error(`MongoDB connection error: ${err}`);
  process.exit(1);
});
db.once('open', () => {
  log.info('Connected to MongoDB');
});
 
// Create Express application
const app = express();
 
// Apply middleware
app.use(cors());
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(httpContext.middleware);
 
// Apply routes
app.use('/api/v1', routes);
 
// Error handling middleware
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  log.error(`Unhandled error: ${err.message}`, {
    stack: err.stack,
    code: err.code,
  });
  
  res.status(500).json({
    message: 'Internal Server Error',
    error: err.message,
  });
});
 
// Export the Cloud Function handler
export const apiIssues = (req: any, res: any) => {
  return app(req, res);
};
 
// For local development
if (process.env.NODE_ENV === 'development') {
  const PORT = process.env.PORT || 3000;
  app.listen(PORT, () => {
    log.info(`API-Issues service running on port ${PORT}`);
  });
}

15.2. Dependency Initialization

The service initializes various dependencies in the domain.ts file, which serves as a dependency injection container:

// From src/routes/domain.ts
import { database, firestore, messaging } from 'firebase-admin';
import mongoose from 'mongoose';
import sgMail from '@sendgrid/mail';
// Other imports...
 
// Initialize SendGrid
sgMail.setApiKey(process.env.SENDGRID_KEY);
 
// Firebase instances
const firebaseDatabase = database();
const firestoreDatabase = firestore();
const firebaseMessaging = messaging();
 
// Build repositories
const fileMetadataRepository = new FileMetadataRepository(mongoose.connection, tracer);
const userRepository = new UserMongoRepository(mongoose.connection, tracer);
// Other repositories...
 
// Build use cases
const issueMessageUsecase = new IssueMessageUsecase({
  issueMessageRepository,
  pubsubRepository,
  fileMetadataRepository,
  // Other dependencies...
});
// Other use cases...
 
// Build controllers
const issueController = new IssueController(issueUsecase);
const issueApprovalController = new IssueApprovalController(issueApprovalUsecase);
// Other controllers...
 
// Export domain objects
export default {
  issueController,
  indicatorController,
  issueUserReadStatusController,
  issueValidator,
  // Other exports...
};

15.3. Firebase Initialization

Firebase is initialized in src/config/firebase.ts:

import * as admin from 'firebase-admin';
import { log } from '@nimbly-technologies/nimbly-backend-utils';
 
try {
  // Initialize Firebase with provided credentials
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
    }),
    databaseURL: process.env.FIREBASE_DATABASE_URL,
  });
  
  log.info('Firebase initialized successfully');
} catch (error) {
  log.error(`Firebase initialization error: ${error.message}`);
  throw error;
}
 
export default admin;

15.4. Route Registration

Routes are registered in src/routes/index.ts:

import { Router } from 'express';
import { tracer } from '../app';
 
import issueRouter from './issue.router';
import issueMessageRouter from './issueMessage.router';
import autoDueDateRouter from './autoDueDate.router';
import healthIndicators from './health.router';
import issueTrackerFilterRouter from './issueTrackerFilter.router';
import issueApprovalRouter from './issueApproval.router';
 
import { log } from '@nimbly-technologies/nimbly-backend-utils';
 
const SERVICE_NAME = process.env.SERVICE_NAME!;
 
const router = Router();
 
// Add trace handler for request tracing
router.use(tracer.expressRequestHandler(SERVICE_NAME));
 
// Add logging middleware
router.use((req, res, next) => {
  log.info('Called: ', req.path);
  next();
});
 
// Register route handlers
router.use('/issues', issueRouter);
router.use('/issueApproval', issueApprovalRouter);
router.use('/issueMessages', issueMessageRouter);
router.use('/auto-due-dates', autoDueDateRouter);
router.use('/healthIndicators', healthIndicators);
router.use('/issue-tracker-filters', issueTrackerFilterRouter);
 
export default router;

15.5. Initialization Flow

graph TD
    A[Application Start] -->|Load| B[Environment Variables]
    B -->|Initialize| C[Trace Agent]
    C -->|Connect to| D[MongoDB]
    D -->|Initialize| E[Firebase Admin]
    E -->|Create| F[Express Application]
    F -->|Apply| G[Middleware]
    G -->|Register| H[Routes]
    H -->|Instantiate| I[Controllers]
    I -->|Instantiate| J[Use Cases]
    J -->|Instantiate| K[Repositories]
    K -->|Connect to| L[External Services]
    L -->|Start| M[HTTP Server]

16. Enhanced API Endpoints

The API-Issues service provides a comprehensive set of endpoints for managing issues, messages, approvals, and related resources. Below is a detailed overview of key endpoints with their purposes, parameters, and responses.

16.1. Issue Management Endpoints

EndpointMethodPurposeKey ParametersResponse
/issuesGETGet filtered issue dataresolvedAt[startAt], resolvedAt[endAt], dueDate[startAt], dueDate[endAt], qDepartment, qStatus, qPriority, sortBy, sortTypeArray of filtered issues with pagination
/issues/v2GETEnhanced filtered issue dataSame as above, plus additional query optionsArray of filtered issues with extended data and pagination
/issues/v3POSTFilter issues with body parametersFilter criteria in request bodyArray of filtered issues with pagination
/issues/countGETCount total issuesFilter criteria as query parametersCount of matching issues
/issues/:issueIDGETGet single issue dataissueID in pathSingle issue data
/issues/v2/:issueIDGETGet extended issue dataissueID in pathExtended issue data with additional properties
/issues/createPOSTCreate new issueassignedDepartments, category, createdBy, priority, severity, siteID, statusCreated issue ID
/issues/updatePUTUpdate issue propertiesissueID, properties to updateUpdated issue properties
/issues/bulkUpdatePUTUpdate multiple issuesissueIDs, properties to updateUpdate result
/issues/quickResolvePUTQuickly resolve an issueissueID, resolvedByUpdate result
/issues/:issueID/statusHistoryGETGet issue status historyissueID in pathTimeline of status changes
/issues/:issueID/[notification](../Settings/Notification/NotificationOverview.md)POSTSend issue notificationissueID in path, recipient info in bodyNotification result

16.2. Issue Message Endpoints

EndpointMethodPurposeKey ParametersResponse
/issueMessagesGETGet issue messagesissueID, sortBy, sortTypeArray of messages
/issueMessages/:messageIDGETGet specific messagemessageID in pathSingle message data
/issueMessages/createPOSTCreate new messageissueID, message, type, attachmentsCreated message ID
/issueMessages/create/bulkPOSTCreate multiple messagesArray of message dataArray of created message IDs
/issueMessages/:messageIDPUTUpdate messagemessageID in path, properties to updateUpdate result
/issueMessages/:messageIDDELETEDelete messagemessageID in pathDelete result
/issueMessages/resolveAllRequiredMessagePOSTResolve all required messagesissueIDResolution result

16.3. Issue Approval Endpoints

EndpointMethodPurposeKey ParametersResponse
/issueApproval/issueApprovePOSTApprove an issueissueID, commentsApproval result
/issueApproval/issueRejectPOSTReject an issueissueID, commentsRejection result
/issueApproval/issueBulkApprovePOSTBulk approve issuesissueIDs, commentsBulk approval result
/issueApproval/issueBulkRejectPOSTBulk reject issuesissueIDs, commentsBulk rejection result
/issueApproval/validateMediaApprovalTokenPOSTValidate media approval tokentokenValidation result

16.4. Auto Due Date Endpoints

EndpointMethodPurposeKey ParametersResponse
/auto-due-datesGETGet auto due dates-Array of auto due date configurations
/auto-due-dates/:idGETGet specific due date configid in pathSingle auto due date configuration
/auto-due-datesPOSTCreate auto due date configsiteID, period, unitCreated configuration ID
/auto-due-dates/:idPUTUpdate auto due date configid in path, properties to updateUpdate result
/auto-due-dates/:idDELETEDelete auto due date configid in pathDelete result

16.5. Issue Tracker Filter Endpoints

EndpointMethodPurposeKey ParametersResponse
/issue-tracker-filtersGETGet issue tracker filters-Array of filter configurations
/issue-tracker-filters/:idGETGet specific filterid in pathSingle filter configuration
/issue-tracker-filtersPOSTCreate filtername, isDefault, filterConfigCreated filter ID
/issue-tracker-filters/:idPUTUpdate filterid in path, properties to updateUpdate result
/issue-tracker-filters/:idDELETEDelete filterid in pathDelete result

17. Conclusion

The API-Issues service is a comprehensive solution for issue tracking and management within the Nimbly Technologies ecosystem. The service follows a well-structured architecture with clear separation of concerns among controllers, use cases, and repositories.

17.1. Key Features

  • Issue Management: Creating, updating, filtering, and resolving issues
  • Issue Messages: Communication and collaboration through issue messages
  • File Attachments: Support for file uploads and attachments to issues and messages
  • Notifications: Comprehensive notification system for issue events
  • History Tracking: Detailed history and audit trail for issues
  • Approval Workflows: Support for issue approval processes
  • Auto Due Dates: Automatic deadline assignment based on configurable rules
  • Custom Filters: Saved filter configurations for efficient issue monitoring
  • Health Monitoring: Endpoints for monitoring service health and performance

17.2. Technology Stack

The service leverages modern technologies and follows best practices:

  • Node.js: JavaScript runtime environment
  • Express: Web framework for RESTful API implementation
  • TypeScript: Type safety and improved developer experience
  • MongoDB: Primary database for persistent storage
  • Firebase: Real-time database for specific use cases
  • Google Cloud Platform: Infrastructure for deployment and operations
  • PubSub: Messaging for cross-service communication
  • Cloud Storage: File storage for attachments

17.3. Integration Points

The service integrates with various other services in the ecosystem:

  • Notification Service: For delivering notifications through multiple channels
  • Attachment Gallery: For centralized management of attachments
  • Entity Service: For shared entity definitions and repositories
  • Access Control: For authentication and authorization

This comprehensive documentation provides a deep understanding of the API-Issues service, tracing function flows from endpoints to their implementations. It covers the architecture, main components, flows, and implementation details to facilitate understanding and maintenance of the service. then echo “::set-output name=environment::production” echo “::set-output name=memory::2048MB” else echo “::set-output name=environment::staging” echo “::set-output name=memory::1024MB” fi

  - name: Deploy to Cloud Functions
    run: |
      bash scripts/deployment/deploy.sh ${{ steps.env.outputs.environment }} --memory ${{ steps.env.outputs.memory }}

### Environment Configuration

Environment variables are managed using configuration files and scripts:

- `.env`: Local development environment variables
- `.config-staging/`: Staging environment configuration
- `.config-production/`: Production environment configuration
- `scripts/env/`: Scripts for managing environment variables

The service uses the `dotenv` package to load environment variables during local development.

### Monitoring and Logging

The service integrates with Google Cloud monitoring and logging:

- **Trace Agent**: The `@google-cloud/trace-agent` package is used for distributed tracing
- **Logging**: The service uses structured logging via the `log` utility from `@nimbly-technologies/nimbly-backend-utils`
- **Error Reporting**: Errors are captured and reported to Google Cloud Error Reporting

Example logging usage:

```typescript
import { log } from '@nimbly-technologies/nimbly-backend-utils';

try {
  // Some operation
} catch (error) {
  log.error(`[ERROR] Operation failed: ${error.message}`, {
    code: error.code,
    stack: error.stack,
    context: {
      userId: ctx.user.uid,
      organizationId: ctx.user.organizationID,
    },
  });
  
  throw error;
}

17.5. Resource Requirements

The service is configured with the following resource allocations:

  • Staging: 1024MB memory, 540s timeout
  • Production: 2048MB memory, 540s timeout

These allocations can be adjusted based on load and performance requirements.


This comprehensive documentation provides a deep understanding of the API-Issues service, tracing function flows from endpoints to their implementations. The documentation covers the architecture, main components, flows, and implementation details to facilitate understanding and maintenance of the service.<AutoDueDate | null> { const autoDueDate = await this.autoDueDateModel.findById(id).lean(); if (!autoDueDate) return null;

return {
  id: autoDueDate._id.toString(),
  siteID: autoDueDate.siteID,
  period: autoDueDate.period,
  unit: autoDueDate.unit,
  createdAt: autoDueDate.createdAt,
  updatedAt: autoDueDate.updatedAt,
  isActive: autoDueDate.isActive,
};

}

async getAll(): Promise<AutoDueDate[]> { const autoDueDates = await this.autoDueDateModel.find().lean();

return autoDueDates.map(autoDueDate => ({
  id: autoDueDate._id.toString(),
  siteID: autoDueDate.siteID,
  period: autoDueDate.period,
  unit: autoDueDate.unit,
  createdAt: autoDueDate.createdAt,
  updatedAt: autoDueDate.updatedAt,
  isActive: autoDueDate.isActive,
}));

}

async getBySiteID(siteID: string): Promise<AutoDueDate | null> { const autoDueDate = await this.autoDueDateModel.findOne({ siteID, isActive: true }).lean(); if (!autoDueDate) return null;

return {
  id: autoDueDate._id.toString(),
  siteID: autoDueDate.siteID,
  period: autoDueDate.period,
  unit: autoDueDate.unit,
  createdAt: autoDueDate.createdAt,
  updatedAt: autoDueDate.updatedAt,
  isActive: autoDueDate.isActive,
};

} }


### Auto Due Date Use Case

The `AutoDueDateUsecase` in `src/domains/autoDueDate/usecase/autoDueDate.usecase.ts` implements the business logic for auto due date management:

```typescript
export default class AutoDueDateUsecase {
  constructor(
    private autoDueDateRepository: AutoDueDateMongoRepository,
    private siteRepository: ISiteRepository
  ) {}

  async create(data: AutoDueDateCreateParams): Promise<string> {
    // Verify site exists
    const site = await this.siteRepository.getSite(data.siteID);
    if (!site) {
      throw new Error('Site not found');
    }

    // Check if configuration already exists for this site
    const existingConfig = await this.autoDueDateRepository.getBySiteID(data.siteID);
    if (existingConfig) {
      throw new Error('Auto due date configuration already exists for this site');
    }

    return this.autoDueDateRepository.create(data);
  }

  async update(id: string, data: AutoDueDateUpdateParams): Promise<void> {
    // Verify configuration exists
    const config = await this.autoDueDateRepository.getById(id);
    if (!config) {
      throw new Error('Auto due date configuration not found');
    }

    // If siteID is being changed, verify new site exists
    if (data.siteID) {
      const site = await this.siteRepository.getSite(data.siteID);
      if (!site) {
        throw new Error('Site not found');
      }
      
      // Check if configuration already exists for the new site
      if (data.siteID !== config.siteID) {
        const existingConfig = await this.autoDueDateRepository.getBySiteID(data.siteID);
        if (existingConfig) {
          throw new Error('Auto due date configuration already exists for this site');
        }
      }
    }

    await this.autoDueDateRepository.update(id, data);
  }

  async delete(id: string): Promise<void> {
    // Verify configuration exists
    const config = await this.autoDueDateRepository.getById(id);
    if (!config) {
      throw new Error('Auto due date configuration not found');
    }

    await this.autoDueDateRepository.delete(id);
  }

  async getAll(): Promise<AutoDueDate[]> {
    return this.autoDueDateRepository.getAll();
  }

  async getById(id: string): Promise<AutoDueDate | null> {
    return this.autoDueDateRepository.getById(id);
  }

  async calculateDueDate(siteID: string, creationDate: string): Promise<string | null> {
    const config = await this.autoDueDateRepository.getBySiteID(siteID);
    if (!config || !config.isActive) {
      return null;
    }

    const baseDate = moment(creationDate);
    
    switch (config.unit) {
      case PeriodUnit.DAYS:
        return baseDate.add(config.period, 'days').toISOString();
      case PeriodUnit.WEEKS:
        return baseDate.add(config.period, 'weeks').toISOString();
      case PeriodUnit.MONTHS:
        return baseDate.add(config.period, 'months').toISOString();
      default:
        return null;
    }
  }
}

17.6. Integration with Issue Creation

When an issue is created, the auto due date configuration is used to automatically set the due date if one is not explicitly provided:

// From issueUsecase.ts - simplified for clarity
public async addIssues(
  ctx: Context<UserAuth>,
  data: CreateIssueRequest,
  triggerNotification: boolean
): Promise<string> {
  // Other validation and processing...
  
  // If due date not provided, try to get from auto due date configuration
  if (!data.dueDate) {
    const autoDueDate = await this.getIssueAutoDueDate(data.siteID, new Date().toISOString());
    if (autoDueDate) {
      data.dueDate = autoDueDate;
    }
  }
  
  // Continue with issue creation...
}
 
private async getIssueAutoDueDate(siteID: string, creationDate: string): Promise<string | null> {
  try {
    const autoDueDate = await this.autoDueDateRepository.getBySiteID(siteID);
    if (!autoDueDate || !autoDueDate.isActive) {
      return null;
    }
    
    // Calculate due date based on configuration
    return getIssueAutoDueDates(autoDueDate.period, autoDueDate.unit, creationDate);
  } catch (error) {
    log.error(`[ERROR] Failed to get auto due date: ${error.message}`);
    return null;
  }
}

The getIssueAutoDueDates utility function performs the actual calculation:

// From src/domains/issue/utils/getIssueAutoDueDates.ts
export default function getIssueAutoDueDates(
  period: number,
  unit: PeriodUnit,
  date: string
): string {
  const baseDate = moment(date);
  
  switch (unit) {
    case PeriodUnit.DAYS:
      return baseDate.add(period, 'days').toISOString();
    case PeriodUnit.WEEKS:
      return baseDate.add(period, 'weeks').toISOString();
    case PeriodUnit.MONTHS:
      return baseDate.add(period, 'months').toISOString();
    default:
      throw new Error('Invalid period unit');
  }
}

17.7. Auto Due Date Flow

graph TD
    A[Client] -->|POST /auto-due-dates| B[autoDueDate.router.ts]
    B -->|Routes to| C[autoDueDateController.create]
    C -->|Extracts payload| D[autoDueDateUsecase.create]
    D -->|Verify site exists| E[siteRepository.getSite]
    E -->|Gets from| F[(MongoDB)]
    D -->|Check for existing config| G[autoDueDateRepository.getBySiteID]
    G -->|Gets from| H[(MongoDB)]
    D -->|Create new config| I[autoDueDateRepository.create]
    I -->|Saves to| J[(MongoDB)]
    I --> K[Return success]

    L[Issue Creation] -->|No explicit due date| M[issueUsecase.getIssueAutoDueDate]
    M -->|Get configuration| N[autoDueDateRepository.getBySiteID]
    N -->|Gets from| O[(MongoDB)]
    N -->|If active| P[Calculate due date]
    P -->|Use calculated date| Q[Set issue due date]