All checks were successful
continuous-integration/drone/push Build is passing
589 lines
16 KiB
Markdown
589 lines
16 KiB
Markdown
# Примеры использования API для мобильной разработки
|
||
|
||
## 🚀 Быстрый старт
|
||
|
||
### 1. Авторизация пользователя
|
||
```javascript
|
||
// Регистрация
|
||
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 оповещения
|
||
```javascript
|
||
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. Поиск ближайших оповещений
|
||
```javascript
|
||
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. Отклик на оповещение
|
||
```javascript
|
||
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
|
||
```jsx
|
||
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
|
||
```jsx
|
||
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>
|
||
);
|
||
};
|
||
```
|
||
|
||
## 🔄 Обновление местоположения в реальном времени
|
||
|
||
```javascript
|
||
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)
|
||
```javascript
|
||
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);
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🔐 Безопасность и шифрование
|
||
|
||
### Хранение токенов
|
||
```javascript
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Шифрование местоположения
|
||
```javascript
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📊 Аналитика и мониторинг
|
||
|
||
### Отслеживание использования
|
||
```javascript
|
||
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 клиента
|
||
```javascript
|
||
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);
|
||
});
|
||
});
|
||
```
|
||
|
||
## 📈 Оптимизация производительности
|
||
|
||
### Кэширование данных
|
||
```javascript
|
||
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 в реальном мобильном приложении, включая безопасность, производительность и тестирование. |