Сервис для голосования на Flask. Позволяет создавать и изменять голосования, регистрировать пользователей, голосовать и изменять голос.
Необходимо написать http-сервис с помощью Flask и развернуть его на сервере (heroku или любой другой).
Приложение должно предоставлять API, позволяющее проходить регистрацию, авторизовываться по Basic Auth, позволяющее добавлять, редактировать, удалять голосования и ответы пользователей со следующими условиями:
- В голосовании содержится только один вопрос;
- Вопрос имеет два или более вариантов ответов;
- Каждый пользователь может ответить и переголосовать;
- Если пользователь уже проголосовал:
- по GET выводить статистику по голосованию о суммарном количестве ответов в голосовании, а также по каждому из вопросов, процентном соотношении популярности ответов. Также необходимо отметить, какой из ответов принадлежит данному пользователю;
- по POST/PATCH/PUT (в зависимости от реализации) предоставить возможность переголосовать. Соответственно, новый голос должен перезаписать старый, чтобы не позволить пользователю иметь несколько действующих ответов на одно голосование одновременно.
Модели объектов и связи между ними, а также выбор хранилища для данных на свое усмотрение. Для представления данных предлагаю использовать JSON формат.
Авторизация: Basic (предоставить логин и пароль закодированные в Base64). Если для эндпойнта необходима авторизация, соответствующий заголовок необходимо включить в запрос в виде Authorization: Basic {логин:пароль в base64}
.
/create-voting
- Создание нового голосования.
- Методы: POST
- Авторизация: требуется
- Формат запроса:
- body: {"question": строка, "options": [строки]}
- Возвращает: 201 при успехе, 400 при отсутствии строки в JSON
/delete-voting/<voting_id>
Удаление голосования с id <voting_id>.
- Методы: GET
- Авторизация: требуется
- Возвращает: 200 при успехе, 400 при неправильном формате id, 404 при неудаче (отсутствие голосования с таким id)
/update-voting/<voting_id>
Редактирование голосования.
- Методы: POST
- Авторизация: требуется
- Формат запроса:
- body: {"question": строка, "options": [строки]}
- Возвращает: 200 при успехе, 400 при отсутствии строки в JSON, неверном формате id или неудаче при обновлении голосования
/create-user
Регистрация нового пользователя.
- Методы: POST
- Авторизация: не требуется
- Формат запроса:
- body: {"username": строка, "password": строка}
- Возвращает: 200 при успехе, 400 при отсутствии строки в JSON
/vote/<voting_id>
Голосование и просмотр результатов голосования.
- Методы: POST - для голосования, GET - для просмотра результатов.
- Авторизация: требуется для голосования
- Формат запроса POST:
- body: {"option": строка}
- Возвращает: 200 при успехе, 400 при отсутствии строки в JSON.
- GET-запрос возвращает JSON (200) при наличии голосования с указанным id в БД, 400 при неверном id, 404 при отстутствии голосвания в БД. Если в запрос включена авторизация, в возвращаемый JSON включается выбранный пользователем вариант.
/list-votings
Просмотр всех голосований.
- Метод: GET
- Авторизация: не требуется
- Возвращает: JSON со всеми голосованиями и статистикой.
NB: все эндпойнты возвращают текст, если не указано иное
Проект выполнен на Flask (задеплоен на Heroku и работает через gunicorn) с использованием MongoDB (mLab). MongoDB выбрана из-за простоты работы и формата выдачи запросов (словари, которые можно отдавать напрямую в JSON); возможно, реляционная БД была бы лучшим выбором с точки зрения эффективности (позволяет устанавливать связи между таблицами - можно быстрее обновлять информацию о голосах пользователей при изменении голосований), но интереснее было поразбираться в Mongo. Также использовались библиотеки Flask-HTTPAuth для работы с авторизацией и pymongo для работы с MongoDB, библиотека requests для генерации тестовых запросов. Пароли хранятся в БД в виде строки "логин:пароль", закодированной в Base64.
Коллекция votings хранит данные о голосованиях (вопрос, варианты ответов и имя создавшего пользователя для каждого голосования). Коллекция users хранит данные о пользователях: имя, закодированный в base64 пароль и словарь с голосами в формате {voting_id: option}. При составлении статистики по голосованию объекты пользователей в БД опрашиваются по id голосования и формируется суммарная статистика. При изменении вариантов ответа в голосовании у пользователей, выбравших удалённый или изменённый вариант ответа, голос удаляется; при удалении голосования соответствующая запись удаляется у всех пользователей.
- Использование SQL вместо NoSQL со связью полей
- Валидация URL - в API изменяемые эндпойнты содержат только id голосований, которые потом валидируются на сервере в db_client. Это должны быть шестнадцатеричные строки определённой длины.
- Для защиты от SQL инъекций необходимо проверять тип и, возможно, длину передаваемых пользователем данных. Я сделал валидацию только в функции create_voting и решил не раздувать код дальше. Но нужно учитывать, что в текущем состоянии приложение может быть уязвимо для SQL-инъекций (см. например https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection). Кроме того, в идеале в приложение нужно ввести определённые ограничения на количество создаваемых голосований (например, несколько голосований на пользователя в час), чтобы контролировать размер БД.
- Безопасность паролей - очевидно, полноценная реализация должна использовать нереверсивный алгоритм шифрования паролей. Кроме того, данные для доступа к БД нужно хранить в отдельном скрытом конфиге, в коде они исключительно для наглядности и удобства разработки и деплоя.
- ООП отсутствует - приложение довольно простое, не хранит никаких состояний и я не увидел необходимости в создании классов. Возможно, стоит создать класс для клиента БД, чтобы в интерфейсе IDE получать список доступных методов, и инициализировать его при запуске сервера, делая коннект к БД, но других применений для ООП здесь я придумать не смог.
- Записать данные для коннекта к БД в setup.py, запустить файл для создания коллекций votings и users, а также пользователя admin. Это необходимо сделать только один раз для подготовки БД к работе. Пользователь admin может удалять и редактировать любые голосования (пароль: admin).
- Записать данные для коннекта к БД в db_client.py.
- flask run server.py для запуска сервера. При деплое на Heroku этот шаг можно пропустить - сервер стартует автоматически. Опциональные файлы: db_tests.py для заполнения базы голосованиями, случайно сгенерированными пользователями и случайными голосами; http_tests.py для исследования запросов и ответов.
Важно! Проект должен работать на Python 3.6+ из-за использования f-strings.