SAFETYBody modification carries risk of injury or death. Consult a qualified professional. BME publishes no how-to.
Schema
iam.bmezine.com — data model · engineering
Annotated entity-relationship diagram
The schema, as it is lived in
Nineteen tables across nine domain clusters. Every relationship carries crow's-foot cardinality; every table carries the annotations that make it legible to the next engineer. The rigid frame is the diagram; the notes are the record of why. This document is the frame, specified.
⇄ PairSorted-pair invariant: two same-type FKs with a<b, DB-enforced, unique per pair.
◆ HubDrawn once, centrally. Every FK→users across the schema resolves here.
Column markers
PKPrimary key. Where noted inline, the PK is a FK or a natural key — not the default id.
FKForeign key; its target is tagged → table on the same row.
PIIPersonal data — readable only by owner, staff, admin.
Cardinality key
1exactly one
Nmany
0..1zero or one (optional)
Mmany, in an N—M join
Omitted & assumed
Every table has id (uuid PK) plus inserted_at / updated_at — all three omitted from the diagrams. Exceptions (PK is a FK or natural key) are noted inline.
All enum columns are DB CHECK-constrained; full value lists shown inline.
Plain text everywhere — no HTML is stored anywhere in this schema.
Callout 1
diagram-level annotation
The access model
Content gates on a single visibility enum, resolved against four facts about the viewer. The site is login-walled, so every value assumes an authenticated member.
Tier
users.tier
Ordered ladder: open → community → gated. A registration code grants community.
Role
users.role
member / staff / admin / system.
Credential
users.verified_practitioner
A boolean — vouched, verified practitioners.
Relationship
friendships
An accepted friendship with the author.
visibility value
who passes
open
Any signed-in user — the site is login-walled.
community
Tier ≥ community.
gated
Tier = gated.
practitioners
verified_practitioner = true.
moderators
Moderates ≥ 1 board, or role is staff / admin.
admins
role = admin.
friends
An accepted friendship with the author.
private
The author only.
01 · Cluster
3 tables
Accounts
users
◆ Hub
Central hub · every FK→users resolves here
·
usernamecitextunique
·
emailcitextPIIunique
·
birthdaydatePII
18+ enforced at registration
·
hashed_passwordstring
·
tierenum
opencommunitygated
a registration code grants community
·
roleenum
memberstaffadminsystem
·
verified_practitionerboolean
Note
PII fields are readable only by owner, staff, and admin. One seeded the_desk user (role = system) sends platform DMs.
tokens
PK = jti (string) — no uuid id
PK
jtistring
·
subjectstring
·
purposestring
·
expires_attimestamp
·
extra_datajsonb
FK
user_iduuid→ users
Note
Auth machinery. A daily sweep expires stale tokens.
user_blocks
FK
blocker_iduuid→ users
FK
blocked_iduuid→ users
unique (blocker, blocked)directional — no pair sort
Note
Blocks veto new conversations, messages, and friend requests in both directions.
Relationships — crow's-foot
users1Ntokens
One member holds many auth tokens.
users1Nuser_blocks
As blocker — the member who initiated each block.
users1Nuser_blocks
As blocked — the member on the receiving end.
02 · Cluster
2 tables
Profiles
profiles
PK = user_id (FK→users) · 1—1
PK
user_iduuid→ users
·
headlinestring
·
descriptiontext
plain text
·
locationstring
·
pronounsstring
·
themeenum
brutalistcyberpunklavenderbme2005
·
modificationstext[]
chip list, ≤24
FK
avatar_blob_iduuid→ media_blobsnullable
profile_gallery_images
FK
user_iduuid→ users
FK
blob_iduuid→ media_blobs
·
positioninteger
unique (user, blob)≤24 per user
Note
A single flat gallery per profile, shown below friends.
Relationships — crow's-foot
users11profiles
Exactly one profile per member.
users1Nprofile_gallery_images
A member owns many gallery images.
media_blobs1Nprofile_gallery_images
Each blob may be placed in many gallery rows.
03 · Cluster
3 tables
Diary
diary_entries
† Soft del
FK
user_iduuid→ users
author
·
titlestring
·
bodytext
plain text
·
statusenum
draftpublished
immutable published_at · per-user slug
·
visibilityenum
opencommunitygatedfriendsprivate
Note
private = published but author-only (a journal) — distinct from draft.
diary_entry_media
FK
entry_iduuid→ diary_entries
CASCADE
FK
blob_iduuid→ media_blobs
·
positioninteger
unique (entry, blob)≤24 per entryimages only at MVP
diary_comments
† Soft del
FK
entry_iduuid→ diary_entries
CASCADE
FK
user_iduuid→ users
author
·
bodytext
plain text
Note
Read access follows the entry’s visibility; the author or entry owner may delete.
Relationships — crow's-foot
users1Ndiary_entries
A member authors many entries.
diary_entries1Ndiary_entry_media
An entry carries many ordered images.
diary_entries1Ndiary_comments
An entry gathers many comments.
users1Ndiary_comments
A member authors many comments.
04 · Cluster
5 tables
Forums
forum_categories
·
namestring
·
subtitlestring
·
positioninteger
·
visibilityenum
opencommunitygatedpractitionersmoderatorsadmins
Note
Admin-curated; four seeded categories.
forum_boards
FK
category_iduuid→ forum_categories
·
namestring
·
descriptiontext
·
positioninteger
·
visibilityenum
opencommunitygatedpractitionersmoderatorsadmins
Note
Board visibility is the effective gate — there is no inheritance from the category.
forum_board_moderators
FK
board_iduuid→ forum_boards
FK
user_iduuid→ users
unique (board, user)
Note
Grants pin / lock / delete on that board; membership in ≥1 board passes moderators visibility. Staff and admin hold these powers everywhere without a row.
forum_threads
† Soft del
FK
board_iduuid→ forum_boards
FK
user_iduuid→ users
starter
·
titlestring
·
pinnedboolean
·
lockedboolean
·
view_countinteger
Note
No body — the opening post is post #1, created in the same transaction. Reply / view counts and last-post are live aggregates.
forum_posts
† Soft del
FK
thread_iduuid→ forum_threads
FK
user_iduuid→ users
author
·
bodytext
plain text
·
edited_attimestamp
visible "edited" stamp
FK
reply_to_post_iduuid→ forum_postsnullable
self-ref · SET NULL
Note
reply_to renders a reply-context box; it is the future threading parent.
Relationships — crow's-foot
forum_categories1Nforum_boards
A category holds many boards.
forum_boards1Nforum_threads
A board holds many threads.
forum_threads1Nforum_posts
A thread holds many posts.
forum_posts0..1Nforum_posts
Self-referential reply-to; a post may answer one parent.
forum_boardsNMusers
Moderators — many-to-many via forum_board_moderators.
Venue is inline — no separate venue table. Cancelled events stay visible.
event_sessions
FK
event_iduuid→ events
·
daydate
·
starts_attime
·
ends_attime
open-ended end allowed
·
titlestring
·
positioninteger
·
participation_tierenum
opencommunitygatedpractitioners
Note
Deliberately NOT named visibility — sessions are readable by anyone who can read the parent event. This gates ATTENDANCE only.
event_rsvps
FK
event_iduuid→ events
FK
user_iduuid→ users
·
statusenum
goinginterestednot_attending
·
reminded_attimestamp
one email, 7 days out
unique (event, user)
Note
No row = no RSVP. Only going counts as attending.
Relationships — crow's-foot
users1Nevents
A member hosts many events.
events1Nevent_sessions
An event has many scheduled sessions.
eventsNMusers
Attendees — many-to-many via event_rsvps.
06 · Cluster
2 tables
Messages
conversations
⇄ Pair
FK
participant_a_iduuid→ users
FK
participant_b_iduuid→ users
·
last_message_attimestamp
·
last_read_at_a / _btimestamp
per-side read cursor ×2
·
unread_count_a / _binteger
per-side ×2
pair: participant_a < participant_bunique pair
Note
Exactly two participants; rows are permanent (no delete).
messages
† Soft del
FK
conversation_iduuid→ conversations
CASCADE
FK
sender_iduuid→ users
·
bodytext
plain text
·
kindenum
textfriend_request
·
participant_a_id / _buuid
denormalized, write-once
Note
Deleted messages render as tombstones ("[message deleted]"). Denormalized participant ids let read policies skip a join; the desk messages members through this same pipeline.
Relationships — crow's-foot
conversations1Nmessages
A conversation holds many messages.
users1Nmessages
A member sends many messages.
usersNMusers
Two participants per conversation (via conversations pair).
07 · Cluster
1 table
Friendships
friendships
⇄ Pair
FK
user_a_iduuid→ users
FK
user_b_iduuid→ users
FK
requested_by_iduuid→ users
must be a or b, DB-enforced
·
statusenum
pendingaccepteddeclined
·
responded_attimestamp
pair: user_a < user_bunique pair
Note
One row per unordered pair; friend counts are live aggregates. Accepted friendships satisfy friends visibility.
Relationships — crow's-foot
usersNMusers
Symmetric friendship graph — one row per unordered pair.
08 · Cluster
1 table
Activity
activity_entries
PK = user_id (FK→users) · 1—1
PK
user_iduuid→ users
·
last_active_attimestamp
Note
Forward-only upsert, fed ONLY by diary publishes; drives the home-page diary-update timeline ordering. Unpublish never rolls it back.
Relationships — crow's-foot
users11activity_entries
One activity row per member.
09 · Cluster
1 table
Media
The orphan reaper runs nightly, deleting any blob no row references. Consumers: profiles.avatar_blob_id, events.image_blob_id, diary_entry_media.blob_id, profile_gallery_images.blob_id.
media_blobs
FK
owner_iduuid→ users
·
bucketenum
avatarsevent_imagesdiary_mediagallery_images
·
keystring
unique per bucket
·
content_typestring
·
byte_sizebigint
·
coveredboolean
settable by owner at upload; toggled by owner/staff/admin via a dedicated action only
Note
covered is a harm filter, orthogonal to visibility (see Callout 2). Never allowed on avatar blobs.
Relationships — crow's-foot
media_blobs10..1profiles
Avatar — a blob backs at most one profile.
media_blobs10..1events
Event image — a blob backs at most one event.
media_blobs1Ndiary_entry_media
A blob may appear in many diary media rows.
media_blobs1Nprofile_gallery_images
A blob may appear in many gallery rows.
users1Nmedia_blobs
A member owns many blobs.
Callout 2
near the Media cluster
Covered is not visibility
visibility
Answers who may access a row. Resolved against tier, role, credential, and friendship (Callout 1).
media_blobs.covered
A harm filter, orthogonal to visibility. Answers blur behind a click-through. Someone allowed to access a blob may still meet it covered.
Never allowed on avatar blobs. A covered avatar would break identity across the platform.
Checklist
Users fans out to everything
Drawn once, centrally — never duplicated per cluster. Every edge below terminates on the one users hub.