init codebase
This commit is contained in:
+186
@@ -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`)
|
||||
|
||||
Reference in New Issue
Block a user