Steps 1-11, still need to do 12
This commit is contained in:
252
src/index.js
252
src/index.js
@@ -9,6 +9,7 @@
|
||||
|
||||
const { getConfig } = require("./config/environment");
|
||||
const ProductService = require("./services/product");
|
||||
const ScheduleService = require("./services/schedule");
|
||||
const Logger = require("./utils/logger");
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,7 @@ class ShopifyPriceUpdater {
|
||||
constructor() {
|
||||
this.logger = new Logger();
|
||||
this.productService = new ProductService();
|
||||
this.scheduleService = new ScheduleService(this.logger);
|
||||
this.config = null;
|
||||
this.startTime = null;
|
||||
}
|
||||
@@ -136,14 +138,30 @@ class ShopifyPriceUpdater {
|
||||
`Starting price updates with ${this.config.priceAdjustmentPercentage}% adjustment`
|
||||
);
|
||||
|
||||
// Update product prices
|
||||
const results = await this.productService.updateProductPrices(
|
||||
products,
|
||||
this.config.priceAdjustmentPercentage
|
||||
);
|
||||
// Mark operation as in progress to prevent cancellation during updates
|
||||
if (this.setOperationInProgress) {
|
||||
this.setOperationInProgress(true);
|
||||
}
|
||||
|
||||
return results;
|
||||
try {
|
||||
// Update product prices
|
||||
const results = await this.productService.updateProductPrices(
|
||||
products,
|
||||
this.config.priceAdjustmentPercentage
|
||||
);
|
||||
|
||||
return results;
|
||||
} finally {
|
||||
// Mark operation as complete
|
||||
if (this.setOperationInProgress) {
|
||||
this.setOperationInProgress(false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure operation state is cleared on error
|
||||
if (this.setOperationInProgress) {
|
||||
this.setOperationInProgress(false);
|
||||
}
|
||||
await this.logger.error(`Price update failed: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
@@ -280,11 +298,29 @@ class ShopifyPriceUpdater {
|
||||
|
||||
await this.logger.info(`Starting price rollback operations`);
|
||||
|
||||
// Execute rollback operations
|
||||
const results = await this.productService.rollbackProductPrices(products);
|
||||
// Mark operation as in progress to prevent cancellation during rollback
|
||||
if (this.setOperationInProgress) {
|
||||
this.setOperationInProgress(true);
|
||||
}
|
||||
|
||||
return results;
|
||||
try {
|
||||
// Execute rollback operations
|
||||
const results = await this.productService.rollbackProductPrices(
|
||||
products
|
||||
);
|
||||
|
||||
return results;
|
||||
} finally {
|
||||
// Mark operation as complete
|
||||
if (this.setOperationInProgress) {
|
||||
this.setOperationInProgress(false);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure operation state is cleared on error
|
||||
if (this.setOperationInProgress) {
|
||||
this.setOperationInProgress(false);
|
||||
}
|
||||
await this.logger.error(`Price rollback failed: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
@@ -418,6 +454,14 @@ class ShopifyPriceUpdater {
|
||||
return await this.handleCriticalFailure("API connection failed", 1);
|
||||
}
|
||||
|
||||
// Check for scheduled execution and handle scheduling if configured
|
||||
if (this.config.isScheduled) {
|
||||
const shouldProceed = await this.handleScheduledExecution();
|
||||
if (!shouldProceed) {
|
||||
return 0; // Operation was cancelled during scheduling
|
||||
}
|
||||
}
|
||||
|
||||
// Display operation mode indication in console output (Requirements 9.3, 8.4)
|
||||
await this.displayOperationModeHeader();
|
||||
|
||||
@@ -467,6 +511,57 @@ class ShopifyPriceUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle scheduled execution workflow
|
||||
* @returns {Promise<boolean>} True if execution should proceed, false if cancelled
|
||||
*/
|
||||
async handleScheduledExecution() {
|
||||
try {
|
||||
// Use the already validated scheduled time from config
|
||||
const scheduledTime = this.config.scheduledExecutionTime;
|
||||
|
||||
// Display scheduling confirmation and countdown
|
||||
await this.logger.info("🕐 Scheduled execution mode activated");
|
||||
await this.scheduleService.displayScheduleInfo(scheduledTime);
|
||||
|
||||
// Wait until scheduled time with cancellation support
|
||||
const shouldProceed = await this.scheduleService.waitUntilScheduledTime(
|
||||
scheduledTime,
|
||||
() => {
|
||||
// Cancellation callback - log the cancellation
|
||||
this.logger.info("Scheduled operation cancelled by user");
|
||||
}
|
||||
);
|
||||
|
||||
if (!shouldProceed) {
|
||||
// Update scheduling state - no longer waiting
|
||||
if (this.setSchedulingActive) {
|
||||
this.setSchedulingActive(false);
|
||||
}
|
||||
await this.logger.info("Operation cancelled. Exiting gracefully.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scheduling wait period is complete, operations will begin
|
||||
if (this.setSchedulingActive) {
|
||||
this.setSchedulingActive(false);
|
||||
}
|
||||
|
||||
// Log that scheduled execution is starting
|
||||
await this.logger.info(
|
||||
"⏰ Scheduled time reached. Beginning operation..."
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// Update scheduling state on error
|
||||
if (this.setSchedulingActive) {
|
||||
this.setSchedulingActive(false);
|
||||
}
|
||||
await this.logger.error(`Scheduling error: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe wrapper for initialization with enhanced error handling
|
||||
* @returns {Promise<boolean>} True if successful
|
||||
@@ -760,30 +855,111 @@ class ShopifyPriceUpdater {
|
||||
async function main() {
|
||||
const app = new ShopifyPriceUpdater();
|
||||
|
||||
// Handle process signals for graceful shutdown with enhanced logging
|
||||
process.on("SIGINT", async () => {
|
||||
console.log("\n🛑 Received SIGINT (Ctrl+C). Shutting down gracefully...");
|
||||
try {
|
||||
// Attempt to log shutdown to progress file
|
||||
const logger = new Logger();
|
||||
await logger.warning("Operation interrupted by user (SIGINT)");
|
||||
} catch (error) {
|
||||
console.error("Failed to log shutdown:", error.message);
|
||||
}
|
||||
process.exit(130); // Standard exit code for SIGINT
|
||||
});
|
||||
// Enhanced signal handling state management
|
||||
let schedulingActive = false;
|
||||
let operationInProgress = false;
|
||||
let signalHandlersSetup = false;
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
console.log("\n🛑 Received SIGTERM. Shutting down gracefully...");
|
||||
/**
|
||||
* Enhanced signal handler that coordinates with scheduling and operation states
|
||||
* @param {string} signal - The signal received (SIGINT, SIGTERM)
|
||||
* @param {number} exitCode - Exit code to use
|
||||
*/
|
||||
const handleShutdown = async (signal, exitCode) => {
|
||||
// During scheduled waiting period - provide clear cancellation message
|
||||
if (schedulingActive && !operationInProgress) {
|
||||
console.log(`\n🛑 Received ${signal} during scheduled wait period.`);
|
||||
console.log("📋 Cancelling scheduled operation...");
|
||||
|
||||
try {
|
||||
// Clean up scheduling resources
|
||||
if (app.scheduleService) {
|
||||
app.scheduleService.cleanup();
|
||||
}
|
||||
|
||||
// Log cancellation to progress file
|
||||
const logger = new Logger();
|
||||
await logger.warning(
|
||||
`Scheduled operation cancelled by ${signal} signal`
|
||||
);
|
||||
console.log(
|
||||
"✅ Scheduled operation cancelled successfully. No price updates were performed."
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to log cancellation:", error.message);
|
||||
}
|
||||
|
||||
process.exit(0); // Clean cancellation, exit with success
|
||||
return;
|
||||
}
|
||||
|
||||
// During active price update operations - prevent interruption
|
||||
if (operationInProgress) {
|
||||
console.log(
|
||||
`\n⚠️ Received ${signal} during active price update operations.`
|
||||
);
|
||||
console.log(
|
||||
"🔒 Cannot cancel while price updates are in progress to prevent data corruption."
|
||||
);
|
||||
console.log("⏳ Please wait for current operations to complete...");
|
||||
console.log(
|
||||
"💡 Tip: You can cancel during the countdown period before operations begin."
|
||||
);
|
||||
return; // Do not exit, let operations complete
|
||||
}
|
||||
|
||||
// Normal shutdown for non-scheduled operations or after operations complete
|
||||
console.log(`\n🛑 Received ${signal}. Shutting down gracefully...`);
|
||||
try {
|
||||
// Clean up scheduling resources
|
||||
if (app.scheduleService) {
|
||||
app.scheduleService.cleanup();
|
||||
}
|
||||
|
||||
// Attempt to log shutdown to progress file
|
||||
const logger = new Logger();
|
||||
await logger.warning("Operation terminated by system (SIGTERM)");
|
||||
await logger.warning(`Operation interrupted by ${signal}`);
|
||||
} catch (error) {
|
||||
console.error("Failed to log shutdown:", error.message);
|
||||
}
|
||||
process.exit(143); // Standard exit code for SIGTERM
|
||||
});
|
||||
process.exit(exitCode);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up enhanced signal handlers with proper coordination
|
||||
*/
|
||||
const setupSignalHandlers = () => {
|
||||
if (signalHandlersSetup) {
|
||||
return; // Avoid duplicate handlers
|
||||
}
|
||||
|
||||
process.on("SIGINT", () => handleShutdown("SIGINT", 130));
|
||||
process.on("SIGTERM", () => handleShutdown("SIGTERM", 143));
|
||||
signalHandlersSetup = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update scheduling state for signal handler coordination
|
||||
* @param {boolean} active - Whether scheduling is currently active
|
||||
*/
|
||||
const setSchedulingActive = (active) => {
|
||||
schedulingActive = active;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update operation state for signal handler coordination
|
||||
* @param {boolean} inProgress - Whether price update operations are in progress
|
||||
*/
|
||||
const setOperationInProgress = (inProgress) => {
|
||||
operationInProgress = inProgress;
|
||||
};
|
||||
|
||||
// Make state management functions available to the app
|
||||
app.setSchedulingActive = setSchedulingActive;
|
||||
app.setOperationInProgress = setOperationInProgress;
|
||||
|
||||
// Set up enhanced signal handlers
|
||||
setupSignalHandlers();
|
||||
|
||||
// Handle unhandled promise rejections with enhanced logging
|
||||
process.on("unhandledRejection", async (reason, promise) => {
|
||||
@@ -820,10 +996,34 @@ async function main() {
|
||||
});
|
||||
|
||||
try {
|
||||
// Check if scheduling is active to coordinate signal handling
|
||||
const { getConfig } = require("./config/environment");
|
||||
const config = getConfig();
|
||||
|
||||
// Set initial scheduling state
|
||||
if (config.isScheduled) {
|
||||
setSchedulingActive(true);
|
||||
}
|
||||
|
||||
const exitCode = await app.run();
|
||||
|
||||
// Clear all states after run completes
|
||||
setSchedulingActive(false);
|
||||
setOperationInProgress(false);
|
||||
|
||||
process.exit(exitCode);
|
||||
} catch (error) {
|
||||
console.error("Fatal error:", error.message);
|
||||
|
||||
// Clean up scheduling resources on error
|
||||
if (app.scheduleService) {
|
||||
app.scheduleService.cleanup();
|
||||
}
|
||||
|
||||
// Clear states on error
|
||||
setSchedulingActive(false);
|
||||
setOperationInProgress(false);
|
||||
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user