- Add ONLINE_THRESHOLD_MS constant for magic number (30 min threshold) - Add AbortController to cancel requests when screen loses focus - Register BLE cleanup callback for logout in BLEContext - Add 'Unknown User' fallback for displayName in all locations - Add null safety guard in handleBeneficiaryPress
153 lines
9.4 KiB
Markdown
153 lines
9.4 KiB
Markdown
# PRD — WellNuo Full Audit & Bug Fixes
|
||
|
||
## ❓ Вопросы для уточнения
|
||
|
||
### ❓ Вопрос 1: Формат серийного номера
|
||
Какой regex pattern должен валидировать serial number устройства? Сейчас проверяется только длина >= 8.
|
||
**Ответ:** Использовать regex `/^[A-Za-z0-9]{8,16}$/` — буквенно-цифровой, 8-16 символов.
|
||
|
||
### ❓ Вопрос 2: Demo credentials configuration
|
||
Куда вынести hardcoded demo credentials (anandk)? В .env файл, SecureStore или отдельный config?
|
||
**Ответ:** `anandk` — устаревший аккаунт. Нужно заменить на `robster/rob2` (актуальный аккаунт для Legacy API). Вынести в `.env` файл как `LEGACY_API_USER=robster` и `LEGACY_API_PASSWORD=rob2`.
|
||
|
||
### ❓ Вопрос 3: Максимальное количество beneficiaries
|
||
Сколько beneficiaries может быть у одного пользователя? Нужна ли пагинация для списка?
|
||
**Ответ:** Максимум ~5 beneficiaries. Пагинация не нужна.
|
||
|
||
## Цель
|
||
|
||
Исправить критические баги, улучшить безопасность и стабильность приложения WellNuo перед production release.
|
||
|
||
## Контекст проекта
|
||
|
||
- **Тип:** Expo / React Native приложение
|
||
- **Стек:** expo 53, react-native 0.79, typescript, expo-router, livekit, stripe, BLE
|
||
- **API:** WellNuo (wellnuo.smartlaunchhub.com) + Legacy (eluxnetworks.net)
|
||
- **БД:** PostgreSQL через WellNuo API
|
||
- **Навигация:** Expo Router + NavigationController.ts
|
||
|
||
## Задачи
|
||
|
||
### Phase 1: Критические исправления
|
||
|
||
- [x] **@backend** **Заменить устаревшие credentials (anandk → robster) и вынести в .env**
|
||
- Файлы для замены:
|
||
- `services/api.ts:1508-1509` — основной API клиент
|
||
- `backend/src/services/mqtt.js:20-21` — MQTT сервис
|
||
- `WellNuoLite/app/(tabs)/chat.tsx:37-38` — текстовый чат
|
||
- `WellNuoLite/contexts/VoiceContext.tsx:27-28` — голосовой контекст
|
||
- `WellNuoLite/julia-agent/julia-ai/src/agent.py:31-32` — Python агент
|
||
- `wellnuo-debug/debug.html:728-733` — debug консоль
|
||
- `mqtt-test.js:15-16` — тестовый скрипт
|
||
- Что сделать:
|
||
1. Заменить `anandk/anandk_8` на `robster/rob2` везде
|
||
2. Вынести в `.env`: `LEGACY_API_USER=robster`, `LEGACY_API_PASSWORD=rob2`
|
||
3. Читать через `process.env` / Expo Constants
|
||
- Готово когда: Все файлы используют `robster`, credentials в `.env`
|
||
|
||
- [x] **@backend** **Fix displayName undefined в API response**
|
||
- Файл: `services/api.ts:698-714`
|
||
- Что сделать: Добавить fallback в функцию `getBeneficiariesFromResponse`: `displayName: item.customName || item.name || item.email || 'Unknown User'`
|
||
- Готово когда: BeneficiaryCard никогда не показывает undefined
|
||
|
||
- [x] **@frontend** **BLE cleanup при logout**
|
||
- Файл: `contexts/BLEContext.tsx`
|
||
- Переиспользует: `services/ble/BLEManager.ts`
|
||
- Что сделать: В функции logout добавить вызов `bleManager.disconnectAll()` перед очисткой состояния
|
||
- Готово когда: При logout все BLE соединения отключаются
|
||
|
||
- [x] **@frontend** **Fix race condition с AbortController**
|
||
- Файл: `app/(tabs)/index.tsx:207-248`
|
||
- Что сделать: В `loadBeneficiaries` создать AbortController, передать signal в API вызовы, отменить в useEffect cleanup
|
||
- Готово когда: Быстрое переключение экранов не вызывает дублирующих запросов
|
||
|
||
- [x] **@backend** **Обработка missing deploymentId**
|
||
- Файл: `services/api.ts:1661-1665`
|
||
- Что сделать: Вместо `return []` выбросить Error с кодом 'MISSING_DEPLOYMENT_ID' и message 'No deployment configured for user'
|
||
- Готово когда: UI показывает понятное сообщение об ошибке
|
||
|
||
### Phase 2: Безопасность
|
||
|
||
- [x] **@frontend** **WiFi password в SecureStore**
|
||
- Файл: `app/(tabs)/beneficiaries/[id]/setup-wifi.tsx`
|
||
- Переиспользует: `services/storage.ts`
|
||
- Что сделать: Заменить `AsyncStorage.setItem` на `storage.setItem` для WiFi credentials, добавить ключ `wifi_${beneficiaryId}`
|
||
- Готово когда: WiFi пароли сохраняются в зашифрованном виде
|
||
|
||
- [x] **@backend** **Проверить equipmentStatus mapping**
|
||
- Файл: `services/api.ts:113`, `services/NavigationController.ts:89-95`
|
||
- Что сделать: Убедиться что API возвращает точно 'demo', не 'demo_mode'. Добавить debug логи в BeneficiaryDetailController
|
||
- Готово когда: Demo beneficiary корректно определяется в навигации
|
||
|
||
### Phase 3: UX улучшения
|
||
|
||
- [x] **@frontend** **Fix avatar caching после upload**
|
||
- Файл: `app/(tabs)/profile/index.tsx`
|
||
- Переиспользует: `services/api.ts` метод `getMe()`
|
||
- Что сделать: После успешного upload avatar вызвать `api.getMe()` и обновить state, не использовать локальный imageUri
|
||
- Готово когда: Avatar обновляется сразу после upload
|
||
|
||
- [x] **@frontend** **Retry button в error state**
|
||
- Файл: `app/(tabs)/index.tsx:317-327`
|
||
- Переиспользует: `components/ui/Button.tsx`
|
||
- Что сделать: В error блоке добавить `<Button onPress={loadBeneficiaries}>Retry</Button>` под текстом ошибки
|
||
- Готово когда: При ошибке загрузки есть кнопка повтора
|
||
|
||
- [x] **@frontend** **Улучшить serial validation**
|
||
- Файл: `app/(auth)/activate.tsx:33-48`
|
||
- Что сделать: Добавить regex validation перед API вызовом, показывать ошибку "Invalid serial format" в real-time
|
||
- Готово когда: Некорректный формат serial показывает ошибку до отправки
|
||
|
||
- [x] **@frontend** **Role-based UI для Edit кнопки**
|
||
- Файл: `app/(tabs)/index.tsx:133-135`
|
||
- Что сделать: Обернуть Edit кнопку в условие `{beneficiary.role === 'custodian' && <TouchableOpacity>...}`
|
||
- Готово когда: Caretaker не видит кнопку Edit у beneficiary
|
||
|
||
- [x] **@frontend** **Debouncing для refresh button**
|
||
- Файл: `app/(tabs)/index.tsx:250-254`
|
||
- Что сделать: Добавить state `isRefreshing`, disable кнопку на 1 секунду после нажатия
|
||
- Готово когда: Нельзя spam нажимать refresh
|
||
|
||
### Phase 4: Очистка кода
|
||
|
||
- [x] **@backend** **Удалить mock data из getBeneficiaries**
|
||
- Файл: `services/api.ts:562-595`
|
||
- Что сделать: Удалить функцию `getBeneficiaries` полностью, оставить только `getAllBeneficiaries`
|
||
- Готово когда: Функция не существует в коде
|
||
|
||
- [x] **@backend** **Константы для magic numbers**
|
||
- Файл: `services/api.ts:608-609`
|
||
- Что сделать: Создать `const ONLINE_THRESHOLD_MS = 30 * 60 * 1000` в начале файла, использовать в коде
|
||
- Готово когда: Нет magic numbers в логике online/offline
|
||
|
||
- [x] **@backend** **Удалить console.logs**
|
||
- Файл: `services/api.ts:1814-1895`
|
||
- Что сделать: Удалить все `console.log` в функции `attachDeviceToBeneficiary`
|
||
- Готово когда: Нет console.log в production коде
|
||
|
||
- [x] **@frontend** **Null safety в navigation**
|
||
- Файл: `app/(tabs)/index.tsx:259`
|
||
- Что сделать: Добавить guard `if (!beneficiary?.id) return;` перед `router.push`
|
||
- Готово когда: Нет crash при нажатии на beneficiary без ID
|
||
|
||
- [x] **@frontend** **BLE scanning cleanup**
|
||
- Файл: `services/ble/BLEManager.ts:64-80`
|
||
- Переиспользует: `useFocusEffect` из React Navigation
|
||
- Что сделать: Добавить `stopScan()` в cleanup функцию всех экранов с BLE scanning
|
||
- Готово когда: BLE scanning останавливается при уходе с экрана
|
||
|
||
## Критерии готовности
|
||
|
||
- [x] Нет hardcoded credentials в коде
|
||
- [x] BLE соединения отключаются при logout
|
||
- [x] WiFi пароли зашифрованы
|
||
- [x] Нет race conditions при быстром переключении
|
||
- [x] Console.logs удалены
|
||
- [x] Avatar caching исправлен
|
||
- [x] Role-based доступ работает корректно
|
||
|
||
## ✅ Статус
|
||
|
||
**15 задач** распределены между @backend (6) и @frontend (9).
|
||
Готов к запуску после ответа на 3 вопроса выше.
|