Skip to content

refactor(data-access): move tag logic onto the repository pattern - #1070

Closed
realcodesiman wants to merge 6 commits into
mainfrom
refactor/tag-repository-migration
Closed

refactor(data-access): move tag logic onto the repository pattern#1070
realcodesiman wants to merge 6 commits into
mainfrom
refactor/tag-repository-migration

Conversation

@realcodesiman

Copy link
Copy Markdown
Contributor

Summary

  • Introduces packages/database/src/repositories/tag (pure data access) and rewrites packages/business/src/tag/service.ts to call it instead of querying db directly, adding create/update/deleteMany methods that didn't exist before.
  • Shrinks the contact-tag actions (add/remove/update-contact-tag.action.ts) by ~90% — the sync logic (attach, detach, diff-based update) moves into the service.
  • Renames tags/api/token-auth.tsworkspace-token.ts onto the @chatbotx.io/api-contract/tag contract (from feat(api-contract): add @chatbotx.io/api-contract package and implement public routers from it #1069) now that tagService has the CRUD methods the contract-based handlers need.
  • Adds apps/builder/src/lib/action-field-error.ts — a mapExceptionToFieldError helper bridging service-thrown ChatbotXException domain errors into next-safe-action field errors (i18n-translated; verified messages.nameAlreadyExists and fields.tag.label exist).

⚠️ Branch dependency

Stacked on #1065, #1066, #1068, #1069 — needs the @chatbotx.io/api-contract/tag contract from #1069.

This is the highest bug-risk section of the whole original migration branch — here's what I did about that

The plan flagged re-homing the 898-line deleted contact-tag-actions.test.ts as the top risk. Before touching any code:

  • Compared every test name in the 3 deleted files (contact-tag-actions.test.ts, delete-tag-action.test.ts, tag-crud-actions.test.ts) against the 3 new files (packages/database/__tests__/tag-repository.test.ts, packages/business/src/tag/__tests__/{service,contact-tag-sync}.test.ts) one by one. Confirmed every scenario maps to an equivalent — some legitimately consolidated (three chunk-boundary tests at counts 200/201/400 became one test at the 200/201 boundary, since chunking moved from redundant action-level tests to one service-level test).
  • invariant-guard caught two real gaps this comparison missed on a first pass: TagRepository.create/.update had zero dedicated tests, and the re-homed softDeleteMany test dropped the old workspace-scoping assertion entirely. Fixed by adding 4 new tests, including a collectBoundValues() helper that walks Drizzle's real and(eq(...)) SQL condition tree to verify workspace scoping against actual bound parameter values — not a mock's own shape.
  • Net effect: builder lost 53 tests (the 3 deleted files), packages/business gained 43, packages/database gained 26 (22 original re-homed + 4 from the coverage-gap fix). Total test count across the three packages went up, not down.

A real regression found and fixed during review

The source branch's new tagService.attachToContact() silently dropped a contactInboxId?: string parameter that the old version threaded into emitTagApplied(workspaceId, contactId, tagId, contactInboxId) for ads/CAPI attribution. The event dispatcher (packages/events/src/event-dispatcher.ts) still accepts this 4th argument — it just wasn't being passed anymore. The only caller relying on it, packages/business/src/minigame/minigame-contact-service.ts (an unrelated file, untouched by this PR otherwise), failed to typecheck as a direct result — that's how this surfaced. Restored the parameter and its threading; invariant-guard cross-checked against origin/main to confirm no other call site historically needed the same treatment.

Also found while extracting (not in the original plan's file list)

apps/builder/src/features/contacts/api/private.ts (293 lines, full rewrite) — combines tag-related and custom-field-related contact endpoints. Verified every dependency it calls (tagService, tagRepository, plus the unrelated-but-pre-existing setContactCustomFieldValue/deleteContactCustomFields) was stable before including it. It no longer imports db directly (was: db.query.tagModel.findFirst at the old line 222 — now goes through tagRepository.findById).

Test plan

  • pnpm lint — clean
  • pnpm --filter builder check-types, pnpm --filter @chatbotx.io/business check-types, pnpm --filter @chatbotx.io/database check-types — all clean
  • pnpm --filter builder test — 1976/1976 (down 53 from the 3 deleted/re-homed test files, as expected)
  • pnpm --filter @chatbotx.io/business test — 1512/1512 (+43)
  • pnpm --filter @chatbotx.io/database test — 552/552 (+26, includes the coverage-gap fix)
  • pnpm check:circular — no new circular deps (69 pre-existing, unchanged)
  • invariant-guard agent review — found 1 violation (test coverage gap), fixed and independently re-verified (helper confirmed to use real, non-mocked and/eq/isNull from drizzle-orm)
  • Manual security review — findByContactId/listByContactId take no workspaceId param, but confirmed this is a pre-existing, unchanged design (identical signature on main), not something this PR introduces

…s to api/private.ts

Pure mechanical rename across ~61 feature directories, extracted fresh off
main instead of cherry-picked from the migration branch since many of those
paths also carry unrelated logic changes there. Excludes ads-campaign,
which no longer exists on the source branch.
Adds a tokenHash column and moves workspace bearer-token lookup to hash-first
with a plaintext fallback for the deploy-to-migration gap. The fallback and
the token column removal are deliberately deferred to a follow-up once the
legacy-plaintext warning log has been silent for a release.

Also generalizes channel-api-rate-limit.ts into api-rate-limit.ts (adds a
scope param) and pulls authorize-workspace-access.ts forward as shared
owner-quota/trial-gate infrastructure, since the token-auth middleware needs
both.
…ernal router

Consolidates the three oRPC auth-stack exports onto one instanceof-based error
mapper (was three duplicated error.name checks), adding an ActionValidationError
-> 422 mapping. Wires the owner-quota/trial gate from authorize-workspace-access.ts
into workspaceAuthorizedMidddleware so an oRPC mutation can't bypass the gate a
server action already enforces for the same operation (invariant #14: read/delete
stays open on an expired workspace).

Splits the OpenAPI REST surface so /api/[[...rest]] only ever serves publicRouter
(workspace-token / channel-token authed procedures) instead of the full
session-authed router. A procedure missing from publicRouter now 404s instead of
silently answering to a session cookie. A dev-only /api-internal mirrors the old
full-router behavior for local Scalar debugging and 404s in production; the
builder UI is unaffected since it calls the untouched /rpc route, not /api.
…nt public routers from it

Introduces a stable, versioned contract package (implement(contract) pattern)
for the public v1 workspace-token API surface, replacing per-feature ad-hoc
.route({...}) builder calls. A contract carries every field the OpenAPI
generator needs (route, input, output, errors) independent of the handler
implementation, so MCP tool names (derived from operationId) and hard-coded
Postman/CLI paths stop drifting silently with unrelated refactors.

Rewires 18 of the public router's workspace-token API modules onto the new
contracts. Left three untouched: tags/api/token-auth.ts still calls
tagService methods (create/update/deleteMany) that don't exist until a later
repository-migration PR, and contacts/webhooks aren't part of this contract
migration at all yet (no contract module exists for them).

Fixes a DB-import leak found during review: two contract resource files
imported schema-only symbols from the bare @chatbotx.io/business package
root, which transitively pulls in service modules that instantiate a
Postgres Pool at module scope -- undermining the package's whole point of
being testable without a live database. Added dedicated schema-only subpath
exports (inbox/schema, integration-whatsapp/schema) instead.

Also fixes a stale packages/public-apis mention in AGENTS.md's package table
and deletes the now-obsolete public-api-tooling skill doc.
Introduces packages/database/src/repositories/tag (pure data access) and
rewrites packages/business/src/tag/service.ts to call it instead of querying
db directly, adding create/update/deleteMany methods. Shrinks the
contact-tag actions (add/remove/update) by ~90% since the sync logic (attach,
detach, diff-based update) moves into the service.

Re-homes test coverage for the deleted action-layer tests into the correct
new layers: packages/database/__tests__/tag-repository.test.ts for SQL-shape
behavior, packages/business/src/tag/__tests__/{service,contact-tag-sync}.test.ts
for business logic and cross-cutting sync behavior. Verified test-by-test
that no scenario from the 898-line deleted contact-tag-actions.test.ts was
silently dropped -- some were consolidated (three chunk-boundary tests at
200/201/400 became one at the 200/201 boundary), and a review pass caught two
real gaps before landing: TagRepository.create/.update had zero dedicated
tests, and softDeleteMany's workspaceId-scoping assertion had no equivalent,
so all three got test coverage that inspects real Drizzle SQL condition
objects rather than a mock's own shape.

Also fixes a silent regression found during review: the new
tagService.attachToContact() dropped a contactInboxId parameter the old
version threaded into emitTagApplied for ads/CAPI attribution -- restored it,
which was the only thing standing between this branch and a build break in
the unrelated minigame-contact-service.ts.

Renames tags/api/token-auth.ts -> workspace-token.ts onto the
@chatbotx.io/api-contract/tag contract now that tagService has the CRUD
methods the contract-based handlers need.
@github-actions github-actions Bot added the improvement Refactor or performance improvement label Aug 30, 2026
@realcodesiman

Copy link
Copy Markdown
Contributor Author

Closing for now — this PR was opened by an automated process without review. Reopening once PR-4 (which this stacks on) has its validation-regression fixes applied and the stack is properly reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Refactor or performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant