Skip to content
TALA
Esc
navigateopen⌘Jpreview
On this page

Architecture

How TALA is put together — two repositories, 14 backend modules, an exclusive user-or-organization ownership model, and a global request pipeline.

TALA has one architectural idea and everything else follows from it: a row is owned by a person or by an organization — exactly one, never both, never neither.

Assets carry owner_user_id alongside organization_id; collections carry the same pair as user_id and organization_id. Each column is individually nullable, but a database CHECK constraint on both tables requires precisely one of the pair to be set. That is what makes ?scope=personal|organization work on the listing endpoints, and it is why every tenant-facing request resolves an org context before it touches the database.

CHK_assets_exactly_one_owner       num_nonnulls("owner_user_id", "organization_id") = 1
CHK_collections_exactly_one_owner  num_nonnulls("user_id", "organization_id") = 1

The shape of a request

Every authenticated call to tenant data walks the same path.

ThrottlerGuard

60 requests per 60 seconds per IP, applied globally as APP_GUARD.

AuthGuard

Decrypts the AES-256-GCM envelope, verifies the JWT with algorithms: ['HS256'] pinned, rejects any account that is not VERIFIED, and scrubs password off req.user.

OrganizationGuard

Resolves the caller’s active membership and writes { organizationId, role } onto req.orgContext.

ValidationPipe

Whitelists the body against its DTO — unknown keys are dropped silently — and coerces param and query types.

Controller and service

The service re-checks ownership against the resolved context. A mismatch returns 404, not 403.

ActivityLogsInterceptor

Records the call after the response via tap(), without awaiting.

Two audit trails, deliberately

They answer different questions and neither replaces the other.

asset_logs activity_logs
Written by services, inside the transaction ActivityLogsInterceptor, after the response
Awaited yes no
Has HTTP context no endpoint, status, IP, user agent, duration
Survives a rollback no — it rolls back with the write yes — it is fire-and-forget
Answers “what happened to this asset?” “who called what, and did it work?”

Identifiers

IDs are CSPRNG-generated, not sequential. generateId(prefix, suffixLength = 12) in src/lib/utils/id.util.ts produces values like ASa7Bk9x2Q….

The old scheme was PREFIX + (1000 + COUNT + 1), which both raced under concurrency and let anyone enumerate the table. It is gone — but legacy sequential IDs still exist in old rows.

Was this page helpful?