feat: AI dev stream over HTTPS (SSE read + draft writeback)

Replaces the Redis-over-Tailscale handoff with a far simpler HTTP surface
on the existing public API — no Redis client, Tailscale, or firewall changes
needed on the dev side.

- GET /ai/stream — SSE feed of every ingested message, reads the internal
  Redis stream; resumes from Last-Event-ID on reconnect (no missed messages);
  heartbeats keep the connection warm through proxies
- POST /ai/drafts — writeback: creates a ContentDraft (PENDING_REVIEW) that
  shows up in the admin /admin/drafts review UI
- AiStreamGuard — static bearer token auth (AI_STREAM_TOKEN), accepts header
  or ?token= query for browser EventSource
- Remove Tailscale sidecar + public Redis port; Redis stays internal-only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 13:06:17 +05:30
parent 7cafbc8ba6
commit 530f81416a
8 changed files with 270 additions and 23 deletions
+1 -23
View File
@@ -32,28 +32,6 @@ services:
networks:
- dokploy-network
# Tailscale sidecar — shares Redis's network namespace so the Redis port
# becomes reachable on the tailnet as `tower-redis:6379`. Uses only outbound
# connections, so no OCI Security List / host firewall changes are needed.
tailscale-redis:
image: tailscale/tailscale:latest
hostname: tower-redis
environment:
TS_AUTHKEY: ${TS_AUTHKEY}
TS_STATE_DIR: /var/lib/tailscale
TS_USERSPACE: 'false'
volumes:
- tailscale_redis_state:/var/lib/tailscale
devices:
- /dev/net/tun
cap_add:
- NET_ADMIN
depends_on:
redis:
condition: service_healthy
restart: unless-stopped
network_mode: service:redis
meilisearch:
image: getmeili/meilisearch:v1.11
ports:
@@ -91,6 +69,7 @@ services:
NODE_ENV: production
LOG_LEVEL: ${LOG_LEVEL:-info}
TOWER_PORTAL_BASE_URL: ${TOWER_PORTAL_BASE_URL}
AI_STREAM_TOKEN: ${AI_STREAM_TOKEN}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_SECURE: ${SMTP_SECURE:-false}
@@ -146,7 +125,6 @@ volumes:
redis_data:
meilisearch_data:
sessions:
tailscale_redis_state:
networks:
dokploy-network: