First-attempt commit3a5c6e184only captured the .gitignore change; the pre-commit hook silently dropped the 343 staged moves/deletes during lint-staged's "no matching task" path. This commit re-applies the intended J1 content on top ofbec75f143(which was pushed in parallel). Uses --no-verify because: - J1 only touches .md/.json/.log/.png/binaries — zero code that would benefit from lint-staged, typecheck, or vitest - The hook demonstrated it corrupts pure-rename commits in this repo - Explicitly authorized by user for this one commit Changes (343 total: 169 deletions + 174 renames): Binaries purged (~167 MB): - veza-backend-api/{server,modern-server,encrypt_oauth_tokens,seed,seed-v2} Generated reports purged: - 9 apps/web/lint_report*.json (~32 MB) - 8 apps/web/tsc_*.{log,txt} + ts_*.log (TS error snapshots) - 3 apps/web/storybook_*.json (1375+ stored errors) - apps/web/{build_errors*,build_output,final_errors}.txt - 70 veza-backend-api/coverage*.out + coverage_groups/ (~4 MB) - 3 veza-backend-api/internal/handlers/*.bak Root cleanup: - 54 audit-*.png (visual regression baselines, ~11 MB) - 9 stale MVP-era scripts (Jan 27, hardcoded v0.101): start_{iteration,mvp,recovery}.sh, test_{mvp_endpoints,protected_endpoints,user_journey}.sh, validate_v0101.sh, verify_logs_setup.sh, gen_hash.py Session docs archived (not deleted — preserved under docs/archive/): - 78 apps/web/*.md → docs/archive/frontend-sessions-2026/ - 43 veza-backend-api/*.md → docs/archive/backend-sessions-2026/ - 53 docs/{RETROSPECTIVE_V,SMOKE_TEST_V,PLAN_V0_,V0_*_RELEASE_SCOPE, AUDIT_,PLAN_ACTION_AUDIT,REMEDIATION_PROGRESS}*.md → docs/archive/v0-history/ README.md and CONTRIBUTING.md preserved in apps/web/ and veza-backend-api/. Note: The .gitignore rules preventing recurrence were already pushed in3a5c6e184and remain in place — this commit does not modify .gitignore. Refs: AUDIT_REPORT.md §11
26 KiB
Plan d'implémentation v0.503 — Stream Server E2E + Chat Hardening + Cleanup
Date : 2026-02-22 Base : v0.502 taguée Durée estimée : 5 sprints (~25 jours ouvrés) Référence : V0_503_RELEASE_SCOPE.md
Vue d'ensemble
Sprint 1 (j1-5) → CL1 : Cleanup chat-server Rust (CI/CD, config, archivage)
Sprint 2 (j6-12) → CH1 : Chat hardening (Redis rate limiter, présence, FTS)
Sprint 3 (j13-18) → SS1a : Stream server HLS backend (endpoints, transcoding)
Sprint 4 (j19-23) → SS1b : Stream server HLS frontend (player, ABR, stories)
Sprint 5 (j24-25) → QA1 : Tests, documentation, tag
Diagramme d'architecture cible
flowchart TD
Browser["Browser (React)"]
subgraph GoBackend["Go Backend (veza-backend-api)"]
GinRouter["Gin Router"]
HLSHandler["HLS Handler"]
StreamSvc["StreamService"]
ChatHub["Chat Hub (WebSocket)"]
PresenceSvc["PresenceService (Redis)"]
RateLimiter["RateLimiter (Redis)"]
MsgRepo["ChatMessageRepository (FTS)"]
end
subgraph StreamServer["Stream Server (Rust)"]
HLSGen["HLS Generator"]
Transcoder["Transcoding Engine"]
FFmpeg["FFmpeg"]
end
subgraph Storage["Storage"]
PostgreSQL["PostgreSQL 16"]
Redis["Redis 7"]
MinIO["MinIO (S3)"]
end
Browser -->|"GET /api/v1/tracks/:id/hls/*"| GinRouter
Browser -->|"WS /api/v1/ws"| ChatHub
GinRouter --> HLSHandler
HLSHandler -->|"proxy"| HLSGen
StreamSvc -->|"POST /internal/jobs/transcode"| Transcoder
Transcoder --> FFmpeg
FFmpeg -->|"segments .ts + .m3u8"| MinIO
HLSGen -->|"read segments"| MinIO
ChatHub --> RateLimiter
ChatHub --> PresenceSvc
ChatHub --> MsgRepo
RateLimiter --> Redis
PresenceSvc --> Redis
MsgRepo -->|"ts_query FTS"| PostgreSQL
Sprint 1 — Cleanup chat-server Rust (jours 1-5)
Objectif : Supprimer toute trace opérationnelle du chat server Rust supprimé en v0.502.
Tâche CL1-01 : Archiver le dossier veza-chat-server/
Action : Supprimer le dossier du repo (l'historique Git le préserve).
rm -rf veza-chat-server/
Fichiers : veza-chat-server/ (~78 fichiers Rust + Cargo.toml + Dockerfile)
Tâche CL1-02 : Nettoyer CI workflow ci.yml
Fichier : .github/workflows/ci.yml
Supprimer les sections qui buildent/auditent/lintent/testent le chat-server :
- Lignes ~84-101 :
Auditing Chat Server,cd veza-chat-server,cargo audit - Lignes ~95-101 :
--filter=veza-chat-serverdansturbo run lint,turbo run build,turbo run test
Résultat : Conserver uniquement --filter=veza-stream-server dans les commandes turbo.
Tâche CL1-03 : Nettoyer CD workflow cd.yml
Fichier : .github/workflows/cd.yml
Supprimer toutes les références à veza-chat-server :
- Ligne 41 :
docker build -t veza-chat-server:... - Lignes 60-66 : Trivy scan
veza-chat-server - Lignes 79, 92, 110 : Retirer
veza-chat-serverdes bouclesfor svc in - Ligne 120 : Retirer la ligne summary chat-server
- Ligne 137 : Retirer
veza-chat-serverde la boucle deploy
Tâche CL1-04 : Nettoyer rust-ci.yml
Fichier : .github/workflows/rust-ci.yml
Supprimer les paths veza-chat-server/** et le job clippy-chat (lignes ~4-24).
Tâche CL1-05 : Nettoyer chat-ci.yml
Fichier : .github/workflows/chat-ci.yml
Supprimer entièrement ce workflow (il est dédié au chat-server Rust).
Tâche CL1-06 : Nettoyer Prometheus config
Fichier : config/prometheus.yml
Supprimer le job veza-chat (lignes 18-21) :
- job_name: 'veza-chat'
static_configs:
- targets: ['chat-server:8081']
metrics_path: '/metrics'
Tâche CL1-07 : Nettoyer Caddyfile staging
Fichier : config/caddy/Caddyfile.staging
Remplacer reverse_proxy /ws chat-server:8081 par # Chat WebSocket is now handled by Go backend at /api/v1/ws.
Tâche CL1-08 : Nettoyer HAProxy config
Fichier : config/haproxy/haproxy.cfg
Supprimer la section backend chat (server chat1 chat-server:3000, lignes ~90-95).
Tâche CL1-09 : Nettoyer scripts Incus
Fichiers :
config/incus/deploy-service-native.sh: retirer le casechat-server)(lignes ~112-457)config/incus/deploy-service.sh: retirer le casechat-server)(lignes ~62-63)config/incus/build-native.sh: retirerbuild_chat_server()et le casechat-server)(lignes ~48-89, ~181-182)config/incus/check-deployment.sh: retirerveza-chat-serverdes listes de containers/servicesconfig/incus/verify-deployment.sh: retirerveza-chat-serverdes listesconfig/incus/fix-network-now.sh: retirer le case*chat-server*)config/incus/setup-basic-incus.sh: retirer mention chat-serverconfig/incus/haproxy.cfg: retirer les commentaires/sections chat-server
Tâche CL1-10 : Nettoyer Dependabot
Fichier : .github/dependabot.yml
Supprimer l'entrée cargo pour /veza-chat-server.
Tâche CL1-11 : Nettoyer templates GitHub
Fichiers :
.github/pull_request_template.md: retirerchat-serverdu scope et la checkboxcargo test (chat-server).github/ISSUE_TEMPLATE/bug_report.md: retirerchat-serverde la liste des services
Tâche CL1-12 : Vérifier go.work et turbo.json
Fichiers : go.work, turbo.json, package.json (root)
Retirer toute référence au chat-server de ces fichiers de configuration monorepo.
Tâche CL1-13 : Mettre à jour documentation Incus
Fichiers :
config/incus/README.md: retirer les lignes chat-server du tableau et des instructionsconfig/incus/DEPLOYMENT_STATUS.md: mettre à jour le statutconfig/incus/DEBIAN13_MIGRATION.md: retirer mentions chat-serverconfig/docker/README.md: retirer la ligneveza-chat-server/docker-compose.ymlconfig/incus/env/env.example: retirer la section=== chat-server.env ===
Validation Sprint 1 :
# Vérifier qu'aucune référence opérationnelle ne reste
git grep -l 'chat-server' -- ':(exclude)docs/' ':(exclude)CHANGELOG.md' ':(exclude)*.md'
# Devrait retourner 0 résultat (hors docs historiques)
Commit Sprint 1 : chore(cleanup): remove veza-chat-server and all operational references
Sprint 2 — Chat Hardening (jours 6-12)
Objectif : Migrer le rate limiter vers Redis, ajouter la présence persistante, et full-text search.
Tâche CH1-01 : Rate limiter Redis
Fichier : veza-backend-api/internal/websocket/chat/rate_limiter.go
Remplacer l'implémentation in-memory par Redis avec fallback.
package chat
import (
"context"
"fmt"
"sync"
"time"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
)
type RateLimiter struct {
redis *redis.Client
limits map[string]rateConfig
logger *zap.Logger
// fallback in-memory (si Redis indisponible)
fallback *inMemoryRateLimiter
}
type rateConfig struct {
maxRequests int
window time.Duration
}
func NewRateLimiter(redisClient *redis.Client, logger *zap.Logger) *RateLimiter {
limits := map[string]rateConfig{
"send_message": {maxRequests: 10, window: time.Second},
"typing": {maxRequests: 5, window: time.Second},
"search": {maxRequests: 2, window: time.Second},
"fetch_history": {maxRequests: 5, window: time.Second},
}
return &RateLimiter{
redis: redisClient,
limits: limits,
logger: logger,
fallback: newInMemoryRateLimiter(limits),
}
}
func (rl *RateLimiter) Allow(userID uuid.UUID, action string) bool {
cfg, ok := rl.limits[action]
if !ok {
return true
}
if rl.redis == nil {
return rl.fallback.Allow(userID, action)
}
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
key := fmt.Sprintf("chat:ratelimit:%s:%s", userID.String(), action)
now := time.Now().UnixMilli()
windowStart := now - cfg.window.Milliseconds()
pipe := rl.redis.Pipeline()
pipe.ZRemRangeByScore(ctx, key, "0", fmt.Sprintf("%d", windowStart))
pipe.ZAdd(ctx, key, redis.Z{Score: float64(now), Member: now})
pipe.ZCard(ctx, key)
pipe.Expire(ctx, key, cfg.window*2)
results, err := pipe.Exec(ctx)
if err != nil {
rl.logger.Warn("Redis rate limiter failed, using fallback", zap.Error(err))
return rl.fallback.Allow(userID, action)
}
count := results[2].(*redis.IntCmd).Val()
return count <= int64(cfg.maxRequests)
}
L'inMemoryRateLimiter reprend l'ancien code (sliding window en mémoire) comme fallback.
Tâche CH1-02 : Présence service Redis
Nouveau fichier : veza-backend-api/internal/websocket/chat/presence_service.go
package chat
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
)
type PresenceInfo struct {
UserID uuid.UUID `json:"user_id"`
Status string `json:"status"`
LastSeen time.Time `json:"last_seen"`
TrackID *string `json:"track_id,omitempty"`
TrackName *string `json:"track_name,omitempty"`
}
type ChatPresenceService struct {
redis *redis.Client
logger *zap.Logger
ttl time.Duration
}
func NewChatPresenceService(redisClient *redis.Client, logger *zap.Logger) *ChatPresenceService {
return &ChatPresenceService{
redis: redisClient,
logger: logger,
ttl: 2 * time.Minute,
}
}
func (s *ChatPresenceService) SetOnline(ctx context.Context, userID uuid.UUID) error {
key := fmt.Sprintf("chat:presence:%s", userID.String())
info := PresenceInfo{
UserID: userID,
Status: "online",
LastSeen: time.Now(),
}
data, _ := json.Marshal(info)
return s.redis.Set(ctx, key, data, s.ttl).Err()
}
func (s *ChatPresenceService) SetOffline(ctx context.Context, userID uuid.UUID) error {
key := fmt.Sprintf("chat:presence:%s", userID.String())
return s.redis.Del(ctx, key).Err()
}
func (s *ChatPresenceService) Heartbeat(ctx context.Context, userID uuid.UUID) error {
key := fmt.Sprintf("chat:presence:%s", userID.String())
return s.redis.Expire(ctx, key, s.ttl).Err()
}
func (s *ChatPresenceService) GetPresence(ctx context.Context, userID uuid.UUID) (*PresenceInfo, error) {
key := fmt.Sprintf("chat:presence:%s", userID.String())
data, err := s.redis.Get(ctx, key).Result()
if err == redis.Nil {
return &PresenceInfo{UserID: userID, Status: "offline"}, nil
}
if err != nil {
return nil, fmt.Errorf("failed to get presence: %w", err)
}
var info PresenceInfo
if err := json.Unmarshal([]byte(data), &info); err != nil {
return nil, fmt.Errorf("failed to unmarshal presence: %w", err)
}
return &info, nil
}
Intégration Hub : hub.go → appeler presenceService.SetOnline() sur Register et SetOffline() sur Unregister.
Tâche CH1-03 : Migration FTS
Nouveau fichier : veza-backend-api/migrations/113_messages_fts.sql
-- Full-text search on messages
ALTER TABLE messages ADD COLUMN IF NOT EXISTS content_tsv tsvector;
CREATE INDEX IF NOT EXISTS idx_messages_content_fts
ON messages USING GIN (content_tsv);
-- Trigger to auto-update tsvector on insert/update
CREATE OR REPLACE FUNCTION messages_content_tsv_trigger() RETURNS trigger AS $$
BEGIN
NEW.content_tsv := to_tsvector('simple', COALESCE(NEW.content, ''));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_messages_content_tsv ON messages;
CREATE TRIGGER trg_messages_content_tsv
BEFORE INSERT OR UPDATE OF content ON messages
FOR EACH ROW EXECUTE FUNCTION messages_content_tsv_trigger();
-- Backfill existing messages
UPDATE messages SET content_tsv = to_tsvector('simple', COALESCE(content, ''))
WHERE content_tsv IS NULL;
Tâche CH1-04 : Remplacer ILIKE par FTS
Fichier : veza-backend-api/internal/repositories/chat_message_repository.go
Modifier la méthode Search :
func (r *ChatMessageRepository) Search(ctx context.Context, roomID uuid.UUID, query string, limit, offset int) ([]models.ChatMessage, int64, error) {
tsQuery := plainto_tsquery(query) // helper to sanitize
var total int64
r.db.WithContext(ctx).Model(&models.ChatMessage{}).
Where("room_id = ? AND is_deleted = ? AND content_tsv @@ plainto_tsquery('simple', ?)", roomID, false, query).
Count(&total)
var messages []models.ChatMessage
err := r.db.WithContext(ctx).
Where("room_id = ? AND is_deleted = ? AND content_tsv @@ plainto_tsquery('simple', ?)", roomID, false, query).
Order("ts_rank(content_tsv, plainto_tsquery('simple', ?)) DESC, created_at DESC", query).
Limit(limit).
Offset(offset).
Find(&messages).Error
if err != nil {
return nil, 0, fmt.Errorf("failed to search messages: %w", err)
}
return messages, total, nil
}
Tâche CH1-05 : Mettre à jour le modèle ChatMessage GORM
Fichier : veza-backend-api/internal/models/message.go
Ajouter le champ ContentTsv (ignoré par JSON, géré par trigger) :
ContentTsv *string `gorm:"type:tsvector;->" json:"-"`
Le tag -> indique à GORM que ce champ est en lecture seule (géré par le trigger PostgreSQL).
Tâche CH1-06 : Intégrer Redis dans le constructeur MessageHandler
Fichier : veza-backend-api/internal/api/router.go
Modifier la construction du RateLimiter et du MessageHandler pour injecter le client Redis et le ChatPresenceService.
Tâche CH1-07 : Tests unitaires rate limiter Redis
Nouveau fichier : veza-backend-api/internal/websocket/chat/rate_limiter_test.go
Tests :
TestRedisRateLimiter_Allow— vérifie le sliding window RedisTestRedisRateLimiter_FallbackInMemory— vérifie le fallback quand Redis est nilTestRedisRateLimiter_UnknownAction— toujours autorisé
Tâche CH1-08 : Tests unitaires presence service
Nouveau fichier : veza-backend-api/internal/websocket/chat/presence_service_test.go
Tests :
TestPresence_SetOnline_GetPresence— round-tripTestPresence_SetOffline— supprime la cléTestPresence_Heartbeat_ExtendsExpiry— vérifie le TTL
Tâche CH1-09 : Benchmark WebSocket
Nouveau fichier : veza-backend-api/internal/websocket/chat/benchmark_test.go
Benchmark :
- 100 connexions simultanées
- Chaque client envoie 10 messages
- Mesurer le temps de bout en bout
- Vérifier 0 erreur
Validation Sprint 2 :
cd veza-backend-api && go test ./internal/websocket/chat/... -v -count=1
cd veza-backend-api && go test ./internal/websocket/chat/ -bench=. -benchtime=10s
Commit Sprint 2 : feat(chat): add Redis rate limiter, persistent presence, full-text search
Sprint 3 — HLS Backend Integration (jours 13-18)
Objectif : Rendre le pipeline HLS fonctionnel côté backend.
État actuel
| Élément | Existe ? | Fonctionne ? |
|---|---|---|
HLSHandler.GetStreamInfo |
✅ | Retourne info track |
HLSHandler.GetStreamStatus |
✅ | Retourne statut transcoding |
HLSHandler.ServeMasterPlaylist |
✅ | Lit depuis filesystem/S3 |
HLSHandler.ServeQualityPlaylist |
✅ | Lit depuis filesystem/S3 |
HLSHandler.ServeSegment |
✅ | Lit depuis filesystem/S3 |
HLSHandler.TriggerTranscode |
✅ | Appelle stream server |
StreamService.StartProcessing |
✅ | POST /internal/jobs/transcode |
| Stream server HLS endpoints | ✅ | Compilation OK |
Route GET /tracks/:id/hls/info |
✅ | Enregistrée dans router |
Route GET /tracks/:id/hls/status |
✅ | Enregistrée dans router |
Route GET /tracks/:id/hls/master.m3u8 |
❌ | Non enregistrée |
Route GET /tracks/:id/hls/quality.m3u8 |
❌ | Non enregistrée |
Route GET /tracks/:id/hls/segment |
❌ | Non enregistrée |
Config HLSEnabled |
❌ | Absent |
| Callback HLS-ready | ❓ | À vérifier |
Tâche SS1-01 : Ajouter config HLS
Fichier : veza-backend-api/internal/config/config.go
// Ajout dans le struct Config
HLSEnabled bool // Active le streaming HLS adaptatif
HLSStorageDir string // Répertoire de stockage des segments HLS
// Ajout dans le chargement
HLSEnabled: getEnvBool("HLS_STREAMING", false),
HLSStorageDir: getEnv("HLS_STORAGE_DIR", "/tmp/veza-hls"),
Tâche SS1-02 : Enregistrer les routes HLS dans le router
Fichier : veza-backend-api/internal/api/router.go
Ajouter les routes HLS manquantes (protégées par stream-token auth) :
// HLS streaming routes (stream token auth)
if r.config.HLSEnabled {
hlsGroup := tracks.Group("/:id/hls")
hlsGroup.Use(streamTokenAuth)
{
hlsGroup.GET("/master.m3u8", hlsHandler.ServeMasterPlaylist)
hlsGroup.GET("/:quality/playlist.m3u8", hlsHandler.ServeQualityPlaylist)
hlsGroup.GET("/:quality/:segment", hlsHandler.ServeSegment)
}
}
Tâche SS1-03 : Étendre StreamService pour HLS
Fichier : veza-backend-api/internal/services/stream_service.go
Ajouter les méthodes :
// GetHLSStatus vérifie si les segments HLS sont disponibles pour un track
func (s *StreamService) GetHLSStatus(ctx context.Context, trackID uuid.UUID) (*HLSStatus, error)
// TriggerHLSTranscode déclenche le transcoding HLS adaptatif
func (s *StreamService) TriggerHLSTranscode(ctx context.Context, trackID uuid.UUID, filePath string) error
HLSStatus retourne les qualités disponibles, le nombre de segments, et la durée totale.
Tâche SS1-04 : Vérifier callback stream-ready
Fichier : veza-backend-api/internal/handlers/ (chercher le handler de callback interne)
Vérifier que le callback POST /api/v1/internal/tracks/:id/stream-ready :
- Met à jour
track.stream_manifest_urlavec l'URL HLS - Met à jour le statut de transcoding
- Envoie une notification (optionnel)
Si le handler n'est pas complet, le compléter pour mettre à jour le champ stream_manifest_url du track avec l'URL du master playlist HLS.
Tâche SS1-05 : Mettre à jour docker-compose pour HLS
Fichiers : docker-compose.yml, docker-compose.staging.yml, docker-compose.prod.yml
Ajouter les variables d'environnement HLS :
environment:
HLS_STREAMING: "true"
HLS_STORAGE_DIR: "/data/hls"
Ajouter un volume partagé pour les segments HLS entre le stream server et le backend (ou utiliser MinIO/S3).
Tâche SS1-06 : Mettre à jour Caddy pour HLS
Fichier : config/caddy/Caddyfile.staging
S'assurer que les routes /hls/* sont correctement proxiées :
reverse_proxy /hls/* stream-server:3001
(Déjà présent, vérifier que c'est correct.)
Tâche SS1-07 : Test E2E HLS pipeline
Nouveau fichier : veza-backend-api/internal/integration/e2e_hls_test.go
Test :
- Upload un fichier audio
- Vérifier que le transcoding est déclenché
- Attendre le callback stream-ready
- Vérifier que le master playlist est accessible
- Vérifier qu'au moins 1 qualité est disponible
Validation Sprint 3 :
cd veza-backend-api && go test ./internal/... -v -count=1 -run HLS
cd veza-backend-api && go build ./...
Commit Sprint 3 : feat(streaming): wire HLS pipeline end-to-end (backend)
Sprint 4 — HLS Frontend Integration (jours 19-23)
Objectif : Le player utilise HLS quand disponible, avec ABR et quality selector.
Tâche SS1-05 : Intégrer useHLSPlayer dans le player
Fichier : apps/web/src/features/player/hooks/usePlayer.ts (ou équivalent)
Logique :
- Quand un track a un
stream_manifest_urlnon-null → utiliseruseHLSPlayer - Sinon → fallback sur l'URL directe (comportement actuel)
// Pseudo-code d'intégration
const hlsPlayer = useHLSPlayer(audioRef, currentTrack?.id, currentTrack?.streamStatus);
useEffect(() => {
if (currentTrack?.stream_manifest_url && hlsPlayer.isHLSActive) {
// HLS gère le chargement via hls.js
return;
}
// Fallback : chargement direct
audioPlayerService.loadTrack(currentTrack);
}, [currentTrack]);
Tâche SS1-06 : Quality selector dans le player
Fichier : apps/web/src/features/player/components/ (nouveau composant ou intégré dans le player existant)
Composant QualitySelector :
- Affiche les niveaux disponibles (128k, 256k, 320k, Auto)
- Appelle
hlsPlayer.setQuality(levelIndex)au clic - Affiche le niveau actuel et la bande passante estimée
- Caché si HLS non actif
Tâche SS1-07 : Mettre à jour le type Track
Fichier : apps/web/src/features/ (types track)
Ajouter stream_manifest_url?: string et stream_status?: string au type Track si pas déjà présent.
Tâche SS1-08 : MSW handler HLS
Nouveau fichier : apps/web/src/mocks/handlers-streaming.ts
import { http, HttpResponse } from 'msw';
export const streamingHandlers = [
http.get('/api/v1/tracks/:id/hls/info', ({ params }) => {
return HttpResponse.json({
success: true,
data: {
track_id: params.id,
hls_available: true,
qualities: [
{ name: 'low', bitrate: 128000 },
{ name: 'medium', bitrate: 256000 },
{ name: 'high', bitrate: 320000 },
],
duration: 240,
},
});
}),
http.get('/api/v1/tracks/:id/hls/status', ({ params }) => {
return HttpResponse.json({
success: true,
data: {
track_id: params.id,
status: 'ready',
progress: 100,
},
});
}),
];
Enregistrer dans handlers.ts.
Tâche SS1-09 : Story Storybook player HLS
Fichier : apps/web/src/features/player/ (story existante ou nouvelle)
États :
Default— player avec HLS actif, quality selector visibleLoading— transcoding en cours (progress bar)Error— HLS indisponible, fallback sur URL directeNoHLS— track sans HLS, quality selector caché
Tâche SS1-10 : Mettre à jour env.ts et .env
Fichier : apps/web/src/config/env.ts
Vérifier que HLS_BASE_URL est correctement dérivé :
export const HLS_BASE_URL = import.meta.env.VITE_STREAM_URL || deriveStreamURL();
Fichiers : .env.example, .env.storybook
Ajouter/vérifier :
VITE_FEATURE_HLS_STREAMING=true
Validation Sprint 4 :
cd apps/web && npx tsc --noEmit
cd apps/web && npm run build
cd apps/web && npm run storybook -- --ci
Commit Sprint 4 : feat(player): integrate HLS streaming with ABR quality switching
Sprint 5 — Tests, Documentation & Tag (jours 24-25)
Objectif : Finaliser, documenter, tagger.
Tâche QA1-01 : Mettre à jour FEATURE_STATUS.md
Fichier : docs/FEATURE_STATUS.md
- HLS Streaming : passer de "en intégration" à "opérationnel (v0.503)"
- Chat WebSocket : mettre à jour la note "Partiel" → "Opérationnel (Go, v0.502+)"
- Ajouter section "Livré en v0.503"
Tâche QA1-02 : Mettre à jour PROJECT_STATE.md
Fichier : docs/PROJECT_STATE.md
- Ajouter la section v0.503 dans "Ce qui est livré"
- Mettre à jour "Prochaine version" → v0.601
- Mettre à jour la stack technique (retirer Chat Server Rust)
- Incrémenter les indicateurs
Tâche QA1-03 : Smoke test
Nouveau fichier : docs/SMOKE_TEST_V0503.md
Checklist :
- HLS : upload → transcode → playback HLS
- HLS : ABR switch de qualité
- Chat : rate limiter Redis fonctionne
- Chat : présence persistée dans Redis
- Chat : recherche full-text retourne des résultats pertinents
- Chat : benchmark 100 connexions OK
- Cleanup :
git grep chat-server(hors docs) = 0 résultat - CI : workflows passent sans erreur
Tâche QA1-04 : CHANGELOG.md
Fichier : CHANGELOG.md
## v0.503 — Stream Server E2E + Chat Hardening + Cleanup
### Nouveautés
- **HLS Streaming E2E** : pipeline upload → transcode → HLS player fonctionnel
- **Player ABR** : quality selector (128k, 256k, 320k, Auto)
- **Chat FTS** : recherche full-text PostgreSQL (tsvector + GIN index)
- **Chat présence** : tracking persistant Redis avec heartbeat
### Améliorations
- **Rate limiter** : migré in-memory → Redis (sliding window, fallback)
- **Benchmark** : 100+ connexions WebSocket simultanées validé
### Suppressions
- **veza-chat-server/** : dossier Rust archivé/supprimé du repo
- **chat-ci.yml** : workflow CI dédié au chat Rust supprimé
- Références chat-server retirées de CI/CD, Prometheus, Caddy, HAProxy, scripts Incus
### Infrastructure
- Migration 113 : tsvector + GIN index sur messages.content
Tâche QA1-05 : Archiver scope et préparer v0.601
- Déplacer
V0_503_RELEASE_SCOPE.md→docs/archive/ - Créer placeholder
V0_601_RELEASE_SCOPE.md - Mettre à jour
SCOPE_CONTROL.md→ référence v0.601
Tâche QA1-06 : Rétrospective
Nouveau fichier : docs/RETROSPECTIVE_V0503.md
Tâche QA1-07 : Tag
git tag -a v0.503 -m "v0.503 — Stream Server E2E + Chat Hardening + Cleanup"
Commit Sprint 5 : docs(v0.503): update documentation, changelog, tag release
Résumé
| Sprint | Jours | Tâches | Focus |
|---|---|---|---|
| 1 | 1-5 | CL1-01 à CL1-13 | Cleanup chat-server Rust |
| 2 | 6-12 | CH1-01 à CH1-09 | Chat hardening (Redis, FTS) |
| 3 | 13-18 | SS1-01 à SS1-07 | HLS backend E2E |
| 4 | 19-23 | SS1-05 à SS1-10 | HLS frontend (player, ABR) |
| 5 | 24-25 | QA1-01 à QA1-07 | Documentation & tag |
Total : ~39 tâches, 25 jours ouvrés
Dépendances entre lots
CL1 (Cleanup) → indépendant, peut commencer en premier
CH1 (Chat Hardening) → indépendant de CL1 et SS1
SS1a (HLS Backend) → indépendant de CL1 et CH1
SS1b (HLS Frontend) → dépend de SS1a (backend endpoints disponibles)
QA1 (Documentation) → dépend de CL1, CH1, SS1a, SS1b
Risques et mitigations
| Risque | Probabilité | Impact | Mitigation |
|---|---|---|---|
| Stream server HLS ne produit pas de segments | Moyenne | Élevé | Tester manuellement avec FFmpeg avant d'intégrer |
| FFmpeg absent du container stream-server | Faible | Élevé | Vérifier le Dockerfile du stream server |
| Redis down en production | Faible | Moyen | Fallback in-memory pour rate limiter et présence |
| FTS performance sur gros volume | Faible | Moyen | Index GIN + simple config (pas de stemming) |
| Breaking change frontend player | Moyenne | Moyen | Fallback sur URL directe si HLS échoue |