Files
cardy/development specification.md
2026-09-29 13:50:01 +03:00

9.9 KiB

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? Нет.