Live Stream history — backend
Yayın geçmişi API ve veri modeli
Live Stream History - Backend
Endpointler
GET /api/v1/live-stream/my-broadcasted-streamsGET /api/v1/live-stream/my-watched-streams- Auth:
@ApiAuth(Role.USER) - Response:
PaginatedResponseDto<LiveStreamResponseDto> - Swagger response:
whats-live-nowile aynı ortakApiPaginatedResponse({ type: LiveStreamResponseDto })formatı
Amac
Kullanıcının geçmiş canlı yayınlarını iki ayrı liste halınde sunar:
- Broadcasted: kullanıcının
hostveyaguestolarak katıldığı bitmiş yayınlar. - Watched: kullanıcının
audienceolarak izlediği bitmiş yayınlar.
Bu endpointler public profil endpointleri değildir. Sadece authenticated current user için çalışır.
Dosya Haritasi
- Controller:
src/live-stream/live-stream.controller.ts - Swagger decorator:
src/live-stream/decorators/api-get-my-live-stream-history.decorator.ts - Query DTO:
src/live-stream/dto/get-my-live-stream-history-query.dto.ts - Use-case:
src/live-stream/use-cases/get-my-broadcasted-live-streams.usecase.tssrc/live-stream/use-cases/get-my-watched-live-streams.usecase.ts
- Repository:
src/live-stream/repository/live-stream.repository.tssrc/live-stream/repository/internal/live-stream-repository.query.service.tssrc/live-stream/repository/live-stream.repository.types.ts
- Participant write paths:
src/live-stream/use-cases/join-live-stream.usecase.tssrc/live-stream-chat/stream-chat.gateway.tssrc/live-stream/gateways/handlers/live-stream-join-leave.handler.ts
- Index:
src/participant/schemas/participant.schema.ts
- Tests:
src/live-stream/use-cases/get-my-live-stream-history.usecase.spec.tssrc/live-stream/repository/live-stream.repository.spec.tssrc/live-stream/live-stream.controller.auth.spec.tssrc/live-stream/live-stream.controller.versioning.spec.ts
Controller Sozlesmesi
@Get('my-broadcasted-streams')
@ApiAuth(Role.USER)
async getMyBroadcastedLiveStreams(
@CurrentUser() user: JwtPayload,
@Query() query: GetMyLiveStreamHistoryQueryDto,
): Promise<PaginatedResponseDto<LiveStreamResponseDto>>
@Get('my-watched-streams')
@ApiAuth(Role.USER)
async getMyWatchedLiveStreams(
@CurrentUser() user: JwtPayload,
@Query() query: GetMyLiveStreamHistoryQueryDto,
): Promise<PaginatedResponseDto<LiveStreamResponseDto>>Query:
page: default1, min1limit: default10, min1, max100
Use-case Davranisi
GetMyBroadcastedLiveStreamsUseCase
- Repository role filtresi:
['host', 'guest'] - Response item
roledeğeri matched participant rolüne görehostveyaguestolarak set edilir.
GetMyWatchedLiveStreamsUseCase
- Repository role filtresi:
['audience'] - Response item
roledeğeri endpoint bağlamındaaudienceolarak set edilir.
Ortak:
pagevelimituse-case içinde normalize edilir.LiveStreamMapper.mapToResponseDto(stream, userId)kullanılır.- Finansal alan eklenmez.
- Response yalnızca
{ list, pagination }içerir.
Repository Sorgu Kaynagi
Kaynak collection: participants
Neden LiveStream yerine Participant?
- "Gerçekten katıldı/izledi" bilgisinin kanonik kaynağı participant kaydıdır.
- Host/guest/audience ayrımı participant role üzerinden yapılır.
- Aynı yayına tekrar giriş varsa
liveStreamIdbazında dedupe yapılabilir.
Participant kaydı şu akışlarda yazılır:
- REST
POST /api/v1/live-stream/join /stream-chatnamespacejoinStream/live-streamnamespacejoinStream
Mobil/web izleme deneyimi yalnızca /live-stream socket odasına katılıyorsa da history için audience participant kaydı oluşur. Authenticated olmayan socket join işlemlerinde participant yazılmaz.
Aggregation Akisi
findEndedStreamsByParticipantRolesWithPagination(userId, roles, page, limit)
userIdObjectId formatında değilse boş page döner.rolesboşsa veyaparticipantModelyoksa boş page döner.- Participant match:
{
$or: [
{ userId },
{ userId: new Types.ObjectId(userId) },
],
role: { $in: roles }
}- En son katılım kaydını seçmek için sort:
{ joinedAt: -1, _id: -1 }liveStreamIdstring normalize edilir:
{ liveStreamIdString: { $toString: '$liveStreamId' } }Bu adım önemlidir. Eski veya farklı cast edilmiş participant kayıtlarında liveStreamId string/ObjectId farklılığı olabilir. Aggregation otomatik cast yapmadığı için lookup string karşılaştırma ile yapılır.
liveStreamIdStringbazında dedupe:
{
_id: '$liveStreamIdString',
role: { $first: '$role' },
joinedAt: { $first: '$joinedAt' }
}livestreamslookup:
{
$lookup: {
from: 'livestreams',
let: { streamId: '$_id' },
pipeline: [
{
$match: {
$expr: {
$eq: [{ $toString: '$_id' }, '$$streamId']
}
}
}
],
as: 'liveStream'
}
}- Live stream filtresi:
{
'liveStream.deletedAt': null,
'liveStream.status': LiveStreamStatus.ENDED
}- Response shape için live stream root'a taşınır, matched participant metadata korunur.
- Sort:
{
endedAt: -1,
startedAt: -1,
createdAt: -1,
_id: -1
}- Count pipeline: aynı base pipeline +
$count. - Data pipeline: aynı base pipeline +
$skip+$limit+ user/media lookup stages.
Bilerek Uygulanmayan Filtreler
Bu endpointlerde aşağıdaki filtreler yoktur:
recordingUrlzorunluluğurecording = truezorunluluğuisActiveReplayOnCreatorProfile = truezorunluluğu- Replay fiyat veya erişim filtresi
- Billing/transaction filtresi
Bu nedenle kayıt/replay'i olmayan bitmiş yayınlar da listelenir.
Finansal Alanlar
Bu endpointlerde harcama/kazanç bilgisi yoktur.
totalSpentdönmez.totalEarneddönmez.streambillingtransactionslookup yapılmaz.
Finansal bilgi tekrar istenirse ayrı bir endpoint veya açıkça finans odaklı bir response kontratı tercih edilmeli. History endpointlerine billing lookup eklemek liste performansını ve sözleşme kapsamını genişletir.
Index
Participant history sorgusu için index:
ParticipantSchema.index({ userId: 1, role: 1, liveStreamId: 1, joinedAt: -1 });Bu index participant match, role filtresi ve aynı yayına son katılım kaydını seçme akışı için eklenmiştir.
Test Kapsami
Hedefli test komutu:
npm test -- --runInBand src/live-stream/use-cases/get-my-live-stream-history.usecase.spec.ts src/live-stream/repository/live-stream.repository.spec.ts src/live-stream/live-stream.controller.auth.spec.ts src/live-stream/live-stream.controller.versioning.spec.tsGeniş regresyon:
npm test -- --runInBand live-streamBeklenenler:
- Invalid user id boş sonuç döner.
- Broadcasted endpoint audience kayıtlarını dışlar.
- Watched endpoint host/guest kayıtlarını dışlar.
- Sadece
ENDEDyayınlar döner. - Recording olmayan bitmiş yayınlar da döner.
- Aynı
liveStreamIdtek item'a düşer. - Pagination metadata doğru hesaplanır.
- Route path ve
Role.USERauth metadata korunur.
Bos Liste Diagnostik Loglari
Endpoint gecersiz veya eksik sorgu nedeniyle calismazsa:
LIVE_STREAM_HISTORY QUERY_SKIPPEDGecerli istek bos dönerse repository su tag ile warning log basar:
LIVE_STREAM_HISTORY EMPTY_RESULT_DIAGNOSTICSAggregation bos döner ama Mongoose participant sorgusu kayit bulursa guvenli fallback devreye girer:
LIVE_STREAM_HISTORY FALLBACK_RECOVEREDFallback da yayin donduremezse:
LIVE_STREAM_HISTORY FALLBACK_EMPTYLog içindeki kritik alanlar:
directParticipantCount:userIdstring olarak participant eslesmesiobjectIdParticipantCount:userIdObjectId olarak participant eslesmesicoercedParticipantCount:$toString(userId)ile eslesmestreamDiagnostic.participantStreamCount: role filtresinden sonra dedupe edilen yayin sayisistreamDiagnostic.missingLiveStreamCount: participant var ama live stream lookup bulunamadistreamDiagnostic.endedStreamCount: bulunan yayinlardanENDEDolanlarstreamDiagnostic.nonDeletedEndedStreamCount: endpointin donebilecegi nihai aday sayisistreamDiagnostic.statuses: bulunan yayin status dagilimini gormek için status setistreamDiagnostic.samples: ilk 5 aday yayin içinliveStreamId,role,status,deletedAt,titleFALLBACK_RECOVERED.total: fallback ile bulunanENDEDve soft-delete olmayan yayin sayisiFALLBACK_EMPTY.reason: fallback'in neden sonuc donduremedigi
Bu loglar bos listenin nedenini ayirmak için yeterlidir:
directParticipantCount = 0amaobjectIdParticipantCount > 0: participantuserIdtipi eski kayitlarda ObjectId olabilir.participantStreamCount > 0amamissingLiveStreamCount > 0: participantliveStreamIdreferansi live stream collection'da yoktur.endedStreamCount = 0: kullanıcının ilgili role sahip kayitlari var ama yayinlar henuzENDEDdeğildir.endedStreamCount > 0amanonDeletedEndedStreamCount = 0: yayinlar soft-deleted durumdadir.FALLBACK_EMPTY reason = NO_ENDED_NON_DELETED_STREAMS: participant kaydi vardir ama bagli yayinlar endpoint filtresine takiliyordur.
Bakim Notlari
- Route isimleri
my-broadcasted-streamsvemy-watched-streamsolarak kalmalı. - "Participated" yerine ürün dilinde "broadcasted" kullanılır.
LiveStreamMapper.mapToResponseDtoresponse shape için tek kaynak olmaya devam etmeli.- Participant
liveStreamIdlookup'ında$toStringkarşılaştırması kaldırılmamalı; aksi halde bazı kayıtlar boş listeye düşebilir.