Corteksa

Backend Integration

CRM — Backend Integration

For backend developers extending or maintaining the CRM (the object module, class CrmModule in crm.module.ts).

Services

Controllers call DataService (records/services/data.service.ts) — the facade — and nothing else. It delegates:

TypeExamplesRule
FacadeDataServiceThe only records entry point for controllers.
Queryrecords/queries/list, search, lookup, single, facets, relation-batchRead paths + pagination + filtering. No writes.
Mutationrecords/mutations/record-create, record-update, record-delete, record-bulkWrites in a transaction, then emit a data.* event.
ResponseDataResponseServiceShape rows into the { fields, data } envelope via response DTOs.

Add a read concern → a new query service; a write concern → a new mutation service. Don't grow the controllers.

Validation (field-type registry)

Record data is validated per field before persistence:

  • DynamicValidationService (fields/services/validation/dynamic-validation.service.ts) walks the object's fields, checks required-ness (FieldProcessor — a missing slug on a required field throws Field '<name>' is required), and dispatches each value to its type handler.
  • FieldTypeRegistry (_shared/field-types/) is the factory: at boot it auto-discovers every @HandlesFieldType(...) provider and builds a FieldType → handler map (validate + serialize). It fails fast if any of the 17 FieldTypes has no handler — a new type can't silently pass through.
  • ValidationEngineService additionally enforces conditional-requirement rules (a field required only when another field has a given value).

select / multi_select / status / priority / tag values are validated against the field's allowed options after the required check.

Controllers & guards

Record and schema controllers are reachable by both a human JWT and an API key — they stack @UseGuards(ApiAuthGuard, PermissionGuard):

  • DataReadController / DataWriteController (object/data)
  • ObjectController (object), FieldController (object/field)

ApiAuthGuard extends AdminAuthGuard: it routes by credential (crtk_live_… → API key, crtk_oauth_… → OAuth, else JWT) and attaches the same acting-as-admin AdminUser — so PermissionGuard and the row-level filters behave identically downstream. API keys never inherit the super-admin bypass.

Sibling controllers are admin-JWT-only (AdminAuthGuard, no API key): RelationController (object/relation), CustomViewController (object/custom/view), DataExportImportController, AnalyticsController, and CommentController (comments/:objectSlug/:dataSlug, AdminAuthGuard only — no PermissionGuard).

Permissions

Record endpoints use the derived route names {verb}.{objectSlug} (read.deals, create.deals, …), tagged with @RouteName('create.{objectSlug}')PermissionGuard resolves {objectSlug} from the request param. Object schema endpoints use static object.* / relation.* route names. There is no separate binary per-object permission — the level owns the gate (the single-gate, Kommo-style model). See Authorization.

Row-level scope is applied even past the gate via access-filter.builder.ts (A: no filter, G: group members, M: admin_id = me, D: 1=0).

Events

Mutations emit through RecordEventsService (records/mutations/helpers/):

EventWhenPayload highlights
data.createdRecord createdobjectSlug, dataSlug, newData, sourceType, enrichedRecord
data.updatedRecord updatedoldData, newData, changedRelations
data.deletedRecord deletedobjectSlug, dataSlug
data.bulk_deleted / bulk_updated / bulk_assignedBulk opsaffectedSlugs, affectedCount

sourceType is manual / automation / system / api / ai; API-key and OAuth writes are auto-tagged api with sourceApiKeyId / sourceOauthClientId for webhook loop prevention. See Events.

Multi-tenant safety

The CRM runs on the shared hyper-tenant DB. All DB access must go through the pinned repoProvider; Bull processors (import/export) decorate the handler with @TenantScoped(). Never reach the raw DataSource — RLS would return zero rows or reject the insert. Read the isolation rules before touching any query or worker.

Next

On this page