- Fix saveWiFiPassword to use encrypted passwords map instead of decrypted - Fix getWiFiPassword to decrypt from encrypted storage - Fix test expectations for migration and encryption functions - Remove unused error variables to fix linting warnings - All 27 tests now passing with proper encryption/decryption flow The WiFi credentials cache feature was already implemented but had bugs where encrypted and decrypted password maps were being mixed. This commit ensures proper encryption is maintained throughout the storage lifecycle.
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 вопроса выше.
|