Gift entegrasyonu
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
- Genel Bakış
- API Endpoint'leri
- Socket Events
- Veri Modelleri
- React Native Entegrasyon Örnekleri
- Hata Yönetimi
- Akış Diyagramı
- Önemli Notlar
- 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
newGiftevent'i broadcast edilir /live-streamnamespace'inde toplam hediye adedigiftCountUpdatedile güncellenir- Yayın için toplam kredi miktarı güncellenir
Sistem Mimarisi
Gift sistemi şu bileşenlerden oluşur:
- API Endpoint'leri: Gift gönderme ve toplam kredi miktarını getirme
- Socket Events: Gerçek zamanlı gift bildirimleri
- Transaction Güvenliği: MongoDB transaction ile atomik işlemler
- 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/jsonRequest 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 Status | Hata Mesajı | Açıklama |
|---|---|---|
400 Bad Request | Live stream not found | Belirtilen live stream bulunamadı |
400 Bad Request | User not found | Kullanıcı bulunamadı |
400 Bad Request | Gift type is not active | Gift type aktif değil |
400 Bad Request | Insufficient credit balance | Kullanıcının kredi bakiyesi yetersiz |
401 Unauthorized | - | Token geçersiz veya eksik |
404 Not Found | Gift type not found | Belirtilen 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 Status | Hata 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: 0ile 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 Status | Hata Mesajı | Açıklama |
|---|---|---|
400 Bad Request | Invalid live stream ID, must be a valid MongoDB ObjectId string | Geçersiz stream ID |
401 Unauthorized | - | Token geçersiz veya eksik |
404 Not Found | Canlı 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:
joinStreamsonrası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ı
endSocket 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:
/stream-chatnamespace'ine bağlanılmalıjoinStreamevent'i ile yayına katılınmalınewGiftevent'i dinlenmeli- Yayından ayrılırken
leaveStreamevent'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/:liveStreamIdendpoint'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
newGiftevent'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:
- Email: [email protected]
- Telefon: +905372505893
Versiyonlama
Bu dokümantasyon API versiyonu ile birlikte güncellenir. Mevcut versiyon: v1
Son Güncelleme: 2024-01-01