Этот сервис представляет собой интеграцию с Mattermost для создания и управления голосованиями внутри каналов при помощи slash-команд. Пользователи могут создавать опросы, голосовать, просматривать результаты и управлять жизненным циклом голосований.
- Go 1.24
- Tarantool
- Docker & Docker Compose
- Chi Router
- Mattermost API
- zerolog
- viper
- go-playground/validator
- gomock
- swaggo/swag
git clone https://github.com/mihailpestrikov/vk-test-assignment-mattermost-polls
cd vk-test-assignment-mattermost-polls
Создайте файл .env
в корне проекта со следующим содержимым:
HOST=0.0.0.0
PORT=8080
REQUEST_TIMEOUT=30
GIN_MODE=release
APP_ENV=production
LOG_LEVEL=info
TARANTOOL_HOST=tarantool
TARANTOOL_PORT=3301
TARANTOOL_USER=guest
TARANTOOL_PASS=testpass
TARANTOOL_SPACE_POLLS=polls
TARANTOOL_SPACE_VOTES=votes
MATTERMOST_URL=http://mattermost:8065
MATTERMOST_TOKEN=
MATTERMOST_WEBHOOK_SECRET=
DEFAULT_POLL_DURATION=86600
MAX_OPTIONS=10
(значения MATTERMOST_TOKEN и MATTERMOST_WEBHOOK_SECRET будут заполнены позже)
make dev
- Откройте браузер и перейдите на http://localhost:8065
- Заполните форму регистрации (первый пользователь автоматически становится системным администратором)
- Перейдите в меню (левый верхний угол)
- Выберите "Integrations" - "Bot accounts"
- По подсказке нужно разрешить создание бот-аккаунтов в System Console
- Нажмите "Add bot account", заполните поля, done
- Скопируйте созданный токен
- Добавьте этот токен в файл
.env
- Перейдите в "Slash commands"
- Нажмите "Add Slash command"
- Заполните форму:
- Заголовок: Poll Bot
- Описание: Bot for polls
- Команда: poll
- URL запроса: http://poll-bot:8080/command
- Метод: POST
- Сохраните команду и скопируйте созданный токен
- Добавьте этот токен в файл
.env
make stop
make dev
Теперь вы можете использовать следующие команды в канале Mattermost, в котором добавлен бот:
/poll create "Вопрос" "Вариант1" "Вариант2" "Вариант3"
- создание голосования/poll vote [poll_id] [option_index]
- голосование (индексы вариантов начинаются с 1)/poll results [poll_id]
- просмотр текущих результатов/poll end [poll_id]
- завершение голосования/poll delete [poll_id]
- удаление голосования/poll info [poll_id]
- получение информации о голосовании/poll help
- получение справки
Команда:
/poll create "Какой язык программирования лучше для микросервисов?" "Go" "Java" "Python" "Rust" --duration=86600
Команда:
/poll vote 5fa3d8e6-7b21-4f4a-9c5e-b7d58c9874a2 1
Вывод (виден только проголосовавшему):
Команда:
/poll results 5fa3d8e6-7b21-4f4a-9c5e-b7d58c9874a2
Вывод (для автора голосования - виден всем, для участника - только проголосовавшему):
Команда:
/poll end 5fa3d8e6-7b21-4f4a-9c5e-b7d58c9874a2
Команда:
/poll info 5fa3d8e6-7b21-4f4a-9c5e-b7d58c9874a2
Команда:
/poll delete 5fa3d8e6-7b21-4f4a-9c5e-b7d58c9874a2
Вывод (виден только создателю):
Команда:
/poll help
Вывод:
Available commands:
/poll create "Question" "Option 1" "Option 2" [--duration=86600]
Create a new poll with specified options and optional duration in seconds
/poll vote POLL_ID OPTION_NUMBER
Vote for an option in the specified poll
/poll results POLL_ID
Show current results of the poll
/poll end POLL_ID
End the poll and show final results (only creator can end)
/poll delete POLL_ID
Delete the poll (only creator can delete)
/poll info POLL_ID
Show detailed information about the poll
/poll help
Show this help message
Бот реализует механизм автоматического завершения голосований, срок действия которых истек. Это достигается с помощью фонового процесса, работающего в отдельной горутине:
func (s *PollService) StartPollWatcher(ctx context.Context) {
go func() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.FinishExpiredPolls()
case <-ctx.Done():
return
}
}
}()
}
Процесс запускается при старте приложения и каждую минуту проверяет наличие голосований с истекшим сроком. Когда такие голосования обнаруживаются, их статус автоматически изменяется на "CLOSED", и пользователи больше не могут в них голосовать.
В системе используется soft delete для голосований. Когда пользователь удаляет голосование, оно не удаляется физически из базы данных, а помечается статусом "DELETED". Для периодической очистки таких записей реализован специальный механизм:
func (s *PollService) StartPollCleaner(ctx context.Context) {
go func() {
ticker := time.NewTicker(24 * time.Hour) // Запуск раз в сутки
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.repo.PurgeDeletedPolls(30 * 24 * time.Hour) // Удаление записей старше 30 дней
case <-ctx.Done():
return
}
}
}()
}
Этот процесс запускается один раз в сутки и удаляет из базы данных голосования, которые были помечены как удаленные более 30 дней назад. Такой подход позволяет:
- Сохранять возможность восстановления недавно удаленных голосований
- Предотвращать неограниченный рост базы данных
.
├── cmd # Точки входа приложения
│ └── pollbot # Основной сервис
├── docker # Файлы для Docker контейнеров
│ ├── bot # Dockerfile для сервиса
│ └── tarantool # Dockerfile и скрипты для Tarantool
├── docs # Документация API (Swagger)
├── internal # Внутренние пакеты
│ ├── api # HTTP обработчики
│ │ └── dto # Объекты передачи данных
│ ├── model # Бизнес-модели
│ ├── repository # Слой доступа к данным
│ └── service # Бизнес-логика
├── pkg # Повторно используемые пакеты
│ ├── config # Конфигурация приложения
│ ├── logger # Логирование
│ └── mattermost # Интеграция с Mattermost
└── Makefile # Команды для управления проектом
Покрытие тестами:
Пакет | Покрытие |
---|---|
internal/model | 96.4% |
internal/service | 87.3% |
pkg/mattermost | 83.4% |
internal/api | 67.3% |
# Сборка Docker-образов
make build
# Сборка только образа бота
make build-bot
# Запуск только бота и Tarantool
make run
# Запуск с Mattermost для разработки
make dev
# Остановка всех контейнеров
make stop
# Полная очистка
make clean
# Запуск тестов с отчетом о покрытии
make test-cover
# Запуск линтера
make lint