Skip to content

Commit

Permalink
fix: README.md, add rabbitmq publisher for feedbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
WildEgor committed May 5, 2024
1 parent b9e9f5f commit 4b957d2
Show file tree
Hide file tree
Showing 34 changed files with 348 additions and 26 deletions.
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ dist
vendor
docker-compose.yml
.air.toml
.env
.env
.env.docker
.env.example
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ TELEGRAM_BOT_TOKEN=
TELEGRAM_BOT_PREFIX=eshop_support_bot

DEFAULT_LOCALE=ru-RU

PUBLISHER_TOPIC=collector
PUBLISHER_ADDR=amqp://guest:[email protected]:5672/
PUBLISHER_TYPE=rabbitmq
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ tmp
# Dependency directories (remove the comment below to include it)
# vendor/

.env
.env
.env.docker
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ Support agents can receive feedback gathered for analytics.

## Features

- **Question Submission**: Users can submit questions through the Telegram bot interface;
- **Question Processing**: Incoming questions stored in a database and processed by support group;
- **Analytics**: Optionally collects statistics like feedbacks for insights into user behavior;
- [x] **Question Submission**: Users can submit questions through the Telegram bot interface;
- [x] **Question Processing**: Incoming questions stored in a database and processed by support group;
- [ ] **Analytics**: Optionally collects statistics like feedbacks for insights into user behavior;

## Requirements

- [Git](http://git-scm.com/)
- [Go >= 1.22](https://go.dev/dl/)
- [Docker](https://www.docker.com/products/docker-desktop/)
- [Postgres and Redis](https://github.com/WildEgor/e-shop-dot/blob/develop/docker-compose.yaml)
- [Postgres]()
- [Redis]()
- [Optional. Clickhouse]()

## Quick start

1. Создайте бота с помощью [BotFather](https://t.me/BotFather) ([video instruction](https://www.youtube.com/watch?v=UQrcOj63S2o))
For example, [eShopSupport](t.me/eshop_support_bot)
2. Run application locally
Clone repository, modify environment and run it
1. Create bot using [BotFather](https://t.me/BotFather) ([video instruction](https://www.youtube.com/watch?v=UQrcOj63S2o)). For example, [eShopSupport](t.me/eshop_support_bot);
2. Run application locally. Clone repository, modify environment and run it
```shell
git pull https://github.com/WildEgor/e-shop-support-bot &&
cd e-shop-support-bot &&
Expand All @@ -52,18 +52,26 @@ or using Docker
```shell
docker-compose up bot
```
Please, use [this](https://github.com/WildEgor/e-shop-dot/blob/develop/docker-compose.yaml) config if you want run Postgres, Redis and Clickhouse

3. Create group in Telegram, add bot and make it admin. Next you can check saved group id using redis-cli
3. Create Telegram group, add bot and make it admin. Next you can check saved group id using redis-cli
![img.png](assets/img.png)

In a support group, you will receive "tickets" with user questions. Group users can "accept" or "reject" a ticket. If a ticket is "rejected," a new one can be created.
![img.png](assets/img1.png)
![img.png](assets/img_1.png)


If the ticket is "accepted," users can discuss the issue through messages to the bot. Messages "duplicate" because I'm testing the functionality with the same account (pay attention to the sender of the message).
![img.png](assets/img2.png)
![img.png](assets/img_2.png)
![img.png](assets/img_3.png)
![img.png](assets/img_4.png)
![img.png](assets/img_5.png)

You can find "tickets" in the database table ```public.topics```
![img.png](assets/img3.png)
![img.png](assets/img_6.png)

Optional. If run [this service](https://github.com/WildEgor/e-shop-collector) you can find saved events in Clickhouse
![img.png](assets/img_7.png)

## Contributing

Expand Down
Binary file removed assets/img1.png
Binary file not shown.
Binary file removed assets/img2.png
Binary file not shown.
Binary file removed assets/img3.png
Binary file not shown.
Binary file added assets/img_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img_7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
version: "3.8"

volumes:
pg_data:
redis_data:

services:
bot:
hostname: bot-dev
Expand All @@ -12,7 +16,7 @@ services:
dockerfile: Dockerfile
target: dev
env_file:
- .env
- .env.docker
volumes:
- ./:/app
ports:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/testcontainers/testcontainers-go v0.30.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.30.0
github.com/testcontainers/testcontainers-go/modules/redis v0.30.0
github.com/wagslane/go-rabbitmq v0.13.0
)

require (
Expand Down Expand Up @@ -78,6 +79,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rabbitmq/amqp091-go v1.9.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
Expand Down Expand Up @@ -228,6 +230,8 @@ github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7g
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/wagslane/go-rabbitmq v0.13.0 h1:u2JfKbwi3cbxCExKV34RrhKBZjW2HoRwyPTA8pERyrs=
github.com/wagslane/go-rabbitmq v0.13.0/go.mod h1:1sUJ53rrW2AIA7LEp8ymmmebHqqq8ksH/gXIfUP0I0s=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand All @@ -251,6 +255,8 @@ go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lI
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
18 changes: 18 additions & 0 deletions internal/adapters/publisher/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package publisher

func NewPublisher(cfg IPublisherConfigFactory) IEventPublisher {

config := cfg.Config()

switch config.Type {
case PublisherTypeRabbitMQ:
pub, err := NewRabbitPublisher(cfg)
if err != nil {
return nil
}

return pub
default:
return nil
}
}
33 changes: 33 additions & 0 deletions internal/adapters/publisher/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package publisher

import (
"fmt"
"github.com/wagslane/go-rabbitmq"
"log/slog"
)

var _ rabbitmq.Logger = (*PublisherLogger)(nil)

type PublisherLogger struct {
}

func (t PublisherLogger) Fatalf(s string, i ...interface{}) {
slog.Error(fmt.Sprintf(s, i))

Check failure on line 15 in internal/adapters/publisher/logger.go

View workflow job for this annotation

GitHub Actions / lint

printf: missing ... in args forwarded to printf-like function (govet)

Check failure on line 15 in internal/adapters/publisher/logger.go

View workflow job for this annotation

GitHub Actions / lint

printf: missing ... in args forwarded to printf-like function (govet)
panic(fmt.Sprintf(s, i))

Check failure on line 16 in internal/adapters/publisher/logger.go

View workflow job for this annotation

GitHub Actions / lint

printf: missing ... in args forwarded to printf-like function (govet)

Check failure on line 16 in internal/adapters/publisher/logger.go

View workflow job for this annotation

GitHub Actions / lint

printf: missing ... in args forwarded to printf-like function (govet)
}

func (t PublisherLogger) Errorf(s string, i ...interface{}) {
slog.Error(fmt.Sprintf(s, i))

Check failure on line 20 in internal/adapters/publisher/logger.go

View workflow job for this annotation

GitHub Actions / lint

printf: missing ... in args forwarded to printf-like function (govet)
}

func (t PublisherLogger) Warnf(s string, i ...interface{}) {
slog.Warn(fmt.Sprintf(s, i))
}

func (t PublisherLogger) Infof(s string, i ...interface{}) {
slog.Info(fmt.Sprintf(s, i))
}

func (t PublisherLogger) Debugf(s string, i ...interface{}) {
slog.Debug(fmt.Sprintf(s, i))
}
44 changes: 44 additions & 0 deletions internal/adapters/publisher/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package publisher

import "context"

type PublisherType string

const (
PublisherTypeRabbitMQ PublisherType = "rabbitmq"
)

type PublisherConfig struct {
Type PublisherType
Topic string
Addr string
}

type IPublisherConfigFactory interface {
Config() PublisherConfig
}

type Event struct {
Data any
}

type IEventPublisher interface {
Publish(context.Context, string, *Event) error
Close() error
}

type TopicFeedbackEvent struct {
Pattern string `json:"pattern"`
Data TopicFeedbackEventData `json:"data"`
}

type TopicFeedbackEventData struct {
ID string `json:"id"`
TopicID int64 `json:"t_id"`
SupportTelegramID int64 `json:"s_tid"`
SupportTelegramUsername string `json:"s_tun"`
SupportUserID string `json:"s_uid"`
CreatorTelegramID int64 `json:"u_tid"`
CreatorTelegramUsername string `json:"u_tun"`
Rating uint8 `json:"rating"`
}
73 changes: 73 additions & 0 deletions internal/adapters/publisher/rabbit_publisher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package publisher

import (
"context"
"encoding/json"
"fmt"
"github.com/wagslane/go-rabbitmq"
)

var _ IEventPublisher = (*RabbitPublisher)(nil)

// RabbitPublisher represent event publisher for RabbitMQ.
type RabbitPublisher struct {
pt string `wire:"-"`
conn *rabbitmq.Conn `wire:"-"`
publisher *rabbitmq.Publisher `wire:"-"`
}

// NewRabbitPublisher create new RabbitPublisher instance.
func NewRabbitPublisher(cfg IPublisherConfigFactory) (*RabbitPublisher, error) {
conn, err := rabbitmq.NewConn(cfg.Config().Addr)
if err != nil {
return nil, fmt.Errorf("new conn: %w", err)
}

publisher, err := rabbitmq.NewPublisher(
conn,
rabbitmq.WithPublisherOptionsLogger(&PublisherLogger{}),
rabbitmq.WithPublisherOptionsLogging,
rabbitmq.WithPublisherOptionsExchangeName(cfg.Config().Topic),
rabbitmq.WithPublisherOptionsExchangeDeclare,
rabbitmq.WithPublisherOptionsExchangeKind("topic"),
rabbitmq.WithPublisherOptionsExchangeDurable,
)
if err != nil {
return nil, fmt.Errorf("publisher: %w", err)
}

return &RabbitPublisher{
cfg.Config().Topic,
conn,
publisher,
}, nil
}

// Publish send events, implements eventPublisher.
func (p *RabbitPublisher) Publish(ctx context.Context, topic string, event *Event) error {
body, err := json.Marshal(event.Data)
if err != nil {
return err
}

return p.publisher.PublishWithContext(
ctx,
body,
[]string{""},
rabbitmq.WithPublishOptionsContentEncoding("utf-8"),
rabbitmq.WithPublishOptionsExchange(topic),
rabbitmq.WithPublishOptionsContentType("application/json"),
rabbitmq.WithPublishOptionsExchange(p.pt),
)
}

// Close represent finalization for RabbitMQ publisher.
func (p *RabbitPublisher) Close() error {
if err := p.conn.Close(); err != nil {
return fmt.Errorf("connection close: %w", err)
}

p.publisher.Close()

return nil
}
3 changes: 3 additions & 0 deletions internal/adapters/wire.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package adapters

import (
"github.com/WildEgor/e-shop-support-bot/internal/adapters/publisher"
"github.com/WildEgor/e-shop-support-bot/internal/adapters/telegram"
"github.com/google/wire"
)

var AdaptersSet = wire.NewSet(
telegram.NewTelegramBotAdapter,
telegram.NewTelegramListener,
publisher.NewRabbitPublisher,
wire.Bind(new(publisher.IEventPublisher), new(*publisher.RabbitPublisher)),
)
12 changes: 12 additions & 0 deletions internal/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
slogger "github.com/WildEgor/e-shop-gopack/pkg/libs/logger/handlers"
"github.com/WildEgor/e-shop-gopack/pkg/libs/logger/models"
"github.com/WildEgor/e-shop-support-bot/internal/adapters"
"github.com/WildEgor/e-shop-support-bot/internal/adapters/publisher"
"github.com/WildEgor/e-shop-support-bot/internal/adapters/telegram"
"github.com/WildEgor/e-shop-support-bot/internal/configs"
eh "github.com/WildEgor/e-shop-support-bot/internal/handlers/errors"
Expand All @@ -30,6 +31,7 @@ var AppSet = wire.NewSet(
type Server struct {
App *fiber.App
Bot *telegram.TelegramListener
Publisher publisher.IEventPublisher
AppConfig *configs.AppConfig
}

Expand All @@ -54,6 +56,14 @@ func (srv *Server) Shutdown() {
Err: err,
}))
}

slog.Debug("shutdown publisher")
err := srv.Publisher.Close()
if err != nil {
slog.Error("unable to shutdown publisher.", models.LogEntryAttr(&models.LogEntry{
Err: err,
}))
}
}

func NewApp(
Expand All @@ -64,6 +74,7 @@ func NewApp(
br *router.BotRouter,
sr *router.SwaggerRouter,
bot *telegram.TelegramListener,
ps *publisher.RabbitPublisher,
pc *configs.PostgresConfig,
) *Server {
logger := slogger.NewLogger(
Expand Down Expand Up @@ -106,6 +117,7 @@ func NewApp(
return &Server{
App: app,
Bot: bot,
Publisher: ps,
AppConfig: ac,
}
}
2 changes: 1 addition & 1 deletion internal/configs/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"log/slog"
)

// AppConfig holds the main app configurations
// PublisherConfig holds the main app configurations
type AppConfig struct {
Name string `env:"APP_NAME" envDefault:"app"`
Port string `env:"APP_PORT" envDefault:"8888"`
Expand Down
Loading

0 comments on commit 4b957d2

Please sign in to comment.