Developer Docs

Community Orchestration

Assignment workflows for Issues and Discussions with real-time notification dispatch to Malet staff via the Alerts service.


Architecture Overview

Community Orchestration bridges two services: the community subgraph owns the assignment state machine and GraphQL API, while the alerts subgraph handles notification fan-out with preference-aware, multi-channel delivery.

Component Service Transport Purpose
Assignment Mutations community GraphQL Assign, unassign, state-change
Event Dispatch community โ†’ alerts TCP (NestJS Microservices) Fire-and-forget event emission
Notification Delivery alerts Email + Push Preference-based routing

Event Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    TCP emit     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Community Service  โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚   Alerts Service    โ”‚
โ”‚                     โ”‚                 โ”‚                     โ”‚
โ”‚  AssignmentService  โ”‚  issue_assigned โ”‚ CommunityAlerts-    โ”‚
โ”‚  AssignmentResolver โ”‚  issue_created  โ”‚ Controller          โ”‚
โ”‚  AlertsClient       โ”‚  state_changed  โ”‚                     โ”‚
โ”‚                     โ”‚  disc_assigned  โ”‚  โ†’ NodesClient      โ”‚
โ”‚                     โ”‚  disc_created   โ”‚  โ†’ AlertDelivery    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                               โ”‚
                                     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                                     โ–ผ         โ–ผ         โ–ผ
                                   Email     Push    (In-App)

GraphQL Mutations

Issue Assignment

Assign a Malet staff member to triage an Issue. Only Malet Owners and authorized members should execute assignment mutations.

mutation AssignIssue($input: AssignIssueInput!) {
	assignIssue(input: $input) {
		id
		issueID
		title
		assignedTo
		assignedAt
		state
	}
}

Variables:

{
	"input": {
		"issueId": "680abcdef1234567890abcde",
		"assigneeId": "user-staff-member"
	}
}

Unassign Issue

Remove the current assignee from an Issue, returning it to the unassigned queue.

mutation UnassignIssue($issueId: ID!) {
	unassignIssue(issueId: $issueId) {
		id
		assignedTo
	}
}

Change Issue State

Transition an Issue through the state machine: OPEN โ†’ IN_PROGRESS โ†’ RESOLVED โ†’ CLOSED.

mutation ChangeIssueState($issueId: ID!, $state: IssueState!) {
	changeIssueState(issueId: $issueId, state: $state) {
		id
		issueID
		state
	}
}

Valid states: OPEN, IN_PROGRESS, RESOLVED, CLOSED

When an Issue transitions to CLOSED, the system automatically records a resolvedAt timestamp for SLA compliance tracking.


Discussion Assignment

Discussions follow the same assignment pattern as Issues, allowing Malet staff to take ownership.

mutation AssignDiscussion($input: AssignDiscussionInput!) {
	assignDiscussion(input: $input) {
		id
		discussionID
		name
		assignedTo
		assignedAt
	}
}
mutation UnassignDiscussion($discussionId: ID!) {
	unassignDiscussion(discussionId: $discussionId) {
		id
		assignedTo
	}
}

Event Types

Every assignment and state-change mutation emits a TCP event to the Alerts service. These are fire-and-forget โ€” the community service does not wait for delivery confirmation.

Event Trigger Notification
issue_created New Issue submitted by a Visitor Email to Malet staff
issue_assigned Issue assigned to a staff member Email + Push to assignee
issue_unassigned Assignee removed from Issue โ€”
issue_state_changed Issue state transition Email to Malet staff
discussion_created New Discussion started Email to Malet staff
discussion_assigned Discussion assigned to staff Email + Push to assignee
discussion_unassigned Assignee removed from Discussion โ€”

Event Payload Example

// issue_assigned event payload
interface IssueAssignedEvent {
	issueId: string; // MongoDB document ID
	issueID: string; // Human-readable ID (e.g. "I-a8f3b2")
	title: string; // Issue title
	subjectId: string; // Malet, Product, or Service ID
	subjectType: string; // "MALET" | "PRODUCT" | "SERVICE" | "ORDER"
	assignedTo: string; // User ID of the assignee
	assignedBy: string; // User ID of the actor who assigned
}

Notification Delivery

The CommunityAlertsController in the Alerts service consumes events and applies preference-aware routing before dispatching.

Delivery Logic

  1. Profile Resolution โ€” Fetch assignee profile via NodesClientService to obtain email, push tokens, and notification preferences.
  2. Quiet Hours Check โ€” If the user has configured quiet hours (e.g. 22:00โ€“08:00), notifications are suppressed during that window.
  3. Channel Dispatch โ€” Based on the user's saved preferences:
    • Email: Always sent for assigned and state_changed events (unless quiet hours apply).
    • Push: Sent for assigned events and URGENT priority issue_created events.
  4. Priority Escalation โ€” Issues with URGENT priority override normal rules and trigger immediate push notifications on creation, ensuring critical customer issues surface instantly.

Preference Integration

Notification preferences are managed via the existing Account Settings โ†’ Notifications panel. The community events respect the same email/sms/push toggles used by Murchase and Workroom notifications.

// Preferences checked per delivery
type Channel = 'email' | 'sms' | 'push';
type Category = 'murchases' | 'workroom' | 'promotions' | 'security' | 'community';

Entity Schema

Issue Fields (Added)

Field Type Description
assignedTo String? User ID of the assigned Malet staff member
assignedAt DateTime? Timestamp when the assignment was made

Discussion Fields (Added)

Field Type Description
assignedTo String? User ID of the assigned Malet staff member
assignedAt DateTime? Timestamp when the assignment was made

Environment Configuration

The community service requires TCP connectivity to the alerts service for event dispatch.

# Community service (.env)
COMMUNITY_SERVICE_PORT=3007
ALERTS_SERVICE_HOST=localhost      # Alerts TCP hostname
ALERTS_SERVICE_PORT_TCP=3012      # Alerts TCP port

No configuration changes are needed on the alerts service โ€” it already listens on the configured TCP port.


Module Structure

apps/community/src/
โ”œโ”€โ”€ assignment/
โ”‚   โ”œโ”€โ”€ assignment.module.ts         # Module wiring
โ”‚   โ”œโ”€โ”€ assignment.service.ts        # Core assignment logic
โ”‚   โ”œโ”€โ”€ assignment.resolver.ts       # GraphQL mutations
โ”‚   โ”œโ”€โ”€ assignment.service.spec.ts   # 14 unit tests
โ”‚   โ”œโ”€โ”€ assignment.resolver.spec.ts  # 6 unit tests
โ”‚   โ””โ”€โ”€ dto/
โ”‚       โ”œโ”€โ”€ assign-issue.input.ts
โ”‚       โ””โ”€โ”€ assign-discussion.input.ts
โ””โ”€โ”€ external/
    โ”œโ”€โ”€ alerts.client.ts             # TCP event emitter
    โ”œโ”€โ”€ events.ts                    # 7 event payload interfaces
    โ””โ”€โ”€ external.module.ts           # ClientsModule registration

apps/alerts/src/
โ””โ”€โ”€ community-alerts/
    โ”œโ”€โ”€ community-alerts.controller.ts       # @EventPattern handlers
    โ”œโ”€โ”€ community-alerts.controller.spec.ts  # 8 unit tests
    โ”œโ”€โ”€ community-alerts.module.ts
    โ””โ”€โ”€ dto/
        โ””โ”€โ”€ index.ts                         # Payload DTOs

Testing

Suite Tests Coverage
assignment.service.spec.ts 14 Assign/unassign/state-change for Issues & Discussions
assignment.resolver.spec.ts 6 Mutation delegation to service layer
community-alerts.controller.spec.ts 8 Event handlers, quiet hours, profile resolution
community.e2e-spec.ts 14 Full workflow: create โ†’ assign โ†’ unassign โ†’ state-change

Running Tests

# Unit tests (community + alerts)
make test-pattern PATTERN=community
make test-pattern PATTERN=alerts

# E2E tests
make test-e2e-pattern PATTERN=community

Note: E2E tests override AlertsClient with a mock since the Alerts service isn't running during test execution. This follows the same pattern used by the Murchases E2E suite.