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
| Endpoint | Method | Description |
|---|---|---|
/issues | GET | Get filtered issue data based on parameters |
/issues/v2 | GET | Get filtered issue data (version 2) |
/issues/v3 | POST | Get filtered issue data (version 3) |
/issues/v2 | POST | Get filtered issue data (version 1 POST method) |
/issues/count | GET | Count total issues |
/issues/count | POST | Count total issues (v1) |
/issues/zapier | GET | Get issue data for Zapier integration |
/issues/:issueID | GET | Get single issue data |
/issues/v2/:issueID | GET | Get single extended issue data |
/issues/create/effect | POST | Create issue with side effects |
/issues/create | POST | Create new issue |
/issues/create/v2 | POST | Create new issue (version 2) |
/issues/report-issue | POST | Create issue from report |
/issues/cf-issue | POST | Create issue from custom form |
/issues/custom-deadline | POST | Set custom deadline for issue |
/issues/update/effect | POST | Update issue with side effects |
/issues/update | PUT | Update issue properties |
/issues/bulkUpdate | PUT | Bulk update multiple issues |
/issues/quickResolve | PUT | Quick resolve issue |
/issues/:issueID/notification | POST | Send issue notification to auditors |
/issues/:issueID/statusHistory | GET | Get issue status history |
/issues/:issueID/notification/:auditorID | POST | Send issue notification to specific auditor |
/issues/internal/update | PUT | Internal update for issues |
/issues/readStatus | POST | Send read status |
/issues/readStatus/upsert | POST | Upsert read status |
/issues/readStatus/sync | POST | Re-sync issue status |
/issues/readAll | DELETE | Delete all read status by user |
/issues/readStatus/:issueID | GET | Get issue user read status |
/issues/readStatus/bulk-update | POST | Bulk update issue user read status |
/issueMessages | GET | Get issue messages |
/issueMessages/:messageID | GET | Get specific issue message |
/issueMessages/create | POST | Create issue message |
/issueMessages/create/bulk | POST | Create bulk issue messages |
/issueMessages/resolveAllRequiredMessage | POST | Resolve all required messages |
/issueMessages/checkAllRequiredMessage | POST | Check all required messages |
/issueMessages/:messageID | PUT | Update issue message |
/issueMessages/:messageID | DELETE | Delete issue message |
/issueApproval/issueBulkApprove | POST | Bulk approve issues |
/issueApproval/issueBulkReject | POST | Bulk reject issues |
/issueApproval/issueApprove | POST | Approve single issue |
/issueApproval/issueReject | POST | Reject single issue |
/issueApproval/validateMediaApprovalToken | POST | Validate media approval token |
/auto-due-dates | GET | Get auto due dates |
/auto-due-dates/:id | GET | Get specific auto due date |
/auto-due-dates | POST | Create auto due date |
/auto-due-dates/:id | PUT | Update auto due date |
/auto-due-dates/:id | DELETE | Delete auto due date |
/healthIndicators | GET | Get health indicators |
/issue-tracker-filters | GET | Get issue tracker filters |
/issue-tracker-filters/:id | GET | Get specific issue tracker filter |
/issue-tracker-filters | POST | Create issue tracker filter |
/issue-tracker-filters/:id | PUT | Update issue tracker filter |
/issue-tracker-filters/:id | DELETE | Delete 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:
- Routers: Define API endpoints and HTTP method handlers
- Controllers: Handle HTTP requests and responses
- Use Cases: Implement business logic
- Repositories: Provide data access abstractions
- 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 identifierassignedDepartments: Departments assigned to the issueassignedTo: User assigned to the issuecategory: Issue categorycreatedAt: Creation timestampcreatedBy: User who created the issuedueDate: Due date for resolutionpriority: Issue priority (normal/high)questionText: Description of the issueresolvedAt: Resolution timestampresolvedBy: User who resolved the issueseverity: 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 identifierissueID: Related issuemessage: Message contentcreatedAt: Creation timestampcreatedBy: User who created the messageattachments: Attached files
6.3. Issue Approval
Handles approval workflows for issues:
approvalID: Unique identifierissueID: Related issuestatus: Approval statusapprovedBy: User who approvedrejectedBy: User who rejectedcomments: Approval comments
6.4. Auto Due Date
Configuration for automatic due date assignment:
id: Unique identifiersiteID: Related siteperiod: Time periodunit: Time unit (days, weeks, etc.)
6.5. Issue Tracker Filter
Custom filters for issue tracking:
id: Unique identifiername: Filter namecriteria: Filter criteriacreatedBy: User who created the filterisDefault: 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 issuessingleIssue: Get a single issuecreate: Create new issueupdate: Update issuebulkUpdate: Bulk update issuesquickResolve: Quick resolve an issuepushNotification: Send issue notificationsstatusHistory: Get issue status history
7.2. Issue Message Controller
Located in src/controller/issueMessage.controller.ts, handles issue message endpoints:
create: Create new messagecreateBulk: Create multiple messagesgetMessage: Get a specific messagegetMessages: Get all messages for an issueupdate: Update a messagedelete: Delete a message
7.3. Issue Approval Controller
Located in src/controller/issueApproval.controller.ts, handles approval workflows:
issueApprove: Approve an issueissueReject: Reject an issueissueBulkApprove: Bulk approve issuesissueBulkReject: 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 datesgetById: Get a specific auto due datecreate: Create new auto due dateupdate: Update auto due datedelete: Delete auto due date
7.5. Issue Tracker Filter Controller
Located in src/controller/issueTrackerFilter.controller.ts, manages filter configurations:
getAll: Get all filtersgetById: Get a specific filtercreate: Create new filterupdate: Update filterdelete: 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 issuesupdateIssues: Update existing issuesbulkUpdateIssues: Update multiple issuesgetSingleIssue: Get a specific issuegetFilteredIssuesInOrganizationPaginate: Get filtered issues with paginationcreateIssueFromReport: Create an issue from a reportstatusHistory: 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 messagecreateBulkIssueMessage: Create multiple messagesupdateIssueMessage: Update a messagedeleteIssueMessage: Delete a messagegetIssueMessages: 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 issueupsertReadStatus: Create or update read statusreSyncIssueStatus: Synchronize issue read statusgetIssueUserReadStatus: 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 issueissueReject: Reject an issueissueBulkApprove: Bulk approve issuesissueBulkReject: Bulk reject issues
8.5. Auto Due Date Use Case
Located in src/domains/autoDueDate/usecase/autoDueDate.usecase.ts:
create: Create auto due date configurationupdate: Update configurationdelete: Delete configurationgetAll: Get all configurationsgetById: Get specific configuration
8.6. Issue Tracker Filter Use Case
Located in src/domains/issueTrackerFilter/usecase/issueTrackerFilter.usecase.ts:
create: Create filterupdate: Update filterdelete: Delete filtergetAll: Get all filtersgetById: 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 issuegetFilteredIssuesInOrganization: Get filtered issuescreateIssue: Create a new issueupdateIssue: Update an issuedeleteIssue: Delete an issue
9.2. Issue Message Repository
Located in src/domains/issueMessage/repository/issueMessage.repository.ts:
createIssueMessage: Create a messageupdateIssueMessage: Update a messagedeleteIssueMessage: Delete a messagegetIssueMessages: 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 statusupsertReadStatus: Create or update read statusgetIssueUserReadStatus: Get read status for an issue
9.4. Issue Approval Repository
Located in src/domains/issueApproval/repository/issueApproval.repository.ts:
approveIssue: Approve an issuerejectIssue: Reject an issuegetIssueApproval: Get approval status
9.5. Auto Due Date Repository
Located in src/domains/autoDueDate/repository/autoDueDate.mongo.repository.ts:
create: Create configurationupdate: Update configurationdelete: Delete configurationgetAll: Get all configurationsgetById: Get specific configuration
9.6. Issue Tracker Filter Repository
Located in src/routes/issueTrackerFilter.router.ts:
create: Create filterupdate: Update filterdelete: Delete filtergetAll: Get all filtersgetById: Get specific filter
10. Implementation Details
10.1. Issue Creation Flow
The issue creation process follows these steps:
- Client sends a POST request to
/issues/create - The request is routed through
issue.router.tstoissueController.create issueController.createextracts the request payload and callsissueUsecase.addIssuesissueUsecase.addIssuesperforms validation and creates the issue- The issue is saved to MongoDB through the
issueRepository.createIssuemethod - If notification is enabled, notifications are sent using the
NotificationPubsubRepository - The controller returns a success response with the created issue ID
10.2. Issue Update Flow
The issue update process follows these steps:
- Client sends a PUT request to
/issues/update - The request is routed through
issue.router.tstoissueController.update issueController.updateextracts the request payload and callsissueUsecase.updateIssuesissueUsecase.updateIssuesretrieves the existing issue, validates the changes, and updates it- The issue is updated in MongoDB through the
issueRepository.updateIssuemethod - Issue history is recorded using the
issueHistoryRepository.createmethod - If notification is enabled, notifications are sent using the
NotificationPubsubRepository - The controller returns a success response with the updated issue properties
10.3. Issue Notification Flow
The notification process follows these steps:
- When an issue is created, updated, or requires notification
- The appropriate use case calls
pubsubNotificationRepository.publish - A message is published to Google Cloud PubSub
- The Notification service receives the message and processes it
- 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:
- Client sends a POST request to
/issues/readStatus - The request is routed to
issueUserReadStatusController.sendReadStatus issueUserReadStatusController.sendReadStatuscallsissueUserReadStatusUsecase.sendReadStatus- The read status is updated in Firebase through
issueUserReadStatusRepository.sendReadStatus - 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:
- User roles and permissions are checked for specific operations
- Department/site access is validated based on user context
- Operations are restricted based on user’s role in the organization
12. Error Handling
The service implements a consistent error handling pattern:
- Errors are caught at the use case level
- HTTP-appropriate error codes and messages are generated
- 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 errorerrors.INVALID_DATA: Invalid data formaterrors.NOT_FOUND: Resource not founderrors.UNAUTHORIZED: Unauthorized accesserrors.FORBIDDEN: Forbidden accesserrors.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:
- Identifies the changes (e.g., status, assigned user, etc.)
- Determines which users should be notified based on their roles and the notification settings
- Generates appropriate notification content for each channel
- 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
| Endpoint | Method | Purpose | Key Parameters | Response |
|---|---|---|---|---|
/issues | GET | Get filtered issue data | resolvedAt[startAt], resolvedAt[endAt], dueDate[startAt], dueDate[endAt], qDepartment, qStatus, qPriority, sortBy, sortType | Array of filtered issues with pagination |
/issues/v2 | GET | Enhanced filtered issue data | Same as above, plus additional query options | Array of filtered issues with extended data and pagination |
/issues/v3 | POST | Filter issues with body parameters | Filter criteria in request body | Array of filtered issues with pagination |
/issues/count | GET | Count total issues | Filter criteria as query parameters | Count of matching issues |
/issues/:issueID | GET | Get single issue data | issueID in path | Single issue data |
/issues/v2/:issueID | GET | Get extended issue data | issueID in path | Extended issue data with additional properties |
/issues/create | POST | Create new issue | assignedDepartments, category, createdBy, priority, severity, siteID, status | Created issue ID |
/issues/update | PUT | Update issue properties | issueID, properties to update | Updated issue properties |
/issues/bulkUpdate | PUT | Update multiple issues | issueIDs, properties to update | Update result |
/issues/quickResolve | PUT | Quickly resolve an issue | issueID, resolvedBy | Update result |
/issues/:issueID/statusHistory | GET | Get issue status history | issueID in path | Timeline of status changes |
/issues/:issueID/[notification](../Settings/Notification/NotificationOverview.md) | POST | Send issue notification | issueID in path, recipient info in body | Notification result |
16.2. Issue Message Endpoints
| Endpoint | Method | Purpose | Key Parameters | Response |
|---|---|---|---|---|
/issueMessages | GET | Get issue messages | issueID, sortBy, sortType | Array of messages |
/issueMessages/:messageID | GET | Get specific message | messageID in path | Single message data |
/issueMessages/create | POST | Create new message | issueID, message, type, attachments | Created message ID |
/issueMessages/create/bulk | POST | Create multiple messages | Array of message data | Array of created message IDs |
/issueMessages/:messageID | PUT | Update message | messageID in path, properties to update | Update result |
/issueMessages/:messageID | DELETE | Delete message | messageID in path | Delete result |
/issueMessages/resolveAllRequiredMessage | POST | Resolve all required messages | issueID | Resolution result |
16.3. Issue Approval Endpoints
| Endpoint | Method | Purpose | Key Parameters | Response |
|---|---|---|---|---|
/issueApproval/issueApprove | POST | Approve an issue | issueID, comments | Approval result |
/issueApproval/issueReject | POST | Reject an issue | issueID, comments | Rejection result |
/issueApproval/issueBulkApprove | POST | Bulk approve issues | issueIDs, comments | Bulk approval result |
/issueApproval/issueBulkReject | POST | Bulk reject issues | issueIDs, comments | Bulk rejection result |
/issueApproval/validateMediaApprovalToken | POST | Validate media approval token | token | Validation result |
16.4. Auto Due Date Endpoints
| Endpoint | Method | Purpose | Key Parameters | Response |
|---|---|---|---|---|
/auto-due-dates | GET | Get auto due dates | - | Array of auto due date configurations |
/auto-due-dates/:id | GET | Get specific due date config | id in path | Single auto due date configuration |
/auto-due-dates | POST | Create auto due date config | siteID, period, unit | Created configuration ID |
/auto-due-dates/:id | PUT | Update auto due date config | id in path, properties to update | Update result |
/auto-due-dates/:id | DELETE | Delete auto due date config | id in path | Delete result |
16.5. Issue Tracker Filter Endpoints
| Endpoint | Method | Purpose | Key Parameters | Response |
|---|---|---|---|---|
/issue-tracker-filters | GET | Get issue tracker filters | - | Array of filter configurations |
/issue-tracker-filters/:id | GET | Get specific filter | id in path | Single filter configuration |
/issue-tracker-filters | POST | Create filter | name, isDefault, filterConfig | Created filter ID |
/issue-tracker-filters/:id | PUT | Update filter | id in path, properties to update | Update result |
/issue-tracker-filters/:id | DELETE | Delete filter | id in path | Delete 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]