Activities Module
๐ฏ Overviewโ
The Activities module orchestrates every REST endpoint that reads, filters, and mutates CRM activity timelines for contacts and deals. It keeps customer-facing feeds aligned with real-world interactions by hydrating each timeline entry with type-specific payloads, author metadata, and threaded comments.
It exists to unify several distinct feature surfacesโmanual notes, automated emails and SMS, reminders, Instasites/Instareports, and general logsโunder a single audit trail with consistent permissions. The router centralizes validation, visibility rules, and population logic so that any client (dashboard, public APIs, automations) receives the same normalized activity response structure.
Operationally, the module acts as a faรงade over multiple shared models, applying opinionated business rules (ownership, followers, account preferences) before persisting or exposing data. This allows downstream analytic jobs and UI components to consume activities without re-implementing guardrails.
๐ Directory Structureโ
internal/api/v1/activities/
โโโ ๐ index.js # Express router exporting all /v1/activities routes
โโโ ๐ README.md # Legacy readme (kept for historical context)
โโโ ๐ helper/
โ โโโ ๐ helper.js # Custom Joi helpers (ObjectId, date, time validation)
โโโ ๐ middleware/
โ โโโ ๐ index.js # Log activity validator selecting subtype schemas
โโโ ๐ schemas/
โโโ ๐ index.js # Route-to-Joi schema map consumed by validation utility
โโโ ๐ post_activity_details.js # Post payload schemas for log subtypes (call/email/meeting)
โโโ ๐ put_activity_details.js # Put payload schemas mirroring post definitions
๐๏ธ MongoDB Collectionsโ
Collection: _activityโ
- Purpose: Canonical activity documents representing every timeline event.
- Model Reference:
shared/models/activity.js - Key Fields:
account,created_by,contact,deal,activity_type,logs_type,activity_details,comments,pinned,type - Indexes: Compound indexes on
(account, type),(contact, created), and(deal, created)support timeline queries.
Collection: _activity.commentsโ
- Purpose: Stores threaded comments attached to activity records (primarily notes).
- Model Reference:
shared/models/activity-comment.js - Key Fields:
account,created_by,activity_id,content,created,updated - Indexes: Indexed on
activity_idfor efficient hydration and deletion.
Collection: crm.notesโ
- Purpose: Primary note bodies that can appear on timelines.
- Model Reference:
shared/models/crm-note.js - Key Fields:
account,created_by,contact,deal,content,attachments,pinned - Indexes:
(account, contact)and(account, deal)for ownership filtering.
Collection: crm.notes.commentsโ
- Purpose: Persist threaded comments on CRM notes surfaced inside activities.
- Model Reference:
shared/models/crm-note-comment.js - Key Fields:
account,created_by,note_id,content, timestamps - Indexes:
note_idindex for fast aggregation.
Collection: crm.remindersโ
- Purpose: Reminder payloads associated with activities and assigned users.
- Model Reference:
shared/models/crm-reminder.js - Key Fields:
account,contact,deal,headline,content,assigned,due_date_time - Indexes:
(account, due_date_time)for scheduling dashboards.
Collection: communicationsโ
- Purpose: Messages (email, SMS) and delivery event logs that feed activity entries.
- Model Reference:
shared/models/communication.js - Key Fields:
account_id,contact_id,sent_by,message_type,events,data - Indexes:
(account_id, contact_id)plus TTL/event indexes maintained by the communications service.
Collection: crm.contactsโ
- Purpose: CRM contact metadata and ownership flags used for visibility enforcement.
- Model Reference:
shared/models/contact.js - Key Fields:
parent_account,account,owner,followers,visibility,last_activity_date - Indexes: Ownership and visibility indexes support guardrail queries.
Collection: crm.dealsโ
- Purpose: CRM deal metadata parallel to contacts.
- Model Reference:
shared/models/deal.js - Key Fields:
account_id,owner,followers,visibility,last_activity_date - Indexes:
(account_id, pipeline_stage)and follower indexes for permission checks.
Collection: instasitesโ
- Purpose: Landing page generation artifacts surfaced as activities.
- Model Reference:
shared/models/instasite.js - Key Fields:
account_id,created_by,status,details - Indexes: Status-based indexes for deployment telemetry.
Collection: instareportsโ
- Purpose: Audit reports tied to activities.
- Model Reference:
shared/models/instareport.js - Key Fields:
account_id,created_by,status,details,generated_at - Indexes:
account_idandgenerated_atindexes for reporting windows.
๐ Note: For full schema definitions and relationships, review the shared models under
shared/models/.
๐๏ธ Architecture Overviewโ
- Pattern: Layered Express router with validation middleware, service-style helpers, and shared Mongoose models.
- Key Dependencies:
verifyScope,validateRequestSchemaV2,generatePagination,setActivityID, andactivityExpandOptionsfrominternal/api/v1/utilities. - External Services: Leverages shared communications data and Instasite/Instareport services via MongoDB models (no direct HTTP calls within the module).
- Auth Context: Relies on upstream middleware populating
req.auth(account id, user id, owner flag, preferences) to apply visibility rules.
๐ Submodulesโ
- ๐
./helperโ Joi custom validators for Mongo ObjectIds and date/time values. - ๐
./middlewareโ Conditional middleware that selects the right log subtype schema before hitting the controller logic. - ๐
./schemasโ Route-level Joi definitions plus per-log-type payload schemas for create/update flows.
๐ Quick Startโ
// Fetch the latest contact activities with expanded entity payloads
const { data, pagination } = await apiClient.get('/v1/activities', {
params: {
type: 'contact',
contact_id: '64f4b0f0b2a3f7123b6c1234',
expand: true,
page: 1,
limit: 25,
},
headers: {
X-Session-Id: "MongoObjectId",
},
});
console.log(pagination.total, 'activities available');
๐ Required Environment Variablesโ
| Variable | Description | Required |
|---|---|---|
| โ | No module-specific environment variables. Requires upstream authentication middleware to set req.auth. | โ Optional |
๐ Key Metrics & Performanceโ
- Sorting by
pinnedthencreatedkeeps high-priority activities surfaced without additional queries. - Concurrency is handled via
Promise.all(count + fetch); pagination defaults should stay under 100 to avoid large populate chains. - Email/SMS hydration reads the latest communication events to compute delivery flags; heavy historical accounts may benefit from communication event archiving.
๐ง Business Logicโ
๐ Common Processing Pipelineโ
flowchart TD
A[๐ฅ Request Received] --> B[๐ verifyScope activities scope check]
B --> C{โ
Authenticated?}
C -->|No| Z[โ 403 Not Allowed]
C -->|Yes| D[๐งพ validateRequestSchemaV2]
D --> E[๐ก๏ธ Apply Visibility Guardrails]
E --> F[๐๏ธ Resolve ActivityType + populate plan]
F --> G[๐ Query + Hydrate]
G --> H[๐งฎ generatePagination]
H --> I[๐งผ setActivityID + post-process]
I --> J[๐ค Standardized Response]
classDef start fill:#e1f5ff,stroke:#333,stroke-width:1px;
classDef decision fill:#fff3cd,stroke:#333,stroke-width:1px;
classDef error fill:#f8d7da,stroke:#333,stroke-width:1px;
class A start;
class C decision;
class Z error;
Contract Summaryโ
- Inputs: HTTP request with path/query/body, authenticated user context on
req.auth. - Outputs:
{ success, message, data, pagination? }with activities normalized viasetActivityID. - Success Criteria: Caller sees only owned/followed entities, hydrated payload aligns with activity type, pagination metadata accurate.
๐ฅ GET /v1/activitiesโ
Purpose โ GETโ
Returns a mixed activity feed for a contact or deal with optional type filtering and entity expansion.
Input Contract โ GETโ
type('contact' | 'deal') โ required; selects entity collection.contact_id/deal_idโ required pertypefor scoped feeds.- Pagination โ
page,limit(defaults applied by validation). expand(boolean) โ include full entity/creator payloads instead of lean projection.activity_type[]โ optional filter array.
Processing Flow โ GETโ
- Resolve entity visibility via
Entity[type].findOneusing owner/follower preferences fromreq.auth.account.preferences. - Construct base filter:
{ account: req.auth.account_id, type, [contact|deal]: entityId }. - Apply optional activity type filter and pinned ordering (
sort: [['pinned', -1], ['created', -1]]). - Execute
Promise.allforcountDocumentsand populated query withskip/limit. - Call
ActivityType[activity.activity_type].populatefor type-specific hydrate (notes, reminders, communications, Instasites/reports). - Invoke
getCommentson note activities and compute communication flags (sent,delivered,opened,clicked). - Append sentinel โcontact_createdโ or โdeal_createdโ records to last page using account/entity metadata.
- Normalize IDs (
setActivityID) and build pagination object viageneratePagination.
Business Rules โ GETโ
- Non-owners only see entities they own/follow or that are globally visible per account preferences.
- Pinned activities always float above chronological items.
- Creation sentinel inserted only when requesting the final page to avoid duplicate context.
Response Shape โ GETโ
- Array of normalized activity objects with hydrated
activity_details,entity,creator, andcomments(if applicable). - Pagination object with
total,limit,page,pages.
Failure Modes โ GETโ
- Throws
notFoundif entity is inaccessible or missing. - Throws
notAllowedif visibility guardrails fail for non-owner.
๐ฌ POST /v1/activities/filterโ
Purpose โ POST Filterโ
Advanced search endpoint layering filters over the base feed for analytics-style views.
Input Contract โ POST Filterโ
- Same query params as the list route.
- Body filters:
date({ from, to }),created_by[],activity_type[],logs_type[].
Processing Flow โ POST Filterโ
- Validate body with
schemas.activities_filterensuring arrays and date bounds are well-formed. - Build dynamic
$andpipeline combining date range, creators, and type filters. - Merge permission clauses identical to
GET /v1/activities. - Execute count + populated query concurrently; note sentinel entries are omitted.
- Post-process with
getComments, communication event parsing, andsetActivityID.
Business Rules โ POST Filterโ
- Activity and log type filters operate on both
activity_typeandlogs_typefields to catch log subtypes. - Date filter is inclusive and uses account timezone preferences from
req.auth.account.preferenceswhen present.
Response Shape โ POST Filterโ
- Hydrated activities array, pagination metadata.
Failure Modes โ POST Filterโ
- Returns empty array if filters exclude all activities; validation errors bubble up with
badRequestresponses.
๐๏ธ GET /v1/activities/:activity_typeโ
Purpose โ GET Activity Typeโ
Fetches timeline items constrained to a single activity type (e.g., notes).
Processing Flow โ GET Activity Typeโ
- Forces
filter.activity_type = params.activity_typebefore executing the standard list flow. - Reuses permission and hydration logic; sentinel creation events suppressed to keep type-specific feeds pure.
๐ POST /v1/activities/:activity_typeโ
Purpose โ POST Activity Typeโ
Creates a new activity entry tied to a contact or deal.
Input Contract โ POST Activity Typeโ
type('contact' | 'deal') and matching entity id.activity_detailsobject with type-specific payload (note content, reminder fields, communication metadata, etc.).- Optional
logs_typeforactivity_type === 'logs'.
Processing Flow โ POST Activity Typeโ
- Run shared schema validation plus
validateActivityDetailsSchemafor log subtypes (CALL/EMAIL/MEETING). - Confirm entity accessibility (
Entity[type].findOne). - Call
Activity.createwith normalized payload (account, creator, entity references, pinned default false). - Hydrate the resulting activity using the same pipeline as the read endpoints.
- Update entity
last_activity_datevia Mongoose hooks.
Business Rules โ POST Activity Typeโ
- Only owners/followers can log activities unless account preferences allow global visibility.
- Log activities enforce subtype-specific schemas to ensure metrics parity downstream.
Response Shape โ POST Activity Typeโ
- Newly created, populated activity object (ID normalized, comments array empty for notes).
Side Effects โ POST Activity Typeโ
- Mongoose middleware updates
crm.contacts/crm.dealslast_activity_date.
Failure Modes โ POST Activity Typeโ
- Validation errors return
badRequestdescribing the offending field. - Unauthorized access throws
notAllowed.
๐ GET /v1/activities/:activity_type/:idโ
Purpose โ GET Activity By IDโ
Retrieves a single activity by ID for detail views.
Processing Flow โ GET Activity By IDโ
- Filters by
{ _id: params.id, account: req.auth.account_id, activity_type }. - Non-owners must be entity owners/followers or the activity creator; otherwise
notAllowedis raised. - Response retains array structure for backward compatibility (
data: [activity]).
Post-Processing โ GET Activity By IDโ
- Hydrates entity, creator, comments, and type-specific payload prior to normalization.
โป๏ธ PUT /v1/activities/:activity_type/:idโ
Purpose โ PUT Activityโ
Updates activity metadata, pinned status, or payload details.
Input Contract โ PUT Activityโ
- Optional
pinnedboolean. - Updated
activity_detailsobject (dot-flattened internally for$set). - For logs,
logs_typeand nested payload must passput_activity_detailsschema.
Processing Flow โ PUT Activityโ
- Validate payload (route schema + conditional log schema).
- Confirm visibility and fetch existing document.
- Build
$setobject, flattening nested objects to dot paths for Mongoose updates. - Execute
findOneAndUpdatewith{ new: true }to retrieve updated doc. - Rehydrate + normalize like read endpoints.
Business Rules โ PUT Activityโ
- Pinned state can only be toggled by users with visibility rights.
- Reminders propagate assigned user updates to the hydrated payload.
Response Shape โ PUT Activityโ
- Updated, normalized activity.
Failure Modes โ PUT Activityโ
- Returns
notFoundwhen ID does not exist for callerโs account.
๐๏ธ DELETE /v1/activities/:activity_type/:idโ
Purpose โ DELETE Activityโ
Removes an activity from the timeline.
Processing Flow โ DELETE Activityโ
- Performs the same ownership check as read/update routes.
- Calls
findByIdAndDelete; cascades to comment cleanup handled in model hooks. - Responds with
{ success: true, message: 'Activity deleted successfully.' }.
Failure Modes โ DELETE Activityโ
notAllowedfor unauthorized deletions;notFoundif already removed.
๐ฌ Comment Routes (POST/PUT/DELETE /:activity_type/:id/comments)โ
Purpose โ Comment Routesโ
Manage note comment threads attached to activities.
Processing Flow โ Comment Routesโ
- Validate params/body with comment-specific Joi schemas.
- Call respective Activity model helpers (
addComment,updateComment,deleteComment). - Helpers mutate
_activity.commentsarray and persistActivityCommentdocuments. - Rehydrate parent activity (including refreshed comments) before responding.
Business Rules โ Comment Routesโ
- Comments are limited to note-based activities; helper short-circuits for other types.
- Updated/deleted comments log
updated_bymetadata for audit compatibility.
Failure Modes โ Comment Routesโ
- Missing comment IDs return
notFound. - Permission checks mirror parent activity rules.
๐ ๏ธ Helper Functionsโ
activityExpandOptions(expand: boolean)โ Determines lean vs. full projections when populating activities and related entities.getComments(noteId)โ Aggregatescrm.notes.commentsfor the supplied note, ordered by creation time.validateMongoID/Date/Timeโ Joi extensions reused across schemas to ensure consistent ID and temporal validation.setActivityID(activity)โ Clones_idtoidon the activity and nestedactivity_detailsdocuments for client consumption.
๐ Data Flowโ
graph TB
A[๐ฅ Client Request] --> B[๐ Scope + Auth Validation]
B --> C[๐งพ Joi Schema Validation]
C --> D[๐ก๏ธ Visibility Filter]
D --> E[๐๏ธ Mongo Query]
E --> F[๐งฌ Populate Type-Specific Details]
F --> G[๐ฌ Attach Comments / Flags]
G --> H[๐งฎ Pagination Metadata]
H --> I[๐ค Response Builder]
style A fill:#e1f5ff
style I fill:#e1ffe1
๐งช Edge Cases & Special Handlingโ
- Empty Feeds: Returns empty
dataarray with pagination totals of zero; sentinel entries are skipped to avoid confusion. - Unpinned to Pinned Transitions: Clients must refetch after pinning because sort order changes; API ensures pinned flag updates atomically.
- Orphaned Comments: Model hooks remove orphaned comment IDs during delete flows so subsequent hydration will not fail.
- Legacy Single Activity Response:
GET /:activity_type/:idcontinues to return an array for backward compatibilityโclient adapters should handle single-item arrays.
โ ๏ธ Important Notesโ
- ๐จ Permissions: Always pass the authenticated userโs token with the
activitiesscope. Non-owner users inherit strict visibility filters; bypassing through service tokens is discouraged unless executing system jobs. - ๐ก Best Practice: Request small page sizes (25โ50) when
expand=trueto limit populate cost. - ๐ Security: The module never exposes raw communication payloads containing PII unless the caller has access to the related contact/deal.
๐ Related Documentationโ
shared/models/activity.jsโ Source of activity schema hooks and static helpers.internal/api/v1/utilities/validateRequestSchemaV2.jsโ Shared validation utility consumed by this module.internal/api/v1/utilities/verifyScope.jsโ Scope enforcement used across internal services.