diff --git a/Database-Schema.md b/Database-Schema.md new file mode 100644 index 0000000..d2725c6 --- /dev/null +++ b/Database-Schema.md @@ -0,0 +1,123 @@ +# Database Schema + +PostgreSQL. UUIDs as primary keys to prevent enumeration and ease future federation. All timestamps include timezone (`TIMESTAMPTZ`). + +## ERD (simplified) + +``` +users ──< thoughts ──< thought_tags >── tags + │ + ├──< follows + ├──< top_friends + └──< api_keys +``` + +## Tables + +### `users` + +Stores user account and profile data. + +| Column | Type | Constraints | Description | +|---|---|---|---| +| `id` | UUID | PK, DEFAULT gen_random_uuid() | Unique user identifier | +| `username` | VARCHAR(32) | NOT NULL, UNIQUE | User handle | +| `email` | VARCHAR(255) | NOT NULL, UNIQUE | Email address | +| `password_hash` | TEXT | NOT NULL | Argon2 or bcrypt hash | +| `display_name` | VARCHAR(50) | NULL | Public display name | +| `bio` | VARCHAR(160) | NULL | Public biography | +| `avatar_url` | TEXT | NULL | URL to avatar image | +| `header_url` | TEXT | NULL | URL to header/banner image | +| `custom_css` | TEXT | NULL | Custom profile CSS (sanitized) | +| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Account creation time | +| `updated_at` | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Last profile update | + +--- + +### `thoughts` + +Stores the content of each post. + +| Column | Type | Constraints | Description | +|---|---|---|---| +| `id` | UUID | PK, DEFAULT gen_random_uuid() | Unique thought identifier | +| `user_id` | UUID | NOT NULL, FK → users(id) | Author | +| `content` | VARCHAR(128) | NOT NULL | Post text | +| `reply_to` | UUID | NULL, FK → thoughts(id) | Parent thought (if reply) | +| `visibility` | ENUM | NOT NULL, DEFAULT 'public' | `public`, `followers`, `private` | +| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Post timestamp | + +--- + +### `follows` + +Join table for the follower/following relationship. + +| Column | Type | Constraints | Description | +|---|---|---|---| +| `follower_id` | UUID | NOT NULL, FK → users(id) | User who follows | +| `following_id` | UUID | NOT NULL, FK → users(id) | User being followed | +| | | PK (follower_id, following_id) | Prevents duplicate follows | + +--- + +### `top_friends` + +Ordered "Top Friends" list per user (up to 8 entries). + +| Column | Type | Constraints | Description | +|---|---|---|---| +| `user_id` | UUID | NOT NULL, FK → users(id) | Owner of the list | +| `friend_id` | UUID | NOT NULL, FK → users(id) | Friend being displayed | +| `position` | SMALLINT | NOT NULL | Display order (1–8) | +| | | PK (user_id, friend_id) | No duplicates | +| | | UNIQUE (user_id, position) | No duplicate positions | + +--- + +### `tags` + +Unique hashtag names. + +| Column | Type | Constraints | Description | +|---|---|---|---| +| `id` | SERIAL | PK | Tag identifier | +| `name` | VARCHAR(50) | NOT NULL, UNIQUE | Tag name (e.g. `welcome`) | + +--- + +### `thought_tags` + +Many-to-many join between thoughts and tags. + +| Column | Type | Constraints | Description | +|---|---|---|---| +| `thought_id` | UUID | NOT NULL, FK → thoughts(id) | The thought | +| `tag_id` | INTEGER | NOT NULL, FK → tags(id) | The tag | +| | | PK (thought_id, tag_id) | No duplicate tags per post | + +--- + +### `api_keys` + +Hashed API keys for third-party access. + +| Column | Type | Constraints | Description | +|---|---|---|---| +| `id` | UUID | PK, DEFAULT gen_random_uuid() | Key identifier | +| `user_id` | UUID | NOT NULL, FK → users(id) | Owning user | +| `key_hash` | TEXT | NOT NULL, UNIQUE | Hashed key value | +| `name` | VARCHAR(50) | NOT NULL | User-provided label | +| `created_at` | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Creation timestamp | + +## Migrations + +Migrations are managed by **SeaORM** in the `thoughts-backend/migration/` crate. + +```bash +# Run pending migrations +cd thoughts-backend +cargo run -p migration +``` + +Migration files follow the naming pattern `m{YYYYMMDD}_{HHMMSS}_{description}.rs`.