# pnpm монорепо приложение для хранения скидочных карт (штрих/qr-код + тип кода, EAN-13, EAN-8, Code128, QR, PDF417, Aztec и т.д. + название + цвет + опционально лого + опционально скриншоты). # серверное приложение (fastify+react+vite+ts): * хранение пользователей в файле users.yaml (по email) и их групп (по названиям). для пользователя может быть храниться признак того, что это - администратор или суперадминистратор. * пользователь является администратором только для своей группы. суперадминистратор - администратор всех групп. * аутентификация пользователя для доступа к UI (не API) через заголовок Remote-Email * для каждого пользователя может храниться несколько строковых ключей доступа. ключи хранятся в зашифрованном обратимым шифрованием AES-256-GCM виде, ключ расшифровки (32-байта в base64) в переменной окружении. * административный (для суперадмина) CRUD UI для управления группами пользователей и выбора одной группы, как текущей. группу, к которой принадлежит хотя бы один суперадмин, невозможно удалить. * административный UI CRUD пользователей текущей группы и приязанных к ним ключей доступа. ключи генерируются случайно (32 символа) * хранилище списка карт в yaml файле в папке соответственной группы пользователей, картинки рядом с ним на диске в виде файлов. количество картинок на карту не ограничено. карты общие для всех пользователей одной группы. для каждой карты указывается числовой номер версии картинок. * административный UI для CRUD всех хранящихся, включая удаленные, карт текущей группы пользователей, загрузки картинок к ним. изменение/добавление/удаление любой картинки увеличивает номер версии в карте. загруженные картинки хранятся как есть. * UI для показа списка ключей текущего пользователя. Для одного выбранного ключа генерирутся QR код, содержащий адрес сервера и сам ключ. * API для клиентского приложения: * аутентификация пользователя по ключу (Authorization: Bearer) * получение списка всех карт (без пагинации). удаленные карты получить невозможно * получение картинок по выбранной карте * редактирование, удаление и добавление карт с их лого. удалённые карты помечаются, как удаленные и не удаляются * удаление и добавление картинок карт * весь UI на английском # клиентское мобильное приложение (react/vite/ts pwa + capacitor андроид): * хранение ключа и пинкода для входа * экран нового входа * поле для ввода адреса серверного API * показывает камеру, даёт отсканировать ключ из QR кода * позволяет ввести ключ текстом в открытом виде. обязательна аутентификация на сервере для подтверждения правильности ключа. экран можно вызвать даже если ключ уже хранится в приложении, в этом случае старый ключ удаляется. хранится всегда только один последний ключ * экран цифрового кода (= "пинкода"). показывается для ввода пинкода (4 цифры) при смене ключа (пинкод сохраняется) и при каждом открытии приложения (пинкод сверяется с хранящимся). защиты от перебора нет. * экран карт. список всех хранящихся локально карт (цветными названия с логотипами или, если их нет, с аббревиатурами на цветном фоне) с возможностью выбрать одну из них во весь экран. сортировка по давности использования * экран (одной) карты: просмотр её названия, штрих/qr-кода и её хранящиеся локально картинки, если есть. яркость экрана на андроиде поднимается на максимум. открывание экрана считается использованием карты и сохраняет дату и время * на экране карт кнопка "фильтр по названию" (по подстроке длиной от 1 символа) * на экране карт кнопка "обновить". перекачивает заново список всех карт с сервера и обновляет сохраненный список (только если скачалось успешно). для изменившихся карт асинхронно по очереди (макс 5 запросов одновременно) скачиваются их картинки. * экран редактирования/добавления новой карты: поле для ввода названия, выбора цвета (по умолчанию случайный), загрузки опционального лого, загрузки опциональных фото, ввода кода и выбора типа код карты текстом или распознаванием QR/штрих-кода и его типа с карты или из загруженной картинки. лого и картинки перед отправкой, пережимаются в webp до размера 1000px по наибольшей стороне, EXIF удаляется * на экране карт кнопка "добавить", вызывает экран редактирования/добавления новой карты * на экране карты кнопка "удалить" с подтверждением. * на экране карты кнопка "редактировать", вызывает экран редактирования/добавления новой карты * весь UI на английском клиентское приложение должно работать без интернета за исключением функций аутентификации, скачивания, редактирования, удаления и добавления карт и картинок. функции, требующие наличия сети, показывают ошибки сообщениями на экране и отваливаются. вопросы деплоя в этом задании не рассматриваются # Дополнительная информация Пользователи и группы * Пользователь состоит ровно в одной группе * Первый суперадмин создаётся из переменной окружения SUPERADMIN_EMAIL * При удалении группы все пользователи, ключи и карты удаляются. * Текущая группа пользователя читается из users.yaml и сохраняется в cookie. UI суперадмина позволяет записать в эту cookie другое значение. * При ошибка аутентификации отвечать ошибкой 401. * Новые ключи генерируются только админом или суперадмином Сессии и ключи * Шифрование ключей: AES-256-GCM с 32-байтным ключом в base64 в env подойдёт? Данные * Где генерируется ID карты? На сервере, при добавлении. Возвращается клиенту. * Конкурентные правки одной карты с разных устройств: побеждает последняя запись? Да. * Дата последнего использования карты хранится только локально на устройстве и сохраняется при «обновить» по ID карты? Да. Клиент и деплой * Как менять PIN без смены ключа? Или только через повторный вход? Да. * PWA раздаётся с того же домена что и API. * Нужны ли Dockerfile и docker-compose с томом под данные и пример конфига прокси с bypass для /api? Нет.