Allmine API
Live Stream

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-streams
  • GET /api/v1/live-stream/my-watched-streams
  • Auth: @ApiAuth(Role.USER)
  • Response: PaginatedResponseDto<LiveStreamResponseDto>
  • Swagger response: whats-live-now ile aynı ortak ApiPaginatedResponse({ 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 host veya guest olarak katıldığı bitmiş yayınlar.
  • Watched: kullanıcının audience olarak 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.ts
    • src/live-stream/use-cases/get-my-watched-live-streams.usecase.ts
  • Repository:
    • src/live-stream/repository/live-stream.repository.ts
    • src/live-stream/repository/internal/live-stream-repository.query.service.ts
    • src/live-stream/repository/live-stream.repository.types.ts
  • Participant write paths:
    • src/live-stream/use-cases/join-live-stream.usecase.ts
    • src/live-stream-chat/stream-chat.gateway.ts
    • src/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.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.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: default 1, min 1
  • limit: default 10, min 1, max 100

Use-case Davranisi

GetMyBroadcastedLiveStreamsUseCase

  • Repository role filtresi: ['host', 'guest']
  • Response item role değeri matched participant rolüne göre host veya guest olarak set edilir.

GetMyWatchedLiveStreamsUseCase

  • Repository role filtresi: ['audience']
  • Response item role değeri endpoint bağlamında audience olarak set edilir.

Ortak:

  • page ve limit use-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 liveStreamId bazında dedupe yapılabilir.

Participant kaydı şu akışlarda yazılır:

  • REST POST /api/v1/live-stream/join
  • /stream-chat namespace joinStream
  • /live-stream namespace joinStream

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)

  1. userId ObjectId formatında değilse boş page döner.
  2. roles boşsa veya participantModel yoksa boş page döner.
  3. Participant match:
{
  $or: [
    { userId },
    { userId: new Types.ObjectId(userId) },
  ],
  role: { $in: roles }
}
  1. En son katılım kaydını seçmek için sort:
{ joinedAt: -1, _id: -1 }
  1. liveStreamId string 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.

  1. liveStreamIdString bazında dedupe:
{
  _id: '$liveStreamIdString',
  role: { $first: '$role' },
  joinedAt: { $first: '$joinedAt' }
}
  1. livestreams lookup:
{
  $lookup: {
    from: 'livestreams',
    let: { streamId: '$_id' },
    pipeline: [
      {
        $match: {
          $expr: {
            $eq: [{ $toString: '$_id' }, '$$streamId']
          }
        }
      }
    ],
    as: 'liveStream'
  }
}
  1. Live stream filtresi:
{
  'liveStream.deletedAt': null,
  'liveStream.status': LiveStreamStatus.ENDED
}
  1. Response shape için live stream root'a taşınır, matched participant metadata korunur.
  2. Sort:
{
  endedAt: -1,
  startedAt: -1,
  createdAt: -1,
  _id: -1
}
  1. Count pipeline: aynı base pipeline + $count.
  2. Data pipeline: aynı base pipeline + $skip + $limit + user/media lookup stages.

Bilerek Uygulanmayan Filtreler

Bu endpointlerde aşağıdaki filtreler yoktur:

  • recordingUrl zorunluluğu
  • recording = true zorunluluğu
  • isActiveReplayOnCreatorProfile = true zorunluluğ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.

  • totalSpent dönmez.
  • totalEarned dönmez.
  • streambillingtransactions lookup 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.ts

Geniş regresyon:

npm test -- --runInBand live-stream

Beklenenler:

  • 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 ENDED yayınlar döner.
  • Recording olmayan bitmiş yayınlar da döner.
  • Aynı liveStreamId tek item'a düşer.
  • Pagination metadata doğru hesaplanır.
  • Route path ve Role.USER auth metadata korunur.

Bos Liste Diagnostik Loglari

Endpoint gecersiz veya eksik sorgu nedeniyle calismazsa:

LIVE_STREAM_HISTORY QUERY_SKIPPED

Gecerli istek bos dönerse repository su tag ile warning log basar:

LIVE_STREAM_HISTORY EMPTY_RESULT_DIAGNOSTICS

Aggregation bos döner ama Mongoose participant sorgusu kayit bulursa guvenli fallback devreye girer:

LIVE_STREAM_HISTORY FALLBACK_RECOVERED

Fallback da yayin donduremezse:

LIVE_STREAM_HISTORY FALLBACK_EMPTY

Log içindeki kritik alanlar:

  • directParticipantCount: userId string olarak participant eslesmesi
  • objectIdParticipantCount: userId ObjectId olarak participant eslesmesi
  • coercedParticipantCount: $toString(userId) ile eslesme
  • streamDiagnostic.participantStreamCount: role filtresinden sonra dedupe edilen yayin sayisi
  • streamDiagnostic.missingLiveStreamCount: participant var ama live stream lookup bulunamadi
  • streamDiagnostic.endedStreamCount: bulunan yayinlardan ENDED olanlar
  • streamDiagnostic.nonDeletedEndedStreamCount: endpointin donebilecegi nihai aday sayisi
  • streamDiagnostic.statuses: bulunan yayin status dagilimini gormek için status seti
  • streamDiagnostic.samples: ilk 5 aday yayin için liveStreamId, role, status, deletedAt, title
  • FALLBACK_RECOVERED.total: fallback ile bulunan ENDED ve soft-delete olmayan yayin sayisi
  • FALLBACK_EMPTY.reason: fallback'in neden sonuc donduremedigi

Bu loglar bos listenin nedenini ayirmak için yeterlidir:

  • directParticipantCount = 0 ama objectIdParticipantCount > 0: participant userId tipi eski kayitlarda ObjectId olabilir.
  • participantStreamCount > 0 ama missingLiveStreamCount > 0: participant liveStreamId referansi live stream collection'da yoktur.
  • endedStreamCount = 0: kullanıcının ilgili role sahip kayitlari var ama yayinlar henuz ENDED değildir.
  • endedStreamCount > 0 ama nonDeletedEndedStreamCount = 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-streams ve my-watched-streams olarak kalmalı.
  • "Participated" yerine ürün dilinde "broadcasted" kullanılır.
  • LiveStreamMapper.mapToResponseDto response shape için tek kaynak olmaya devam etmeli.
  • Participant liveStreamId lookup'ında $toString karşılaştırması kaldırılmamalı; aksi halde bazı kayıtlar boş listeye düşebilir.

On this page