Files
dinggadingga-backend/CLAUDE.md
quentin_duwa 8b341c4933 chore: add Claude Code configuration (#2)
## Summary
- Add `CLAUDE.md`: project description (K-pop shop backend, buffer in front of Hiboutik), architecture, domain notes, git workflow.
- Add shared `.claude/settings.json` (allowed dev commands, `.env` read protection).
- Pin the Prisma agent skills in `skills-lock.json` (restore with `npx skills experimental_install`, documented in the README).
- Update `.gitignore` (generated skills, local settings, Postman skills) and add `.gitattributes` (LF line endings).

## Note
This branch is based on `feat/customer-addresses-orders`, so the PR also contains the 3 feature commits (addresses, company details, orders) that `CLAUDE.md` describes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: MastersGame <quentin.duwa1210@gmail.com>
Reviewed-on: #2
2026-11-07 12:14:08 +00:00

7.2 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Backend of a K-pop online shop. The real shop (catalog, stock, customers, sales) lives in Hiboutik, a POS/e-commerce platform; Hiboutik stays the source of truth. This backend sits in front of it as a buffer: it mirrors Hiboutik data into its own database so the storefront does not hammer the Hiboutik API with requests. It is also the place to build shop features Hiboutik does not offer, on top of the mirrored data.

Stack: NestJS 11, PostgreSQL via Prisma 7, Hiboutik REST API as the upstream.

Commands

npm run start:dev            # watch mode (port from PORT, default 3000)
npm run build                # nest build -> dist/
npm run lint                 # eslint --fix on src/test
npm run format               # prettier (singleQuote, trailingComma all, printWidth 140)
npm test                     # unit tests (*.spec.ts under src/)
npx jest src/app.controller.spec.ts   # single test file
npx jest -t "test name"               # single test by name
npm run test:e2e             # e2e tests (test/jest-e2e.json)

Jest runs in CommonJS: its config (in package.json and test/jest-e2e.json) transpiles the ESM-only @nestjs/config (transformIgnorePatterns) and maps ./x.js imports to ./x (moduleNameMapper) for the generated Prisma client. Keep both configs in sync. Unit tests build services directly with mocked repositories; the e2e test boots AppModule with PrismaService and HiboutikService overridden, so it needs neither a database nor Hiboutik.

Prisma: the config file is prisma7.config.ts (non-default name), so pass it explicitly:

npx prisma generate --config prisma7.config.ts
npx prisma migrate dev --name <name> --config prisma7.config.ts

The client is generated into prisma/generated/ (generator prisma-client, not the legacy prisma-client-js) and must be regenerated after any schema.prisma change. Code imports types from there, e.g. prisma/generated/client and prisma/generated/models/<Model> (ProductCreateInput, etc.).

Environment

Read from .env (@nestjs/config, global): DATABASE_URL (used by Prisma and PrismaService), HIBOUTIK_BASE_URL, HIBOUTIK_USERNAME, HIBOUTIK_PASSWORD (HTTP Basic auth). HTTP calls to Hiboutik time out after 30 s (HttpModule.register in AppModule).

Architecture

Everything is registered as providers in a single AppModule (src/app.module.ts); new repositories/converters/services must be added there. PrismaModule (in prisma/, outside src/) is global.

Sync pipeline, one entity per layer:

  1. src/hiboutik/*.hiboutik.repository.ts – fetch raw data from Hiboutik through HiboutikClient (get, and getAllPages, which walks the p page param until a page smaller than 250 results, Hiboutik's page size). /suppliers and /stock_available/all_wh are not paginated.
  2. src/models/hiboutikDTO/ – snake_case types mirroring Hiboutik API payloads.
  3. src/models/converter/ – classes extending abstract Converter<Model, Dto>; fromHiboutik maps a DTO to a Prisma *CreateInput. Relations use connect: { hiboutikId }.
  4. src/database/*.database.repository.ts – Prisma access, mainly upsert keyed on the unique hiboutikId.
  5. src/services/hiboutik/*.hiboutik.service.ts – per-entity sync orchestration; HiboutikService.syncAll runs them in dependency order: brands → categories → suppliers → products → customers (products connect to the first three by hiboutikId, so order matters).

Domain notes:

  • Synced models carry a unique hiboutikId (join key with Hiboutik), except Order (nullable hiboutikSaleId), ProductVersion (unique [productId, hiboutikSizeId]) and OrderLine; the internal id is a UUID. Empty Hiboutik barcodes are stored as null. ProductVersion.barcode is indexed but not unique (Hiboutik may reuse a barcode).
  • Hiboutik is the source of truth: brands, categories, suppliers, products and customers missing from it are deleted (skipped if Hiboutik returns an empty list). Products are deleted before the upserts (so a recreated product can reuse its barcode); their versions go by cascade, and versions missing from a product are deleted before its versions are upserted. Customer addresses go by cascade with their customer; other relations (product brand/category/supplier, sub-categories, order customer) are set to null and orders are kept.
  • Brands, categories and suppliers are upserted through forEachSafely (src/utils/sync.ts): a failing row is logged and skipped. Each sync step and each product/customer is in a try/catch: failures are logged, the sync goes on, and a summary is logged at the end.
  • Categories form a tree: parents are linked in a second pass (setParent). enabled mirrors category_enabled (POS), webEnabled mirrors category_enabled_www.
  • Products: ProductVersion rows come from product_size_details (barcode, price/discount overrides from product_specific_rules, stock from the stocks endpoint); no sizes means one version with hiboutikSizeId: 0. globalStock is the sum of available stock. archived mirrors product_arch.
  • GET /customer/:id embeds addresses and sales (typed in customer.hiboutik.dto.ts); sales only has headers, lines come from GET /sales/:id (SaleHiboutikRepository).
  • Addresses (from the embedded array): missing ones are deleted, the rest upserted via forEachSafely; no array means stored addresses are left untouched. Each has its own recipient fields, empty optional values stored as null. Company details (taxNumber, companyNumber, legalStatus) come from GET /customers_addresses/:id, only for addresses with a company (null otherwise, untouched if the call fails).
  • Orders come from the customer's sales: sales already stored as COMPLETED are skipped, the others are fetched and upserted on hiboutikSaleId (source: STORE on creation only). status is COMPLETED or PENDING, orderedAt is the completion date else the creation date. Lines are replaced on each upsert and snapshot name, version, barcode, price and Hiboutik ids; the ProductVersion link is optional (SetNull). unitPrice is the paid price, taxes and discount included. Quotations are skipped, anonymous sales are never synced (only customers' sales are read), and orders are never deleted.
  • Shared Prisma payload types (e.g. ProductWithVersions) live in prisma/customObjects/.

API

If the Postman plugin is installed, load its postman:api-engineer skill before any non-trivial API engineering task (designing, implementing, mocking, testing, monitoring, documenting, deploying). Pure questions and trivial one-line edits do not need it. The local postman/ and .postman/ folders are generated by postman init and are git-ignored.

Git workflow

The remote is a Gitea instance (origin). Use plain git or the Gitea CLI (gt) for pull requests and other Gitea operations.

  • Never commit or push directly to the integration branch: always push changes on a feature/<name> branch.
  • Merges go into develop. Until develop exists (early project phase), they go into main.
  • Commit messages follow Conventional Commits (feat(sync): ..., fix(db): ...).