init codebase

This commit is contained in:
2026-02-17 17:30:45 -05:00
parent a3b28549b4
commit f7e2755a91
175 changed files with 21600 additions and 232 deletions
+186
View File
@@ -47,6 +47,10 @@ Auth Gateway → API Service → PostgreSQL
016-create-webhooks.up.sql
017-create-invites.up.sql
018-add-search-indexes.up.sql
019-create-bans.up.sql
020-create-mutes.up.sql
021-create-oauth-providers.up.sql
022-create-system-settings.up.sql
```
### 3.2 Tables
@@ -75,8 +79,41 @@ idx_messages_search ON messages USING GIN(to_tsvector('english', body_md))
idx_notifications_user_unread ON notifications(user_id, created_at) WHERE read = false
idx_channel_members_user ON channel_members(user_id)
idx_community_members_user ON community_members(user_id)
-- Ban/mute enforcement
bans (community_id uuid FK, user_id uuid FK, reason text, banned_by uuid FK, created_at timestamptz, PK(community_id, user_id))
mutes (community_id uuid FK, user_id uuid FK, expires_at timestamptz, muted_by uuid FK, created_at timestamptz, PK(community_id, user_id))
idx_mutes_expires ON mutes(expires_at) WHERE expires_at IS NOT NULL
-- OAuth providers (runtime-configurable)
oauth_providers (id uuid PK, provider_type text CHECK(github/gitea/oidc), name text, client_id text, client_secret_encrypted text, base_url text NULL, issuer_url text NULL, enabled boolean DEFAULT true, created_at timestamptz, updated_at timestamptz)
idx_oauth_providers_type ON oauth_providers(provider_type)
-- System settings (key-value for deployment-wide config)
system_settings (key text PK, value jsonb, updated_at timestamptz)
```
### 3.3 Ban & Mute Enforcement
**Bans:**
- Ban record in `bans` table prevents user from:
- Sending messages in any channel of the community
- Joining channels
- Accepting invites to the community
- Banned user is removed from all channels and community membership on ban
- Ban check runs in middleware for all community-scoped API endpoints
- Bans are permanent until explicitly lifted by Admin+
**Mutes:**
- Mute record in `mutes` table with `expires_at` timestamp
- Muted user cannot:
- Send messages (POST to message endpoints returns 403)
- Add reactions
- Send typing indicators
- Muted user CAN still read messages and channels
- Expired mutes are ignored (no cleanup needed — checked on read)
- Duration specified as interval: `10m`, `1h`, `24h`, `7d`
## 4. API Endpoints
### 4.1 Communities
@@ -590,6 +627,43 @@ Updates `channel_members.last_read_message_id`. Used by SMs to calculate unread
---
### 4.16 Admin: OAuth Provider Management
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | `/api/admin/oauth-providers` | List all OAuth providers | Owner |
| POST | `/api/admin/oauth-providers` | Create OAuth provider | Owner |
| PUT | `/api/admin/oauth-providers/:id` | Update OAuth provider | Owner |
| DELETE | `/api/admin/oauth-providers/:id` | Delete OAuth provider | Owner |
**POST /api/admin/oauth-providers**
```
Request: {"provider_type": "github", "name": "GitHub", "client_id": "...", "client_secret": "...", "enabled": true}
Response: {"id": "uuid", "provider_type": "github", "name": "GitHub", "client_id": "...", "enabled": true, "created_at": "..."}
Note: client_secret is encrypted at rest and never returned in responses.
```
**PUT /api/admin/oauth-providers/:id**
```
Request: {"name": "GitHub Org", "client_id": "...", "client_secret": "...", "enabled": false}
Response: {"id": "uuid", "provider_type": "github", "name": "GitHub Org", ...}
Note: Omitting client_secret from the request leaves it unchanged.
```
**Test Cases:**
| ID | Test | Description |
|----|------|-------------|
| AOP-T1 | Create OAuth provider | POST creates provider, returns without client_secret |
| AOP-T2 | List providers | GET returns all providers without secrets |
| AOP-T3 | Update provider | PUT updates name/enabled, secret unchanged if omitted |
| AOP-T4 | Delete provider | DELETE removes provider |
| AOP-T5 | Non-owner access | Non-owner user returns 403 |
| AOP-T6 | Invalid provider_type | POST with unknown type returns 422 |
| AOP-T7 | Duplicate provider_type | Allowed (multiple GitHub providers for different orgs) |
---
## 5. Cross-Cutting Concerns
### 5.1 Error Response Format
@@ -609,3 +683,115 @@ Updates `channel_members.last_read_message_id`. Used by SMs to calculate unread
### 5.4 Audit Trail (P2)
- Log all admin actions (kick, ban, role change, channel delete) with actor + target + timestamp
- Queryable by Owner via future admin API
---
## 6. Service Configuration
### 6.1 Config Shape
```clojure
{:server {:host "0.0.0.0" :port 3001}
:db {:host "localhost" :port 5432 :dbname "ajet_chat"
:user "ajet" :password "..." :pool-size 10
:migrations {:enabled true :location "migrations"}}
:nats {:url "nats://localhost:4222"
:stream-name "ajet-events"
:publish-timeout-ms 5000}
:minio {:endpoint "http://localhost:9000"
:access-key "minioadmin" :secret-key "minioadmin"
:bucket "ajet-chat"}
:limits {:max-message-length 4000 ;; characters
:max-upload-size 10485760 ;; 10MB in bytes
:edit-window-minutes 60
:default-page-size 50
:max-page-size 100}}
```
### 6.2 Middleware Pipeline
Requests flow through middleware in this order:
```
1. Ring defaults (params, cookies, multipart)
2. Request logging (method, path, start time)
3. Exception handler (catch-all → 500 JSON error)
4. Trace ID extraction (X-Trace-Id → MDC)
5. User context extraction (X-User-Id, X-User-Role, X-Community-Id → request map)
6. Ban check (community-scoped: reject if user is banned)
7. Mute check (write endpoints: reject if user is muted)
8. Reitit routing → handler
9. Response logging (status, duration)
```
### 6.3 Startup / Shutdown Sequence
**Startup:**
```
1. Load config (EDN + env vars)
2. Create DB connection pool (HikariCP)
3. Run Migratus migrations (if enabled)
4. Connect to NATS
5. Connect to MinIO, ensure bucket exists
6. Start http-kit server
7. Log "API service started on port {port}"
```
**Shutdown (graceful):**
```
1. Stop accepting new HTTP connections
2. Wait for in-flight requests (max 30s)
3. Close NATS connection
4. Close DB connection pool
5. Log "API service stopped"
```
### 6.4 Health Check
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/health` | None | Service health status |
**Response (200):**
```json
{"status": "ok", "checks": {"db": "ok", "nats": "ok", "minio": "ok"}}
```
**Response (503 — degraded):**
```json
{"status": "degraded", "checks": {"db": "ok", "nats": "error", "minio": "ok"}}
```
**Test Cases:**
| ID | Test | Description |
|----|------|-------------|
| HLT-T1 | Health check all up | Returns 200 with all checks "ok" |
| HLT-T2 | Health check DB down | Returns 503 with db check "error" |
| HLT-T3 | Health check NATS down | Returns 503 with nats check "error" |
| HLT-T4 | Health check MinIO down | Returns 503 with minio check "error" |
---
## 7. Migration SQL (Outlines)
### 7.1 Migration Naming Convention
```
{NNN}-{description}.up.sql — forward migration
{NNN}-{description}.down.sql — rollback migration
```
Migrations are sequential and must be applied in order. Each migration is idempotent — re-running a completed migration is a no-op (handled by Migratus tracking table).
### 7.2 Key Migration Notes
- **001-create-users:** `users` table + unique index on `username` + unique index on `email`
- **006-create-channels:** `channels` table, `community_id` is nullable (DMs have NULL)
- **008-create-messages:** `messages` table + composite index `(channel_id, created_at)` for pagination
- **010-create-reactions:** Composite PK `(message_id, user_id, emoji)` — one reaction per user per emoji per message
- **018-add-search-indexes:** `to_tsvector('english', body_md)` GIN index on `messages` for full-text search
- **019-create-bans:** `bans` table with composite PK `(community_id, user_id)`
- **020-create-mutes:** `mutes` table with composite PK `(community_id, user_id)`, index on `expires_at`
- **021-create-oauth-providers:** `oauth_providers` table for runtime-configurable OAuth providers
- **022-create-system-settings:** `system_settings` key-value table for deployment-wide settings (e.g., `setup_completed`)