eutopic/docs/DATA-MODEL.md
2026-05-31 16:12:30 +03:00

4.2 KiB

DATA-MODEL.md — eutopic

what gets stored and how. for interaction decisions see UIUX.md (pending). ? = nullable. // = notes. // constraint: = multi-field constraint.


user

id: uri;                         // activitypub, internal truth
username: string;                // unique within instance
display_name: string;
avatar_url: string?;
bio: string?;
email: string;                   // unique
created_at: timestamp;
is_admin: bool;                  // default false. survey creation only.
federation_ready: bool;          // always false until federation flag flips

display_social_handle: none | mutuals | all;
social_handles: [               // array, one entry per platform
  platform: signal | telegram | custom;
  handle: string;
  label: string?;                // only for custom platform
  active: bool;                  // hides without deleting
]
contact_prefs: string?;          // free text e.g. "bad texter", "weekends only"

post

id: uri;
author_id: user.id;
body: string?;
photos: string[];                // urls, max 10
created_at: timestamp;
is_event: bool;                  // default false
saveable: bool;                  // default false, toggled by poster at compose
reaction_set: string[]?;         // emoji, max 5. null = open picker only.

event_date: timestamp?;          // required if is_event
event_ends_at: timestamp?;       // required if is_event
event_location: string?;
event_tags: string[];            // max 2, for bulletin board filters
archived_at: timestamp?;         // set by background job 1 day after event_ends_at

story

id: uri;
author_id: user.id;
media_url: string;
filter: string?;
created_at: timestamp;
expires_at: timestamp;           // created_at + 24h

story_witness

// private. one row per viewer per story. author sees flat witness list. no public count.

id: uri;
story_id: story.id;
viewer_id: user.id;
witnessed_at: timestamp;
expires_at: timestamp;           // mirrors story.expires_at
// constraint: unique(story_id, viewer_id)

reaction

// no counters stored. counts derived at read time.

id: uri;
post_id: post.id;
user_id: user.id;
emoji: string;
created_at: timestamp;
// constraint: unique(post_id, user_id) // one reaction per user per post

comment

id: uri;
post_id: post.id;
author_id: user.id;
body: string;
created_at: timestamp;
deleted_at: timestamp?;          // soft delete. author or post owner can delete.

follow

// junction table: tracks directional relationships between users

id: uri;
follower_id: user.id;            // the one who initiates
followee_id: user.id;            // the one being followed
created_at: timestamp;
status: pending | accepted | blocked; // default accepted. no follow requests in v1, may change.
// constraint: unique(follower_id, followee_id) // no duplicate follows
// constraint: follower_id != followee_id       // cannot follow yourself

invite

id: uri;
code: string;                    // unique
created_by: user.id;
used_by: user.id[];              // multi-use: one link per social cluster
max_uses: int?;
expires_at: timestamp?;
created_at: timestamp;

save

id: uri;
post_id: post.id;                // only valid if post.saveable = true
user_id: user.id;
created_at: timestamp;
// constraint: unique(post_id, user_id)

survey

// admin-created. mc only. ephemeral. dev use only — no results shown to users.

id: uri;
created_by: user.id;             // must be is_admin
question: string;
options: string[];               // multiple choice only
expires_at: timestamp;           // short-lived, exact duration tbd
created_at: timestamp;

survey_response

id: uri;
survey_id: survey.id;
user_id: user.id;
chosen_option: int;              // index into survey.options
created_at: timestamp;
// constraint: unique(survey_id, user_id)

notes

  • all ids are uris internally for activitypub readiness. display layer strips domain until federation flips.
  • no counters stored anywhere. counts derived from queries.
  • event reactions surfaced to post author only. no public counts on bulletin board.
  • background job handles: story expiry, story_witness expiry, event post archiving.