Allmine API
Live Stream

Live Stream watched summary

İzleyici özet endpoint kontratı

Live Stream Watched Summary Endpoint Spec

Endpoint

GET /api/v1/live-stream/my-watched-stream-summaries

Kullanıcının audience olarak izlediği bitmiş yayınları, yayın başına izleme ve harcama özetleriyle listeler.

Auth

Zorunlu:

Authorization: Bearer <JWT>

Backend decorator:

@ApiAuth(Role.USER)

Query Parameters

ParamTypeDefaultMinMaxDescription
pagenumber11-Sayfa numarası
limitnumber101100Sayfa başına item sayısı

Örnek:

GET /api/v1/live-stream/my-watched-stream-summaries?page=1&limit=10

Response Shape

Backend standart BaseResponseDto<T> wrapper döner. Asıl payload data içindedir.

BaseResponseDto<PaginatedResponseDto<MyWatchedLiveStreamSummaryResponseDto>>

data.list içindeki her item:

type MyWatchedLiveStreamSummaryResponseDto = {
  liveStream: LiveStreamResponseDto;
  summary: MyWatchedLiveStreamSummaryMetricsDto;
};

liveStream alanı mevcut LiveStreamResponseDto formatındadır ve whats-live-now response item formatıyla uyumludur. Bu endpoint bağlamında liveStream.role her zaman audience olarak set edilir.

Summary Metrics

FieldTypeDescription
totalCreditsSpentnumberBu yayın için kullanıcının toplam harcadığı kredi. watchBillingCreditsSpent + giftCreditsSpent
watchBillingCreditsSpentnumberDakikalık izleme ücretlendirmesi için harcanan kredi
giftsSentCountnumberKullanıcının bu yayında gönderdiği gift sayısı
giftCreditsSpentnumberKullanıcının bu yayında gift için harcadığı kredi
watchedSecondsnumberKullanıcının bu yayını izlediği toplam süre, saniye
chatMessagesSentCountnumberKullanıcının bu yayında gönderdiği live stream chat mesajı sayısı
streamDurationSecondsnumberYayının toplam sürdüğü süre, saniye
uniqueAudienceViewerCountnumberYayını izleyen toplam tekil audience kullanıcı sayısı

Example Response

{
  "isSuccess": true,
  "statusCode": 200,
  "data": {
    "list": [
      {
        "liveStream": {
          "id": "65f000000000000000000001",
          "title": "Yayin basligi",
          "liveStreamType": "solo",
          "channelName": "channel-name",
          "broadcasters": [],
          "guests": [],
          "creator": {
            "_id": "65f000000000000000000010",
            "username": "creator",
            "name": "Creator",
            "surname": "User",
            "profilePhoto": null
          },
          "thumbnailUrl": null,
          "recording": false,
          "recordingUrl": null,
          "status": "ended",
          "accessType": "paid",
          "price": 10,
          "interest": "music",
          "durationGoal": null,
          "motivation": null,
          "isActiveReplayOnCreatorProfile": null,
          "replayCreditPrice": null,
          "startedAt": "2026-05-11T10:00:00.000Z",
          "plannedStartDate": null,
          "endedAt": "2026-05-11T11:00:00.000Z",
          "plannedEndDate": null,
          "createdAt": "2026-05-11T09:55:00.000Z",
          "updatedAt": "2026-05-11T11:00:00.000Z",
          "fundingGoal": null,
          "collectedFunding": null,
          "fundingPercentage": null,
          "role": "audience",
          "miniCrowdFundings": []
        },
        "summary": {
          "totalCreditsSpent": 45,
          "watchBillingCreditsSpent": 30,
          "giftsSentCount": 2,
          "giftCreditsSpent": 15,
          "watchedSeconds": 1200,
          "chatMessagesSentCount": 4,
          "streamDurationSeconds": 3600,
          "uniqueAudienceViewerCount": 18
        }
      }
    ],
    "pagination": {
      "currentPage": 1,
      "itemsPerPage": 10,
      "totalItems": 1,
      "totalPages": 1,
      "hasNextPage": false,
      "hasPrevPage": false
    }
  },
  "errors": [],
  "timestamp": "2026-05-14T12:00:00.000Z"
}

Empty Response

Kullanıcının audience olarak izlediği bitmiş yayın yoksa:

{
  "isSuccess": true,
  "statusCode": 200,
  "data": {
    "list": [],
    "pagination": {
      "currentPage": 1,
      "itemsPerPage": 10,
      "totalItems": 0,
      "totalPages": 0,
      "hasNextPage": false,
      "hasPrevPage": false
    }
  },
  "errors": [],
  "timestamp": "2026-05-14T12:00:00.000Z"
}

Data Rules

  • Sadece current user için çalışır.
  • Sadece Participant.role = audience kayıtları dikkate alınır.
  • Sadece LiveStream.status = ended yayınlar döner.
  • Soft-deleted live stream kayıtları dönmez.
  • Aynı yayına birden fazla audience katılımı varsa liste itemı liveStreamId bazında tekilleştirilir.
  • recordingUrl, recording veya isActiveReplayOnCreatorProfile filtresi uygulanmaz.
  • Host/guest olarak katıldığı yayınlar bu endpointte dönmez.

Metric Calculation

watchedSeconds

  • Kaynak: participants
  • Filtre: userId = currentUserId, role = audience, liveStreamId in pageStreamIds
  • Her session için süre:
(leftAt ?? liveStream.endedAt ?? participant.updatedAt) - joinedAt
  • Süre saniye olarak toplanır.
  • Toplam değer, yayın süresi biliniyorsa streamDurationSeconds değerini aşmayacak şekilde sınırlandırılır.

streamDurationSeconds

  • Kaynak: livestreams
  • Hesap:
endedAt - startedAt

watchBillingCreditsSpent

  • Kaynak: streambillingtransactions
  • Filtre:
    • userId = currentUserId
    • liveStreamId in pageStreamIds
    • transactionType = charge
    • status = completed
  • Hesap: sum(amount)

giftsSentCount ve giftCreditsSpent

  • Kaynak: gifts
  • Filtre:
    • metadata.senderUserId = currentUserId
    • metadata.liveStreamId in pageStreamIds
  • Hesap:
    • giftsSentCount = count(*)
    • giftCreditsSpent = sum(metadata.creditAmount)

chatMessagesSentCount

  • Kaynak: streamchatmessages
  • Filtre:
    • senderId = currentUserId
    • streamId in pageStreamIds
  • Hesap: count(*)

uniqueAudienceViewerCount

  • Kaynak: participants
  • Filtre:
    • role = audience
    • liveStreamId in pageStreamIds
  • Hesap: yayın bazında distinct userId count.

totalCreditsSpent

watchBillingCreditsSpent + giftCreditsSpent

TypeScript Client Types

type Pagination = {
  currentPage: number;
  itemsPerPage: number;
  totalItems: number;
  totalPages: number;
  hasNextPage: boolean;
  hasPrevPage: boolean;
};

type PaginatedResponseDto<T> = {
  list: T[];
  pagination: Pagination;
};

type BaseResponseDto<T> = {
  isSuccess: boolean;
  statusCode: number;
  data: T;
  errors: string[];
  timestamp: string;
};

type WatchedStreamSummary = {
  totalCreditsSpent: number;
  watchBillingCreditsSpent: number;
  giftsSentCount: number;
  giftCreditsSpent: number;
  watchedSeconds: number;
  chatMessagesSentCount: number;
  streamDurationSeconds: number;
  uniqueAudienceViewerCount: number;
};

type MyWatchedLiveStreamSummaryItem = {
  liveStream: LiveStreamResponseDto;
  summary: WatchedStreamSummary;
};

type MyWatchedLiveStreamSummaryResponse = PaginatedResponseDto<MyWatchedLiveStreamSummaryItem>;

RTK Query Example

export const liveStreamApi = baseApi.injectEndpoints({
  endpoints: (builder) => ({
    getMyWatchedStreamSummaries: builder.query<
      MyWatchedLiveStreamSummaryResponse,
      { page?: number; limit?: number } | void
    >({
      query: (params) => ({
        url: '/v1/live-stream/my-watched-stream-summaries',
        method: 'GET',
        params: {
          page: params?.page ?? 1,
          limit: params?.limit ?? 10,
        },
      }),
      transformResponse: (
        response: BaseResponseDto<MyWatchedLiveStreamSummaryResponse>,
      ) => response.data,
    }),
  }),
});

Error Cases

StatusCondition
401JWT yok veya geçersiz
403Kullanıcı rolü endpoint için uygun değil
400Query validation hatası
500Beklenmeyen sunucu hatası

Backend Implementation Map

  • Controller: src/live-stream/live-stream.controller.ts
  • DTO: src/live-stream/dto/my-watched-live-stream-summary-response.dto.ts
  • Use-case: src/live-stream/use-cases/get-my-watched-live-stream-summaries.usecase.ts
  • Swagger decorator: src/live-stream/decorators/api-get-my-live-stream-history.decorator.ts
  • Tests:
    • src/live-stream/use-cases/get-my-watched-live-stream-summaries.usecase.spec.ts
    • src/live-stream/live-stream.controller.auth.spec.ts
    • src/live-stream/live-stream.controller.versioning.spec.ts

Indexes

Endpoint metrikleri için kullanılan indexler:

GiftSchema.index({
  'metadata.senderUserId': 1,
  'metadata.liveStreamId': 1,
  timestamp: -1,
});

StreamChatMessageSchema.index({ senderId: 1, streamId: 1, createdAt: -1 });

StreamBillingTransactionSchema.index({
  userId: 1,
  liveStreamId: 1,
  transactionType: 1,
  status: 1,
});

On this page