Files
chat/docs/MOBILE_INTEGRATION_EXAMPLES.md
Andrew K. Choi 7c22664daf
All checks were successful
continuous-integration/drone/push Build is passing
sdf
2025-09-26 12:22:14 +09:00

16 KiB
Raw Blame History

Примеры использования API для мобильной разработки

🚀 Быстрый старт

1. Авторизация пользователя

// Регистрация
const registerUser = async (userData) => {
  const response = await fetch('http://192.168.0.103:8000/api/auth/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      username: userData.username,
      email: userData.email,
      password: userData.password,
      full_name: userData.full_name,
      phone: userData.phone
    })
  });
  return await response.json();
};

// Вход в систему
const loginUser = async (username, password) => {
  const response = await fetch('http://192.168.0.103:8000/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  const data = await response.json();
  
  // Сохранить токен для дальнейших запросов
  localStorage.setItem('access_token', data.access_token);
  return data;
};

2. Создание SOS оповещения

const createEmergencyAlert = async (location, alertType, message) => {
  const token = localStorage.getItem('access_token');
  
  const response = await fetch('http://192.168.0.103:8000/api/emergency/alerts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
      latitude: location.latitude,
      longitude: location.longitude,
      alert_type: alertType, // 'general', 'medical', 'violence', etc.
      message: message,
      contact_emergency_services: true,
      notify_emergency_contacts: true
    })
  });
  
  return await response.json();
};

3. Поиск ближайших оповещений

const findNearbyAlerts = async (userLocation, radiusKm = 5) => {
  const token = localStorage.getItem('access_token');
  
  const response = await fetch(
    `http://192.168.0.103:8000/api/emergency/nearby?latitude=${userLocation.latitude}&longitude=${userLocation.longitude}&radius_km=${radiusKm}`,
    {
      headers: { 'Authorization': `Bearer ${token}` }
    }
  );
  
  return await response.json();
};

4. Отклик на оповещение

const respondToAlert = async (alertId, responseType, message, eta) => {
  const token = localStorage.getItem('access_token');
  
  const response = await fetch(`http://192.168.0.103:8000/api/emergency/alerts/${alertId}/responses`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({
      response_type: responseType, // 'help_on_way', 'contacted_authorities', etc.
      message: message,
      eta_minutes: eta,
      location_sharing: true
    })
  });
  
  return await response.json();
};

📱 React Native компоненты

SOS Button Component

import React, { useState } from 'react';
import { View, TouchableOpacity, Text, Alert } from 'react-native';
import Geolocation from '@react-native-community/geolocation';

const SOSButton = ({ onEmergencyCreated }) => {
  const [isCreating, setIsCreating] = useState(false);

  const handleSOSPress = () => {
    Alert.alert(
      'Экстренная ситуация',
      'Вы уверены, что хотите отправить SOS сигнал?',
      [
        { text: 'Отмена', style: 'cancel' },
        { text: 'SOS', style: 'destructive', onPress: createEmergencyAlert }
      ]
    );
  };

  const createEmergencyAlert = () => {
    setIsCreating(true);
    
    Geolocation.getCurrentPosition(
      async (position) => {
        try {
          const alert = await createEmergencyAlert(
            {
              latitude: position.coords.latitude,
              longitude: position.coords.longitude
            },
            'general',
            'Экстренная помощь необходима!'
          );
          
          onEmergencyCreated(alert);
          Alert.alert('Успешно', 'SOS сигнал отправлен');
        } catch (error) {
          Alert.alert('Ошибка', 'Не удалось отправить SOS сигнал');
        }
        setIsCreating(false);
      },
      (error) => {
        Alert.alert('Ошибка', 'Не удалось получить местоположение');
        setIsCreating(false);
      }
    );
  };

  return (
    <View style={{ alignItems: 'center', margin: 20 }}>
      <TouchableOpacity
        style={{
          width: 120,
          height: 120,
          borderRadius: 60,
          backgroundColor: isCreating ? '#ccc' : '#ff4444',
          justifyContent: 'center',
          alignItems: 'center',
          elevation: 5
        }}
        onPress={handleSOSPress}
        disabled={isCreating}
      >
        <Text style={{ color: 'white', fontSize: 18, fontWeight: 'bold' }}>
          {isCreating ? 'Отправка...' : 'SOS'}
        </Text>
      </TouchableOpacity>
    </View>
  );
};

Nearby Alerts List

import React, { useEffect, useState } from 'react';
import { View, FlatList, Text, TouchableOpacity } from 'react-native';

const NearbyAlertsList = ({ userLocation }) => {
  const [alerts, setAlerts] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (userLocation) {
      loadNearbyAlerts();
    }
  }, [userLocation]);

  const loadNearbyAlerts = async () => {
    setLoading(true);
    try {
      const nearbyAlerts = await findNearbyAlerts(userLocation);
      setAlerts(nearbyAlerts);
    } catch (error) {
      console.error('Ошибка загрузки оповещений:', error);
    }
    setLoading(false);
  };

  const handleRespondToAlert = async (alertId) => {
    try {
      await respondToAlert(alertId, 'help_on_way', 'Еду на помощь!', 10);
      Alert.alert('Успешно', 'Ваш отклик отправлен');
      loadNearbyAlerts(); // Обновить список
    } catch (error) {
      Alert.alert('Ошибка', 'Не удалось отправить отклик');
    }
  };

  const renderAlert = ({ item }) => (
    <View style={{ 
      backgroundColor: 'white', 
      margin: 10, 
      padding: 15, 
      borderRadius: 10,
      elevation: 2 
    }}>
      <Text style={{ fontWeight: 'bold', fontSize: 16 }}>
        {item.alert_type.toUpperCase()}
      </Text>
      <Text style={{ color: '#666', marginTop: 5 }}>
        📍 {item.address || 'Адрес не указан'}
      </Text>
      <Text style={{ color: '#666' }}>
        📏 {item.distance_km.toFixed(1)} км от вас
      </Text>
      <Text style={{ color: '#666' }}>
        👥 Откликов: {item.responded_users_count}
      </Text>
      
      <TouchableOpacity
        style={{
          backgroundColor: '#007AFF',
          padding: 10,
          borderRadius: 5,
          marginTop: 10,
          alignItems: 'center'
        }}
        onPress={() => handleRespondToAlert(item.id)}
      >
        <Text style={{ color: 'white', fontWeight: 'bold' }}>
          Помочь
        </Text>
      </TouchableOpacity>
    </View>
  );

  return (
    <View style={{ flex: 1 }}>
      <Text style={{ fontSize: 18, fontWeight: 'bold', margin: 15 }}>
        Ближайшие оповещения
      </Text>
      
      {loading ? (
        <Text style={{ textAlign: 'center', margin: 20 }}>
          Загрузка...
        </Text>
      ) : (
        <FlatList
          data={alerts}
          renderItem={renderAlert}
          keyExtractor={(item) => item.id.toString()}
          refreshing={loading}
          onRefresh={loadNearbyAlerts}
          ListEmptyComponent={
            <Text style={{ textAlign: 'center', margin: 20, color: '#666' }}>
              Поблизости нет активных оповещений
            </Text>
          }
        />
      )}
    </View>
  );
};

🔄 Обновление местоположения в реальном времени

class LocationService {
  constructor() {
    this.watchId = null;
    this.lastUpdate = null;
  }

  startLocationTracking() {
    this.watchId = Geolocation.watchPosition(
      (position) => {
        this.updateLocation(position.coords);
      },
      (error) => {
        console.error('Location error:', error);
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 10, // Обновлять при изменении на 10 метров
        interval: 30000,    // Обновлять каждые 30 секунд
      }
    );
  }

  async updateLocation(coords) {
    const now = Date.now();
    
    // Не отправляем слишком часто
    if (this.lastUpdate && (now - this.lastUpdate) < 30000) {
      return;
    }

    try {
      const token = localStorage.getItem('access_token');
      
      await fetch('http://192.168.0.103:8000/api/location/update', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        },
        body: JSON.stringify({
          latitude: coords.latitude,
          longitude: coords.longitude,
          accuracy: coords.accuracy,
          speed: coords.speed,
          heading: coords.heading,
          timestamp: new Date().toISOString()
        })
      });
      
      this.lastUpdate = now;
    } catch (error) {
      console.error('Failed to update location:', error);
    }
  }

  stopLocationTracking() {
    if (this.watchId) {
      Geolocation.clearWatch(this.watchId);
      this.watchId = null;
    }
  }
}

🔔 Push уведомления

Настройка Firebase (Android) / APNS (iOS)

import messaging from '@react-native-firebase/messaging';

class PushNotificationService {
  async requestPermission() {
    const authStatus = await messaging().requestPermission();
    const enabled =
      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
      authStatus === messaging.AuthorizationStatus.PROVISIONAL;

    if (enabled) {
      console.log('Push permissions granted');
      await this.getToken();
    }
  }

  async getToken() {
    try {
      const token = await messaging().getToken();
      await this.registerToken(token);
    } catch (error) {
      console.error('Failed to get push token:', error);
    }
  }

  async registerToken(token) {
    const apiToken = localStorage.getItem('access_token');
    
    await fetch('http://192.168.0.103:8000/api/notifications/register-token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiToken}`
      },
      body: JSON.stringify({
        token: token,
        platform: Platform.OS, // 'ios' or 'android'
        app_version: '1.0.0'
  });
  }

  setupForegroundHandler() {
    messaging().onMessage(async remoteMessage => {
      // Показать уведомление когда приложение активно
      Alert.alert(
        remoteMessage.notification.title,
        remoteMessage.notification.body
      );
    });
  }

  setupBackgroundHandler() {
    messaging().setBackgroundMessageHandler(async remoteMessage => {
      console.log('Background message:', remoteMessage);
    });
  }
}

🔐 Безопасность и шифрование

Хранение токенов

import * as Keychain from 'react-native-keychain';

class SecureStorage {
  static async storeToken(token) {
    try {
      await Keychain.setInternetCredentials(
        'women_safety_app',
        'access_token',
        token
      );
    } catch (error) {
      console.error('Failed to store token:', error);
    }
  }

  static async getToken() {
    try {
      const credentials = await Keychain.getInternetCredentials('women_safety_app');
      if (credentials) {
        return credentials.password;
      }
    } catch (error) {
      console.error('Failed to get token:', error);
    }
    return null;
  }

  static async removeToken() {
    try {
      await Keychain.resetInternetCredentials('women_safety_app');
    } catch (error) {
      console.error('Failed to remove token:', error);
    }
  }
}

Шифрование местоположения

import CryptoJS from 'crypto-js';

class LocationEncryption {
  static encryptLocation(lat, lng, secretKey) {
    const locationData = JSON.stringify({ lat, lng, timestamp: Date.now() });
    return CryptoJS.AES.encrypt(locationData, secretKey).toString();
  }

  static decryptLocation(encryptedData, secretKey) {
    try {
      const bytes = CryptoJS.AES.decrypt(encryptedData, secretKey);
      const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
      return JSON.parse(decryptedData);
    } catch (error) {
      console.error('Failed to decrypt location:', error);
      return null;
    }
  }
}

📊 Аналитика и мониторинг

Отслеживание использования

class AnalyticsService {
  static trackEmergencyAlert(alertType, responseTime) {
    // Интеграция с аналитикой (Firebase Analytics, etc.)
    analytics().logEvent('emergency_alert_created', {
      alert_type: alertType,
      response_time_seconds: responseTime,
      timestamp: Date.now()
    });
  }

  static trackUserResponse(alertId, responseType) {
    analytics().logEvent('emergency_response', {
      alert_id: alertId,
      response_type: responseType,
      timestamp: Date.now()
    });
  }

  static trackLocationUpdate(accuracy) {
    analytics().logEvent('location_update', {
      accuracy_meters: accuracy,
      timestamp: Date.now()
    });
  }
}

🧪 Тестирование API

Unit тесты для API клиента

import { APIClient } from './APIClient';

describe('APIClient', () => {
  let client;

  beforeEach(() => {
    client = new APIClient('http://192.168.0.103:8000');
  });

  test('should login successfully', async () => {
    const result = await client.login('testuser', 'testpass');
    expect(result.access_token).toBeDefined();
    expect(result.token_type).toBe('bearer');
  });

  test('should create emergency alert', async () => {
    // Сначала авторизоваться
    await client.login('testuser', 'testpass');
    
    const alert = await client.createAlert({
      latitude: 55.7558,
      longitude: 37.6176,
      alert_type: 'general',
      message: 'Test emergency'
    });

    expect(alert.id).toBeDefined();
    expect(alert.alert_type).toBe('general');
    expect(alert.status).toBe('active');
  });

  test('should get nearby alerts', async () => {
    await client.login('testuser', 'testpass');
    
    const alerts = await client.getNearbyAlerts(55.7558, 37.6176, 5);
    expect(Array.isArray(alerts)).toBe(true);
  });
});

📈 Оптимизация производительности

Кэширование данных

class CacheService {
  static cache = new Map();
  static CACHE_DURATION = 5 * 60 * 1000; // 5 минут

  static set(key, data) {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }

  static get(key) {
    const cached = this.cache.get(key);
    if (!cached) return null;

    if (Date.now() - cached.timestamp > this.CACHE_DURATION) {
      this.cache.delete(key);
      return null;
    }

    return cached.data;
  }

  static async getNearbyAlertsWithCache(lat, lng, radius) {
    const cacheKey = `nearby_${lat}_${lng}_${radius}`;
    let alerts = this.get(cacheKey);

    if (!alerts) {
      alerts = await findNearbyAlerts({ latitude: lat, longitude: lng }, radius);
      this.set(cacheKey, alerts);
    }

    return alerts;
  }
}

Эти примеры покрывают все основные сценарии использования API в реальном мобильном приложении, включая безопасность, производительность и тестирование.