Allmine API

Gift entegrasyonu

~20 dkMobil / WebKararlı

Hediye gönderimi mobil ve API

Gift Özelliği Dokümantasyonu

Migration rehberi

Yapılandırma sırası için Migration şablonu. Yeni düzenlemelerde Amaç → Önkoşullar → Endpoint → Request/Response → Hata kodları → Client adımları → İlgili sayfalar bölümlerini tercih edin.

Bu dokümantasyon, Gift özelliğinin kullanımı ve React Native entegrasyonu için detaylı bilgiler içermektedir.

İçindekiler

  1. Genel Bakış
  2. API Endpoint'leri
  3. Socket Events
  4. Veri Modelleri
  5. React Native Entegrasyon Örnekleri
  6. Hata Yönetimi
  7. Akış Diyagramı
  8. Önemli Notlar
  9. Test Senaryoları

Genel Bakış

Gift sistemi, kullanıcıların canlı yayınlarda yayıncılara kredi göndermesini sağlar. Her gift bir gift type'a bağlıdır ve belirli bir kredi miktarına sahiptir. Gift gönderildiğinde:

  • Kullanıcının kredi bakiyesinden düşülür
  • Gift veritabanına kaydedilir
  • Socket üzerinden tüm yayın izleyicilerine newGift event'i broadcast edilir
  • /live-stream namespace'inde toplam hediye adedi giftCountUpdated ile güncellenir
  • Yayın için toplam kredi miktarı güncellenir

Sistem Mimarisi

Gift sistemi şu bileşenlerden oluşur:

  1. API Endpoint'leri: Gift gönderme ve toplam kredi miktarını getirme
  2. Socket Events: Gerçek zamanlı gift bildirimleri
  3. Transaction Güvenliği: MongoDB transaction ile atomik işlemler
  4. Gift Type Yönetimi: Admin tarafından yönetilen gift tipleri

API Endpoint'leri

Base URL'ler

  • Local: http://localhost:3000
  • Development: https://dev.allmine.win
  • Staging: https://staging.allmine.win
  • Production: https://api.allmine.win

Tüm endpoint'ler /api prefix'i ile başlar.

1. Gift Gönderme

Endpoint: POST /api/gifts/create

Authentication: JWT Token gerekli (Bearer token)

Request Headers:

Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json

Request Body:

{
  giftTypeId: string;      // MongoDB ObjectId
  liveStreamId: string;     // MongoDB ObjectId
}

Request Örneği:

{
  "giftTypeId": "65a1234bcde5678f90123456",
  "liveStreamId": "65a1234bcde5678f90123457"
}

Response:

{
  isSuccess: true,
  statusCode: 201,
  data: {
    timestamp: "2024-01-01T12:00:00.000Z",
    metadata: {
      giftTypeId: "65a1234bcde5678f90123456",
      creditAmount: 9,
      svgUrl: "https://example.com/gifts/gift_9.svg",
      senderUserId: "65a1234bcde5678f90123458",
      liveStreamId: "65a1234bcde5678f90123457"
    }
  },
  errors: [],
  timestamp: "2024-01-01T12:00:00.000Z"
}

cURL Örneği:

curl -X POST https://api.allmine.win/api/gifts/create \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "giftTypeId": "65a1234bcde5678f90123456",
    "liveStreamId": "65a1234bcde5678f90123457"
  }'

Hata Durumları:

HTTP StatusHata MesajıAçıklama
400 Bad RequestLive stream not foundBelirtilen live stream bulunamadı
400 Bad RequestUser not foundKullanıcı bulunamadı
400 Bad RequestGift type is not activeGift type aktif değil
400 Bad RequestInsufficient credit balanceKullanıcının kredi bakiyesi yetersiz
401 Unauthorized-Token geçersiz veya eksik
404 Not FoundGift type not foundBelirtilen gift type bulunamadı

2. Yayın İçin Toplam Kredi Miktarını Getirme

Endpoint: GET /api/gifts/total-credit-amount/:liveStreamId

Authentication: JWT Token gerekli (Bearer token)

Path Parameters:

  • liveStreamId (string, required): MongoDB ObjectId formatında live stream ID'si

Request Headers:

Authorization: Bearer <JWT_TOKEN>

Response:

{
  isSuccess: true,
  statusCode: 200,
  data: 150,  // Toplam kredi miktarı (number)
  errors: [],
  timestamp: "2024-01-01T12:00:00.000Z"
}

cURL Örneği:

curl -X GET https://api.allmine.win/api/gifts/total-credit-amount/65a1234bcde5678f90123457 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Hata Durumları:

HTTP StatusHata MesajıAçıklama
401 Unauthorized-Token geçersiz veya eksik

3. Yayın Gift Tür Dağılımını ve Toplam Adedi Getirme

Endpoint: GET /api/live-stream/:id/gift-statistics

Authentication: JWT Token gerekli (Bearer token)

Path Parameters:

  • id (string, required): MongoDB ObjectId formatında live stream ID'si

Request Headers:

Authorization: Bearer <JWT_TOKEN>

Response:

{
  isSuccess: true,
  statusCode: 200,
  data: {
    liveStreamId: "65a1234bcde5678f90123457",
    totalCount: 12,
    giftTypeCounts: [
      {
        giftType: {
          id: "65a1234bcde5678f90111111",
          creditAmount: 9,
          svgUrl: "https://example.com/gifts/gift_9.svg",
          isActive: true
        },
        count: 4
      },
      {
        giftType: {
          id: "65a1234bcde5678f90122222",
          creditAmount: 19,
          svgUrl: "https://example.com/gifts/gift_19.svg",
          isActive: true
        },
        count: 0
      }
    ]
  },
  errors: [],
  timestamp: "2024-01-01T12:00:00.000Z"
}

Notlar:

  • totalCount, yayındaki tüm gift kayıtlarının toplam adedidir.
  • giftTypeCounts, sadece aktif gift type'ları içerir.
  • Hiç gönderilmeyen aktif gift type'lar da count: 0 ile döner.

cURL Örneği:

curl -X GET https://api.allmine.win/api/live-stream/65a1234bcde5678f90123457/gift-statistics \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Hata Durumları:

HTTP StatusHata MesajıAçıklama
400 Bad RequestInvalid live stream ID, must be a valid MongoDB ObjectId stringGeçersiz stream ID
401 Unauthorized-Token geçersiz veya eksik
404 Not FoundCanlı yayın bulunamadıBelirtilen live stream bulunamadı

Socket Events

Gift sistemi iki namespace ile çalışır:

  • /stream-chat: Gift detay event'i (newGift)
  • /live-stream: Yayın bazlı toplam hediye adedi event'i (giftCountUpdated)

Socket Base URL'ler

  • Local: ws://localhost:3000
  • Development: wss://dev.allmine.win
  • Staging: wss://staging.allmine.win
  • Production: wss://api.allmine.win

Namespace: /stream-chat

Gift event'lerini dinlemek için önce /stream-chat namespace'ine bağlanılmalı ve joinStream event'i ile yayına katılınmalıdır.

Bağlantı

import { io } from 'socket.io-client';

const socket = io(`${SOCKET_BASE_URL}/stream-chat`, {
  transports: ['websocket', 'polling']
});

Yayına Katılma

socket.emit('joinStream', {
  streamId: '65a1234bcde5678f90123457',
  token: 'YOUR_JWT_TOKEN'
});

Server → Client Event: newGift

Gift gönderildiğinde tüm yayın izleyicilerine broadcast edilir.

Event: newGift

Namespace: /stream-chat

Payload:

{
  giftTypeId: string;
  creditAmount: number;
  svgUrl: string;
  senderUserId: string;
  senderInfo?: {
    _id: string;
    username?: string;
    name?: string | null;
    surname?: string | null;
    profilePhoto?: {
      _id: string;
      url: string;
      // ... diğer media alanları
    } | null;
  };
  liveStreamId: string;
  timestamp: Date;
}

Örnek:

socket.on('newGift', (giftData) => {
  console.log('Yeni gift:', giftData);
  // Gift animasyonunu göster
  // Toplam kredi miktarını güncelle
});

Payload Örneği:

{
  "giftTypeId": "65a1234bcde5678f90123456",
  "creditAmount": 9,
  "svgUrl": "https://example.com/gifts/gift_9.svg",
  "senderUserId": "65a1234bcde5678f90123458",
  "senderInfo": {
    "_id": "65a1234bcde5678f90123458",
    "username": "johndoe",
    "name": "John",
    "surname": "Doe",
    "profilePhoto": {
      "_id": "65a1234bcde5678f90123459",
      "url": "https://example.com/profile.jpg"
    }
  },
  "liveStreamId": "65a1234bcde5678f90123457",
  "timestamp": "2024-01-01T12:00:00.000Z"
}

Namespace: /live-stream

Toplam hediye adedi event'ini dinlemek için /live-stream namespace'ine bağlanıp joinStream ile yayına katılın.

Bağlantı

import { io } from 'socket.io-client';

const liveStreamSocket = io(`${SOCKET_BASE_URL}/live-stream`, {
  transports: ['websocket']
});

Yayına Katılma

liveStreamSocket.emit('joinStream', {
  streamId: '65a1234bcde5678f90123457'
});

Server → Client Event: giftCountUpdated

Bu event iki durumda gönderilir:

  • joinStream sonrasında sadece ilgili kullanıcıya başlangıç toplamı
  • Yeni bir gift gönderildiğinde stream room'undaki herkese güncel toplam

Event: giftCountUpdated

Namespace: /live-stream

Payload:

{
  streamId: string;
  totalGiftCount: number;
  timestamp: string; // ISO 8601
}

Örnek:

liveStreamSocket.on('giftCountUpdated', (data) => {
  console.log('Toplam gift adedi:', data.totalGiftCount);
});

Veri Modelleri

GiftType (Aktif Gift Tipleri)

Gift tipleri admin tarafından oluşturulur ve sadece aktif olanlar kullanıcılara gösterilir.

interface GiftType {
  _id: string;                    // MongoDB ObjectId
  creditAmount: number;            // Kredi miktarı (9, 19, 29, 59, 79, 99 gibi)
  svgUrl: string;                  // Gift SVG görselinin URL'i
  isActive: boolean;               // Aktif mi?
  createdAt: Date;                 // Oluşturulma tarihi
  updatedAt: Date;                 // Güncellenme tarihi
}

Örnek:

{
  "_id": "65a1234bcde5678f90123456",
  "creditAmount": 9,
  "svgUrl": "https://s.allminelive.com/gifts/gift_9.svg",
  "isActive": true,
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-01T00:00:00.000Z"
}

Not: Gift tiplerini listelemek için admin endpoint'i kullanılmalı. Mobil uygulamada gift tipleri genellikle:

  • Statik olarak tanımlanır (hardcoded)
  • Veya admin panelinden alınır (admin yetkisi gerektirir)

Gift

interface Gift {
  timestamp: Date;                  // Gift gönderilme zamanı
  metadata: {
    giftTypeId: string;             // Gift type ID'si
    creditAmount: number;           // Kredi miktarı
    svgUrl: string;                 // Gift SVG URL'i
    senderUserId: string;           // Gönderen kullanıcı ID'si
    liveStreamId: string;           // Yayın ID'si
  };
}

Örnek:

{
  "timestamp": "2024-01-01T12:00:00.000Z",
  "metadata": {
    "giftTypeId": "65a1234bcde5678f90123456",
    "creditAmount": 9,
    "svgUrl": "https://s.allminelive.com/gifts/gift_9.svg",
    "senderUserId": "65a1234bcde5678f90123458",
    "liveStreamId": "65a1234bcde5678f90123457"
  }
}

UserSummaryDto

Socket event'lerinde gönderen kullanıcı bilgisi için kullanılır.

interface UserSummaryDto {
  _id: string;
  username?: string;
  name?: string | null;
  surname?: string | null;
  profilePhoto?: {
    _id: string;
    url: string;
    // ... diğer media alanları
  } | null;
}

React Native Entegrasyon Örnekleri

1. Gift Gönderme

import axios from 'axios';

const API_BASE_URL = 'https://api.allmine.win/api';

interface CreateGiftRequest {
  giftTypeId: string;
  liveStreamId: string;
}

async function sendGift(
  giftTypeId: string,
  liveStreamId: string,
  accessToken: string
): Promise<void> {
  try {
    const response = await axios.post(
      `${API_BASE_URL}/gifts/create`,
      {
        giftTypeId,
        liveStreamId,
      },
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
      }
    );

    if (response.data.isSuccess) {
      console.log('Gift başarıyla gönderildi');
      // Socket event'i otomatik olarak broadcast edilir
    }
  } catch (error) {
    if (axios.isAxiosError(error)) {
      if (error.response?.status === 400) {
        const errorMessage = error.response.data?.errors?.[0] || 'Gift gönderilemedi';
        throw new Error(errorMessage);
      }
      if (error.response?.status === 401) {
        throw new Error('Oturum süresi dolmuş. Lütfen tekrar giriş yapın.');
      }
      if (error.response?.status === 404) {
        throw new Error('Gift type bulunamadı.');
      }
    }
    throw error;
  }
}

2. Toplam Kredi Miktarını Getirme

async function getTotalGiftCredit(
  liveStreamId: string,
  accessToken: string
): Promise<number> {
  try {
    const response = await axios.get(
      `${API_BASE_URL}/gifts/total-credit-amount/${liveStreamId}`,
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

    if (response.data.isSuccess) {
      return response.data.data;
    }
    return 0;
  } catch (error) {
    console.error('Toplam kredi miktarı alınamadı:', error);
    return 0;
  }
}

3. Socket ile Gift Dinleme

import { io, Socket } from 'socket.io-client';

const SOCKET_BASE_URL = 'wss://api.allmine.win';

interface GiftData {
  giftTypeId: string;
  creditAmount: number;
  svgUrl: string;
  senderUserId: string;
  senderInfo?: {
    _id: string;
    username?: string;
    name?: string | null;
    surname?: string | null;
    profilePhoto?: {
      _id: string;
      url: string;
    } | null;
  };
  liveStreamId: string;
  timestamp: Date;
}

class GiftSocketManager {
  private socket: Socket | null = null;

  connect(accessToken: string): void {
    this.socket = io(`${SOCKET_BASE_URL}/stream-chat`, {
      transports: ['websocket', 'polling'],
    });

    this.socket.on('connect', () => {
      console.log('Socket bağlandı');
    });

    this.socket.on('disconnect', () => {
      console.log('Socket bağlantısı kesildi');
    });
  }

  joinStream(streamId: string, accessToken: string): void {
    if (!this.socket) {
      console.error('Socket bağlantısı yok');
      return;
    }

    this.socket.emit('joinStream', {
      streamId,
      token: accessToken,
    });

    this.socket.on('joinStreamSuccess', (data) => {
      console.log('Yayın chat\'ine katıldınız:', data);
    });

    this.socket.on('joinStreamError', (error) => {
      console.error('Chat\'e katılma hatası:', error);
    });
  }

  onNewGift(callback: (gift: GiftData) => void): void {
    if (!this.socket) {
      console.error('Socket bağlantısı yok');
      return;
    }

    this.socket.on('newGift', (giftData: GiftData) => {
      callback(giftData);
    });
  }

  leaveStream(streamId: string): void {
    if (!this.socket) {
      return;
    }

    this.socket.emit('leaveStream', { streamId });

    this.socket.on('leaveStreamSuccess', () => {
      console.log('Yayın chat\'inden ayrıldınız');
    });
  }

  disconnect(): void {
    if (this.socket) {
      this.socket.disconnect();
      this.socket = null;
    }
  }
}

// Kullanım örneği
const giftSocketManager = new GiftSocketManager();

// Bağlan
giftSocketManager.connect(accessToken);

// Yayına katıl
giftSocketManager.joinStream(liveStreamId, accessToken);

// Gift dinle
giftSocketManager.onNewGift((gift) => {
  console.log('Yeni gift:', gift);
  // Gift animasyonunu göster
  // Toplam kredi miktarını güncelle
});

4. Gift UI Komponenti Örneği

import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, Image, StyleSheet, Alert } from 'react-native';
import { GiftSocketManager } from './GiftSocketManager';
import { sendGift, getTotalGiftCredit } from './giftService';

interface GiftType {
  _id: string;
  creditAmount: number;
  svgUrl: string;
}

interface GiftComponentProps {
  liveStreamId: string;
  accessToken: string;
  userCreditBalance: number;
  giftTypes: GiftType[]; // Admin'den veya statik olarak alınır
  onGiftSent?: () => void;
  onBalanceUpdate?: (newBalance: number) => void;
}

export const GiftComponent: React.FC<GiftComponentProps> = ({
  liveStreamId,
  accessToken,
  userCreditBalance,
  giftTypes,
  onGiftSent,
  onBalanceUpdate,
}) => {
  const [totalCredit, setTotalCredit] = useState<number>(0);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [socketManager] = useState(() => new GiftSocketManager());

  useEffect(() => {
    // Socket bağlantısını kur
    socketManager.connect(accessToken);
    socketManager.joinStream(liveStreamId, accessToken);

    // Yeni gift'leri dinle
    socketManager.onNewGift((gift) => {
      // Toplam kredi miktarını güncelle
      setTotalCredit((prev) => prev + gift.creditAmount);
      
      // Gift animasyonunu göster (örn: Lottie, Animated API)
      showGiftAnimation(gift);
    });

    // İlk yüklemede toplam kredi miktarını al
    loadTotalCredit();

    return () => {
      socketManager.leaveStream(liveStreamId);
      socketManager.disconnect();
    };
  }, []);

  const loadTotalCredit = async () => {
    try {
      const total = await getTotalGiftCredit(liveStreamId, accessToken);
      setTotalCredit(total);
    } catch (error) {
      console.error('Toplam kredi yüklenemedi:', error);
    }
  };

  const handleSendGift = async (giftTypeId: string, creditAmount: number) => {
    // Client-side bakiye kontrolü
    if (userCreditBalance < creditAmount) {
      Alert.alert(
        'Yetersiz Bakiye',
        'Kredi bakiyeniz yetersiz. Lütfen kredi satın alın.',
        [{ text: 'Tamam' }]
      );
      return;
    }

    setIsLoading(true);
    try {
      await sendGift(giftTypeId, liveStreamId, accessToken);
      
      // Başarılı gönderim sonrası
      onGiftSent?.();
      
      // Kullanıcı bakiyesini güncelle
      const newBalance = userCreditBalance - creditAmount;
      onBalanceUpdate?.(newBalance);
      
      // Başarı mesajı (opsiyonel)
      // Alert.alert('Başarılı', 'Gift başarıyla gönderildi!');
    } catch (error) {
      console.error('Gift gönderme hatası:', error);
      
      // Hata mesajını kullanıcıya göster
      const errorMessage = error instanceof Error 
        ? error.message 
        : 'Gift gönderilemedi. Lütfen tekrar deneyin.';
      
      Alert.alert('Hata', errorMessage, [{ text: 'Tamam' }]);
    } finally {
      setIsLoading(false);
    }
  };

  const showGiftAnimation = (gift: GiftData) => {
    // Gift animasyonunu göster
    // Örn: Lottie animasyonu, SVG animasyonu, vs.
    // Bu kısım animasyon kütüphanenize göre implement edilmelidir
    console.log('Gift animasyonu gösteriliyor:', gift);
  };

  return (
    <View style={styles.container}>
      <View style={styles.totalCreditContainer}>
        <Text style={styles.totalCreditLabel}>Toplam Kredi</Text>
        <Text style={styles.totalCreditText}>{totalCredit}</Text>
      </View>

      <View style={styles.giftTypesContainer}>
        {giftTypes.map((giftType) => {
          const canAfford = userCreditBalance >= giftType.creditAmount;
          
          return (
            <TouchableOpacity
              key={giftType._id}
              style={[
                styles.giftButton,
                !canAfford && styles.giftButtonDisabled,
                isLoading && styles.giftButtonLoading,
              ]}
              onPress={() => handleSendGift(giftType._id, giftType.creditAmount)}
              disabled={!canAfford || isLoading}
            >
              <Image
                source={{ uri: giftType.svgUrl }}
                style={styles.giftImage}
                resizeMode="contain"
              />
              <Text style={styles.giftCreditText}>
                {giftType.creditAmount} 💎
              </Text>
            </TouchableOpacity>
          );
        })}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 16,
    backgroundColor: 'rgba(0, 0, 0, 0.8)',
    borderRadius: 12,
  },
  totalCreditContainer: {
    marginBottom: 16,
    alignItems: 'center',
    paddingVertical: 12,
    backgroundColor: 'rgba(255, 255, 255, 0.1)',
    borderRadius: 8,
  },
  totalCreditLabel: {
    fontSize: 12,
    color: '#fff',
    opacity: 0.7,
    marginBottom: 4,
  },
  totalCreditText: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#fff',
  },
  giftTypesContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    flexWrap: 'wrap',
  },
  giftButton: {
    alignItems: 'center',
    margin: 8,
    padding: 12,
    borderRadius: 12,
    backgroundColor: 'rgba(255, 255, 255, 0.15)',
    minWidth: 80,
  },
  giftButtonDisabled: {
    opacity: 0.5,
  },
  giftButtonLoading: {
    opacity: 0.7,
  },
  giftImage: {
    width: 60,
    height: 60,
    marginBottom: 8,
  },
  giftCreditText: {
    color: '#fff',
    fontSize: 14,
    fontWeight: '600',
  },
});

5. Gift Service (API İşlemleri)

// giftService.ts
import axios from 'axios';

const API_BASE_URL = 'https://api.allmine.win/api';

export interface CreateGiftRequest {
  giftTypeId: string;
  liveStreamId: string;
}

export interface GiftResponse {
  isSuccess: boolean;
  statusCode: number;
  data: {
    timestamp: string;
    metadata: {
      giftTypeId: string;
      creditAmount: number;
      svgUrl: string;
      senderUserId: string;
      liveStreamId: string;
    };
  };
  errors: string[];
  timestamp: string;
}

export interface TotalCreditResponse {
  isSuccess: boolean;
  statusCode: number;
  data: number;
  errors: string[];
  timestamp: string;
}

export async function sendGift(
  giftTypeId: string,
  liveStreamId: string,
  accessToken: string
): Promise<GiftResponse> {
  const response = await axios.post<GiftResponse>(
    `${API_BASE_URL}/gifts/create`,
    {
      giftTypeId,
      liveStreamId,
    },
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
    }
  );

  return response.data;
}

export async function getTotalGiftCredit(
  liveStreamId: string,
  accessToken: string
): Promise<number> {
  const response = await axios.get<TotalCreditResponse>(
    `${API_BASE_URL}/gifts/total-credit-amount/${liveStreamId}`,
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    }
  );

  if (response.data.isSuccess) {
    return response.data.data;
  }
  
  throw new Error('Toplam kredi miktarı alınamadı');
}

Hata Yönetimi

Yaygın Hatalar ve Çözümleri

1. Yetersiz Kredi Bakiyesi

Hata: 400 Bad Request - Insufficient credit balance

Açıklama: Kullanıcının kredi bakiyesi gift göndermek için yetersiz.

Çözüm:

  • Kullanıcıya yetersiz bakiye mesajı göster
  • Kredi satın alma ekranına yönlendir
  • Client-side'da gift göndermeden önce bakiye kontrolü yap

Örnek:

if (userCreditBalance < giftType.creditAmount) {
  Alert.alert('Yetersiz Bakiye', 'Kredi bakiyeniz yetersiz.');
  return;
}

2. Gift Type Aktif Değil

Hata: 400 Bad Request - Gift type is not active

Açıklama: Seçilen gift type aktif değil veya devre dışı bırakılmış.

Çözüm:

  • Gift type listesini yenile
  • Aktif olmayan gift'leri UI'dan kaldır
  • Kullanıcıya uygun mesaj göster

3. Live Stream Bulunamadı

Hata: 400 Bad Request - Live stream not found

Açıklama: Belirtilen live stream bulunamadı veya yayın kapalı.

Çözüm:

  • Yayın durumunu kontrol et
  • Yayın kapalıysa gift göndermeyi engelle
  • Kullanıcıya yayının kapalı olduğunu bildir

4. Token Geçersiz

Hata: 401 Unauthorized

Açıklama: JWT token geçersiz, süresi dolmuş veya eksik.

Çözüm:

  • Token'ı yenile
  • Kullanıcıyı tekrar giriş yapmaya yönlendir
  • Token refresh mekanizması kullan

Örnek:

if (error.response?.status === 401) {
  // Token'ı yenile veya login ekranına yönlendir
  await refreshToken();
  // Veya
  navigation.navigate('Login');
}

5. Gift Type Bulunamadı

Hata: 404 Not Found - Gift type not found

Açıklama: Belirtilen gift type ID'si geçersiz veya silinmiş.

Çözüm:

  • Gift type listesini yenile
  • Geçerli gift type ID'leri kullan
  • Kullanıcıya hata mesajı göster

Hata Yakalama Örneği

try {
  await sendGift(giftTypeId, liveStreamId, accessToken);
} catch (error) {
  if (axios.isAxiosError(error)) {
    const status = error.response?.status;
    const errorMessage = error.response?.data?.errors?.[0] || 'Bir hata oluştu';
    
    switch (status) {
      case 400:
        Alert.alert('Hata', errorMessage);
        break;
      case 401:
        Alert.alert('Oturum Süresi Doldu', 'Lütfen tekrar giriş yapın.');
        // Token yenileme veya login'e yönlendirme
        break;
      case 404:
        Alert.alert('Bulunamadı', errorMessage);
        break;
      default:
        Alert.alert('Hata', 'Beklenmeyen bir hata oluştu.');
    }
  } else {
    Alert.alert('Hata', 'Bağlantı hatası. Lütfen internet bağlantınızı kontrol edin.');
  }
}

Akış Diyagramı

Gift Gönderme Akışı

sequenceDiagram
    participant User
    participant App
    participant API
    participant Socket
    participant StreamViewers

    User->>App: Gift göndermek için tıklar
    App->>App: Kredi bakiyesini kontrol et
    
    alt Yeterli bakiye var
        App->>API: POST /gifts/create
        API->>API: Live stream kontrolü
        API->>API: Gift type kontrolü
        API->>API: Kullanıcı kontrolü
        API->>API: Transaction başlat
        API->>API: Kredi bakiyesini düş
        API->>API: Gift'i kaydet
        API->>API: Transaction commit
        API->>Socket: newGift event'i broadcast et
        Socket->>StreamViewers: newGift event'i gönder
        API->>App: 201 Created
        App->>User: Gift gönderildi mesajı
        StreamViewers->>StreamViewers: Gift animasyonunu göster
        StreamViewers->>StreamViewers: Toplam kredi miktarını güncelle
    else Yetersiz bakiye
        App->>User: Yetersiz bakiye hatası
    end

Socket Bağlantı ve Gift Dinleme Akışı

sequenceDiagram
    participant App
    participant Socket
    participant Server

    App->>Socket: /stream-chat namespace'ine bağlan
    Socket->>Server: Connection
    Server->>Socket: Connected
    
    App->>Socket: joinStream event'i gönder
    Socket->>Server: joinStream (streamId, token)
    Server->>Server: Token doğrula
    Server->>Server: Stream kontrolü
    Server->>Socket: joinStreamSuccess
    Socket->>App: joinStreamSuccess
    
    Note over App,Server: Gift gönderme işlemi başka bir client tarafından yapılır
    
    Server->>Socket: newGift event'i broadcast
    Socket->>App: newGift event'i
    App->>App: Gift animasyonunu göster
    App->>App: Toplam kredi miktarını güncelle
    
    App->>Socket: leaveStream event'i gönder
    Socket->>Server: leaveStream
    Server->>Socket: leaveStreamSuccess
    Socket->>App: leaveStreamSuccess
    
    App->>Socket: Disconnect
    Socket->>Server: Disconnect

Önemli Notlar

1. Transaction Güvenliği

Gift gönderme işlemi MongoDB transaction kullanır. Kredi düşme ve gift kaydetme işlemleri atomik olarak gerçekleşir. Bu sayede:

  • Kredi düşülürken gift kaydedilemezse, kredi geri alınır
  • Gift kaydedilirken kredi düşülemezse, işlem iptal edilir
  • Veri tutarlılığı garanti edilir

2. Socket Bağlantısı

Gift'leri dinlemek için:

  1. /stream-chat namespace'ine bağlanılmalı
  2. joinStream event'i ile yayına katılınmalı
  3. newGift event'i dinlenmeli
  4. Yayından ayrılırken leaveStream event'i gönderilmeli

Önemli: Socket bağlantısı kesilirse, yeniden bağlanma mekanizması implement edilmelidir.

3. Gift Tipleri

Gift tipleri admin tarafından yönetilir. Mobil uygulamada gift tipleri genellikle:

  • Statik olarak tanımlanır (hardcoded): Gift tipleri uygulama içinde sabit olarak tanımlanır
  • Admin endpoint'inden alınır: Admin yetkisi gerektirir, genellikle kullanıcı endpoint'i yoktur

Öneri: Gift tipleri statik olarak tanımlanmalı ve admin tarafından değiştirildiğinde uygulama güncellenmelidir.

4. Kredi Bakiyesi

Kullanıcının kredi bakiyesi gift göndermeden önce kontrol edilmelidir:

  • Client-side kontrol: UX için hızlı geri bildirim sağlar
  • Server-side kontrol: Güvenlik için zorunludur

Client-side kontrol sadece UX içindir, asıl kontrol server-side'da yapılır.

5. Gift Animasyonları

Gift gönderildiğinde socket üzerinden newGift event'i gelir. Bu event'i kullanarak:

  • Gift animasyonları gösterilebilir (Lottie, Animated API, vs.)
  • Gift gönderen kullanıcının bilgileri gösterilebilir
  • Toplam kredi miktarı güncellenebilir

Öneri: Gift animasyonları için Lottie veya React Native Animated API kullanılabilir.

6. Toplam Kredi Takibi

Yayın için toplam kredi miktarı:

  • GET /gifts/total-credit-amount/:liveStreamId endpoint'i ile alınabilir
  • Her yeni gift geldiğinde local state'te güncellenebilir
  • Yayın başladığında ilk yüklemede alınmalıdır

Öneri: İlk yüklemede API'den alın, sonrasında socket event'leri ile güncelle.

7. Performans Optimizasyonları

  • Gift animasyonları: Çok sayıda gift geldiğinde animasyon kuyruğu kullanılmalı
  • Socket reconnection: Bağlantı kesildiğinde otomatik yeniden bağlanma implement edilmeli
  • Debouncing: Gift gönderme butonuna debounce eklenebilir (spam önleme)

Test Senaryoları

1. Başarılı Gift Gönderme

Senaryo:

  • Kullanıcının yeterli kredi bakiyesi var
  • Live stream aktif
  • Gift type aktif
  • Socket bağlantısı kurulu

Beklenen Sonuç:

  • API 201 döner
  • Kredi bakiyesi düşer
  • Gift veritabanına kaydedilir
  • Socket üzerinden newGift event'i gelir
  • Toplam kredi miktarı güncellenir

Test Kodu:

// 1. Socket bağlantısını kur
socketManager.connect(accessToken);
socketManager.joinStream(liveStreamId, accessToken);

// 2. Gift event'ini dinle
let receivedGift = null;
socketManager.onNewGift((gift) => {
  receivedGift = gift;
});

// 3. Gift gönder
await sendGift(giftTypeId, liveStreamId, accessToken);

// 4. Event'in geldiğini kontrol et
expect(receivedGift).not.toBeNull();
expect(receivedGift.giftTypeId).toBe(giftTypeId);

2. Yetersiz Bakiye

Senaryo:

  • Kullanıcının kredi bakiyesi gift miktarından az
  • Gift göndermeye çalışır

Beklenen Sonuç:

  • API 400 döner
  • Hata mesajı: "Insufficient credit balance"
  • Kredi bakiyesi değişmez
  • Gift kaydedilmez
  • Socket event'i gelmez

Test Kodu:

// Yetersiz bakiye ile gift göndermeye çalış
try {
  await sendGift(giftTypeId, liveStreamId, accessToken);
  fail('Hata fırlatılmalıydı');
} catch (error) {
  expect(error.response.status).toBe(400);
  expect(error.response.data.errors[0]).toContain('Insufficient credit balance');
}

3. Socket Bağlantı Kesilmesi

Senaryo:

  • Socket bağlantısı kurulu
  • Gift'ler dinleniyor
  • Bağlantı kesilir
  • Yeniden bağlanır

Beklenen Sonuç:

  • Disconnect event'i gelir
  • Yeniden bağlanma mekanizması devreye girer
  • Bağlantı yeniden kurulur
  • Gift'ler tekrar dinlenmeye başlar

Test Kodu:

// Bağlantıyı kes
socketManager.disconnect();

// Yeniden bağlan
socketManager.connect(accessToken);
socketManager.joinStream(liveStreamId, accessToken);

// Gift'lerin tekrar dinlendiğini kontrol et
socketManager.onNewGift((gift) => {
  // Gift geldi
});

4. Çoklu Gift Gönderme

Senaryo:

  • Kısa süre içinde birden fazla gift gönderilir
  • Her gift farklı gift type'dan

Beklenen Sonuç:

  • Her gift başarıyla işlenir
  • Tüm gift'ler socket üzerinden broadcast edilir
  • Toplam kredi miktarı doğru şekilde güncellenir
  • Kredi bakiyesi doğru şekilde düşer

Test Kodu:

// Birden fazla gift gönder
const gifts = [
  { giftTypeId: 'type1', creditAmount: 9 },
  { giftTypeId: 'type2', creditAmount: 19 },
  { giftTypeId: 'type3', creditAmount: 29 },
];

const receivedGifts = [];
socketManager.onNewGift((gift) => {
  receivedGifts.push(gift);
});

// Tüm gift'leri gönder
await Promise.all(
  gifts.map(gift => sendGift(gift.giftTypeId, liveStreamId, accessToken))
);

// Tüm gift'lerin geldiğini kontrol et
expect(receivedGifts.length).toBe(gifts.length);

5. Gift Type Aktif Değil

Senaryo:

  • Gift type aktif değil
  • Gift göndermeye çalışır

Beklenen Sonuç:

  • API 400 döner
  • Hata mesajı: "Gift type is not active"
  • Gift kaydedilmez

Test Kodu:

// Aktif olmayan gift type ile göndermeye çalış
try {
  await sendGift(inactiveGiftTypeId, liveStreamId, accessToken);
  fail('Hata fırlatılmalıydı');
} catch (error) {
  expect(error.response.status).toBe(400);
  expect(error.response.data.errors[0]).toContain('Gift type is not active');
}

6. Live Stream Bulunamadı

Senaryo:

  • Geçersiz veya olmayan live stream ID'si
  • Gift göndermeye çalışır

Beklenen Sonuç:

  • API 400 döner
  • Hata mesajı: "Live stream not found"
  • Gift kaydedilmez

Test Kodu:

// Geçersiz stream ID ile göndermeye çalış
try {
  await sendGift(giftTypeId, 'invalid-stream-id', accessToken);
  fail('Hata fırlatılmalıydı');
} catch (error) {
  expect(error.response.status).toBe(400);
  expect(error.response.data.errors[0]).toContain('Live stream not found');
}

API Base URL'leri

REST API

  • Local: http://localhost:3000
  • Development: https://dev.allmine.win
  • Staging: https://staging.allmine.win
  • Production: https://api.allmine.win

WebSocket

  • Local: ws://localhost:3000
  • Development: wss://dev.allmine.win
  • Staging: wss://staging.allmine.win
  • Production: wss://api.allmine.win

Destek

Sorularınız için backend ekibi ile iletişime geçin.

İletişim:


Versiyonlama

Bu dokümantasyon API versiyonu ile birlikte güncellenir. Mevcut versiyon: v1

Son Güncelleme: 2024-01-01

On this page