mattermost-mobile-upstream/docs/database/server/server.md
Guillermo Vayá b541867fbf
Add BoardView, PropertyField, PropertyValue WatermelonDB tables (#9708)
* Add BoardView, PropertyField, PropertyValue WatermelonDB tables

Data-layer groundwork for the Integrated Boards feature. Bumps the
server schema from v19 to v20 and adds three new tables with their
models, transformers, handler mixin, and tests. No queries, actions,
or UI yet — those arrive with follow-up work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Align boards types with server contract (BO/BP, KanbanProps)

Widen ChannelType to include BO/BP, add OPEN_BOARD/PRIVATE_BOARD and
CHANNEL_PROPS.BOARD_LINKED_PROPERTIES constants, and replace the
free-form View.props with the typed KanbanProps/KanbanGroupBy/
KanbanColumn shape from mattermost/mattermost#35887.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Allow null View.props to match transformer behavior

The boards transformer assigns null to view.props when neither the
incoming record nor existing row provides one, but the View type
declared props as non-nullable. Widen to KanbanProps | null so
consumers don't need unsafe non-null assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* remove test that don't provide value

* Throw synchronously in boards transformers for consistency

Other server transformers (channel, category, post, custom_profile)
throw synchronously from their non-async Promise-returning signatures
when a non-create action is called without an existing record. The
boards transformer was the odd one out using Promise.reject; align
it with the prevailing pattern and update the two tests to assert the
synchronous throw via expect(() => ...).toThrow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Allow null View.description to match transformer behavior

The schema marks description as isOptional, the model types it as
string | null, and the transformer assigns null when the raw payload
omits it. The wire type was string | undefined only — widen to
string | null | undefined so callers handle the null the transformer
can write through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Make View.props optional to match nullable schema contract

Schema marks props as isOptional and model types it as KanbanProps | null,
so ViewPatch consumers should be able to omit or clear it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Rename View → BoardView across the boards data layer

Avoids the symbol clash with React Native's View component and gives
the migration/handler names a clearer boards-specific identity.

Renames:
- API type View → BoardView, ViewPatch → BoardViewPatch
- Model class ViewModel → BoardViewModel
- MM_TABLES.SERVER.VIEW → BOARD_VIEW (table name string unchanged)
- transformViewRecord → transformBoardViewRecord
- handleViews → handleBoardViews (views arg → boardViews)
- HandleViewsArgs → HandleBoardViewsArgs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Extract PropertyField/Value transformers and handlers into properties.ts

PropertyField and PropertyValue are shared primitives, not boards-specific
(future features such as cards and content flagging will reuse them).
Moving them out of the boards files keeps the boards namespace focused on
BoardView and lets other product features import the property handlers
without a boards dependency.

- transformers/properties.ts: transformPropertyFieldRecord, transformPropertyValueRecord
- handlers/properties.ts: PropertiesHandler mixin + PropertiesHandlerMix
- boards.ts files keep only BoardView code
- ServerDataOperator now composes PropertiesHandler alongside BoardsHandler
- Tests split into properties.test.ts and boards.test.ts accordingly

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Move boards code under app/products/boards (mirroring playbooks layout)

BoardView is a boards-specific feature, so its model, schema, transformer,
and handler should live alongside the rest of the product code rather than
in the top-level database namespace. Adopt the same layout used by
playbooks and agents.

Structure:
  app/products/boards/
    constants/database.ts                       (BOARDS_TABLES)
    database/
      models/{index.ts,board_view.ts}
      schema/{index.ts,board_view.ts}
      operators/
        transformers/{index.ts,index.test.ts}
        handlers/{index.ts,index.test.ts}
    types/
      api.d.ts                                   (ambient BoardView/KanbanProps/...)
      database/models/board_view.ts              (BoardViewModel interface)

Wiring:
- tsconfig.json + babel.config.js: add @boards/* alias
- DatabaseManager (+ mock): import BoardViewModel from @boards
- Server schema index: import BoardViewSchema from @boards
- ServerDataOperator: import BoardsHandler from @boards
- Migration: import BOARD_VIEW from @boards/constants/database
- MM_TABLES.SERVER no longer carries BOARD_VIEW
- HandleBoardViewsArgs moved into the boards handler module
  (matches the playbooks pattern of co-locating handler args)

PropertyField/PropertyValue remain at the top-level operator because they
are shared infrastructure not tied to boards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* update docs

* fix(boards): register board database imports

* test(boards): isolate handler database lifecycle

* test(properties): isolate handler database lifecycle

* snapshot

* fix snapshots back

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 12:46:40 +02:00

11 KiB

Server Database - Schema Version 20

Please bump the version by 1, any time the schema changes.

Also, include the migration plan under app/database/migration/server,

update all models, relationships and types.

Lastly, export all PNGs, SVGs, etc under the source project (./docs/database)

If you have any question/queries that you would like to clarify, please reach out to the Mobile Platform Team.

Category

id PK string # server-generated collapsed bool display_name string muted bool sort_order number sorting string # alpha, recent, manual team_id string INDEX FK >- Team.id type string # 'channels' | 'direct_messages' | 'favorites' | 'custom'

CategoryChannel

id PK string # composition ID Team.id_Channel.id category_id string INDEX FK >- Category.id channel_id string INDEX sort_order number

Channel

id PK string FK - CategoryChannel.channel_id # server-generated create_at string creator_id string INDEX FK >- User.id delete_at number display_name string is_group_constrained bool name string INDEX shared bool team_id string INDEX FK >- Team.id type string update_at number banner_info string abac_policy_enforced boolean autotranslation boolean

ChannelInfo

id PK string FK - Channel.id# same value as Channel.id guest_count number header string member_count number pinned_post_count number files_count number purpose string

ChannelMembership

id PK string # composition ID Channel.id-User.id channel_id string INDEX FK >- Channel.id user_id string INDEX FK >- User.id scheme_admin bool

CustomEmoji

id PK string # auto-generated name string

CustomProfileField

id PK string # server-generated group_id string name string type string target_id string target_type string create_at number update_at number delete_at number attrs string NULL # stringified JSON

CustomProfileAttribute

id PK string # composition ID User.id-CustomProfileField.id field_id string INDEX FK >- CustomProfileField.id user_id string INDEX FK >- User.id value string

Draft

id PK string # auto-generated channel_id string INDEX FK >- Channel.id files string #stringify (array) message string root_id string INDEX NULL FK >- Post.id type string NULL

ScheduledPost

id PK string # auto-generated channel_id string INDEX FK >- Channel.id files string #stringify (array) message string root_id string INDEX NULL FK >- Post.id metadata string NULL create_at number update_at number scheduled_at number processed_at number NULL error_code string type string NULL

File

id PK string # server-generated extension string height number image_thumbnail string #base64 data string or filepath for video thumbnails local_path string NULL mime_type string name string post_id string INDEX FK >- Post.id size number width number is_blocked bool #Determines if a file has been blocked and cannot be opened anymore

Group

id PK string # server-generated name string INDEX display_name string description string remote_id string INDEX source string created_at number updated_at number deleted_at number

GroupChannel

id PK string # composition ID Group.id-Channel.id group_id string INDEX FK >- Group.id channel_id string INDEX FK >- Channel.id created_at number updated_at number deleted_at number

GroupMembership

id PK string # composition ID Group.id-User.id group_id string INDEX FK >- Group.id user_id string INDEX FK >- User.id created_at number updated_at number deleted_at number

GroupTeam

id PK string # composition ID Group.id-Team.id group_id string INDEX FK >- Group.id team_id string INDEX FK >- Team.id created_at number updated_at number deleted_at number

MyChannel

id PK string FK - Channel.id # same as Channel.id is_unread boolean last_post_at number last_viewed_at number manually_unread boolean mentions_count number message_count number roles string viewed_at number autotranslation_disabled boolean

MyChannelSettings

id PK string FK - MyChannel.id # same as Channel.id notify_props string

MyTeam

id PK string FK - Team.id # same as Team.id roles string

PlaybookRun

id PK string # server-generated playbook_id string name string description string is_active boolean active_stage number active_stage_title string participant_ids string # stringified array of user IDs summary string current_status string # (valid values InProgres, Finished) owner_user_id string INDEX FK >- User.id team_id string INDEX FK >- Team.id channel_id string INDEX FK >- Channel.id post_id string INDEX FK >- Post.id create_at number end_at number delete_at number last_status_update_at number retrospective_enabled boolean retrospective string retrospective_published_at number synced string NULL INDEX # optional field for sync status last_sync_at number NULL # optional field for last sync timestamp

PlaybookChecklist

id PK string # server-generated run_id string INDEX FK >- PlaybookRun.id title string sort_order number synced string NULL INDEX # optional field for sync status last_sync_at number NULL # optional field for last sync timestamp

PlaybookChecklistItem

id PK string # server-generated checklist_id string INDEX FK >- PlaybookChecklist.id title string description string state string # e.g., 'open', 'closed' state_modified number assignee_id string INDEX FK >- User.id assignee_modified number command string command_last_run number due_date number task_actions string # stringified array of TaskAction condition_action string # condition action for the checklist item (values should be '' or 'hidden') condition_reason string # condition reason for the checklist item (default empty string meaning there is no condition affecting) order number completed_at number synced string NULL INDEX # optional field for sync status last_sync_at number NULL # optional field for last sync timestamp

PlaybookRunAttribute

id PK string # server-generated group_id string name string type string target_id string target_type string create_at number update_at number delete_at number attrs string NULL # stringified JSON

PlaybookRunAttributeValue

id PK string # composition ID PlaybookRun.id-PlaybookRunAttribute.id attribute_id string INDEX FK >- PlaybookRunAttribute.id run_id string INDEX FK >- PlaybookRun.id value string

Post

id PK string # server generated channel_id string INDEX FK >- Channel.id create_at number delete_at number edit_at number is_pinned boolean message string metadata string NULL original_id string pending_post_id string INDEX previous_post_id string props string root_id string type string INDEX update_at number user_id string INDEX FK >- User.id

PostsInChannel

id PK string # auto-generated channel_id string INDEX FK >- Channel.id earliest number latest number

PostsInThread

id PK string # auto-generated earliest number latest number root_id string FK >- Post.id

Preference

id string PK # server-generated category string INDEX name string user_id string INDEX FK >- User.id value string

Reaction

id PK string # server-generated create_at number emoji_name string post_id string INDEX FK >- Post.id user_id string INDEX FK >- User.id

Role

id PK string # server-generated name string permissions string #stringify array

System

id PK string # SYSTEM_IDENTIFIERS value string

Team

id PK string # server-generated allowed_domains string description string display_name string is_allow_open_invite boolean is_group_constrained boolean last_team_icon_updated_at number name string type string update_at number

TeamChannelHistory

id PK string FK - Team.id # same as Team.id channel_ids string # stringified JSON array; FIFO

TeamMembership

id PK string # auto-generated team_id string INDEX FK >- Team.id user_id string INDEX FK >- User.id scheme_admin bool

TeamSearchHistory

id PK string # auto-generated created_at number display_term string team_id string FK >- Team.id term string

Thread

id PK string FK - Post.id # similar to Post.id but for root post only is_following boolean last_reply_at number last_viewed_at number reply_count number unread_mentions number unread_replies number
viewed_at number

ThreadsInTeam

id PK string # auto-generated loaded_in_global_threads boolean team_id string INDEX FK >- Team.id thread_id string INDEX FK >- Thread.id

ThreadParticipant # who is participating in this conversation

id PK string # auto-generated thread_id string INDEX FK >- Thread.id user_id string INDEX FK >- User.id

User

id PK string # server generated auth_service string delete_at number email string first_name string is_bot boolean is_guest boolean last_name string last_picture_update number locale string nickname string notify_props string position string props string remote_id string NULL roles string status string timezone string update_at number username string

AiBot

id PK string display_name string username string last_icon_update number dm_channel_id string INDEX channel_access_level number channel_ids string user_access_level number user_ids string team_ids string

AiThread

id PK string message string title string channel_id string INDEX FK >- Channel.id reply_count number update_at number INDEX

BoardView

WatermelonDB table name is BoardView; the domain model class is BoardViewModel.

"BoardView" avoids collision with SQL's reserved VIEW keyword (and with React Native's View) and encodes the boards semantics.

id PK string # server-generated channel_id string INDEX FK >- Channel.id type string # 'kanban' creator_id string FK >- User.id title string description string sort_order number props string # JSON create_at number update_at number INDEX delete_at number # soft-delete, 0 when active

PropertyField

Definition of a card (or other object) property. Shared infrastructure reused by future property-based features.

id PK string # server-generated group_id string # opaque; resolved server-side name string type string # 'text' | 'select' | 'multiselect' | ... attrs string # JSON (options, sort_order, ...) object_type string # 'card' | 'post' | 'user' target_id string INDEX # scope target (channel/team id, empty for system) target_type string # 'system' | 'team' | 'channel' protected boolean permission_field string permission_values string # JSON permission_options string # JSON create_at number update_at number INDEX delete_at number # soft-delete, 0 when active created_by string FK >- User.id updated_by string FK >- User.id

PropertyValue

Single object's value for a single PropertyField.

target_id is polymorphic (keyed by target_type); no typed WatermelonDB relation is declared for it.

id PK string # server-generated field_id string INDEX FK >- PropertyField.id target_id string INDEX # polymorphic target target_type string # 'post' | 'user' | ... group_id string value string # JSON (shape depends on field type) create_at number update_at number delete_at number # soft-delete, 0 when active created_by string FK >- User.id updated_by string FK >- User.id