Files
dinggadingga-backend/src/services/hiboutik/brand.hiboutik.service.ts
MastersGame 35a7c27cb7 feat(sync): validate Hiboutik payloads, read French dates and guard deletions
- Hiboutik payloads are zod schemas, validated by HiboutikClient (getOne
  added for single resources: null on 404 or empty answer).
- Hiboutik dates are read as Europe/Paris whatever the server time zone.
- Paginated lists driving deletions are walked twice when they span several
  pages (brands, categories, products); categories are sorted by id.
- A customer missing from the list is deleted only once Hiboutik confirms
  it; completed orders that lost their customer are linked back.
- Each product and its versions are written in one transaction.
- An email removed in Hiboutik is erased in database.
- A 429 is retried with backoff, then stops the synchronization.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 12:20:58 +01:00

35 lines
1.6 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { BrandHiboutikRepository } from '../../hiboutik/brand.hiboutik.repository';
import { BrandDatabaseRepository } from '../../database/brand.database.repository';
import { BrandConverter } from '../../models/converter/brand.converter';
import { forEachSafely } from '../../utils/sync';
@Injectable()
export class BrandHiboutikService {
private readonly logger = new Logger('BrandHiboutikService');
constructor(
private readonly brandHiboutikRepository: BrandHiboutikRepository,
private readonly brandDatabaseRepository: BrandDatabaseRepository,
private readonly brandConverter: BrandConverter,
) {}
/** Mirrors Hiboutik brands: upserts each of them, then deletes the brands missing from Hiboutik. */
async syncBrands() {
const hiboutikBrands = await this.brandHiboutikRepository.getBrands();
// One upsert per brand: an invalid brand must not prevent the others from being saved
const nbFailed = await forEachSafely(
this.brandConverter.fromHiboutikList(hiboutikBrands),
(brand) => this.brandDatabaseRepository.upsert(brand),
this.logger,
(brand) => `Hiboutik brand ${brand.hiboutikId}`,
);
// Hiboutik is trusted: a successful (2xx) answer, even an empty list, is the truth. An HTTP error throws before this point.
const nbDeleted = await this.brandDatabaseRepository.deleteMissing(hiboutikBrands.map((dto) => dto.brand_id));
this.logger.log(
`Brands sync ended: ${hiboutikBrands.length - nbFailed}/${hiboutikBrands.length} synced, ${nbFailed} failed, ${nbDeleted} deleted.`,
);
}
}