Skip to content
TALA
Esc
navigateopen⌘Jpreview
On this page

Data model

The 19 entities and 3 join tables behind TALA, the dual user-or-organization ownership chain, every enum, and the four migrations that build them.

Schema is migration-driven. Four migrations exist, and TypeORM’s synchronize must be turned off explicitly on every shared database — it is not off by default outside production. See the warning below.

The ownership chain

User ─┬─> Collection ──< Asset ──< AssetVersion
      │       └──< collection_assets >── Asset          (many-to-many)

      └─> Organization ──< OrganizationMember ──> User
              ├──< OrganizationInvitation
              ├──< Project ──< Task ──< TaskComment
              │                 └──< task_assignees >── OrganizationMember
              │                                └──< task_comment_assets >── Asset
              ├──< Collection    (org-scoped)
              └──< Asset         (org-scoped, also project-linked)

An asset can reach a user three different ways — owner_user_id, created_by_user_id, and through its collection — and reach an organization two ways, directly or through its project. Each column is individually nullable, and which one is authoritative depends on the scope the caller asked for.

Ownership is exclusive

Nullable per column does not mean free-form. Both owner-bearing tables carry a CHECK constraint requiring exactly one of the owner pair:

Table Constraint Rule
assets CHK_assets_exactly_one_owner num_nonnulls("owner_user_id", "organization_id") = 1
collections CHK_collections_exactly_one_owner num_nonnulls("user_id", "organization_id") = 1

So an asset is personal or organizational, never both and never orphaned. created_by_user_id, collection_id and project_id are unconstrained and orthogonal — an org-owned asset still records who uploaded it.

Tables

Table Key Notable columns Soft delete
users char(10) role, account_status, provider, provider_id, image_url, plan_id; password is select: false no
organizations char(10) name, unique slug, owner_id yes
organization_members char(10) role, status; unique (org, user); index (org, status) no
organization_invitations char(10) email, role, unique token_hash, expires_at, accepted_at, revoked_at no
collections char(10) title, description, user_id + organization_id — exactly one non-null yes
assets varchar asset_type, asset_name, short + long URL, current_version, collection_id, created_by_user_id, project_id; owner_user_id + organization_id — exactly one non-null yes
asset_versions varchar version_number, s3_key, change_note; unique (asset, version_number) no
asset_logs char(10) message, activity_type; user FK is onDelete: RESTRICT yes
asset_favorites char(14) per-user favourite join no
collection_favorites char(14) per-user favourite join no
projects char(12) status, priority, start_date, due_date, position; index (org, status, position) yes
tasks char(12) status, priority, start_at, due_at, position; indexes (project, status, position) and (start_at, due_at) yes
task_comments char(12) body, author_user_id, attached assets yes
activity_logs varchar action, endpoint, status_code, resource_type, resource_id, ip_address, user_agent, jsonb metadata, duration_ms no
refresh_tokens jti char(36) sha256 token_hash, expires_at, revoked_at no
oauth_exchange_codes code_hash sha256 at rest, ~60s expiry, single use no
email_verification email token no
password_reset email token char(64) no
plans varchar unique name, minimum_seat, maximum_seat no

Join tables carry no entity of their own: collection_assets, task_assignees, task_comment_assets.

All timestamps use TypeORM’s @CreateDateColumn / @UpdateDateColumn / @DeleteDateColumn. Soft delete is deleted_at.

Enums

Every enum and its values
Enum Values Defined in
Role CONTENT_CREATOR, ADMIN auth/auth.types.ts
AccountStatus VERIFIED, PENDING, SUSPENDED auth/auth.types.ts
AuthProvider LOCAL, GOOGLE, GITHUB auth/auth.types.ts
OrgRole OWNER, ADMIN, MEMBER organization/organization.types.ts
MembershipStatus ACTIVE, INVITED, SUSPENDED organization/organization.types.ts
WorkStatus QUEUED, IN_PROGRESS, COMPLETED project/project.types.ts
WorkPriority LOW, MEDIUM, HIGH project/project.types.ts
ActivityType created, updated, deleted, accessed, shared, version_added, version_restored assets/activity-type.ts
LogAction namespaced strings — auth.login, collection.created, asset.uploaded, … activity-logs/log-action.types.ts

WorkStatus and WorkPriority are shared by projects and tasks through named Postgres types (work_status_enum, work_priority_enum), so a value added to one applies to both.

Role is the platform role and gates /api/timeline and /api/seeder. OrgRole is the tenant role and gates membership mutations. They are unrelated — a platform ADMIN is not automatically an org OWNER.

Migrations

Migration What it does
InitialSchema Base tables
SeedPlans Inserts the plan rows — no /api/seeder call needed
LockDownPublicSchema Enables RLS and revokes anon / authenticated grants and default privileges
CoreMvpCollaboration Organizations, invitations, projects, tasks, comments, favourites, dual ownership
npm run migration:generate -- src/migrations/MigrationName
npm run migration:run
npm run migration:revert

Was this page helpful?