Skip to content

feat(api): PurchaseOrderVendor endpoints (1 of 4 newly-migrated tables)#50

Merged
CryptoJones merged 1 commit into
masterfrom
feat/purchase-order-vendor-endpoints
May 17, 2026
Merged

feat(api): PurchaseOrderVendor endpoints (1 of 4 newly-migrated tables)#50
CryptoJones merged 1 commit into
masterfrom
feat/purchase-order-vendor-endpoints

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

Tracker: #49.

Summary

Full CRUD for PurchaseOrderVendors. Direct compId scoping (povCompId) — same auth shape as Worker/BillingType/InventoryItem.

  • POST /v1/purchaseordervendor
  • GET /v1/purchaseordervendor/:id
  • GET /v1/purchaseordervendor/bycompany/:id (paginated)
  • PATCH /v1/purchaseordervendor/:id (povCompId not patchable post-create)
  • DELETE /v1/purchaseordervendor/:id (soft-delete via povArch)

Vendors first because PurchaseOrderHeaders FK-references this table. Headers, Lines, and InventoryTransactions follow in separate PRs.

Test plan

  • vitest: 25 files / 175 passing (was 24 / 167)
  • Auth-contract tests + body validation
  • Live PG integration with the migration applied

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

Full CRUD for the vendors a Company issues purchase orders to.

  POST    /v1/purchaseordervendor
  GET     /v1/purchaseordervendor/:id
  GET     /v1/purchaseordervendor/bycompany/:id  (paginated)
  PATCH   /v1/purchaseordervendor/:id
  DELETE  /v1/purchaseordervendor/:id            (soft-delete via povArch)

Direct compId scoping via povCompId — same auth shape as
Worker/BillingType/InventoryItem. Schema whitelist enforced by
zod at the middleware boundary AND a server-side ALLOWED_FIELDS
allowlist in the controller (mass-assignment defense in depth).
povCompId is not patchable post-create (would amount to moving a
vendor between companies, which would break auth invariants).

This is 1 of 4 endpoints for the tables added by the
20260517000000-purchase-orders-and-archive-columns migration.
Vendors first because PurchaseOrderHeaders FK-references this
table; headers and lines will follow in their own PR. The
fourth table, InventoryTransactions, is unrelated and gets its
own PR too.

Tests: 25 files / 175 tests (was 24 / 167).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CryptoJones CryptoJones merged commit 6be367a into master May 17, 2026
1 check was pending
@CryptoJones CryptoJones deleted the feat/purchase-order-vendor-endpoints branch May 17, 2026 23:49
CryptoJones added a commit that referenced this pull request May 17, 2026
…d tables) (#51)

Two more entities from the 20260517000000 migration. Both
scope auth through the vendor's povCompId via new helpers in
middleware/auth.js:

  - getCompanyIdByPovId(povId)  — header uses this for pohPovId lookup
  - getCompanyIdByPohId(pohId)  — line uses this (header → vendor → company)

Header endpoints:
  POST   /v1/purchaseorderheader
  GET    /v1/purchaseorderheader/:id
  GET    /v1/purchaseorderheader/byvendor/:id   (paginated, newest first)
  PATCH  /v1/purchaseorderheader/:id
  DELETE /v1/purchaseorderheader/:id            (soft-delete via pohArch)

Line endpoints (header-scoped via polpoh — note the lowercase
column name; matches what the migration / BACPAC actually
create rather than renaming):
  POST   /v1/purchaseorderline
  GET    /v1/purchaseorderline/:id
  GET    /v1/purchaseorderline/byheader/:id     (paginated)
  PATCH  /v1/purchaseorderline/:id
  DELETE /v1/purchaseorderline/:id              (soft-delete via polArch)

Vendor (#50) is now complete; this leaves only InventoryTransactions
from the 4-table migration without endpoints.

Tests: 27 files / 191 tests (was 25 / 175).

Co-authored-by: Aaron K. Clark <akclark@thenetwerk.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CryptoJones added a commit that referenced this pull request May 17, 2026
)

Continues the housekeeping pattern of #44 — keep the README endpoint
table and CHANGELOG \`[Unreleased]\` in sync with merged PRs.

README:
  - Append rows for the four PurchaseOrder/Inventory entities
    that gained endpoints in #50, #51, #52.

CHANGELOG (under \`[Unreleased]\`):
  - PurchaseOrder + Inventory API rollout (the tracker, #49, and
    its three PRs)
  - JSON_BODY_LIMIT env hook (#45 / #46 / #47)
  - npm audit fix + dep bumps + Snyk PR triage (#30 / #48)

Co-authored-by: Aaron K. Clark <akclark@thenetwerk.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CryptoJones added a commit that referenced this pull request May 18, 2026
…#59)

setup/TimeTrackerAPI.postman_collection.json — 47 endpoints across
16 entity folders, generated via openapi-to-postmanv2 from the
single source of truth in app/config/openapi.js. README adds a
short pointer + the one-liner to regenerate after API changes.

Why generate, not hand-write:
  - app/config/openapi.js is the spec we already maintain
    (PR #50 conventions kept it in sync with router additions).
    Hand-writing a separate Postman collection would create a
    second source of truth that immediately drifts.
  - openapi-to-postmanv2 is the canonical generator and produces
    a v2.1 collection that imports cleanly into Postman / Bruno /
    Insomnia.

The 1MB collection size is example-payload bloat from the
generator — unavoidable without templating support that Postman
v2.1 doesn't fully expose. Worth the cost for "import → click →
authenticated request" out-of-the-box ergonomics.

Co-authored-by: Aaron K. Clark <akclark@thenetwerk.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CryptoJones added a commit that referenced this pull request May 18, 2026
…sts (P5-M) (#86)

Architect audit P5-M. The last item on the audit issue (#73 GH /
#50 Codeberg) closes here.

Two changes to `app/middleware/auth.js`:

1. **Models instead of raw SQL.** Every DB hit in auth.js now goes
   through the Sequelize model layer (`ApiMaster.findOne`,
   `ApiKey.findOne`, `Customer.findByPk`, `PurchaseOrderVendor`,
   `PurchaseOrderHeader`, `Job` — the last two using
   `include`-based eager loading for the cascade lookups). The
   archive filter relies on P2-E's `defaultScope` instead of a
   repeated `<arch>: false` WHERE clause. Net effect: fewer
   hand-rolled SQL strings, identical semantics, and every code
   path is mockable from a test fixture.

2. **`_setDbForTesting(stub)` injection seam.** vitest's `vi.mock`
   does not reliably intercept this codebase's CJS `require()`
   chains — the model files use sequelize-cli's CJS conventions
   and `require('../config/db.config.js')` was capturing the real
   module before the mock could register. Late-binding via a
   `getDb()` accessor didn't fix it (vitest's mock layer still
   missed the late require). The smallest practical injection
   point is an explicit setter that overrides the lookup; tests
   call `auth._setDbForTesting({ ApiMaster: { findOne: vi.fn() },
   ... })` to wire fixtures, and reset with
   `_setDbForTesting(null)` in afterEach. Production code MUST NOT
   call it — the underscore prefix flags the test-only contract.

Tests: rewritten `tests/unit/auth.test.js` exercises 28 cases vs
the previous 14. New coverage:
- isMaster: success path "row found → returns true" + verifies the
  WHERE clause uses the hashed key (not the raw header value).
- getCompanyId: success path "returns akCompanyId".
- getCompanyIdByCustomerId / ByPovId / ByPohId / ByJobId: each
  helper has its own describe block covering both the success path
  and the "no row" / "broken FK" / "non-positive id" branches.
- requireAuth: full strict-gate matrix (missing key / unknown key
  / master / scoped).

Full suite: 420 pass / 4 skip (was 401/4 — +19 net new tests).
Lint: 0 errors / 0 warnings.

With P5-M shipping, every audit item from issue #73 / #50 is now
landed. The audit tracker can be closed.

Co-authored-by: Aaron K. Clark <akclark@thenetwerk.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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