WellNuo/PRD.md
Sergei 1dd7eb8289 Remove hardcoded credentials and use environment variables
- Remove hardcoded database credentials from all scripts
- Remove hardcoded Legacy API tokens from backend scripts
- Remove hardcoded MQTT credentials from mqtt-test.js
- Update backend/.env.example with DB_HOST, DB_USER, DB_PASSWORD, DB_NAME
- Update backend/.env.example with LEGACY_API_TOKEN and MQTT credentials
- Add dotenv config to all scripts requiring credentials
- Create comprehensive documentation:
  - scripts/README.md - Root scripts usage
  - backend/scripts/README.md - Backend scripts documentation
  - MQTT_TESTING.md - MQTT testing guide
  - SECURITY_CREDENTIALS_CLEANUP.md - Security changes summary

All scripts now read credentials from backend/.env instead of hardcoded values.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-29 12:13:32 -08:00

153 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 останавливается при уходе с экрана
## Критерии готовности
- [ ] Нет hardcoded credentials в коде
- [ ] BLE соединения отключаются при logout
- [ ] WiFi пароли зашифрованы
- [ ] Нет race conditions при быстром переключении
- [ ] Console.logs удалены
- [ ] Avatar caching исправлен
- [ ] Role-based доступ работает корректно
## ✅ Статус
**15 задач** распределены между @backend (6) и @frontend (9).
Готов к запуску после ответа на 3 вопроса выше.