refactor(data-access): move tag logic onto the repository pattern - #1070
Closed
realcodesiman wants to merge 6 commits into
Closed
refactor(data-access): move tag logic onto the repository pattern#1070realcodesiman wants to merge 6 commits into
realcodesiman wants to merge 6 commits into
Conversation
…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.
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
packages/database/src/repositories/tag(pure data access) and rewritespackages/business/src/tag/service.tsto call it instead of queryingdbdirectly, addingcreate/update/deleteManymethods that didn't exist before.add/remove/update-contact-tag.action.ts) by ~90% — the sync logic (attach, detach, diff-based update) moves into the service.tags/api/token-auth.ts→workspace-token.tsonto the@chatbotx.io/api-contract/tagcontract (from feat(api-contract): add @chatbotx.io/api-contract package and implement public routers from it #1069) now thattagServicehas the CRUD methods the contract-based handlers need.apps/builder/src/lib/action-field-error.ts— amapExceptionToFieldErrorhelper bridging service-thrownChatbotXExceptiondomain errors intonext-safe-actionfield errors (i18n-translated; verifiedmessages.nameAlreadyExistsandfields.tag.labelexist).Stacked on #1065, #1066, #1068, #1069 — needs the
@chatbotx.io/api-contract/tagcontract 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.tsas the top risk. Before touching any code: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-guardcaught two real gaps this comparison missed on a first pass:TagRepository.create/.updatehad zero dedicated tests, and the re-homedsoftDeleteManytest dropped the old workspace-scoping assertion entirely. Fixed by adding 4 new tests, including acollectBoundValues()helper that walks Drizzle's realand(eq(...))SQL condition tree to verify workspace scoping against actual bound parameter values — not a mock's own shape.packages/businessgained 43,packages/databasegained 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 acontactInboxId?: stringparameter that the old version threaded intoemitTagApplied(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-guardcross-checked againstorigin/mainto 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-existingsetContactCustomFieldValue/deleteContactCustomFields) was stable before including it. It no longer importsdbdirectly (was:db.query.tagModel.findFirstat the old line 222 — now goes throughtagRepository.findById).Test plan
pnpm lint— cleanpnpm --filter builder check-types,pnpm --filter @chatbotx.io/business check-types,pnpm --filter @chatbotx.io/database check-types— all cleanpnpm --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-guardagent review — found 1 violation (test coverage gap), fixed and independently re-verified (helper confirmed to use real, non-mockedand/eq/isNullfromdrizzle-orm)findByContactId/listByContactIdtake noworkspaceIdparam, but confirmed this is a pre-existing, unchanged design (identical signature onmain), not something this PR introduces