Commit 1ed43a4
authored
feat(automation): implement public API for flows, triggers, keywords, ai-agents, reflinks, ai-triggers (#1102)
* refactor(builder): move flows, triggers, sequences, broadcasts, webhooks data access into business
Removes direct db usage from flows, triggers, sequences, broadcasts,
webhooks, the template resource picker and saved replies per
.agents/rules/data-access.md. Public oRPC router keys, paths and the
public-spec snapshot are unchanged.
- new flow, sequence, broadcast, trigger, condition and
template-selectable-resource repositories; webhook repository gains
paginated list + detail reads
- flowService.createWithDefaultDraft, flowVersionService.publish,
triggerService/webhookService create/update/updateSettings/deleteMany
(trigger and webhook condition-diff semantics kept separate),
new sequenceService, broadcastService.create/resend,
savedReplyService.listByWorkspaceId
- broadcastService.create validates each integration id independently
so the validation error lands on the field that failed
- validationException added to errors.ts in the same form as #1093
- deleteSequence now also scopes by workspaceId (defense in depth)
* feat(automation): widen public API to full CRUD across flows, triggers, keywords, ai-agents, reflinks, ai-triggers
Finishes the data-access chain (action -> service -> repository -> DB)
for flows, sequences, broadcasts, saved-replies, reflinks, bot-fields,
and greenfield ai-triggers, then widens the `automation` token scope
from read-only to full CRUD so MCP/agent clients can build, publish,
and inspect automations end to end.
* fix(automation): close data-loss and correctness bugs in PR #1102's public API
Remediates review findings from PR #1102's data-access refactor and public
API widening:
- PUT /v1/keywords/{id} without `keywords` silently wiped the automation's
keywords to `[]`; the service now leaves the column untouched when the
caller omits it, and moves the text/flowId mutual-exclusion + cross-
workspace flowId validation down from the action into the service so
every caller gets the same invariants.
- POST /v1/ai-agents could return the wrong resource on a duplicate name
(no unique constraint); `create` now returns the inserted id and the
handler re-fetches by id instead of by name.
- public-spec-operations.test.ts had no explicit `beforeAll` timeout,
causing CI-only flakiness as the OpenAPI snapshot grew.
- GET /v1/triggers loaded every trigger in the workspace then re-queried
each one individually; added `triggerService.list` backed by a single
SQL-paginated query with conditions joined in.
- Reflinks' `findReflink` swallowed every error (including infra failures)
as not-found; added a nullable `reflinkService.find()`.
- Public flow imports attributed every import to the workspace owner;
now pass `userId: null`, matching the contacts public-API precedent.
- `triggerResource` published `z.array(z.any())` for conditions; conditions
now has a real, documented shape.
- `resendBroadcast` read a broadcast's contact filter before verifying it
was resendable, and dropped the `deletedAt` predicate; added
`broadcastService.assertResendable` to guard first.
- `updateSequenceAction` masked a 404 as a generic 500 by wrapping the
whole call in a catch-all; now rethrows `ChatbotXException` unchanged.
- `aiTriggerService.list` divided pageCount by an unclamped `perPage`.
Also: dedicated tests for `webhookService.updateWithConditions`,
`sequence/step-payload.ts`'s defaulting logic, `broadcastService.create`'s
insert shape, and bot-field unique-violation mapping; replaced two
hand-rolled `UNIQUE_VIOLATION_CODE` checks with `isUniqueViolationError`;
collapsed `templateSelectableResourceRepository`'s 11 near-identical list
methods into one generic helper; removed dead double-mapping of trigger/
webhook conditions now that the service owns column normalization.
* fix(automation): restore flows list contract and field-level validation errors
- GET /v1/flows again defaults `active: true` and returns `{id, name}`
instead of the full flow row plus embedded flowVersions, avoiding a
breaking change to an existing public-API consumer contract.
- Add isValidationException (a real instanceof ChatbotXException guard)
and use it in place of duck-typed `"code" in error` checks in reflinks,
bot-fields, and sequences actions, which could mis-narrow on unrelated
driver/system errors and always hardcoded the error message.
- Wrap automatedResponseService.update in update-automated-response-action
so the flowId-not-found validation exception (moved into the service by
the prior refactor) still surfaces as a field-level form error instead
of a generic toast.
- Fix aiTriggerService.list to divide pageCount by the same clamped limit
the repository uses (getPaginationWithDefaults), instead of a
Math.min(maxLimit, perPage) expression that could divide by undefined.
* fix(automation): scope keyword writes by type and stabilize public API pagination
Third round of correctness fixes on the public API surface:
- Keywords endpoints now pass `type: "inbound"` through findOrFail, update,
setStatus and deleteMany so an outbound (Page) automated response can no
longer be read, mutated or deleted through the inbound Keywords routes —
one table serves two FolderTypes, so workspaceId + id alone is not a
sufficient scope.
- Add `flowVersionService.updateDraftByFlowId` for callers that only know the
flow id; `PUT /v1/flows/{id}/draft` was passing a flow id where a
flow-version id was expected.
- Give `triggerRepository.listPaginatedWithConditions` a deterministic
`orderBy` so paginated results cannot repeat or skip rows.
- Skip no-op updates in ai-agent, ai-trigger and reflink services so an
all-undefined payload no longer issues an empty SET or a spurious audit
entry.
- Write `questions: []` explicitly on ai-trigger create — the column has no
database default despite the drizzle `.default()`.
- Correct `flowService.list`'s return type to `limit`/`offset`, matching what
`parsePagination` actually spreads.
* fix(automation): unify trigger/webhook list pagination and drop redundant broadcast query
Closes the remaining data-access gaps from the flows/triggers/sequences/
broadcasts/webhooks refactor: the public API and builder pages for triggers
and webhooks each maintained a separate, diverging list implementation, and
the webhook public API loaded every row in the workspace before paginating
in memory.
- webhookService.list: SQL-paginated, conditions joined, shared by
GET /v1/webhooks and the builder's webhooks page (replaces the unbounded
listByWorkspaceId + paginateInMemory path)
- triggerService.list: extended with folderId/name filters so the builder's
triggers page can share it with GET /v1/triggers instead of hand-rolling
a second, unordered pagination; added triggerService.findWithConditions
to remove three direct repository reads from the public API
- resend-broadcast.action: read contactFilter off assertResendable's
already-fetched row instead of issuing a second query for it
- create-broadcast.action: use isValidationException instead of a
duck-typed error check, matching every sibling action
- flow detail pages: call flowService.findById instead of flowRepository
directly
* fix(automation): move audience/list reads and step scheduling into business services
Closes remaining direct-repository access from the builder app layer for
broadcasts (list, audience, findByIdOrName), sequences (list, findWithSteps),
and moves sequence-step contact-schedule recalculation into
packages/business/src/sequence, alongside triggers/ai-agents write handlers
now returning their created/updated model instead of a redundant follow-up
findBy. Automated-response reads/writes are scoped by type end-to-end
(inbound vs outbound) to prevent cross-type leaks.
* fix(automation): move template picker query into business and scope reflink writes
Move the template picker's selectable-resource dispatch out of the builder
query layer into `templateService.listSelectableResources`, so the query file
is a thin adapter and the repository is no longer reached from `apps/`.
Scope `reflinkService` update/deleteMany/listOptions by `type = "refLink"` to
match what `create` stamps, so an entry-point-link row sharing the table can
never be updated or deleted through the reflink surface.
Stop swallowing every error on the flow pages: only a service-thrown
`notFoundException` becomes `notFound()`, and a DB failure propagates as a
real 500 instead of a misleading 404.
Drop the now-unused `broadcastRepository.findIdIfActive` and
`findContactFilter` along with their tests and service mocks.1 parent f8b7e0b commit 1ed43a4
171 files changed
Lines changed: 11500 additions & 5885 deletions
File tree
- apps/builder
- __tests__
- __snapshots__
- src
- app
- (no-sidebar)/space/[workspaceId]/flows/[id]
- analytics
- space/[workspaceId]
- automated-responses/[id]/edit
- page-automated-responses/[id]/edit
- features
- ai-agents/api
- ai-triggers
- actions
- api
- queries
- schema
- automated-response
- actions
- api
- schema
- bot-fields/actions
- broadcasts
- actions
- api
- queries
- conditions
- flows
- actions
- api
- queries
- reflinks
- actions
- api
- queries
- saved-replies
- actions
- queries
- sequences
- actions
- api
- queries
- templates/queries
- triggers
- actions
- api
- queries
- schema
- webhooks
- actions
- api
- queries
- lib/errors
- routers
- docs/developer
- packages
- business
- __tests__
- src
- ai-agent
- ai-trigger
- automated-response
- bot-field
- broadcast
- flow-version
- flow
- import
- reflink
- saved-reply
- sequence
- template
- trigger
- webhook
- database
- __tests__
- src/repositories
- ai-trigger
- broadcast
- condition
- flow
- reflink
- sequence
- template-selectable-resource
- trigger
- webhook
- whatsapp-message-template
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 165 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
5 | 20 | | |
6 | 21 | | |
7 | 22 | | |
8 | 23 | | |
9 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
10 | 60 | | |
11 | 61 | | |
12 | 62 | | |
| |||
412 | 462 | | |
413 | 463 | | |
414 | 464 | | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
415 | 490 | | |
416 | 491 | | |
417 | 492 | | |
418 | 493 | | |
419 | 494 | | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
420 | 515 | | |
421 | 516 | | |
422 | 517 | | |
| |||
482 | 577 | | |
483 | 578 | | |
484 | 579 | | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
485 | 595 | | |
486 | 596 | | |
487 | 597 | | |
488 | 598 | | |
489 | 599 | | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
490 | 610 | | |
491 | 611 | | |
492 | 612 | | |
| |||
532 | 652 | | |
533 | 653 | | |
534 | 654 | | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
535 | 665 | | |
536 | 666 | | |
537 | 667 | | |
538 | 668 | | |
539 | 669 | | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
540 | 680 | | |
541 | 681 | | |
542 | 682 | | |
| |||
582 | 722 | | |
583 | 723 | | |
584 | 724 | | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
585 | 740 | | |
586 | 741 | | |
587 | 742 | | |
588 | 743 | | |
589 | 744 | | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
590 | 755 | | |
591 | 756 | | |
592 | 757 | | |
| |||
0 commit comments