feat(sync): synchronize Hiboutik data through POST /sync
POST /sync starts the full synchronization in the background (202, or 409 when one is already running). Entities are synced in dependency order: brands, categories, suppliers, products (with versions and stocks), customers. Each row is synced independently so that a failure is logged without stopping the rest. Replaces the default AppService.
This commit is contained in:
69
src/services/hiboutik/hiboutik.service.ts
Normal file
69
src/services/hiboutik/hiboutik.service.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { BrandHiboutikService } from './brand.hiboutik.service';
|
||||
import { CategoryHiboutikService } from './category.hiboutik.service';
|
||||
import { CustomerHiboutikService } from './customer.hiboutik.service';
|
||||
import { SupplierHiboutikService } from './supplier.hiboutik.service';
|
||||
import { ProductHiboutikService } from './product.hiboutik.service';
|
||||
|
||||
/** Entry point of the Hiboutik → database synchronization: runs each entity sync in dependency order. */
|
||||
@Injectable()
|
||||
export class HiboutikService {
|
||||
private readonly logger = new Logger('HiboutikService');
|
||||
/** In-memory lock: only valid while the app runs as a single instance. */
|
||||
private syncInProgress = false;
|
||||
|
||||
constructor(
|
||||
private readonly brandHiboutikService: BrandHiboutikService,
|
||||
private readonly categoryHiboutikService: CategoryHiboutikService,
|
||||
private readonly customerHiboutikService: CustomerHiboutikService,
|
||||
private readonly productHiboutikService: ProductHiboutikService,
|
||||
private readonly supplierHiboutikService: SupplierHiboutikService,
|
||||
) {}
|
||||
|
||||
get isSyncInProgress(): boolean {
|
||||
return this.syncInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a full synchronization in the background.
|
||||
* @returns false if a synchronization is already running (nothing is started).
|
||||
*/
|
||||
startSync(): boolean {
|
||||
if (this.syncInProgress) return false;
|
||||
|
||||
this.syncInProgress = true;
|
||||
void this.syncAll()
|
||||
.catch((error) => this.logger.error('Global Hiboutik synchronization crashed', (error as Error)?.stack))
|
||||
.finally(() => (this.syncInProgress = false));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Runs every sync step in order. A failing step is logged and does not stop the next ones. */
|
||||
async syncAll() {
|
||||
this.logger.log('Starting global Hiboutik synchronization');
|
||||
// Order matters: products connect to brands, categories and suppliers by hiboutikId.
|
||||
const steps: [string, () => Promise<void>][] = [
|
||||
['brands', () => this.brandHiboutikService.syncBrands()],
|
||||
['categories', () => this.categoryHiboutikService.syncCategories()],
|
||||
['suppliers', () => this.supplierHiboutikService.syncSuppliers()],
|
||||
['products', () => this.productHiboutikService.syncProducts()],
|
||||
['customers', () => this.customerHiboutikService.syncCustomers()],
|
||||
];
|
||||
|
||||
const failedSteps: string[] = [];
|
||||
for (const [name, step] of steps) {
|
||||
try {
|
||||
await step();
|
||||
} catch (error) {
|
||||
failedSteps.push(name);
|
||||
this.logger.error(`Hiboutik ${name} sync failed`, (error as Error)?.stack);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedSteps.length > 0) {
|
||||
this.logger.warn(`Global Hiboutik synchronization ended with errors (failed steps: ${failedSteps.join(', ')})`);
|
||||
} else {
|
||||
this.logger.log('Global Hiboutik synchronization ended successfully');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user