wip: init

This commit is contained in:
Pavel 2024-05-07 21:49:09 +03:00
commit 37da2aea5f
33 changed files with 1182 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{yaml,yml,json}]
indent_size = 2
[{*.md,go.mod,go.sum}]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

3
.env.dist Normal file
View File

@ -0,0 +1,3 @@
POKER_BOT_POSTGRES_DSN=postgres://app:app@db:5432/app?sslmode=disable
POKER_BOT_TELEGRAM_TOKEN=token
POKER_BOT_DEBUG=false

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
build/
!build/.gitkeep
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
# Go workspace file
go.work
.idea/
# Env files
.env

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2024 Pavel Kovalenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
Makefile Normal file
View File

@ -0,0 +1,30 @@
SHELL = /bin/bash -o pipefail
export PATH := $(shell go env GOPATH)/bin:$(PATH)
ROOT_DIR=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
BIN=$(ROOT_DIR)/build/vegapokerbot
GO_VERSION=$(shell go version | sed -e 's/go version //')
BIN_DIR=$(ROOT_DIR)/build
build: deps fmt
@echo "> building with ${GO_VERSION}"
@CGO_ENABLED=0 go build -buildvcs=false -tags=release -o $(BIN) .
@echo $(BIN)
run:
@${BIN} run
fmt:
@echo "> fmt"
@gofmt -l -s -w `go list -buildvcs=false -f '{{.Dir}}' ${ROOT_DIR}/... | grep -v /vendor/`
deps:
@echo "> deps"
@go mod tidy
@go mod vendor
.PHONY:
clean:
@rm -rf $(BIN_DIR)
@mkdir -p $(BIN_DIR)
@touch $(BIN_DIR)/.gitkeep
@rm -rf coverage.out test-report.{txt,xml}

14
README.md Normal file
View File

@ -0,0 +1,14 @@
# Vega Poker Bot
Vega Poker Bot is a Telegram-based Scrum Poker tool that allows for effective estimation of tasks in Agile development methodologies.
## Features
- Scrum Poker: Create poker polls with ease using the bot's intuitive interface. Select cards (0 - 10 - ?) to vote on tasks.
- Group Management: Specify which group members can participate in voting decisions for each conversation.
- Redmine Integration: Enable integration via API key for seamless task project, Sprint, and tracker retrieval from Redmine.
## How to Configure the Bot
1. Add the bot to your group chat.
2. Start a conversation with Vega Poker Bot and configure the group by specifying which members can vote in poker polls.
3. Configure settings as desired, including enabling Redmine integration via API key for pulling task project, Sprint, and tracker details from Redmine.
4. Start the poll at any time using inline command `@vegapokerbot <task>`

11
cmd/run.go Normal file
View File

@ -0,0 +1,11 @@
package cmd
import (
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/app"
)
type RunCommand struct{}
func (r *RunCommand) Execute(args []string) error {
return (&app.App{}).Start()
}

31
compose.yml Normal file
View File

@ -0,0 +1,31 @@
services:
db:
image: postgres:latest
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DATABASE: app
ports:
- ${POSTGRES_ADDRESS:-127.0.0.1:5432}:${POSTGRES_PORT:-5432}
networks:
- default
app:
image: "neur0toxine/golang-alpine:1.22"
working_dir: /app
user: ${UID:-1000}:${GID:-1000}
networks:
- default
volumes:
- ./:/app
links:
- db
environment:
GOCACHE: /go
ports:
- ${MG_TELEGRAM_ADDRESS:-8090}:8090
command: make build run
networks:
default:
driver: bridge

53
go.mod Normal file
View File

@ -0,0 +1,53 @@
module gitea.neur0tx.site/Neur0toxine/vegapokerbot
go 1.22.0
require (
github.com/cristalhq/aconfig v0.18.5
github.com/cristalhq/aconfig/aconfigtoml v0.17.1
github.com/cristalhq/aconfig/aconfigyaml v0.17.1
github.com/golang-migrate/migrate/v4 v4.17.1
github.com/jessevdk/go-flags v1.5.0
github.com/joho/godotenv v1.5.1
github.com/mymmrac/telego v0.29.2
go.uber.org/zap v1.27.0
gorm.io/datatypes v1.2.0
gorm.io/driver/postgres v1.5.0
gorm.io/gorm v1.25.10
)
require (
github.com/BurntSushi/toml v1.1.0 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.11.3 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/fasthttp/router v1.5.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/grbit/go-json v0.11.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.4 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.20.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.4.7 // indirect
)

216
go.sum Normal file
View File

@ -0,0 +1,216 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig/aconfigtoml v0.17.1 h1:TA3xH8mALD8YeULsr4v87cMbKGBma35lWeRga4Xn11Q=
github.com/cristalhq/aconfig/aconfigtoml v0.17.1/go.mod h1:xt4kCEjhgvHWO/oDGJHQHrW5CnvSEoBjJhRXVBdYbhQ=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg=
github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fasthttp/router v1.5.0 h1:3Qbbo27HAPzwbpRzgiV5V9+2faPkPt3eNuRaDV6LYDA=
github.com/fasthttp/router v1.5.0/go.mod h1:FddcKNXFZg1imHcy+uKB0oo/o6yE9zD3wNguqlhWDak=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/microsoft/go-mssqldb v1.0.0 h1:k2p2uuG8T5T/7Hp7/e3vMGTnnR0sU4h8d1CcC71iLHU=
github.com/microsoft/go-mssqldb v1.0.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mymmrac/telego v0.29.2 h1:5+fQ/b8d8Ld6ihCJ0OLe1CwUdT3t1sIUl3RaSaSvRJs=
github.com/mymmrac/telego v0.29.2/go.mod h1:BsKr+GF9BHqaVaLBwsZeDnfuJcJx2olWuDEtKm4zHMc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

85
internal/app/app.go Normal file
View File

@ -0,0 +1,85 @@
package app
import (
"fmt"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/logger"
"github.com/mymmrac/telego"
"go.uber.org/zap"
)
type App struct {
Logger *zap.SugaredLogger
Telegram *telego.Bot
Config *Config
DB *db.Repositories
}
func (a *App) Start() error {
if err := a.loadConfig(); err != nil {
return err
}
if err := a.initLogger(); err != nil {
return err
}
if a.Config.Debug {
a.Logger.Debugf("loaded configuration: %+v", a.Config)
}
if err := a.migrateDB(); err != nil {
return err
}
if err := a.initDB(); err != nil {
return err
}
if err := a.initTelegram(); err != nil {
return err
}
a.Logger.Info("Vega Poker Bot is running")
return a.longPoll()
}
func (a *App) loadConfig() error {
config, err := LoadConfig()
if err != nil {
return err
}
a.Config = config
return nil
}
func (a *App) initLogger() error {
var err error
a.Logger, err = logger.NewLogger(a.Config.Debug)
return err
}
func (a *App) migrateDB() error {
return db.Migrate(a.Config.PostgresDSN)
}
func (a *App) initDB() error {
conn, err := db.Connect(a.Config.PostgresDSN)
if err != nil {
return err
}
a.DB = db.InitRepositories(conn)
return nil
}
func (a *App) initTelegram() error {
var err error
a.Telegram, err = telego.NewBot(a.Config.TelegramToken, logger.WrapForTelego(a.Logger, a.Config.Debug))
return err
}
func (a *App) longPoll() error {
updates, err := a.Telegram.UpdatesViaLongPolling(nil)
if err != nil {
return err
}
defer a.Telegram.StopLongPolling()
for update := range updates {
fmt.Printf("Update: %+v\n", update)
}
return nil
}

37
internal/app/config.go Normal file
View File

@ -0,0 +1,37 @@
package app
import (
"github.com/cristalhq/aconfig"
"github.com/cristalhq/aconfig/aconfigtoml"
"github.com/cristalhq/aconfig/aconfigyaml"
"github.com/joho/godotenv"
"os"
)
type Config struct {
PostgresDSN string `default:"" env:"POSTGRES_DSN" yaml:"postgres_dsn" toml:"postgres_dsn"`
TelegramToken string `default:"" env:"TELEGRAM_TOKEN" yaml:"telegram_token" toml:"telegram_token"`
Debug bool `default:"false" env:"DEBUG" yaml:"debug" toml:"debug"`
}
func LoadConfig() (*Config, error) {
var cfg Config
if err := godotenv.Load(); err != nil {
return nil, err
}
loader := aconfig.LoaderFor(&cfg, aconfig.Config{
SkipFlags: true,
EnvPrefix: "POKER_BOT",
Envs: os.Environ(),
Files: []string{"config.json", "config.yaml", "config.yml"},
FileDecoders: map[string]aconfig.FileDecoder{
".yaml": aconfigyaml.New(),
".yml": aconfigyaml.New(),
".toml": aconfigtoml.New(),
},
})
if err := loader.Load(); err != nil {
return nil, err
}
return &cfg, nil
}

View File

@ -0,0 +1,31 @@
package app
import (
"github.com/jessevdk/go-flags"
"os"
)
type ParserWrapper struct {
parser *flags.Parser
}
func NewParserWrapper() *ParserWrapper {
return &ParserWrapper{parser: flags.NewParser(nil, flags.Default)}
}
func (w *ParserWrapper) AddCommand(name, description string, cmd flags.Commander) {
_, err := w.parser.AddCommand(name, description, "", cmd)
if err != nil {
panic(err)
}
}
func (w *ParserWrapper) Run() {
if _, err := w.parser.Parse(); err != nil {
if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
os.Exit(0)
} else {
os.Exit(1)
}
}
}

39
internal/db/init.go Normal file
View File

@ -0,0 +1,39 @@
package db
import (
"errors"
_ "gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/migrations"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/repository"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Repositories struct {
Chat *repository.Chat
User *repository.User
}
func Connect(dsn string) (*gorm.DB, error) {
return gorm.Open(postgres.Open(dsn), &gorm.Config{})
}
func InitRepositories(db *gorm.DB) *Repositories {
return &Repositories{
Chat: repository.NewChat(db),
User: repository.NewUser(db),
}
}
func Migrate(dsn string) error {
migrator, err := migrate.New("embed://", dsn)
if err != nil {
return err
}
err = migrator.Up()
if errors.Is(err, migrate.ErrNoChange) {
return nil
}
return err
}

View File

View File

@ -0,0 +1,3 @@
DROP TABLE integration CASCADE;
DROP TABLE chat CASCADE;
DROP TABLE "user" CASCADE;

View File

@ -0,0 +1,20 @@
CREATE TABLE "user" (
id BIGSERIAL PRIMARY KEY,
telegram_id BIGINT NOT NULL
);
CREATE TABLE chat (
id BIGSERIAL PRIMARY KEY,
telegram_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
members JSON,
FOREIGN KEY (user_id) REFERENCES "user"(id) ON DELETE CASCADE
);
CREATE TABLE integration (
id BIGSERIAL PRIMARY KEY,
type VARCHAR(32) NOT NULL,
chat_id BIGINT NOT NULL,
params JSON,
FOREIGN KEY (chat_id) REFERENCES chat(id) ON DELETE CASCADE
);

View File

@ -0,0 +1,28 @@
package migrations
import (
"embed"
"github.com/golang-migrate/migrate/v4/source"
"github.com/golang-migrate/migrate/v4/source/httpfs"
"net/http"
)
//go:embed *.sql
var static embed.FS
func init() {
source.Register("embed", &driver{})
}
type driver struct {
httpfs.PartialDriver
}
func (d *driver) Open(rawURL string) (source.Driver, error) {
err := d.PartialDriver.Init(http.FS(static), ".")
if err != nil {
return nil, err
}
return d, nil
}

48
internal/db/model/chat.go Normal file
View File

@ -0,0 +1,48 @@
package model
import (
"database/sql/driver"
"encoding/json"
"errors"
)
type Chat struct {
ID uint64 `gorm:"primaryKey; autoIncrement" json:"id"`
TelegramID uint64 `gorm:"column:telegram_id; not null" json:"telegramId"`
UserID uint64 `gorm:"column:user_id; not null"`
Members ChatMembers `gorm:"column:members; not null" json:"members"`
Integrations []Integration `gorm:"foreignKey:ChatID" json:"integrations"`
}
func (Chat) TableName() string {
return "chat"
}
type ChatMembers []uint64
func (cm *ChatMembers) Scan(value interface{}) error {
switch v := value.(type) {
case nil:
*cm = nil
return nil
case string:
if err := json.Unmarshal([]byte(v), cm); err != nil {
return err
}
case []byte:
if err := json.Unmarshal(v, cm); err != nil {
return err
}
default:
return errors.New("invalid type")
}
return nil
}
func (cm ChatMembers) Value() (driver.Value, error) {
if cm == nil {
return nil, nil
}
jsonData, err := json.Marshal(cm)
return string(jsonData), err
}

View File

@ -0,0 +1,64 @@
package model
import (
"database/sql/driver"
"errors"
"gorm.io/datatypes"
)
type Integration struct {
ID uint64 `gorm:"primaryKey; autoIncrement;"`
Type IntegrationType `gorm:"column:type; not null"`
ChatID uint64 `gorm:"column:chat_id; not null"`
Params datatypes.JSONMap `gorm:"column:params; not null"`
}
func (Integration) TableName() string {
return "integration"
}
type IntegrationType uint8
const (
InvalidIntegration IntegrationType = iota
RedmineIntegration
)
var (
integrationTypesToIds = map[string]IntegrationType{
"invalid": InvalidIntegration,
"redmine": RedmineIntegration,
}
idsToIntegrationTypes = map[IntegrationType]string{
InvalidIntegration: "invalid",
RedmineIntegration: "redmine",
}
)
func (it *IntegrationType) Scan(value interface{}) error {
switch v := value.(type) {
case nil:
*it = 0 // or whatever default you want to set if the field is null in db
return nil
case IntegrationType:
*it = v
case string:
// lookup the value from the map and assign it
val, ok := integrationTypesToIds[v]
if !ok {
return errors.New("invalid IntegrationType")
}
*it = val
default:
return errors.New("invalid type")
}
return nil
}
func (it IntegrationType) Value() (driver.Value, error) {
val, ok := idsToIntegrationTypes[it]
if ok {
return val, nil
}
return "", errors.New("invalid IntegrationType")
}

11
internal/db/model/user.go Normal file
View File

@ -0,0 +1,11 @@
package model
type User struct {
ID uint64 `gorm:"primary_key;auto_increment" json:"id"`
TelegramID uint64 `gorm:"not null" json:"telegram_id"`
Chats []Chat `gorm:"foreignKey:UserID" json:"chats"`
}
func (User) TableName() string {
return "user"
}

View File

@ -0,0 +1,31 @@
package repository
import (
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/model"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/util"
"gorm.io/gorm"
)
type Chat struct {
db *gorm.DB
}
func NewChat(db *gorm.DB) *Chat {
return &Chat{db: db}
}
func (c *Chat) ByID(id uint64) (*model.Chat, error) {
var chat *model.Chat
if err := c.db.First(&chat, id).Error; err != nil {
return nil, util.HandleRecordNotFound(err)
}
return chat, nil
}
func (c *Chat) ByIDWithIntegrations(id uint64) (*model.Chat, error) {
var chat *model.Chat
if err := c.db.Preload("Integrations").First(&chat, id).Error; err != nil {
return nil, util.HandleRecordNotFound(err)
}
return chat, nil
}

View File

@ -0,0 +1,31 @@
package repository
import (
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/model"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/util"
"gorm.io/gorm"
)
type User struct {
db *gorm.DB
}
func NewUser(db *gorm.DB) *User {
return &User{db: db}
}
func (u *User) ByID(id uint64) (*model.User, error) {
var user *model.User
if err := u.db.First(&user, id).Error; err != nil {
return nil, util.HandleRecordNotFound(err)
}
return user, nil
}
func (u *User) ByIDWithChats(id uint64) (*model.User, error) {
var user *model.User
if err := u.db.Preload("Chats").First(&user, id).Error; err != nil {
return nil, util.HandleRecordNotFound(err)
}
return user, nil
}

13
internal/db/util/error.go Normal file
View File

@ -0,0 +1,13 @@
package util
import (
"errors"
"gorm.io/gorm"
)
func HandleRecordNotFound(err error) error {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err
}

View File

@ -0,0 +1,17 @@
package integration
import (
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/db/model"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/iface"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/null"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/redmine"
)
func New(dbModel model.Integration) iface.Integration {
switch dbModel.Type {
case model.RedmineIntegration:
return redmine.New(dbModel.Params)
default:
return null.New()
}
}

View File

@ -0,0 +1,5 @@
package iface
type Integration interface {
GetTaskInfoText(input string) string
}

View File

@ -0,0 +1,13 @@
package null
import "gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/iface"
type Null struct{}
func New() iface.Integration {
return &Null{}
}
func (n *Null) GetTaskInfoText() string {
return ""
}

View File

@ -0,0 +1,66 @@
package api
import (
"bytes"
"encoding/json"
"io"
"net/http"
"strconv"
"strings"
"time"
)
type Client struct {
URL string
Key string
client *http.Client
}
func New(url, key string) *Client {
return &Client{
URL: strings.TrimSuffix(url, "/"),
Key: key,
client: &http.Client{Timeout: time.Second * 2},
}
}
func (c *Client) Issue(id uint64) (*Issue, error) {
var resp IssueResponse
err := c.sendJSONRequest(http.MethodGet, "/issues/"+strconv.FormatUint(id, 10), nil, &resp)
if err != nil {
return nil, err
}
if resp.IsError() {
return nil, resp
}
return resp.Issue, nil
}
func (c *Client) sendRequest(method, path string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, c.URL+path, body)
if err != nil {
return nil, err
}
req.Header.Set("X-Redmine-API-Key", c.Key)
return c.client.Do(req)
}
func (c *Client) sendJSONRequest(method, path string, body, out interface{}) error {
var buf io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return err
}
buf = bytes.NewBuffer(data)
}
resp, err := c.sendRequest(method, path+".json", buf)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
return json.NewDecoder(resp.Body).Decode(out)
}

View File

@ -0,0 +1,59 @@
package api
import (
"strings"
"time"
)
type ErrorResponse struct {
Errors []string `json:"errors"`
}
func (e ErrorResponse) Error() string {
return strings.Join(e.Errors, "; ")
}
func (e ErrorResponse) IsError() bool {
return len(e.Errors) > 0
}
type IssueResponse struct {
ErrorResponse
Issue *Issue `json:"issue"`
}
type NameWithID struct {
ID int `json:"id"`
Name string `json:"name,omitempty"`
}
type CustomField struct {
ID int `json:"id"`
Name string `json:"name"`
Value string `json:"value"`
}
type Issue struct {
ID int `json:"id"`
Project NameWithID `json:"project"`
Tracker NameWithID `json:"tracker"`
Status NameWithID `json:"status"`
Priority NameWithID `json:"priority"`
Author NameWithID `json:"author"`
AssignedTo NameWithID `json:"assigned_to"`
Parent NameWithID `json:"parent"`
Subject string `json:"subject"`
Description string `json:"description"`
StartDate interface{} `json:"start_date"`
DueDate interface{} `json:"due_date"`
DoneRatio int `json:"done_ratio"`
IsPrivate bool `json:"is_private"`
EstimatedHours interface{} `json:"estimated_hours"`
TotalEstimatedHours interface{} `json:"total_estimated_hours"`
SpentHours float64 `json:"spent_hours"`
TotalSpentHours float64 `json:"total_spent_hours"`
CustomFields []CustomField `json:"custom_fields"`
CreatedOn time.Time `json:"created_on"`
UpdatedOn time.Time `json:"updated_on"`
ClosedOn interface{} `json:"closed_on"`
}

View File

@ -0,0 +1,83 @@
package redmine
import (
"fmt"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/iface"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/null"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/integration/redmine/api"
"regexp"
"strconv"
"strings"
)
var issueNumberMatcher = regexp.MustCompile(`(?m)\/issues\/(\d+)`)
var sprintNames = []string{"sprint", "спринт"}
type Redmine struct {
api *api.Client
}
func New(params map[string]interface{}) iface.Integration {
uri, ok := params["url"]
if !ok {
return null.New()
}
key, ok := params["key"]
if !ok {
return null.New()
}
uriString, ok := uri.(string)
if !ok {
return null.New()
}
keyString, ok := key.(string)
if !ok {
return null.New()
}
return &Redmine{api: api.New(uriString, keyString)}
}
func (r *Redmine) GetTaskInfoText(input string) string {
taskNumber := r.getTaskNumber(input)
if taskNumber == 0 {
return ""
}
task, err := r.api.Issue(taskNumber)
if err != nil {
return ""
}
sprint := r.getSprint(task.CustomFields)
if sprint != "" {
sprint = fmt.Sprintf(" - **%s**", sprint)
}
return fmt.Sprintf("[(%s) %s: %s](%s/issues/%d)%s",
task.Project.Name, task.Tracker.Name, task.Subject, r.api.URL, taskNumber, sprint)
}
func (r *Redmine) getTaskNumber(input string) uint64 {
num, err := strconv.ParseUint(input, 10, 64)
if err == nil && num > 0 {
return num
}
matches := issueNumberMatcher.FindStringSubmatch(input)
if len(matches) < 2 {
return 0
}
number, err := strconv.ParseUint(matches[1], 10, 64)
if err != nil {
return 0
}
return number
}
func (r *Redmine) getSprint(fields []api.CustomField) string {
for _, field := range fields {
for _, name := range sprintNames {
if strings.ToLower(field.Name) == name {
return field.Value
}
}
}
return ""
}

View File

@ -0,0 +1,16 @@
package logger
import (
"go.uber.org/zap"
)
func NewLogger(debug bool) (*zap.SugaredLogger, error) {
log, err := zap.NewProduction()
if debug {
log, err = zap.NewDevelopment()
}
if err != nil {
return nil, err
}
return log.Sugar(), nil
}

46
internal/logger/telego.go Normal file
View File

@ -0,0 +1,46 @@
package logger
import (
"github.com/mymmrac/telego"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"strings"
)
type telegoWrapper struct {
logger *zap.SugaredLogger
replacer *strings.Replacer
debugMode bool
}
func WrapForTelego(logger *zap.SugaredLogger, debugMode bool) telego.BotOption {
return func(bot *telego.Bot) error {
return telego.WithLogger(&telegoWrapper{
logger: logger,
replacer: strings.NewReplacer(bot.Token(), "[token]"),
debugMode: debugMode,
})(bot)
}
}
func (l *telegoWrapper) Debugf(format string, args ...any) {
if l.debugMode {
l.logf(zapcore.DebugLevel, format, args...)
}
}
func (l *telegoWrapper) Errorf(format string, args ...any) {
l.logf(zapcore.ErrorLevel, format, args...)
}
func (l *telegoWrapper) logf(lvl zapcore.Level, template string, args ...interface{}) {
escapedArgs := make([]interface{}, len(args))
for i, arg := range args {
if val, ok := arg.(string); ok {
escapedArgs[i] = l.replacer.Replace(val)
continue
}
escapedArgs[i] = arg
}
l.logger.Logf(lvl, l.replacer.Replace(template), escapedArgs...)
}

12
main.go Normal file
View File

@ -0,0 +1,12 @@
package main
import (
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/cmd"
"gitea.neur0tx.site/Neur0toxine/vegapokerbot/internal/app"
)
func main() {
p := app.NewParserWrapper()
p.AddCommand("run", "Starts the app", &cmd.RunCommand{})
p.Run()
}