Skip to main content

๐Ÿ“‰ 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 (String or Array) - 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 DowngradeResults collection
  • 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โ€‹

  1. Database Write Failure: Log creation fails

    • Error logged to application logger
    • Promise rejected
    • Queue entry not deleted (safe failure)
  2. 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
  3. 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โ€‹

  1. 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();
}
  1. Batch Logging: For multiple downgrades, batch log insertions

  2. 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),
}),
);
});
});

๐Ÿ“ 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

๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM