Skip to content

feat(api): POST /v1/customer/bulk — transaction-wrapped batch create#66

Merged
CryptoJones merged 1 commit into
masterfrom
feat/customer-bulk-create
May 18, 2026
Merged

feat(api): POST /v1/customer/bulk — transaction-wrapped batch create#66
CryptoJones merged 1 commit into
masterfrom
feat/customer-bulk-create

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

Summary

New POST /v1/customer/bulk endpoint. Takes { customers: [{...}, ...] } (capped at 500 per request) and inserts inside a Sequelize transaction. All-or-nothing: any failure rolls the entire batch back. Closes the ETL gap.

Auth applied per-entry: master keys must specify custCompId on each entry; non-master keys auto-scope to their authKey's company and any mismatching entry returns 403 with the offending index.

Test plan

  • vitest: 249 passing + 4 integration skipped (was 241 + 4 skipped)
  • Auth contract (403)
  • Body validation: empty array, oversize batch, unknown top-level + per-entry fields, 500-entry boundary
  • Route mounted (not 404)
  • Live integration verifying transaction rollback on DB error

Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/

ETL / import workflows shouldn't need to POST one customer at a
time. The new endpoint takes an array (capped at 500 per request)
and inserts inside a single Sequelize transaction:

  POST /v1/customer/bulk
  { "customers": [ {...}, {...}, ... ] }

All-or-nothing: if any insert fails (DB constraint, value out of
range, etc.) the whole transaction rolls back and the response is
500 with the error. On success: 201 with `{ count, customers }`.

Auth shape matches POST /v1/customer applied per-entry:
  - missing authKey                                        -> 403
  - non-master, entry with custCompId NOT in auth scope    -> 403 (with index)
  - non-master without custCompId on entry                 -> defaults to scope
  - master without custCompId on entry                     -> 400 (with index)

Validation is zod-driven: each entry runs through createCustomerBody,
so unknown fields per-entry are rejected the same way as on the
single-create endpoint. 500-entry hard cap so a single request
doesn't lock the table for minutes.

Tests: 7 cases covering the auth contract, body validation (empty
array, oversize batch, unknown top-level field, unknown per-entry
field, exactly-500 boundary), and route mounting.

Suite: 34 files / 249 passing + 4 integration skipped (was 33 / 241).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CryptoJones CryptoJones merged commit 6330f4f into master May 18, 2026
2 of 3 checks passed
@CryptoJones CryptoJones deleted the feat/customer-bulk-create branch May 18, 2026 02:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant