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:
parent
c7fb240dc3
commit
1fb80d6c2f
9 changed files with 54 additions and 99 deletions
|
|
@ -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 /)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue