diff --git a/.gitignore b/.gitignore index ded3018..3bf780b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ .idea -.env -config/config.yaml - -deploy/deploy.config \ No newline at end of file +.env \ No newline at end of file diff --git a/.golangci.pipeline.yaml b/.golangci.pipeline.yaml new file mode 100644 index 0000000..c24a6a3 --- /dev/null +++ b/.golangci.pipeline.yaml @@ -0,0 +1,51 @@ +# More info on config here: https://golangci-lint.run/usage/configuration/#config-file +run: + concurrency: 8 + timeout: 10m + issues-exit-code: 1 + tests: true + issues: + exclude-files: + - \.pb\.go$ + - \.pb\.gw\.go$ + exclude-dirs: + - bin + - vendor + - var + - tmp + - .cache + +output: + formats: colored-line-number + print-issued-lines: true + print-linter-name: true + +linters-settings: + govet: + shadow: true + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 2 + +linters: + disable-all: true + enable: + - errcheck + - goconst + - goimports + - gosec + - govet + - ineffassign + - revive + - typecheck + - unused + +issues: + exclude-use-default: false + exclude: + - G104 # _ instead of err checks + - exported func .* returns unexported type .*, which can be annoying to use + - should have a package comment + - don't use an underscore in package name \ No newline at end of file diff --git a/Makefile b/Makefile index 97d8afc..6251c05 100644 --- a/Makefile +++ b/Makefile @@ -4,27 +4,32 @@ DOCKER_COMPOSE ?= docker compose -f docker-compose.yml -include .env -ENV ?= dev - -ifdef ENV - ifneq "$(ENV)" "" - ifneq ("$(wildcard docker-compose.$(ENV).yml)","") - DOCKER_COMPOSE := $(DOCKER_COMPOSE) -f docker-compose.$(ENV).yml - endif - endif -endif - export GOOS=linux export GOARCH=amd64 help: ## Help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +fmt: ## Automatically format source code + go fmt ./... +.PHONY:fmt + +lint: fmt ## Check code (lint) + golangci-lint run ./... --config .golangci.pipeline.yaml +.PHONY:lint + +vet: fmt ## Check code (vet) + go vet -vettool=$(which shadow) ./... +.PHONY:vet + +vet-shadow: fmt ## Check code with detect shadow (vet) + go vet -vettool=$(which shadow) ./... +.PHONY:vet + build: ## Build service containers $(DOCKER_COMPOSE) build -up: ## Start services +up: vet ## Start services $(DOCKER_COMPOSE) up -d $(SERVICES) down: ## Down services diff --git a/cmd/notifier/notifier.go b/cmd/notifier/notifier.go index 48719ee..78deee2 100644 --- a/cmd/notifier/notifier.go +++ b/cmd/notifier/notifier.go @@ -2,18 +2,19 @@ package main import ( "context" - "github.com/go-kit/kit/log" - _ "github.com/lib/pq" "net/http" "os" - "tgtime-notifier/internal/background" - "tgtime-notifier/internal/config" - kafkaLib "tgtime-notifier/internal/kafka" - "tgtime-notifier/internal/notifier/telegram" "time" + + "github.com/go-kit/kit/log" + _ "github.com/lib/pq" + "github.com/robertobadjio/tgtime-notifier/internal/background" + "github.com/robertobadjio/tgtime-notifier/internal/config" + kafkaLib "github.com/robertobadjio/tgtime-notifier/internal/kafka" + "github.com/robertobadjio/tgtime-notifier/internal/notifier/telegram" ) -const CheckSecondsInOffice = 10 +const сheckSecondsInOffice = 10 func main() { var logger log.Logger @@ -31,6 +32,7 @@ func main() { updates := tgNotifier.GetBot().ListenForWebhook("/" + cfg.WebHookPath) go func() { + // TODO: timeouts https://kovardin.ru/articles/go/rukovodstvo-po-nethttp-taimautam-v-go/ err := http.ListenAndServe(":8441", nil) // TODO: const if err != nil { _ = logger.Log("telegram", "updates", "type", "serve", "msg", err) @@ -49,7 +51,7 @@ func startCheckInOffice( ctx context.Context, cfg *config.Config, logger log.Logger, - tgNotifier *telegram.TelegramNotifier, + tgNotifier *telegram.Notifier, ) { f := func() { kafka := kafkaLib.NewKafka(logger, cfg.KafkaHost, cfg.KafkaPort) @@ -58,7 +60,7 @@ func startCheckInOffice( _ = logger.Log("kafka", "consume", "type", "in office message", "msg", err) } } - bc := background.NewBackground(time.Duration(CheckSecondsInOffice)*time.Second, f) + bc := background.NewBackground(time.Duration(сheckSecondsInOffice)*time.Second, f) bc.Start() } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index 08c7d8d..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,45 +0,0 @@ -services: - tgtime-notifier-app: - depends_on: - tgtime-api: - condition: service_healthy - tgtime-api: - build: - context: . - dockerfile: ./docker/tgtime-api/Dockerfile - ports: - - "1080:1080" - environment: - MOCKSERVER_INITIALIZATION_JSON_PATH: /config/initializerJson.json - MOCKSERVER_LOG_LEVEL: INFO - MOCKSERVER_PROPERTY_FILE: /config/mockserver.properties - volumes: - - ./docker/tgtime-api/config:/config - - ./docker/tgtime-api/config/mockserver.properties:/config/mockserver.properties - networks: - - tgtime-notifier-network - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:1080/liveness/probe"] - interval: 10s - timeout: 10s - retries: 5 - zookeeper: - image: confluentinc/cp-zookeeper:latest - environment: - - ZOOKEEPER_CLIENT_PORT=${ZOOKEEPER_PORT} - networks: - - tgtime-notifier-network - kafka: - image: confluentinc/cp-kafka:latest - container_name: kafka - depends_on: - - zookeeper - environment: - - KAFKA_ZOOKEEPER_CONNECT=zookeeper:${ZOOKEEPER_PORT} - - KAFKA_LOG_RETENTION_MS=10000 - - KAFKA_CLEANUP_POLICY=delete - - KAFKA_CLEANUP_ENABLE=true - - KAFKA_LOG_RETENTION_CHECK_INTERVAL_MS=5000 - - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:${ZOOKEEPER_PORT} - networks: - - tgtime-notifier-network \ No newline at end of file diff --git a/docker-compose.test.yml b/docker-compose.local.yml similarity index 100% rename from docker-compose.test.yml rename to docker-compose.local.yml diff --git a/docker-compose.yml b/docker-compose.yml index a22044d..586ec34 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,5 @@ services: tgtime-notifier-app: - image: ghcr.io/tgtime-notifier:master build: context: . dockerfile: ./docker/app/Dockerfile @@ -9,6 +8,49 @@ services: - "1122:8080" networks: - tgtime-notifier-network + depends_on: + tgtime-api: + condition: service_healthy + tgtime-api: + build: + context: . + dockerfile: ./docker/tgtime-api/Dockerfile + ports: + - "1080:1080" + environment: + MOCKSERVER_INITIALIZATION_JSON_PATH: /config/initializerJson.json + MOCKSERVER_LOG_LEVEL: INFO + MOCKSERVER_PROPERTY_FILE: /config/mockserver.properties + volumes: + - ./docker/tgtime-api/config:/config + - ./docker/tgtime-api/config/mockserver.properties:/config/mockserver.properties + networks: + - tgtime-notifier-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:1080/liveness/probe"] + interval: 10s + timeout: 10s + retries: 5 + zookeeper: + image: confluentinc/cp-zookeeper:latest + environment: + - ZOOKEEPER_CLIENT_PORT=${ZOOKEEPER_PORT} + networks: + - tgtime-notifier-network + kafka: + image: confluentinc/cp-kafka:latest + container_name: kafka + depends_on: + - zookeeper + environment: + - KAFKA_ZOOKEEPER_CONNECT=zookeeper:${ZOOKEEPER_PORT} + - KAFKA_LOG_RETENTION_MS=10000 + - KAFKA_CLEANUP_POLICY=delete + - KAFKA_CLEANUP_ENABLE=true + - KAFKA_LOG_RETENTION_CHECK_INTERVAL_MS=5000 + - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:${ZOOKEEPER_PORT} + networks: + - tgtime-notifier-network networks: tgtime-notifier-network: diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index 8c94b43..aa96ad3 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine AS build +FROM golang:1.23-alpine AS build RUN apk update && apk add tzdata diff --git a/go.mod b/go.mod index b7ddd97..a5585a2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module tgtime-notifier +module github.com/robertobadjio/tgtime-notifier -go 1.22.0 +go 1.23.0 require ( github.com/go-kit/kit v0.13.0 diff --git a/internal/aggregator/aggregator.go b/internal/aggregator/aggregator.go index 0c9d5a3..0cb1429 100644 --- a/internal/aggregator/aggregator.go +++ b/internal/aggregator/aggregator.go @@ -3,64 +3,66 @@ package aggregator import ( "context" "fmt" - "github.com/go-kit/kit/log" - pb "github.com/robertobadjio/tgtime-aggregator/api/v1/pb/aggregator" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" - "tgtime-notifier/internal/config" - "time" + + "github.com/go-kit/kit/log" + pb "github.com/robertobadjio/tgtime-aggregator/api/v1/pb/aggregator" + "github.com/robertobadjio/tgtime-notifier/internal/config" ) +// Client gRPC-клиент для подключения к сервису Агрегатор type Client struct { cfg *config.Config logger log.Logger + client pb.AggregatorClient } +// NewClient Конструктор gRPC-клиента для подключения к сервису Агрегатор func NewClient(cfg config.Config, logger log.Logger) *Client { - return &Client{cfg: &cfg, logger: logger} -} - -func (tc Client) GetTimeSummary(ctx context.Context, macAddress, date string) (*pb.GetTimeSummaryResponse, error) { - client, err := grpc.NewClient( - tc.buildAddress(), + conn, _ := grpc.NewClient( + buildAddress(cfg.TgTimeAPIHost, cfg.TgTimeAPIPort), grpc.WithTransportCredentials(insecure.NewCredentials()), ) - if err != nil { - return nil, fmt.Errorf("could not connect: %v", err) - } - defer func() { _ = client.Close() }() - timeAggregatorClient := pb.NewAggregatorClient(client) - ctxTemp, cancel := context.WithTimeout(ctx, 120*time.Second) - defer cancel() + return &Client{cfg: &cfg, logger: logger, client: pb.NewAggregatorClient(conn)} +} +// GetTimeSummary Получение времени сотрудника +func (tc Client) GetTimeSummary( + ctx context.Context, + macAddress, date string, +) (*pb.GetTimeSummaryResponse, error) { filters := make([]*pb.Filter, 0, 2) filters = append(filters, &pb.Filter{Key: "mac_address", Value: macAddress}) filters = append(filters, &pb.Filter{Key: "date", Value: date}) - timeSummary, err := timeAggregatorClient.GetTimeSummary( - ctxTemp, + timeSummary, err := tc.client.GetTimeSummary( + ctx, &pb.GetTimeSummaryRequest{Filters: filters}, ) if err != nil { - if s, ok := status.FromError(err); ok { - // Handle the error based on its status code - if s.Code() == codes.NotFound { - return nil, fmt.Errorf("requested resource not found") - } else { - return nil, fmt.Errorf("RPC error: %v, %v", s.Message(), ctxTemp.Err()) - } - } else { - // Handle non-RPC errors - return nil, fmt.Errorf("Non-RPC error: %v", err) - } + return nil, handleError(ctx, err) } return timeSummary, nil } -func (tc Client) buildAddress() string { - return fmt.Sprintf("%s:%s", tc.cfg.TgTimeAggregatorHost, tc.cfg.TgTimeAggregatorPort) +func handleError(ctx context.Context, err error) error { + if s, ok := status.FromError(err); ok { + // Handle the error based on its status code + if s.Code() == codes.NotFound { + return fmt.Errorf("requested resource not found") + } + return fmt.Errorf("RPC error: %v, %v", s.Message(), ctx.Err()) + } + + return fmt.Errorf("Non-RPC error: %v", err) +} + +func buildAddress(host, port string) string { + return fmt.Sprintf("%s:%s", host, port) } diff --git a/internal/api_pb/api.go b/internal/api_pb/api.go index f3e0bb1..68b0877 100644 --- a/internal/api_pb/api.go +++ b/internal/api_pb/api.go @@ -3,103 +3,79 @@ package api_pb import ( "context" "fmt" - "github.com/go-kit/kit/log" - pb "github.com/robertobadjio/tgtime-api/api/v1/pb/api" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" - "tgtime-notifier/internal/config" - "time" + + "github.com/go-kit/kit/log" + pb "github.com/robertobadjio/tgtime-api/api/v1/pb/api" + "github.com/robertobadjio/tgtime-notifier/internal/config" ) +// Client GRPC-клиент для получения пользователя из API-микросервиса type Client struct { cfg *config.Config logger log.Logger + client pb.ApiClient } +// NewClient Конструктор GRPC-клиента для получения пользователя из API-микросервиса func NewClient(cfg config.Config, logger log.Logger) *Client { - return &Client{cfg: &cfg, logger: logger} -} - -func (tc Client) GetUserByTelegramId(ctx context.Context, telegramId int64) (*pb.GetUserByTelegramIdResponse, error) { - client, err := grpc.NewClient( - tc.buildAddress(), + conn, _ := grpc.NewClient( + buildAddress(cfg.TgTimeAPIHost, cfg.TgTimeAPIPort), grpc.WithTransportCredentials(insecure.NewCredentials()), ) - if err != nil { - return nil, fmt.Errorf("could not connect: %v", err) - } - defer func() { _ = client.Close() }() - - apiClient := pb.NewApiClient(client) - ctxTemp, cancel := context.WithTimeout(ctx, 120*time.Second) - defer cancel() + return &Client{cfg: &cfg, logger: logger, client: pb.NewApiClient(conn)} +} - user, err := apiClient.GetUserByTelegramId( - ctxTemp, - &pb.GetUserByTelegramIdRequest{TelegramId: telegramId}, +// GetUserByTelegramID Получение пользователя по telegram ID +func (tc Client) GetUserByTelegramID( + ctx context.Context, + telegramID int64, +) (*pb.GetUserByTelegramIdResponse, error) { + user, err := tc.client.GetUserByTelegramId( + ctx, + &pb.GetUserByTelegramIdRequest{TelegramId: telegramID}, ) - - fmt.Println(err) - if err != nil { - if s, ok := status.FromError(err); ok { - // Handle the error based on its status code - if s.Code() == codes.NotFound { - return nil, fmt.Errorf("requested resource not found") - } else { - return nil, fmt.Errorf("RPC error: %v, %v", s.Message(), ctxTemp.Err()) - } - } else { - // Handle non-RPC errors - return nil, fmt.Errorf("Non-RPC error: %v", err) - } + return nil, handleError(ctx, err) } return user, nil } -func (tc Client) GetUserByMacAddress(ctx context.Context, macAddress string) (*pb.GetUserByMacAddressResponse, error) { - client, err := grpc.NewClient( - tc.buildAddress(), - grpc.WithTransportCredentials(insecure.NewCredentials()), +// GetUserByMacAddress Получение пользователя по MAC-адресу +func (tc Client) GetUserByMacAddress( + ctx context.Context, + macAddress string, +) (*pb.GetUserByMacAddressResponse, error) { + user, err := tc.client.GetUserByMacAddress( + ctx, + &pb.GetUserByMacAddressRequest{MacAddress: macAddress}, ) if err != nil { - return nil, fmt.Errorf("could not connect: %v", err) + return nil, handleError(ctx, err) } - defer func() { _ = client.Close() }() - - apiClient := pb.NewApiClient(client) - ctxTemp, cancel := context.WithTimeout(ctx, 120*time.Second) - defer cancel() - - user, err := apiClient.GetUserByMacAddress( - ctxTemp, - &pb.GetUserByMacAddressRequest{MacAddress: macAddress}, - ) - fmt.Println(err) + return user, nil +} - if err != nil { - if s, ok := status.FromError(err); ok { - // Handle the error based on its status code - if s.Code() == codes.NotFound { - return nil, fmt.Errorf("requested resource not found") - } else { - return nil, fmt.Errorf("RPC error: %v, %v", s.Message(), ctxTemp.Err()) - } - } else { - // Handle non-RPC errors - return nil, fmt.Errorf("Non-RPC error: %v", err) +func handleError(ctx context.Context, err error) error { + if s, ok := status.FromError(err); ok { + // Handle the error based on its status code + if s.Code() == codes.NotFound { + return fmt.Errorf("requested resource not found") } + return fmt.Errorf("RPC error: %v, %v", s.Message(), ctx.Err()) } - return user, nil + return fmt.Errorf("Non-RPC error: %v", err) } -func (tc Client) buildAddress() string { - return fmt.Sprintf("%s:%s", tc.cfg.TgTimeApiHost, tc.cfg.TgTimeApiPort) +func buildAddress(host, port string) string { + return fmt.Sprintf("%s:%s", host, port) } diff --git a/internal/background/background.go b/internal/background/background.go index 59e0770..4343bb4 100644 --- a/internal/background/background.go +++ b/internal/background/background.go @@ -2,15 +2,18 @@ package background import "time" +// Background Сервис для фонового выполнения функции через определенные интервалы type Background struct { delay time.Duration task func() } +// NewBackground Конструктор сервиса для фонового выполнения функций func NewBackground(delay time.Duration, task func()) *Background { return &Background{delay: delay, task: task} } +// Start Выполнить функцию func (b Background) Start() { ticker := time.NewTicker(b.delay) stop := make(chan struct{}) diff --git a/internal/config/config.go b/internal/config/config.go index 47ea1e6..2a0de4e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,12 +1,14 @@ package config import ( - "github.com/joho/godotenv" "log" "os" "regexp" + + "github.com/joho/godotenv" ) +// Config Конфига приложения type Config struct { BotToken string RouterAddress string @@ -14,15 +16,15 @@ type Config struct { RouterPassword string WebHookPath string WebHookLink string - ApiURL string - ApiMasterEmail string - ApiMasterPassword string + APIURL string + APIMasterEmail string + APIMasterPassword string KafkaHost string KafkaPort string TgTimeAggregatorHost string TgTimeAggregatorPort string - TgTimeApiHost string - TgTimeApiPort string + TgTimeAPIHost string + TgTimeAPIPort string } const projectDirName = "tgtime-notifier" @@ -31,6 +33,7 @@ func init() { loadEnv() } +// New Конструктор конфига приложения func New() *Config { return &Config{ BotToken: getEnv("BOT_TOKEN", ""), @@ -39,15 +42,15 @@ func New() *Config { RouterPassword: getEnv("ROUTER_PASSWORD", ""), WebHookPath: getEnv("WEBHOOK_PATH", ""), WebHookLink: getEnv("WEBHOOK_LINK", ""), - ApiURL: getEnv("API_URL", ""), - ApiMasterEmail: getEnv("API_MASTER_EMAIL", ""), - ApiMasterPassword: getEnv("API_MASTER_PASSWORD", ""), + APIURL: getEnv("API_URL", ""), + APIMasterEmail: getEnv("API_MASTER_EMAIL", ""), + APIMasterPassword: getEnv("API_MASTER_PASSWORD", ""), KafkaHost: getEnv("KAFKA_HOST", ""), KafkaPort: getEnv("KAFKA_PORT", ""), TgTimeAggregatorHost: getEnv("TGTIME_AGGREGATOR_HOST", ""), TgTimeAggregatorPort: getEnv("TGTIME_AGGREGATOR_PORT", ""), - TgTimeApiHost: getEnv("TGTIME_API_HOST", ""), - TgTimeApiPort: getEnv("TGTIME_API_PORT", ""), + TgTimeAPIHost: getEnv("TGTIME_API_HOST", ""), + TgTimeAPIPort: getEnv("TGTIME_API_PORT", ""), } } diff --git a/internal/kafka/kafka.go b/internal/kafka/kafka.go index 6e7a189..af8eeef 100644 --- a/internal/kafka/kafka.go +++ b/internal/kafka/kafka.go @@ -4,35 +4,29 @@ import ( "context" "errors" "fmt" + "io" + "github.com/go-kit/kit/log" + "github.com/robertobadjio/tgtime-notifier/internal/api_pb" + "github.com/robertobadjio/tgtime-notifier/internal/config" + "github.com/robertobadjio/tgtime-notifier/internal/notifier/telegram" kafkaLib "github.com/segmentio/kafka-go" - "io" - "tgtime-notifier/internal/api_pb" - "tgtime-notifier/internal/config" - "tgtime-notifier/internal/notifier/telegram" ) +// Kafka Клиент для подключения к кафке type Kafka struct { logger log.Logger host string port string } +// NewKafka Конструктор клиента func NewKafka(logger log.Logger, host, port string) *Kafka { return &Kafka{logger: logger, host: host, port: port} } -func (k *Kafka) buildReader(topicName string) *kafkaLib.Reader { - return kafkaLib.NewReader(kafkaLib.ReaderConfig{ - Brokers: []string{buildAddress(k.host, k.port)}, - Topic: topicName, - Partition: partition, - GroupID: "", - MaxBytes: 10e3, - }) -} - -func (k *Kafka) ConsumeInOffice(ctx context.Context, tn *telegram.TelegramNotifier) error { +// ConsumeInOffice Чтение сообщений из кафки о приходе сотрудника в офис / на работу +func (k *Kafka) ConsumeInOffice(ctx context.Context, tn *telegram.Notifier) error { r := k.buildReader(inOfficeTopic) defer func() { if err := r.Close(); err != nil { @@ -47,9 +41,9 @@ func (k *Kafka) ConsumeInOffice(ctx context.Context, tn *telegram.TelegramNotifi if err != nil { if errors.Is(err, io.EOF) { break - } else { - return fmt.Errorf("reading message: %w", err) } + + return fmt.Errorf("reading message: %w", err) } //fmt.Printf("message at offset %d: %s = %s\n", m.Offset, string(m.Key), string(m.Value)) //fmt.Printf(string(m.Value)) @@ -72,6 +66,16 @@ func (k *Kafka) ConsumeInOffice(ctx context.Context, tn *telegram.TelegramNotifi return nil } +func (k *Kafka) buildReader(topicName string) *kafkaLib.Reader { + return kafkaLib.NewReader(kafkaLib.ReaderConfig{ + Brokers: []string{buildAddress(k.host, k.port)}, + Topic: topicName, + Partition: partition, + GroupID: "", + MaxBytes: 10e3, + }) +} + func buildAddress(host, port string) string { return host + ":" + port } diff --git a/internal/kafka/message.go b/internal/kafka/message.go index 8e89329..2674796 100644 --- a/internal/kafka/message.go +++ b/internal/kafka/message.go @@ -4,11 +4,11 @@ const inOfficeTopic = "in-office" const previousDayInfoTopic = "previous-day-info" const partition = 0 -type PreviousDayInfoMessage struct { +/*type PreviousDayInfoMessage struct { MacAddress string `json:"mac_address"` Seconds int64 `json:"seconds"` - BreaksJson []byte `json:"breaks"` + BreaksJSON []byte `json:"breaks"` Date string `json:"date"` SecondsStart int64 `json:"seconds_start"` SecondsEnd int64 `json:"seconds_end"` -} +}*/ diff --git a/internal/notifier/notifier.go b/internal/notifier/notifier.go deleted file mode 100644 index b04b5ab..0000000 --- a/internal/notifier/notifier.go +++ /dev/null @@ -1,11 +0,0 @@ -package notifier - -import ( - "context" - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" -) - -type Notifier interface { - SendWelcomeMessage(ctx context.Context, telegramId int64) - SendMessageCommand(ctx context.Context, update tgbotapi.Update) -} diff --git a/internal/notifier/telegram/command/previous_day_info_message.go b/internal/notifier/telegram/command/previous_day_info_message.go index c172546..8d7f1f5 100644 --- a/internal/notifier/telegram/command/previous_day_info_message.go +++ b/internal/notifier/telegram/command/previous_day_info_message.go @@ -14,7 +14,7 @@ func (t *TelegramNotifier) SendPreviousDayInfo( telegramId int64, startTime, endTime time.Time, hours, minutes int, - breaks []*notifier.Break, + breaks []*notifier.timeBreak, ) error { breaksString := breaksToString(buildBreaks(breaks)) message := fmt.Sprintf( diff --git a/internal/notifier/telegram/command/start_command.go b/internal/notifier/telegram/command/start_command.go index 10dfb0f..7c7c12a 100644 --- a/internal/notifier/telegram/command/start_command.go +++ b/internal/notifier/telegram/command/start_command.go @@ -4,8 +4,10 @@ import ( "context" ) +// StartCommand Команда /start type StartCommand struct{} +// GetMessage Метод получения текста сообщения в ответ на команду /start func (StartCommand) GetMessage(_ context.Context) (string, error) { return "Добро пожаловать. Используйте кнопки для получения информации", nil } diff --git a/internal/notifier/telegram/command/unknown_command.go b/internal/notifier/telegram/command/unknown_command.go index 67a0e48..61196f2 100644 --- a/internal/notifier/telegram/command/unknown_command.go +++ b/internal/notifier/telegram/command/unknown_command.go @@ -4,8 +4,10 @@ import ( "context" ) +// UnknownCommand Неизвестная команда type UnknownCommand struct{} +// GetMessage Метод получения текста сообщения о неизвестной команде func (UnknownCommand) GetMessage(_ context.Context) (string, error) { return "Неизвестная команда", nil } diff --git a/internal/notifier/telegram/command/welcome.go b/internal/notifier/telegram/command/welcome.go index f24202f..d153de2 100644 --- a/internal/notifier/telegram/command/welcome.go +++ b/internal/notifier/telegram/command/welcome.go @@ -4,8 +4,10 @@ import ( "context" ) +// WelcomeCommand Сообщение по приходе сотрудника в офис / на работу type WelcomeCommand struct{} +// GetMessage Метод получения текста сообщения по приходе сотрудника в офис / на работу func (WelcomeCommand) GetMessage(_ context.Context) (string, error) { return "Вы пришли в офис", nil } diff --git a/internal/notifier/telegram/command/working_time.go b/internal/notifier/telegram/command/working_time.go index eb5a425..13548cc 100644 --- a/internal/notifier/telegram/command/working_time.go +++ b/internal/notifier/telegram/command/working_time.go @@ -4,24 +4,27 @@ import ( "context" "encoding/json" "fmt" - "github.com/go-kit/kit/log" "os" "strings" - "tgtime-notifier/internal/aggregator" - "tgtime-notifier/internal/api_pb" - "tgtime-notifier/internal/config" "time" + + "github.com/go-kit/kit/log" + "github.com/robertobadjio/tgtime-notifier/internal/aggregator" + "github.com/robertobadjio/tgtime-notifier/internal/api_pb" + "github.com/robertobadjio/tgtime-notifier/internal/config" ) +// WorkingTimeCommand Команда "Рабочее время" type WorkingTimeCommand struct { - TelegramId int64 + TelegramID int64 } -type Break struct { +type timeBreak struct { BeginTime int64 `json:"beginTime"` // TODO: rename StartTime EndTime int64 `json:"endTime"` } +// GetMessage Метод получения текста команды func (wtc WorkingTimeCommand) GetMessage(ctx context.Context) (string, error) { cfg := config.New() @@ -31,7 +34,7 @@ func (wtc WorkingTimeCommand) GetMessage(ctx context.Context) (string, error) { aggregatorClient := aggregator.NewClient(*cfg, logger) apiClient := api_pb.NewClient(*cfg, logger) - user, err := apiClient.GetUserByTelegramId(ctx, wtc.TelegramId) + user, err := apiClient.GetUserByTelegramID(ctx, wtc.TelegramID) if err != nil { return "", fmt.Errorf("error getting user by telegram id: %w", err) } @@ -45,32 +48,31 @@ func (wtc WorkingTimeCommand) GetMessage(ctx context.Context) (string, error) { return "", fmt.Errorf("error getting time summary: %w", err) } if len(timeSummaryResponse.TimeSummary) == 0 { - return "", fmt.Errorf("time summary not found mac address " + user.User.MacAddress + " date " + getNow().Format("2006-01-02")) + return "", fmt.Errorf("time summary not found") } if timeSummaryResponse.TimeSummary[0].SecondsStart == 0 { return "Вы сегодня не были в офисе", nil - } else { - hours, minutes := secondsToHM(int(timeSummaryResponse.TimeSummary[0].Seconds)) - beginTime := time.Unix(timeSummaryResponse.TimeSummary[0].SecondsStart, 0) - mes := fmt.Sprintf( - "Сегодня Вы в офисе с %s\nУчтенное время %d ч. %d м.", - beginTime.Format("15:04"), - hours, - minutes, - ) - - var breaksRaw []*Break - - // TODO: По GRPC отдавать сразу срез - _ = json.Unmarshal([]byte(timeSummaryResponse.TimeSummary[0].GetBreaksJson()), &breaksRaw) - breaks := breaksToString(buildBreaks(breaksRaw)) - if breaks != "" { - mes += fmt.Sprintf("\nПерерывы %s", breaks) - } - - return mes, nil } + + hours, minutes := secondsToHM(int(timeSummaryResponse.TimeSummary[0].Seconds)) + beginTime := time.Unix(timeSummaryResponse.TimeSummary[0].SecondsStart, 0) + mes := fmt.Sprintf( + "Сегодня Вы в офисе с %s\nУчтенное время %d ч. %d м.", + beginTime.Format("15:04"), + hours, + minutes, + ) + + // TODO: По GRPC отдавать сразу срез + var breaksRaw []*timeBreak + _ = json.Unmarshal([]byte(timeSummaryResponse.TimeSummary[0].GetBreaksJson()), &breaksRaw) + breaks := breaksToString(buildBreaks(breaksRaw)) + if breaks != "" { + mes += fmt.Sprintf("\nПерерывы %s", breaks) + } + + return mes, nil } func secondsToHM(seconds int) (int, int) { @@ -89,7 +91,7 @@ func getMoscowLocation() *time.Location { return moscowLocation } -func buildBreaks(breaks []*Break) []string { +func buildBreaks(breaks []*timeBreak) []string { var output []string for _, item := range breaks { beginTime := time.Unix(item.BeginTime, 0) diff --git a/internal/notifier/telegram/factory.go b/internal/notifier/telegram/factory.go index e8b584b..916ded2 100644 --- a/internal/notifier/telegram/factory.go +++ b/internal/notifier/telegram/factory.go @@ -1,30 +1,33 @@ package telegram import ( - "io" - "tgtime-notifier/internal/notifier/telegram/command" + "context" + + "github.com/robertobadjio/tgtime-notifier/internal/notifier/telegram/command" ) -type MessageTypes interface { - MessageType(string) (io.ReadWriteCloser, error) +// Command Интерфейс команды пользователя +type Command interface { + GetMessage(ctx context.Context) (string, error) } +// MessageType Тип сообщения - команда пользователя type MessageType string const ( buttonWorkingTime MessageType = "⏳ Рабочее время" buttonStatCurrentWorkingPeriod MessageType = "🗓 Статистика за рабочий период" buttonStart MessageType = "/start" + welcome MessageType = "welcome" ) -const welcome MessageType = "welcome" - -func NewCommand(t MessageType, telegramId int64) Context { +// NewCommand Фабрика для получения обработчика команды пользователя +func NewCommand(t MessageType, telegramID int64) Command { switch t { case buttonStart: return command.StartCommand{} case buttonWorkingTime: - return command.WorkingTimeCommand{TelegramId: telegramId} + return command.WorkingTimeCommand{TelegramID: telegramID} case welcome: return command.WelcomeCommand{} default: diff --git a/internal/notifier/telegram/strategy.go b/internal/notifier/telegram/strategy.go deleted file mode 100644 index d1994ec..0000000 --- a/internal/notifier/telegram/strategy.go +++ /dev/null @@ -1,17 +0,0 @@ -package telegram - -import ( - "context" -) - -type Context interface { - GetMessage(ctx context.Context) (string, error) -} - -type TypeMessage struct { - Message Context -} - -func (tm *TypeMessage) Handle(ctx context.Context) (string, error) { - return tm.Message.GetMessage(ctx) -} diff --git a/internal/notifier/telegram/telegram.go b/internal/notifier/telegram/telegram.go index b15ccc7..e48b94d 100644 --- a/internal/notifier/telegram/telegram.go +++ b/internal/notifier/telegram/telegram.go @@ -3,17 +3,20 @@ package telegram import ( "context" "fmt" + "github.com/go-kit/kit/log" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" - "tgtime-notifier/internal/config" + "github.com/robertobadjio/tgtime-notifier/internal/config" ) -type TelegramNotifier struct { +// Notifier Telegram-нотификатор +type Notifier struct { logger log.Logger Bot *tgbotapi.BotAPI } -func NewTelegramNotifier(logger log.Logger) *TelegramNotifier { +// NewTelegramNotifier Конструктор для создания Telegram-нотификатора +func NewTelegramNotifier(logger log.Logger) *Notifier { bot, err := initTelegramBot() if err != nil { panic(err) @@ -26,10 +29,11 @@ func NewTelegramNotifier(logger log.Logger) *TelegramNotifier { panic(err) } - return &TelegramNotifier{logger: logger, Bot: bot} + return &Notifier{logger: logger, Bot: bot} } -func (tn *TelegramNotifier) GetBot() *tgbotapi.BotAPI { +// GetBot Получение Telegram Bot API +func (tn *Notifier) GetBot() *tgbotapi.BotAPI { return tn.Bot } @@ -62,8 +66,7 @@ func setWebhook(bot *tgbotapi.BotAPI) error { return nil } -// Keyboard -func (*TelegramNotifier) SetKeyboard(message tgbotapi.MessageConfig) tgbotapi.MessageConfig { +func (*Notifier) setKeyboard(message tgbotapi.MessageConfig) tgbotapi.MessageConfig { message.ReplyMarkup = tgbotapi.NewReplyKeyboard( tgbotapi.NewKeyboardButtonRow( tgbotapi.NewKeyboardButton(string(buttonWorkingTime)), @@ -73,28 +76,43 @@ func (*TelegramNotifier) SetKeyboard(message tgbotapi.MessageConfig) tgbotapi.Me return message } -func (tn *TelegramNotifier) SendMessageCommand(ctx context.Context, update tgbotapi.Update) error { +// SendMessageCommand Метод для отправки сообщения в ответ на команду пользователя +func (tn *Notifier) SendMessageCommand(ctx context.Context, update tgbotapi.Update) error { if update.Message == nil { return fmt.Errorf("telegram message is empty") } + command := NewCommand(MessageType(update.Message.Text), int64(update.Message.From.ID)) - messageHandler := TypeMessage{Message: command} - stringMessage, err := messageHandler.Handle(ctx) - _, err = tn.Bot.Send(tn.SetKeyboard(tgbotapi.NewMessage( + stringMessage, err := command.GetMessage(ctx) + if err != nil { + return fmt.Errorf("error getting text message: %w", err) + } + _, err = tn.Bot.Send(tn.setKeyboard(tgbotapi.NewMessage( int64(update.Message.From.ID), stringMessage, ))) - return fmt.Errorf("error send telegram message: %w", err) + if err != nil { + return fmt.Errorf("error send telegram message: %w", err) + } + + return nil } -func (tn *TelegramNotifier) SendWelcomeMessage(ctx context.Context, telegramId int64) error { - command := NewCommand(welcome, telegramId) +// SendWelcomeMessage Метод отправки приветственного сообщения по приходу в офис / на работу. +func (tn *Notifier) SendWelcomeMessage(ctx context.Context, telegramID int64) error { + command := NewCommand(welcome, telegramID) stringMessage, err := command.GetMessage(ctx) - _, err = tn.Bot.Send(tn.SetKeyboard(tgbotapi.NewMessage( - telegramId, + if err != nil { + return fmt.Errorf("error send welcome message: %w", err) + } + _, err = tn.Bot.Send(tn.setKeyboard(tgbotapi.NewMessage( + telegramID, stringMessage, ))) + if err != nil { + return fmt.Errorf("error send welcome message: %w", err) + } - return fmt.Errorf("error send telegram welcome message: %w", err) + return nil }