๐ Downgrade Logging Utility
๐ Overviewโ
The downgrade_logs.js utility provides specialized logging functionality for subscription downgrade operations. It tracks deletion counts, stores results in MongoDB, emits real-time socket notifications to users, and cleans up completed queue entries.
Source File: queue-manager/common/downgrade_logs.js
๐ฏ Purposeโ
- Result Tracking: Records number of deleted records during downgrade
- Status Management: Tracks success/failure of downgrade operations
- User Notification: Real-time socket events for downgrade completion
- Queue Cleanup: Removes processed queue entries automatically
- Audit Trail: Maintains downgrade history for compliance
๐ Function Signatureโ
module.exports = async(queue_id, recordsDeleted, status, errors, account_id, user_id);
Parametersโ
- queue_id (
ObjectId) - Queue entry ID to cleanup after logging - recordsDeleted (
Number) - Count of records deleted during downgrade - status (
String) - Operation result:'success'or'failed' - errors (
StringorArray) - Error messages if status is'failed' - account_id (
ObjectId) - Account undergoing downgrade - user_id (
ObjectId) - User who initiated downgrade
Returnsโ
Promise<DowngradeResults>- Created log document
๐ง Implementation Detailsโ
Complete Source Codeโ
const mongoose = require('mongoose');
const DowngradeResults = require('../models/downgrade-results');
const { socketEmit } = require('../utilities');
const downgradeQueue = require('../models/queues');
const logger = require('../utilities/logger');
module.exports = async (queue_id, recordsDeleted, status, errors, account_id, user_id) => {
try {
if (status === 'success') {
socketEmit(`subscription_downgrade`, [user_id], {
message: `Successfully downgraded, Please refresh the page`,
type: 'success',
});
}
if (status === 'failed') {
socketEmit(`subscription_downgrade`, [user_id], {
message: `Failed to downgrade ${errors}`,
type: 'error',
});
}
const dataToLog = {
status,
records_deleted: recordsDeleted,
account_id,
user_id,
errors,
};
let downgradeLogResult = await new DowngradeResults(dataToLog).save();
await downgradeQueue.deleteOne({ _id: queue_id });
return Promise.resolve(downgradeLogResult);
} catch (error) {
logger.error({ initiator: 'QM/downgrade-log', error });
return Promise.reject(err);
}
};
๐ Logic Breakdownโ
1. Success Notificationโ
if (status === 'success') {
socketEmit(`subscription_downgrade`, [user_id], {
message: `Successfully downgraded, Please refresh the page`,
type: 'success',
});
}
Behavior:
- Emits socket event to specific user
- Event name:
subscription_downgrade - Instructs user to refresh page to see updated plan
- Message type:
'success'(for UI styling)
Frontend Integration:
// Frontend listens for this event
socket.on('subscription_downgrade', data => {
if (data.type === 'success') {
showSuccessNotification(data.message);
window.location.reload(); // Refresh to update plan display
}
});
2. Failure Notificationโ
if (status === 'failed') {
socketEmit(`subscription_downgrade`, [user_id], {
message: `Failed to downgrade ${errors}`,
type: 'error',
});
}
Behavior:
- Emits error notification to user
- Includes error details in message
- Message type:
'error'(for error UI styling) - User can retry downgrade after fixing issues
3. Log Document Creationโ
const dataToLog = {
status,
records_deleted: recordsDeleted,
account_id,
user_id,
errors,
};
let downgradeLogResult = await new DowngradeResults(dataToLog).save();
Log Structure:
- status: Operation outcome (
'success'or'failed') - records_deleted: Count of deleted records (features, sites, contacts, etc.)
- account_id: Account that was downgraded
- user_id: User who initiated downgrade
- errors: Error details if downgrade failed
4. Queue Cleanupโ
await downgradeQueue.deleteOne({ _id: queue_id });
Cleanup Logic:
- Removes queue entry after logging complete
- Prevents duplicate processing
- Keeps queue collection clean
- Only deleted after successful log creation
Why Delete:
- Queue entries are temporary work items
- Results are preserved in
DowngradeResultscollection - Reduces queue collection size over time
๐ Data Structuresโ
DowngradeResults Documentโ
{
_id: ObjectId,
status: 'success', // 'success' or 'failed'
records_deleted: 150, // Number of records deleted
account_id: ObjectId,
user_id: ObjectId,
errors: null, // String or array if failed
createdAt: Date,
updatedAt: Date
}
Queue Entry (Before Deletion)โ
{
_id: ObjectId,
source: 'subscription_downgrade',
status: 'processing',
account_id: ObjectId,
user_id: ObjectId,
old_plan_id: ObjectId,
new_plan_id: ObjectId,
records_to_delete: {
contacts: 1000,
sites: 5,
features: ['crm', 'analytics']
},
createdAt: Date
}
๐จ Usage Patternsโ
Success Flowโ
const downgradeLog = require('./common/downgrade_logs');
// After successful downgrade processing
try {
const deletedCounts = {
contacts: 1500,
sites: 10,
deals: 250,
};
const totalDeleted = Object.values(deletedCounts).reduce((a, b) => a + b, 0);
await downgradeLog(queueId, totalDeleted, 'success', null, accountId, userId);
console.log('Downgrade logged successfully, queue entry cleaned up');
} catch (error) {
console.error('Failed to log downgrade:', error);
}
Failure Flowโ
const downgradeLog = require('./common/downgrade_logs');
// After downgrade processing fails
try {
const errorMessages = [
'Failed to delete contacts: Database timeout',
'Failed to downgrade sites: External API error',
];
await downgradeLog(
queueId,
0, // No records deleted
'failed',
errorMessages.join('; '),
accountId,
userId,
);
console.log('Downgrade failure logged, user notified');
} catch (error) {
console.error('Failed to log downgrade failure:', error);
}
Typical Downgrade Service Usageโ
// services/store/subscriptions/downgrade.js
const downgradeLog = require('../../common/downgrade_logs');
async function processDowngrade(queueEntry) {
let deletedCount = 0;
let errors = [];
try {
// Delete excess contacts
deletedCount += await Contact.deleteMany({
account_id: queueEntry.account_id,
// ... deletion criteria
}).then(result => result.deletedCount);
// Delete excess sites
deletedCount += await Site.deleteMany({
account_id: queueEntry.account_id,
// ... deletion criteria
}).then(result => result.deletedCount);
// Log success
await downgradeLog(
queueEntry._id,
deletedCount,
'success',
null,
queueEntry.account_id,
queueEntry.user_id,
);
} catch (error) {
errors.push(error.message);
// Log failure
await downgradeLog(
queueEntry._id,
deletedCount,
'failed',
errors.join('; '),
queueEntry.account_id,
queueEntry.user_id,
);
}
}
๐ Complete Flow Diagramโ
sequenceDiagram
participant SERVICE as Downgrade Service
participant LOG as downgrade_logs()
participant DB as MongoDB
participant SOCKET as Socket.IO
participant USER as User Browser
SERVICE->>LOG: Call with results
alt Status: success
LOG->>SOCKET: Emit success notification
SOCKET->>USER: "Successfully downgraded, Please refresh"
else Status: failed
LOG->>SOCKET: Emit error notification
SOCKET->>USER: "Failed to downgrade {errors}"
end
LOG->>DB: Create DowngradeResults document
DB-->>LOG: Created document
LOG->>DB: Delete queue entry (queue_id)
DB-->>LOG: Queue entry deleted
LOG-->>SERVICE: Return log document
โ๏ธ Configurationโ
Required Modelsโ
const DowngradeResults = require('../models/downgrade-results');
const downgradeQueue = require('../models/queues');
Socket Eventโ
// Event name
'subscription_downgrade'
// Success payload
{ message: 'Successfully downgraded, Please refresh the page', type: 'success' }
// Error payload
{ message: 'Failed to downgrade {error details}', type: 'error' }
๐จ Error Handlingโ
Try-Catch Wrapperโ
try {
// Logging logic
} catch (error) {
logger.error({ initiator: 'QM/downgrade-log', error });
return Promise.reject(err); // Note: should be 'error' not 'err'
}
Bug Alert: There's a typo in the error handler:
return Promise.reject(err); // โ 'err' is undefined
Should be:
return Promise.reject(error); // โ
Correct variable name
Error Scenariosโ
-
Database Write Failure: Log creation fails
- Error logged to application logger
- Promise rejected
- Queue entry not deleted (safe failure)
-
Queue Deletion Failure: Log created but queue entry not deleted
- Log still persisted (success)
- Queue entry remains (may cause duplicate processing)
- Requires manual cleanup or retry logic
-
Socket Emit Failure: Notification not sent
- Doesn't throw error (fire-and-forget)
- Log still created
- User may not see notification
๐ Performance Considerationsโ
Database Operationsโ
- 2 Write Operations: 1 insert (DowngradeResults), 1 delete (Queue)
- Atomic: Not wrapped in transaction (could be improved)
- Order: Log created before queue deleted (safe failure mode)
Socket Notificationsโ
- Non-blocking: Socket emit doesn't wait for delivery
- Single User: Only notifies initiating user
- Lightweight: Small payload (~50 bytes)
Optimization Opportunitiesโ
- Transaction Wrapper: Ensure both operations succeed or fail together
const session = await mongoose.startSession();
session.startTransaction();
try {
await new DowngradeResults(dataToLog).save({ session });
await downgradeQueue.deleteOne({ _id: queue_id }, { session });
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
-
Batch Logging: For multiple downgrades, batch log insertions
-
Queue Retention: Consider soft delete (status update) instead of hard delete for audit
๐งช Testing Considerationsโ
Mock Setupโ
jest.mock('../models/downgrade-results');
jest.mock('../models/queues');
jest.mock('../utilities', () => ({
socketEmit: jest.fn(),
}));
const downgradeLog = require('./common/downgrade_logs');
Test Casesโ
describe('downgradeLog', () => {
test('Logs success and emits notification', async () => {
const result = await downgradeLog('queue123', 150, 'success', null, 'account123', 'user123');
expect(socketEmit).toHaveBeenCalledWith(
'subscription_downgrade',
['user123'],
expect.objectContaining({ type: 'success' }),
);
expect(DowngradeResults.prototype.save).toHaveBeenCalled();
expect(downgradeQueue.deleteOne).toHaveBeenCalledWith({ _id: 'queue123' });
});
test('Logs failure with error details', async () => {
const errors = 'Database timeout';
await downgradeLog('queue123', 0, 'failed', errors, 'account123', 'user123');
expect(socketEmit).toHaveBeenCalledWith(
'subscription_downgrade',
['user123'],
expect.objectContaining({
type: 'error',
message: expect.stringContaining(errors),
}),
);
});
});
๐ Related Documentationโ
- Log Data Utility - Similar logging pattern for imports
- Store Subscriptions Downgrade - Primary consumer
- Common Utilities Overview
๐ Notesโ
Status Valuesโ
Only two valid statuses:
'success'- Downgrade completed successfully'failed'- Downgrade encountered errors
No 'pending' or 'in_progress' as this is called at completion.
User Experienceโ
The "Please refresh the page" instruction is important because:
- Subscription plan shown in UI is cached/session-based
- Server-side plan is already updated
- Refresh ensures UI shows correct plan and feature availability
Audit Trailโ
The DowngradeResults collection provides:
- History of all downgrades
- Record deletion counts for compliance
- Error tracking for troubleshooting
- User attribution for support
Queue vs Resultsโ
- Queue Collection: Temporary work items
- DowngradeResults Collection: Permanent audit log
- Queue entries deleted after processing
- Results preserved indefinitely
Complexity: Low
Business Impact: High - Critical for subscription lifecycle
Dependencies: DowngradeResults model, Queue model, socketEmit utility
Last Updated: 2025-10-10