feat(chat): Sprint 4 -- Docker cleanup, frontend migration to Go WS

- Remove Rust chat-server from docker-compose.yml, staging.yml, prod.yml
- Remove VITE_WS_URL from docker frontend env vars (auto-derived from API_URL)
- Update env.ts: derive WS_URL from API_URL (/api/v1/ws) when not explicitly set
- Remove 127.0.0.1:8081 dev hack from useChat.ts
- Add missing types: EditMessage, DeleteMessage, FetchHistory, SearchMessages,
  SyncMessages, MessageEdited, MessageDeleted, SearchResults, SyncChunk
- Update MSW chat/token handler to return ws_url: /api/v1/ws
- Update .env.example and .env.storybook
This commit is contained in:
senke 2026-02-22 20:46:58 +01:00
parent c7fb240dc3
commit 1fb80d6c2f
9 changed files with 54 additions and 99 deletions

View file

@ -16,9 +16,9 @@ VITE_BACKEND_PORT=18080
VITE_API_URL=/api/v1
# WebSocket Configuration
# WebSocket URL for real-time features (can be absolute URL or path starting with /)
# If omitted, auto-derived from VITE_DOMAIN: ws://<domain>:8081/ws
VITE_WS_URL=/ws
# Chat WebSocket URL. If omitted, auto-derived from VITE_API_URL + /ws
# v0.502: Chat WS is now served by the Go backend at /api/v1/ws
# VITE_WS_URL=/api/v1/ws
# Stream Server Configuration
# Stream server URL for audio streaming (can be absolute URL or path starting with /)

View file

@ -3,6 +3,5 @@
# Point API to a relative path so MSW can intercept it easily (same-origin)
VITE_API_URL=/api/v1
VITE_WS_URL=/ws
VITE_IS_STORYBOOK=true
VITE_USE_MSW=true

View file

@ -29,7 +29,7 @@ const domain = import.meta.env.VITE_DOMAIN || 'veza.fr';
const envSchema = z.object({
VITE_DOMAIN: z.string().default('veza.fr'),
VITE_API_URL: urlOrPathSchema.default('/api/v1'),
VITE_WS_URL: urlOrPathSchema.default(`ws://${domain}:8081/ws`),
VITE_WS_URL: urlOrPathSchema.optional(),
VITE_STREAM_URL: urlOrPathSchema.default(`ws://${domain}:8082/stream`),
VITE_HLS_BASE_URL: urlOrPathSchema.optional(),
VITE_UPLOAD_URL: urlOrPathSchema.default('/upload'),
@ -105,6 +105,20 @@ if (import.meta.env.DEV && typeof window !== 'undefined') {
}
}
// WS URL: derive from API_URL when not explicitly set
// v0.502: Chat WS is now at /api/v1/ws on the same backend (no separate Rust server)
const deriveWSUrl = (): string => {
if (validatedEnv.VITE_WS_URL) return validatedEnv.VITE_WS_URL;
const apiUrl = validatedEnv.VITE_API_URL;
if (apiUrl.startsWith('/')) {
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = typeof window !== 'undefined' ? window.location.host : 'localhost';
return `${protocol}//${host}${apiUrl}/ws`;
}
const wsUrl = apiUrl.replace(/^http/, 'ws') + '/ws';
return wsUrl;
};
// HLS base URL: explicit or derived from STREAM_URL (ws://host:port -> http://host:port)
// When STREAM_URL is relative (/stream), use '' so HLS URLs are relative (/hls/...) and get proxied
const deriveHLSBaseURL = (): string => {
@ -120,7 +134,7 @@ const deriveHLSBaseURL = (): string => {
export const env = {
DOMAIN: validatedEnv.VITE_DOMAIN,
API_URL: validatedEnv.VITE_API_URL,
WS_URL: validatedEnv.VITE_WS_URL,
WS_URL: deriveWSUrl(),
STREAM_URL: validatedEnv.VITE_STREAM_URL,
HLS_BASE_URL: deriveHLSBaseURL(),
UPLOAD_URL: validatedEnv.VITE_UPLOAD_URL,

View file

@ -46,17 +46,6 @@ export const useChat = (): UseChatReturn => {
const connect = useCallback(() => {
if (!wsToken || !wsUrl || ws.current?.readyState === WebSocket.OPEN) return;
// CRITIQUE FIX #9: Vérifier si le serveur WebSocket est disponible avant de tenter la connexion
// En développement, si le serveur n'est pas démarré, limiter les tentatives
if (import.meta.env.DEV && wsUrl.includes('127.0.0.1:8081')) {
// En dev, vérifier si on a déjà eu trop d'erreurs de connexion
if (errorCountRef.current >= 3) {
// Trop d'erreurs, ne pas essayer de se connecter pour éviter le spam console
setWsStatus('disconnected');
return;
}
}
// CRITIQUE FIX #39: Nettoyer la connexion précédente si elle existe
if (ws.current) {
ws.current.onopen = null;

View file

@ -14,8 +14,13 @@ export interface OutgoingMessage {
| 'MarkAsRead'
| 'Delivered'
| 'Typing'
| 'EditMessage'
| 'DeleteMessage'
| 'AddReaction'
| 'RemoveReaction'
| 'FetchHistory'
| 'SearchMessages'
| 'SyncMessages'
| 'CallOffer'
| 'CallAnswer'
| 'ICECandidate'
@ -24,6 +29,7 @@ export interface OutgoingMessage {
| 'Ping';
conversation_id?: string;
content?: string;
new_content?: string;
parent_message_id?: string | null;
message_id?: string;
is_typing?: boolean;
@ -34,6 +40,12 @@ export interface OutgoingMessage {
sdp?: string;
candidate?: string;
call_type?: string;
query?: string;
before?: string;
after?: string;
since?: string;
limit?: number;
offset?: number;
}
export interface IncomingMessage {
@ -47,7 +59,11 @@ export interface IncomingMessage {
| 'ReactionRemoved'
| 'MessageRead'
| 'MessageDelivered'
| 'MessageEdited'
| 'MessageDeleted'
| 'HistoryChunk'
| 'SearchResults'
| 'SyncChunk'
| 'CallOffer'
| 'CallAnswer'
| 'ICECandidate'
@ -57,23 +73,31 @@ export interface IncomingMessage {
message_id?: string;
sender_id?: string;
user_id?: string;
editor_id?: string;
deleter_id?: string;
sender_username?: string;
content?: string;
new_content?: string;
created_at?: string;
delivered_at?: string; // ISO string for MessageDelivered
read_at?: string; // ISO string for MessageRead
edited_at?: string;
deleted_at?: string;
delivered_at?: string;
read_at?: string;
action?: string;
success?: boolean;
message?: string;
is_typing?: boolean;
emoji?: string;
attachments?: MessageAttachment[];
messages?: HistoryMessage[]; // For HistoryChunk
messages?: HistoryMessage[];
has_more_before?: boolean;
has_more_after?: boolean;
query?: string;
total?: number;
last_sync?: string;
caller_user_id?: string;
target_user_id?: string;
from_user_id?: string; // For CallAnswer: who sent the answer
from_user_id?: string;
sdp?: string;
candidate?: string;
call_type?: string;

View file

@ -458,7 +458,10 @@ export const handlersMisc = [
}),
http.post('*/api/v1/chat/token', () => {
return HttpResponse.json({ success: true, data: { token: 'mock-chat-token' } });
return HttpResponse.json({
success: true,
data: { token: 'mock-chat-token', ws_url: '/api/v1/ws', expires_in: 900 },
});
}),
http.get('*/api/v1/chat/stats', () => {

View file

@ -188,30 +188,7 @@ services:
timeout: 5s
retries: 3
chat-server:
build:
context: ./veza-chat-server
dockerfile: Dockerfile.production
image: veza-chat-server:latest
container_name: veza_chat_server
restart: unless-stopped
environment:
- DATABASE_URL=postgres://${DB_USER:-veza}:${DB_PASS:?DB_PASS must be set}@postgres:5432/${DB_NAME:-veza}?sslmode=require
- REDIS_URL=redis://:${REDIS_PASSWORD:?REDIS_PASSWORD must be set}@redis:6379
- JWT_SECRET=${JWT_SECRET:?JWT_SECRET must be set for production}
- PORT=3000
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- veza-network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 3
# Chat Server removed in v0.502 -- chat is now handled by backend-api WebSocket at /api/v1/ws
stream-server:
build:
@ -282,12 +259,10 @@ services:
restart: unless-stopped
environment:
- VITE_API_URL=http://haproxy/api/v1
- VITE_WS_URL=ws://haproxy/ws
- VITE_STREAM_URL=ws://haproxy/stream
- VITE_UPLOAD_URL=http://haproxy/api/v1/uploads
depends_on:
- backend-api
- chat-server
- stream-server
networks:
- veza-network
@ -317,7 +292,6 @@ services:
- ./config/ssl:/etc/ssl/veza:ro
depends_on:
- backend-api
- chat-server
- stream-server
- web
networks:

View file

@ -94,27 +94,7 @@ services:
timeout: 5s
retries: 3
chat-server:
build:
context: ./veza-chat-server
dockerfile: Dockerfile.production
container_name: veza_chat_staging
restart: unless-stopped
environment:
- DATABASE_URL=postgresql://veza:${STAGING_DB_PASSWORD:?STAGING_DB_PASSWORD must be set}@postgres:5432/veza_staging?sslmode=require
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${STAGING_JWT_SECRET:?STAGING_JWT_SECRET must be set}
- PORT=8081
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8081/health"]
interval: 10s
timeout: 5s
retries: 3
# Chat Server removed in v0.502 -- chat is now handled by backend WebSocket at /api/v1/ws
stream-server:
build:
@ -146,12 +126,10 @@ services:
restart: unless-stopped
environment:
- VITE_API_URL=/api/v1
- VITE_WS_URL=ws://caddy/ws
- VITE_STREAM_URL=ws://caddy/stream
- VITE_APP_ENV=staging
depends_on:
- backend
- chat-server
- stream-server
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:5173"]
@ -172,7 +150,6 @@ services:
- caddy_config:/config
depends_on:
- backend
- chat-server
- stream-server
- frontend

View file

@ -201,32 +201,7 @@ services:
timeout: 5s
retries: 5
# Chat Server (Rust) - v0.101
chat-server:
build:
context: .
dockerfile: veza-chat-server/Dockerfile
container_name: veza_chat_dev
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER:-veza}:${POSTGRES_PASSWORD:-devpassword}@postgres:5432/${POSTGRES_DB:-veza}?sslmode=disable
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET:-dev-secret-key-minimum-32-characters-long}
- CHAT_SERVER_PORT=3000
- CHAT_SERVER_HOST=0.0.0.0
ports:
- "${PORT_CHAT:-18081}:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- veza-net
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 5
# Chat Server removed in v0.502 -- chat is now handled by backend-api WebSocket at /api/v1/ws
# Stream Server (Rust) - v0.101
stream-server: