187 lines
4.3 KiB
Markdown
187 lines
4.3 KiB
Markdown
# 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?;
|
|
poster_tag: string?; // one of: event | fundraiser | current affairs
|
|
// constraint: poster_tag required if is_event = true
|
|
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.
|
|
```
|