FIRST commit

This commit is contained in:
maximo tejeda 2024-12-18 11:08:21 -04:00
commit 3ec9d26c67
27 changed files with 2385 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/bin/
/.env
/k8s/deployment.yml

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
#FROM golang:latest as builder
#WORKDIR /app
#COPY . .
#RUN go mod download && go mod tidy
#RUN go build -o ./bin/us-dop-bot ./cmd/bot
FROM debian:unstable-slim
ARG BINAME=us-dop-bot-linux-arm64-0.0.0_1
RUN apt-get update
RUN apt-get install -y ca-certificates
COPY ./bin/${BINAME} /app/us-dop-bot
WORKDIR /app
RUN echo "bin name ${BINAME}"
# RUN mv /app/${BINAME} /app/us-dop-bot
CMD ["/app/us-dop-bot"]

66
Makefile Normal file
View File

@ -0,0 +1,66 @@
# must create a .env file with info
# must have compose installed
include .env
export
OS:=${shell go env GOOS}
ARCH=$(shell go env GOARCH)
OOSS="linux"
ARRCHS="arm 386"
DEBUG=1
SERVICE=consulta-c-bot
VERSION=0.0.0_1
BINAME=$(SERVICE)-$(OS)-$(ARCH)-$(VERSION)
BINAMEARM=$(SERVICE)-$(OS)-arm64-$(VERSION)
# can be docker or podman or whatever
CONTAINERS=docker
COMPOSE=$(CONTAINERS)-compose
# Configure local registry
REGADDR=192.168.0.151:32000
K8SRSNAME=$(shell kubectl get rs --no-headers -o custom-columns=":metadata.name" | grep $(SERVICE))
.phony: all clean build test clean-image build-image build-image-debug run-image run-image-debug run-local
build-image: build
# here we made the images and push to registry with buildx
@$(CONTAINERS) buildx build --build-arg="BINAME=$(BINAMEARM)" --platform linux/arm64 --push -t $(REGADDR)/$(SERVICE):latest .
# Here we upload it to local
build-test-image:
@$(CONTAINERS) buildx build --platform linux/arm64 --push -t $(REGADDR)/$(SERVICE):latest .
run-image: build-image
@$(CONTAINERS) compose -f docker-compose.yaml up
build-image-debug: clean
@$(CONTAINERS) compose -f docker-compose-debug.yaml build
run-image-debug: build-image-debug
@$(CONTAINERS) compose -f docker-compose-debug.yaml up
run-local:clean build
@bin/$(BINAME)
debug-local:
@dlv debug cmd/bot/main.go
build: clean
#@mkdir dolardb
@env GOOS=$(OS) GOARCH=$(arch) go build --tags "fts5" -o ./bin/$(BINAME) ./cmd/bot/.
@env GOOS=$(OS) GOARCH=arm64 go build --tags "fts5" -o ./bin/$(BINAMEARM) ./cmd/bot/.
create-descriptors:
@envsubst < k8s/deployment.yml.template > k8s/deployment.yml
deploy: build-image create-descriptors
@kubectl apply -f k8s/deployment.yml
@kubectl scale rs $(K8SRSNAME) --replicas=0
@kubectl scale rs $(K8SRSNAME) --replicas=1
test:
@go -count=1 test ./...
clean:
@rm -rf ./bin
clean-image:
@$(CONTAINERS) system prune -f

101
cmd/bot/main.go Normal file
View File

@ -0,0 +1,101 @@
package main
import (
"context"
"log/slog"
"runtime"
"git.maximotejeda.com/maximo/cedulados-bot/config"
"git.maximotejeda.com/maximo/cedulados-bot/internal/adapter/grpc/cedulados"
user "git.maximotejeda.com/maximo/cedulados-bot/internal/adapter/grpc/users"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/api"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"golang.org/x/sync/semaphore"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var (
maxWorkers = runtime.GOMAXPROCS(4)
sem = semaphore.NewWeighted(2)
)
func main() {
ctx := context.Background()
bot, err := tgb.NewBotAPI(config.GetToken())
log := slog.Default()
if err != nil {
log.Error("initiating bot", "error",err.Error())
return
}
bot.Debug = true
log.Info("Authorized on account %s", "username", bot.Self.UserName, "maxWorkers", 2)
u := tgb.NewUpdate(0)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
sign := make(chan int)
app := api.NewApi(ctx, log, bot)
_, us, cCoon, uConn := CreateAdaptersGRPC()
_ = us.CreateBot(bot.Self.UserName)
cCoon.Close()
uConn.Close()
// break if not admin set
_ = config.GetAdminsList()
for {
select {
case update := <-updates:
if err := sem.Acquire(ctx, 1); err != nil{
log.Error("failed to get semaphore", "err", err.Error)
bot.Send(tgb.NewMessage(update.Message.Chat.ID, "too may request, Please try again"))
continue
}
go func(){
defer sem.Release(1)
c, u, cConn, uConn := CreateAdaptersGRPC()
app.Run(&update, c, u)
defer cConn.Close()
defer uConn.Close()
}()
case <-sign:
}
}
}
func CreateAdaptersGRPC() (ports.CeduladosService, ports.UserService, *grpc.ClientConn, *grpc.ClientConn) {
log := slog.Default()
// we are outside update so we will be querying db to
// get users interested in specific updates ex bpd, brd, apa
// userID inst=> comma separated string
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
cedConn, err := grpc.NewClient(config.GetCeduladosServiceURL(), opts...)
if err != nil {
log.Error("creating gerpc conn", "error", err)
panic(err)
}
userConn, err := grpc.NewClient(config.GetUserServiceURL(), opts...)
if err != nil {
log.Error("creating gerpc conn", "error", err)
panic(err)
}
ced, err := cedulados.NewAdapter(cedConn)
if err != nil {
log.Error("creating service adapter", "error", err)
panic(err)
}
user, err := user.NewAdapter(userConn)
if err != nil {
log.Error("creating service adapter", "error", err)
panic(err)
}
return ced, user, cedConn, userConn
}

38
config/config.go Normal file
View File

@ -0,0 +1,38 @@
package config
import (
"log"
"os"
)
func GetToken() string {
return getEnvVariable("BOT_TOKEN")
}
func GetNatsURI() string {
return getEnvVariable("NATS_SERVICE_URL")
}
func GetCeduladosServiceURL() string {
return getEnvVariable("CEDULADOS_SERVICE_URL")
}
func GetUserServiceURL() string {
return getEnvVariable("TGBUSER_SERVICE_URL")
}
func GetEnvironment() string {
return getEnvVariable("ENV")
}
func GetAdminsList()string {
return getEnvVariable("ADMINS")
}
func getEnvVariable(key string) string {
if os.Getenv(key) == "" {
log.Fatal("error getting key ", key)
}
return os.Getenv(key)
}

19
go.mod Normal file
View File

@ -0,0 +1,19 @@
module git.maximotejeda.com/maximo/cedulados-bot
go 1.22.0
require (
git.maximotejeda.com/maximo/cedulados v0.0.2
git.maximotejeda.com/maximo/tgb-user v0.0.5
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
golang.org/x/sync v0.10.0
golang.org/x/text v0.21.0
google.golang.org/grpc v1.69.0
)
require (
golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/protobuf v1.35.2 // indirect
)

38
go.sum Normal file
View File

@ -0,0 +1,38 @@
git.maximotejeda.com/maximo/cedulados v0.0.1 h1:00ghxI7KnTS/kIjpt0kX8co84YoMTCmUqZMRphrF/Qk=
git.maximotejeda.com/maximo/cedulados v0.0.1/go.mod h1:LngBMRNqF+CCqPaK3wvLs8gvJZgfMqPTYuEPZy9nOPM=
git.maximotejeda.com/maximo/cedulados v0.0.2 h1:xGAUfy6UXPV3nOrW+12JUWCXZrEDkb1kIEZPk+1Ov+o=
git.maximotejeda.com/maximo/cedulados v0.0.2/go.mod h1:LngBMRNqF+CCqPaK3wvLs8gvJZgfMqPTYuEPZy9nOPM=
git.maximotejeda.com/maximo/tgb-user v0.0.5 h1:OTACcjzOld9TsQHqzDqGXgdN3CqWrZ2p1ro0mUErCR0=
git.maximotejeda.com/maximo/tgb-user v0.0.5/go.mod h1:7KpTUAnwap6cp5pHRKgJygxrN3rftAdTkpCG2zJIpYI=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=

View File

@ -0,0 +1,228 @@
package chat
import (
"bytes"
"context"
"fmt"
"regexp"
"strconv"
"strings"
"sync"
"unicode"
"git.maximotejeda.com/maximo/cedulados-bot/config"
"git.maximotejeda.com/maximo/cedulados-bot/internal/adapter/grpc/cedulados"
"git.maximotejeda.com/maximo/cedulados-bot/internal/adapter/helper"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var ChatPool *sync.Pool
type ChatObj struct {
ctx context.Context
update *tgb.Update
bot *tgb.BotAPI
u ports.UserService
c ports.CeduladosService
}
func NewChatObj(bot *tgb.BotAPI, updt *tgb.Update) (chat *ChatObj) {
if ChatPool == nil {
ChatPool = &sync.Pool{
New: func() any { return &ChatObj{} },
}
for i := 0; i < 20; i++ {
ChatPool.Put(ChatPool.New())
}
} else {
fmt.Println("alredy populated")
}
chat = ChatPool.Get().(*ChatObj)
chat.update = updt
chat.bot = bot
return chat
}
func (ch *ChatObj)Empty() {
ch.update = nil
ch.c = nil
ch.u = nil
ChatPool.Put(ch)
}
func (ch *ChatObj)Send(){
}
func (ch *ChatObj)Handler( cSVC ports.CeduladosService, uSVC ports.UserService) {
text := NormalizeText(ch.update.Message.Text)
textList := strings.Split(text, " ")
msg := tgb.NewMessage(ch.update.Message.Chat.ID, "")
if len(textList) >= 1 && MessageChecker(text) == "digit" {
// in case of message match a cedula
ced, err := helper.NewCedula(ch.ctx, text)
if err != nil {
msg.Text = "cedula no reconocida " + err.Error()
} else {
msg, photoMsg := ProcessByCedula(ch.ctx, cSVC, uSVC, ced)
msg.ChatID = ch.update.Message.Chat.ID
if photoMsg != nil {
photoMsg.ChatID = ch.update.Message.Chat.ID
ch.bot.Send(photoMsg)
}
ch.bot.Send(msg)
return
}
} else if len(textList) >= 2 && MessageChecker(text) == "word" {
msg := ProcessByName(ch.ctx, cSVC, uSVC, textList)
msg.ChatID = ch.update.Message.Chat.ID
ch.bot.Send(msg)
}
msg.ReplyToMessageID = ch.update.Message.MessageID
ch.bot.Send(msg)
}
// ProcessByCedula
//
// When a text arrives the chat if it match a cedula try to query db
func ProcessByCedula(ctx context.Context, cSVC ports.CeduladosService, uSVC ports.UserService, ced *domain.Cedula) (message *tgb.MessageConfig, fotoMsg *tgb.PhotoConfig) {
msg := tgb.NewMessage(0, "")
message = &msg
info , err := cSVC.CeduladoByCedula(context.Background(), ced)
if err != nil {
fmt.Println("error on query ", err)
}
fmt.Println("!!!!success on query", info)
if info != nil {
msg.Text = fmt.Sprintf(`
Nombres: %s
Apellidos: %s %s
Cedula: %s
Direccion: %s
Telefono: %s
`,
strings.ToLower(info.Nombres), strings.ToLower(info.Apellido1), strings.ToLower(info.Apellido2),
info.MunCed+info.SeqCed+info.VerCed, RemoveSpaces(info.Direccion), info.Telefono)
}
var opts []grpc.DialOption
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
cedConn, err := grpc.NewClient(config.GetCeduladosServiceURL(), opts...)
if err != nil {
panic(err)
}
c, err := cedulados.NewAdapter(cedConn)
if err != nil {
panic(err)
}
defer cedConn.Close()
foto, err := c.QueryFotoByID(ctx, 1)
if err != nil {
fmt.Println("Photo not found", err.Error())
return
}
if foto != nil {
rq := tgb.FileReader{Name: fmt.Sprintf("%s-%s-%s", ced.MunCed, ced.SeqCed, ced.VerCed), Reader: bytes.NewReader(foto.Imagen)}
fotost := tgb.NewPhoto(msg.ChatID, rq)
fotoMsg = &fotost
}
return
}
func ProcessByName(ctx context.Context, cSVC ports.CeduladosService, uSVC ports.UserService, nameList []string) (message *tgb.MessageConfig) {
var err error
page := int64(0)
// look for if the last part of the list is a number
lastItem := nameList[len(nameList)-1]
if MessageChecker(lastItem) == "digit" && !strings.HasPrefix(lastItem, "0") {
pageInt, err := strconv.ParseInt(lastItem, 10, 64)
if err != nil {
fmt.Println(err)
}
nameList = nameList[:len(nameList)-1]
if pageInt < 20 {
page = pageInt
}
}
rows := &domain.MultipleResults{}
message = &tgb.MessageConfig{}
text := strings.Join(nameList, " ")
res, err := cSVC.CeduladoByFTS(ctx, "maximo tejeda", 0)
if err != nil {
fmt.Println("error oon fts", err)
}
fmt.Println("sucess", res)
rows, err = cSVC.CeduladoByFTS(ctx, text, page)
if err != nil {
message.Text = "no hubo resultados para la busqueda"
return
}
textToSend := fmt.Sprintf(`
Busqueda:
Texto: %s
Pagina: %d
resultados desde: %d A %d
De un total de: %d
`,
text,
(rows.Page),
(rows.Page*10)-9, (rows.Page)*10,
rows.Total)
for _, v := range rows.Data {
textToSend = textToSend + fmt.Sprintf("\n %s %s %s \n %s-%s-%s\n", strings.ToLower(v.Nombres), strings.ToLower(v.Apellido1), strings.ToLower(v.Apellido2), v.MunCed, v.SeqCed, v.VerCed)
}
message.Text = textToSend
return
}
func MessageChecker(text string) string {
// Check is text start with number
checkSpace := regexp.MustCompile(`^[\s]+.*`)
checkNumber := regexp.MustCompile(`^[\d]+.*`)
checkWord := regexp.MustCompile(`^[a-zA-Z% ]+.*`)
if checkNumber.MatchString(text) {
return "digit"
} else if checkWord.MatchString(text) {
return "word"
} else if checkSpace.MatchString(text) {
t := strings.TrimSpace(text)
return MessageChecker(t)
}
return ""
}
// NormalizeText
// remove foreign accent
func NormalizeText(text string) string {
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
result, _, _ := transform.String(t, text)
return result
}
func RemoveSpaces(text string) (res string) {
re := regexp.MustCompile(`[\s]+`)
res = re.ReplaceAllString(text, " ")
return
}

View File

@ -0,0 +1 @@
package command

View File

@ -0,0 +1,190 @@
package cedulados
import (
"context"
"fmt"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
ced "git.maximotejeda.com/maximo/cedulados/proto/golang/cedulados"
"google.golang.org/grpc"
)
// Adapter
type Adapter struct {
cedulados ced.QueryCeduladoClient
conn *grpc.ClientConn
}
// NewAdapter
func NewAdapter(conn *grpc.ClientConn) (*Adapter, error){
client := ced.NewQueryCeduladoClient(conn)
if client == nil{
fmt.Println("error creating client, cant be nil" )
}
return &Adapter{cedulados: client, conn: conn}, nil
}
// CeduladoByCedula
func(a Adapter) CeduladoByCedula(ctx context.Context, c *domain.Cedula)(resp *domain.Info, err error){
cReq := &ced.QueryByCedulaRequest{
Cedula: &ced.Cedula{
MunCed: c.MunCed,
SeqCed: c.SeqCed,
VerCed: c.VerCed,
},
}
r, err := a.cedulados.CeduladosByCedula(ctx, cReq)
if err != nil {
return nil, err
}
resp = mapLocalDomain (r.Cedulado)
return
}
// CeduladoGetByNameLastName
func(a Adapter) CeduladoGetByNameLastName(ctx context.Context, params domain.Info, page int64)(info *domain.MultipleResults, err error){
cReq := &ced.QueryByNameLastNameRequest{
Name: params.Nombres,
Apellido_1: params.Apellido1,
Apellido_2: params.Apellido2,
Page: page,
}
r, err := a.cedulados.CeduladosByNameLastName(ctx, cReq)
if err != nil {
return nil, err
}
info = &domain.MultipleResults{
Page: r.Page,
Total: r.Total,
}
for _, val := range r.Cedulados{
info.Data = append(info.Data, mapLocalDomain (val))
}
return
}
// CeduladoByFTS
func(a Adapter) CeduladoByFTS(ctx context.Context, params string, page int64)(info *domain.MultipleResults, err error){
cReq := &ced.QueryByFTSRequest{
Name: params,
Page: page,
}
r, err := a.cedulados.CeduladosByFTS(ctx, cReq)
if err != nil {
return nil, err
}
info = &domain.MultipleResults{
Page: r.Page,
Total: r.Total,
}
for _, val := range r.Cedulados{
info.Data = append(info.Data, mapLocalDomain ( val))
}
return
}
// CeduladoByNameAndLocation
func(a Adapter) CeduladoByNameAndLocation(ctx context.Context, params domain.Info, page int64, municipio string)(info *domain.MultipleResults, err error){
cReq := &ced.QueryByNamesLocationRequest{
Name: params.Nombres,
Apellido_1: params.Apellido1,
Apellido_2: params.Apellido2,
Municipio: municipio,
Page: page,
}
r, err := a.cedulados.CeduladosByNameAndLocation(ctx, cReq)
if err != nil {
return nil, err
}
info = &domain.MultipleResults{
Page: r.Page,
Total: r.Total,
}
for _, val := range r.Cedulados{
info.Data = append(info.Data, mapLocalDomain ( val))
}
return
}
// QueryFotoByCedula
func(a Adapter) QueryFotoByCedula(ctx context.Context, c *domain.Cedula)(info *domain.Foto, err error){
cReq := &ced.QueryFotoByCedulaRequest{
Cedula: &ced.Cedula{
MunCed: c.MunCed,
SeqCed: c.SeqCed,
VerCed: c.VerCed,
},
}
r, err := a.cedulados.QueryFotoByCedula(ctx, cReq)
if err != nil {
return nil, err
}
// here should be a photo
info = &domain.Foto{}
info.Imagen = r.Foto.Foto
info.MunCed = c.MunCed
info.SeqCed = c.SeqCed
info.VerCed = c.VerCed
info.ID = r.Foto.Id
return
}
// QueryFotoByID
func(a Adapter) QueryFotoByID(ctx context.Context, id int64)(info *domain.Foto, err error){
r, err := a.cedulados.QueryFotoById(ctx, &ced.QueryFotoByIdRequest{Id: id})
if err != nil {
return nil, err
}
info = &domain.Foto{}
info.Imagen = r.Foto.Foto
info.ID = r.Foto.Id
info.MunCed = r.Foto.MunCed
info.VerCed = r.Foto.VerCed
info.SeqCed = r.Foto.SeqCed
return
}
// QueryFotoAllCedulas
func(a Adapter) QueryFotoAllCedulas(ctx context.Context, ceds []*domain.Cedula)(fotos []*domain.Foto, err error){
for _, c := range ceds {
foto := &domain.Foto{}
cReq := &ced.QueryFotoByCedulaRequest{
Cedula: &ced.Cedula{
MunCed: c.MunCed,
SeqCed: c.SeqCed,
VerCed: c.VerCed,
},
}
r, err := a.cedulados.QueryFotoByCedula(ctx, cReq)
if err != nil {
fmt.Println("err on ced: ", c)
continue
}
// here should be a photo
foto.Imagen = r.Foto.Foto
foto.MunCed = c.MunCed
foto.SeqCed = c.SeqCed
foto.VerCed = c.VerCed
foto.ID = r.Foto.Id
fotos = append(fotos, foto)
}
return
}
func mapLocalDomain(info *ced.Cedulado)(ifo *domain.Info){
if info == nil {
fmt.Println("nil info ")
return nil
}
ifo = &domain.Info{
Cedula: domain.Cedula{
VerCed: info.Cedula.VerCed,
MunCed: info.Cedula.MunCed,
SeqCed: info.Cedula.SeqCed,
},
ID: info.Id,
Nombres: info.Nombres,
Apellido1: info.Apellido_1,
Apellido2: info.Apellido_2,
Sexo: info.Sexo,
Direccion: info.Direccion,
Telefono: info.Telefono,
}
return
}

View File

@ -0,0 +1,245 @@
package user
import (
"context"
"log/slog"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
"git.maximotejeda.com/maximo/tgb-user/proto/golang/tgbuser"
tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"google.golang.org/grpc"
)
type Adapter struct {
user tgbuser.UserManagerClient
conn *grpc.ClientConn
log *slog.Logger
}
func NewAdapter(conn *grpc.ClientConn) (*Adapter, error) {
log := slog.Default()
log = log.With("location", "user adapter")
client := tgbuser.NewUserManagerClient(conn)
return &Adapter{user: client, conn: conn, log: log}, nil
}
func (a *Adapter) Get(tgbid int64) (*domain.User, error) {
hr, err := a.user.Get(context.Background(), &tgbuser.GetTGBUserRequest{TgbId: tgbid})
if err != nil {
return nil, err
}
user := &domain.User{
ID: hr.User.Id,
Username: hr.User.Username,
FirstName: hr.User.FirstName,
LastName: hr.User.LastName,
TguID: hr.User.TgbId,
Created: hr.User.Created,
Edited: hr.User.Edited,
}
return user, nil
}
func (a Adapter) Create(user *tgb.User) (b bool, err error) {
_, err = a.user.Create(context.Background(), &tgbuser.CreateTGBUserRequest{
User: &tgbuser.User{
TgbId: user.ID,
FirstName: user.FirstName,
LastName: user.LastName,
Username: user.UserName,
},
})
if err != nil {
return false, err
}
return true, nil
}
func (a Adapter) Edit(user *tgb.User) (b bool, err error) {
_, err = a.user.Edit(context.Background(), &tgbuser.EditTGBUserRequest{
User: &tgbuser.User{
Username: user.UserName,
FirstName: user.FirstName,
LastName: user.LastName,
},
})
if err != nil {
return false, err
}
return true, nil
}
func (a Adapter) Delete(tgbid int64) (b bool, err error) {
_, err = a.user.Delete(context.Background(), &tgbuser.DeleteTGBUserRequest{
TgbId: tgbid,
})
if err != nil {
return false, err
}
return true, nil
}
func (a Adapter) GetBots(tgbid int64) (s []string, err error) {
hr, err := a.user.GetBots(context.Background(), &tgbuser.GetBotsTGBUserRequest{
TgbId: tgbid,
})
if err != nil {
return nil, err
}
s = []string{}
if len(hr.Bots) <= 0 {
return s, nil
}
for _, it := range hr.Bots {
s = append(s, it.BotName)
}
return s, nil
}
func (a Adapter) AddBot(tgbid int64, botname string) (b bool, err error) {
_, err = a.user.AddBot(context.Background(), &tgbuser.AddBotTGBUserRequest{
TgbId: tgbid,
BotName: botname,
})
if err != nil {
return false, err
}
return true, nil
}
func (a Adapter) DeleteBot(tgbid int64, botname string) (b bool, err error) {
_, err = a.user.DeleteBot(context.Background(), &tgbuser.DeleteBotTGBUserRequest{
TgbId: tgbid,
BotName: botname,
})
if err != nil {
return false, err
}
return true, nil
}
func (a Adapter) GetAllBotsUsers(botname string) ([]*domain.User, error) {
users, err := a.user.GetAllBotsUsers(context.Background(), &tgbuser.GetAllBotsUsersRequest{BotName: botname})
if err != nil {
a.log.Error("get all bots users", "error", err)
return nil, err
}
a.log.Info("users", "result", users)
list := []*domain.User{}
for _, us := range users.Users {
user := &domain.User{
ID: us.Id,
TguID: us.TgbId,
Username: us.Username,
FirstName: us.FirstName,
LastName: us.LastName,
Created: us.Created,
Edited: us.Edited,
}
list = append(list, user)
}
return list, nil
}
func (a Adapter) CreateBot(botname string) (error){
_, err := a.user.CreateBot(context.Background(), &tgbuser.TGBBotNameRequest{BotName: botname})
if err != nil {
a.log.Error("creating bot", "error", err)
return err
}
return nil
}
func (a Adapter) CreateAccessRequest(tgbID int64, botName string)(bool, error){
req := tgbuser.TGBUserBotNameRequest{
TgbId: tgbID,
BotName: botName,
}
r,err := a.user.CreateAccessRequest(context.Background(), &req)
if err != nil {
a.log.Error("creating access request", "error", err)
return false, err
}
return r.Response, nil
}
func (a Adapter) GrantAccess(tgbID int64, botName string)(bool, error){
req := tgbuser.TGBUserBotNameRequest{
TgbId: tgbID,
BotName: botName,
}
r,err := a.user.GrantAccess(context.Background(), &req)
if err != nil {
a.log.Error("creating access request", "error", err)
return false, err
}
return r.Response, nil
}
func (a Adapter) GetAllAccessRequest(botName string)(*tgbuser.GetAccessResponse, error){
req := tgbuser.TGBBotNameRequest{
BotName: botName,
}
r,err := a.user.GetAllAccessRequest(context.Background(), &req)
if err != nil {
a.log.Error("creating access request", "error", err)
return nil, err
}
return r, nil
}
func (a Adapter) BanUser(tgbID int64, until int64, botName string)(bool, error){
req:= tgbuser.TGBBanUserRequest{
TgbId: tgbID,
BotName: botName,
Until: until,
}
r, err := a.user.BanUser(context.Background(), &req)
if err != nil {
a.log.Error("banning user", "error", err)
return false, err
}
return r.Response, nil
}
func (a Adapter) UnBanUser(tgbID int64, botName string)(bool, error){
req:= tgbuser.TGBUserBotNameRequest{
TgbId: tgbID,
BotName: botName,
}
r, err := a.user.UnBanUser(context.Background(), &req)
if err != nil {
a.log.Error("unbaning user", "error", err)
return false, err
}
return r.Response, nil
}
func (a Adapter) GetAllBannedUsers(botName string)(*tgbuser.GetBanResponse, error){
req := tgbuser.TGBBotNameRequest{
BotName: botName,
}
r, err := a.user.GetAllBannedUsers(context.Background(), &req)
if err != nil {
a.log.Error("getting all banned users", "error", err)
return nil, err
}
return r, nil
}
func (a Adapter)GetAccessRequest(tgbID int64) (*tgbuser.GetAccessResponse, error){
req := tgbuser.TGBUserRequest{
TgbId: tgbID,
}
r, err := a.user.GetAccessRequest(context.Background(), &req)
if err != nil {
a.log.Error("geting access request", "userID", tgbID,"error", err)
return nil, err
}
return r, nil
}

View File

@ -0,0 +1,126 @@
package helper
import (
"context"
"fmt"
"regexp"
"strings"
"unicode"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
)
// AsserCedulas
// check for cedula format
// 000-+ 0000000 +- 0
func NewCedula(ctx context.Context, ced string)(c *domain.Cedula, err error){
c = &domain.Cedula{}
if len(ced) <= 0 {
return nil, fmt.Errorf("cedula length err lenght:%d", len(ced))
}
// use Regexp to simplify cases
re := regexp.MustCompile(`(?P<munCed>\d{3})[\s-]+(?P<seqCed>\d{7})[\s-]+(?P<verCed>\d{1})$`)
result := re.FindStringSubmatch(ced)
if len(result) != 4 {
return nil, fmt.Errorf("Cedula format Err (%s) is not a good format", ced)
}
for i, name := range re.SubexpNames() {
if i != 0 && name != "" {
switch name {
case "munCed":
c.MunCed = result[i]
case "seqCed":
c.SeqCed = result[i]
case "verCed":
c.VerCed = result[i]
default:
continue
}
}
}
return c, nil
}
// NewMultiCedula
// create a list of Multiple Cedulas from a string
// separated by \n
func NewMultiCedula(ctx context.Context, ceds string) (cList []*domain.Cedula, err error){
splitedCed := strings.Split(ceds, "\n")
for _, ced := range splitedCed{
c, err := NewCedula(ctx, ced)
if err != nil {
fmt.Printf("error querying cedula: %s", err)
continue
}
cList = append(cList, c)
}
return cList, err
}
// RemoveAccent
// helps normalize names in db
// https://stackoverflow.com/questions/24588295/go-removing-accents-from-strings
func RemoveAccent(str string) string {
if str == "" {
return ""
}
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
s, _, _ := transform.String(t, str)
return s
}
// RemoveSpaces
func RemoveSpaces(text string) (res string) {
re := regexp.MustCompile(`[\s]+`)
res = re.ReplaceAllString(text, " ")
return
}
// MessageChecker
// Check if message start with a number or letter
func MessageChecker(text string) string {
// Check is text start with number
checkSpace := regexp.MustCompile(`^[\s]+.*`)
checkNumber := regexp.MustCompile(`^[\d]+.*`)
checkWord := regexp.MustCompile(`^[a-zA-Z% ]+.*`)
if checkNumber.MatchString(text) {
return "digit"
} else if checkWord.MatchString(text) {
return "word"
} else if checkSpace.MatchString(text) {
t := strings.TrimSpace(text)
return MessageChecker(t)
}
return ""
}
// CreateKeyboard
// create keybowrds of two rows of any map[string]string input
func CreateKeyboard(data map[string]string) tgbotapi.InlineKeyboardMarkup {
// hardcoded models
keyboard := tgbotapi.NewInlineKeyboardMarkup()
// subbuttons := []tgbot.InlineKeyboardButton{}
rows := tgbotapi.NewInlineKeyboardRow()
counter := 0
for key, val := range data {
if counter != 0 && counter%3 == 0 {
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, rows)
rows = tgbotapi.NewInlineKeyboardRow()
}
rows = append(rows, tgbotapi.NewInlineKeyboardButtonData(key, val))
if counter >= len(data)-1 {
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, rows)
}
counter++
}
return keyboard
}

View File

@ -0,0 +1 @@
package query

View File

@ -0,0 +1,59 @@
package api
import (
"context"
"log/slog"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/auth"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/command"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/message"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/query"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type api struct {
ctx context.Context
log *slog.Logger
bot *tgbotapi.BotAPI
update *tgbotapi.Update
command ports.Tgb
message ports.Tgb
query ports.Tgb
cedulados ports.CeduladosService
user ports.UserService
}
// NewApi
// Create a new instance for api
func NewApi(ctx context.Context, log *slog.Logger, bot *tgbotapi.BotAPI) *api {
log = log.With("location", "api")
return &api{ctx: ctx, log: log, bot: bot}
}
// Run
// Start bot user interaction process
func (a *api) Run(update *tgbotapi.Update, cedSVC ports.CeduladosService, user ports.UserService) {
au := auth.NewAuth(a.ctx, a.log, a.bot, update, update.SentFrom(), user)
if !(au.Authenticate()) && !update.Message.IsCommand(){
a.log.Error("Authentication failed")
return
}
msg := update.Message
if msg != nil { // message is not nil can be a command or a text message
if msg.IsCommand() {
com := command.Newcommand(a.bot, update, cedSVC, user)
com.Handler()
// is a command
} else if msg.Text != "" {
// is a text message
message := message.NewMessage(a.bot, update, cedSVC, user)
message.Handler()
}
} else if update.CallbackQuery != nil {
// is a cal back
qr := query.NewQuery(a.bot, update, cedSVC, user)
qr.Handler()
}
}

View File

@ -0,0 +1,186 @@
package auth
import (
"context"
"fmt"
"log/slog"
"slices"
"strconv"
"strings"
"git.maximotejeda.com/maximo/cedulados-bot/config"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type Auth struct {
bot *tgbotapi.BotAPI
update *tgbotapi.Update
user *tgbotapi.User
uSVC ports.UserService
log *slog.Logger
ctx context.Context
}
func NewAuth(ctx context.Context, log *slog.Logger, bot *tgbotapi.BotAPI, update *tgbotapi.Update, user *tgbotapi.User, uSVC ports.UserService) *Auth {
a := &Auth{
ctx: ctx,
log: log,
user: user,
bot: bot,
uSVC: uSVC,
update: update,
}
return a
}
func (a *Auth) Authenticate() bool {
switch IsUserAdmin(a.user.ID) {
case true:
// theres an user env admin
// check if user exist on db
_, err := a.uSVC.Get(a.user.ID)
if err != nil {
a.log.Error("geting user", "error", err)
a.log.Debug("user seems to not exists")
if strings.Contains(err.Error(), "sql: no rows in result set") {
// if user does not exist create it
_, err := a.uSVC.Create(a.user)
a.log.Debug("creating user")
if err != nil {
a.log.Error("creating new user for admnin", "err", err)
}
// add bot to user list
_, err = a.uSVC.AddBot(a.user.ID, a.bot.Self.UserName)
if err != nil {
a.log.Error("Adding bot to admin user list", "err", err)
}
}
}
return true
case false:
// user is not admin and need to be authorized
// check if user is on db
_, err := a.uSVC.Get(a.update.SentFrom().ID)
if err != nil {
// user need auth
a.log.Error("user not in db, authorization from an admin is required", "error", err)
// add user to manage it
if strings.Contains(err.Error(), "sql: no rows in result set") {
// check if theres an access request from the same user and bot
_, err := a.uSVC.Create(a.user)
if err != nil {
a.log.Error("creating new user", "user", a.user.ID, "error", err)
}
// create access request
_, err = a.uSVC.CreateAccessRequest(a.user.ID, a.bot.Self.UserName)
if err != nil {
a.log.Error("creating access request for ", "user", a.user.ID, "error", err)
}
}
} else {
bot, err := a.uSVC.GetBots(a.user.ID)
if err != nil {
a.log.Error("checking bots on user access")
}
switch HasUserAccess(bot, a.bot.Self.UserName) {
case true:
return true
case false:
// check for banned user
buser, err := a.uSVC.GetAllBannedUsers(a.bot.Self.UserName)
if err != nil {
a.log.Error("error querying banned user")
}
for _, u := range buser.GetBans(){
if u.TgbId == a.update.SentFrom().ID{
msg := tgbotapi.NewMessage(u.TgbId, "user access is restricted, please ask for permission")
a.bot.Send(msg)
return false
}
}
ac, err := a.uSVC.GetAccessRequest(a.user.ID)
acl := []string{}
for _, val := range ac.Access {
acl = append(acl, val.BotName)
}
if slices.Contains(acl, a.bot.Self.UserName) {
// create access request
a.log.Info("Access Request found returning early", "user", a.user.ID, "error", err)
return false
} else {
// create one
_, err = a.uSVC.CreateAccessRequest(a.user.ID, a.bot.Self.UserName)
if err != nil {
a.log.Error("creating access request", "err", err)
}
}
}
}
}
// get all admins
userL, _ := GetAdminFromEnv()
// send a mesage to all admins
for _, adm := range userL {
msg := GenerateAccessRequestMessage(a.update.SentFrom(), adm, a.bot.Self.UserName)
a.bot.Send(msg)
}
return false
}
// GetAdminFromEnv
// will get an env variable that is a list of tgbID comma separated
// if the user trying to enter is admin auth the user
func GetAdminFromEnv() (adminList []int64, errList []error) {
adminsStrList := config.GetAdminsList()
list := strings.Split(adminsStrList, ",")
adminList = []int64{}
errList = []error{}
for _, item := range list {
adm, err := strconv.ParseInt(item, 10, 64)
if err != nil {
err = fmt.Errorf("parsing tgb admin id: %s\n err: %w", item, err)
fmt.Println(err)
errList = append(errList, err)
continue
}
adminList = append(adminList, adm)
}
return
}
// IsUserAdmin
// check if userID is admin on bot
func IsUserAdmin(userID int64) bool {
userL, errl := GetAdminFromEnv()
if len(errl) > 0 {
fmt.Printf("error no admin in var %v", errl)
}
return slices.Contains(userL, userID)
}
func HasUserAccess(bots []string, botName string) bool {
return slices.Contains(bots, botName)
}
func GenerateAccessRequestMessage(up *tgbotapi.User, adm int64, bn string) *tgbotapi.MessageConfig {
txt := fmt.Sprintf(`User %s is requesting access
ID: %d
FirstName: %s
LastName: %s
ChatID: %d
`, up.UserName, up.ID, up.FirstName, up.LastName, up.ID)
msg := tgbotapi.NewMessage(adm, txt)
keyboard := tgbotapi.InlineKeyboardMarkup{}
row := tgbotapi.NewInlineKeyboardRow()
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Grant", fmt.Sprintf("operation=grant&userID=%d&bot=%s", up.ID, bn)))
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Deny", fmt.Sprintf("operation=deny&userID=%d&bot=%s", up.ID, bn)))
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Ignore", fmt.Sprintf("operation=ignore&userID=%d&bot=%s", up.ID, bn)))
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, row)
msg.ReplyMarkup = keyboard
return &msg
}

View File

@ -0,0 +1,382 @@
package command
import (
"fmt"
"log/slog"
"slices"
"strconv"
"strings"
"sync"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/auth"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var CommandPool *sync.Pool
type command struct {
bot *tgbotapi.BotAPI
update *tgbotapi.Update
msg *tgbotapi.MessageConfig
delMSG *tgbotapi.DeleteMessageConfig
log *slog.Logger
ce ports.CeduladosService
uSVC ports.UserService
}
// NewMessage
// Factory for query handler
func Newcommand(bot *tgbotapi.BotAPI, update *tgbotapi.Update, cSVC ports.CeduladosService, user ports.UserService) *command {
if CommandPool == nil {
CommandPool = &sync.Pool{
New: func() any { return &command{} },
}
for i := 0; i < 20; i++ {
CommandPool.Put(CommandPool.New())
}
}
log := slog.Default()
log = log.With("function", "command", "chat", update.Message.From.ID, "userid", update.Message.From.ID, "username", update.Message.From.UserName)
query := CommandPool.Get().(*command)
query.update = update
query.bot = bot
query.log = log
query.ce = cSVC
query.uSVC = user
return query
}
// Empty
// Returns pointer to pool
func (c *command) Empty() {
c.update = nil
c.msg = nil
c.log = nil
c.ce = nil
c.uSVC = nil
CommandPool.Put(c)
}
// Send
// Process message sending to bot
func (c *command) Send() {
defer c.Empty()
c.bot.Send(c.msg)
if c.delMSG != nil {
c.bot.Send(c.delMSG)
}
}
func (c *command) Handler(){
msg := tgbotapi.NewMessage(c.update.Message.From.ID, "")
delMSG := tgbotapi.NewDeleteMessage(c.update.Message.From.ID, c.update.Message.MessageID)
c.msg = &msg
c.delMSG = &delMSG
command := c.update.Message.Command()
switch strings.ToLower(command) {
case "baned":
if !checkUserIsAdmin(c){
return
}
bannedUserCommand(c, msg)
case "appeal":
appealUserCommand(c, msg)
case "pending":
if !checkUserIsAdmin(c){
return
}
pendingUserCommand(c, msg)
case "users":
if !checkUserIsAdmin(c){
return
}
usersCommand(c, msg)
case "user":
if !checkUserIsAdmin(c){
return
}
userCommand(c)
case "userid":
if !checkUserIsAdmin(c){
return
}
userIDCommand(c)
case "ban":
if !checkUserIsAdmin(c){
return
}
banCommand(c, msg)
}
}
func GenerateAppealRequestMessage(up *tgbotapi.User, adm int64, bn string) *tgbotapi.MessageConfig {
txt := fmt.Sprintf(`User %s appeal
ID: %d
FirstName: %s
LastName: %s
ChatID: %d
`, up.UserName, up.ID, up.FirstName, up.LastName, up.ID)
msg := tgbotapi.NewMessage(adm, txt)
keyboard := tgbotapi.InlineKeyboardMarkup{}
row := tgbotapi.NewInlineKeyboardRow()
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Unban", fmt.Sprintf("operation=unban&userID=%d&bot=%s", up.ID, bn)))
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Delete", fmt.Sprintf("operation=delete&userID=%d&bot=%s", up.ID, bn)))
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Ignore", fmt.Sprintf("operation=ignore&userID=%d&bot=%s", up.ID, bn)))
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, row)
msg.ReplyMarkup = keyboard
return &msg
}
func GenerateUserMessage(up *tgbotapi.User, adm int64, bn string) *tgbotapi.MessageConfig {
txt := fmt.Sprintf(`User %s
ID: %d
FirstName: %s
LastName: %s
ChatID: %d
`, up.UserName, up.ID, up.FirstName, up.LastName, up.ID)
msg := tgbotapi.NewMessage(adm, txt)
keyboard := tgbotapi.InlineKeyboardMarkup{}
row := tgbotapi.NewInlineKeyboardRow()
row = append(row, tgbotapi.NewInlineKeyboardButtonData("ban", fmt.Sprintf("operation=ban&userID=%d&bot=%s", up.ID, bn)))
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Delete", fmt.Sprintf("operation=delete&userID=%d&bot=%s", up.ID, bn)))
row = append(row, tgbotapi.NewInlineKeyboardButtonData("Ignore", fmt.Sprintf("operation=ignore&userID=%d&bot=%s", up.ID, bn)))
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, row)
msg.ReplyMarkup = keyboard
return &msg
}
// checkUserIsAdmin
// if user is admin return true else false
func checkUserIsAdmin(c *command)bool{
if !auth.IsUserAdmin(c.update.Message.From.ID){
text := "only admin can execute this command"
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return false
}
return true
}
// bannedUserCommand
// check for user banned on the bot and send all for revision to admin requesting it
func bannedUserCommand(c *command, msg tgbotapi.MessageConfig){
buser, err := c.uSVC.GetAllBannedUsers(c.bot.Self.UserName)
if err != nil {
c.log.Error("error querying banned user")
}
if len(buser.Bans) <=0{
msg.Text = "users baned not found"
c.bot.Send(msg )
return
}
for _, u := range buser.GetBans(){
us, _ := c.uSVC.Get(u.TgbId)
tgbUs := tgbotapi.User{
ID: us.TguID,
FirstName: us.FirstName,
LastName: us.LastName,
UserName: us.Username,
}
// send message to issuer
msg := GenerateAppealRequestMessage(&tgbUs, c.update.SentFrom().ID, c.bot.Self.UserName)
c.bot.Send(msg )
}
}
// appealUserCommand
// user banned has the oportunity to appeal a command if is unjustify its banning
func appealUserCommand(c *command, msg tgbotapi.MessageConfig){
buser, err := c.uSVC.GetAllBannedUsers(c.bot.Self.UserName)
if err != nil {
c.log.Error("error querying banned user")
}
if len(buser.GetBans())<= 0{
msg.Text = "users baned not found"
c.bot.Send(msg )
return
}
for _, u := range buser.GetBans(){
adms, _:= auth.GetAdminFromEnv()
if u.TgbId == c.update.SentFrom().ID{
for _, adm := range adms{
// send message to all admins
msg := GenerateAppealRequestMessage(c.update.SentFrom(), adm, c.bot.Self.UserName)
c.bot.Send(msg )
}
c.bot.Send(msg)
return
}
}
}
// pendingUserCommand
// render pending access request for bot
func pendingUserCommand(c *command, msg tgbotapi.MessageConfig){
pac, _ := c.uSVC.GetAllAccessRequest(c.bot.Self.UserName)
if len(pac.Access) <= 0 {
msg.Text = "there are no users access request"
c.bot.Send(msg )
return
}
for _, ac := range pac.Access {
us, err := c.uSVC.Get(ac.TgbId)
if err != nil{
c.log.Error("geting user", "tgbID", us.TguID)
return
}
tgbUS := tgbotapi.User{
UserName: us.Username,
FirstName: us.FirstName,
LastName: us.LastName,
ID: us.TguID,
}
msg := auth.GenerateAccessRequestMessage(&tgbUS, c.update.Message.From.ID, c.bot.Self.UserName)
c.bot.Send(msg)
}
}
// usersCommand
// render all users on the bot to ban or delete
func usersCommand(c *command, msg tgbotapi.MessageConfig){
usL , err :=c.uSVC.GetAllBotsUsers(c.bot.Self.UserName)
if err != nil {
c.log.Error("geting users from bot")
return
}
if len(usL) <= 0 {
msg.Text = "users not found registered"
c.bot.Send(msg )
return
}
for _, us := range usL{
if auth.IsUserAdmin(us.TguID){
continue
}
tgbUS := tgbotapi.User{
UserName: us.Username,
FirstName: us.FirstName,
LastName: us.LastName,
ID: us.TguID,
}
msg := GenerateUserMessage(&tgbUS, c.update.SentFrom().ID, c.bot.Self.UserName)
c.bot.Send(msg)
}
}
// userCommand
// render an user message with querys ban delete
func userCommand(c *command){
msgTXT := strings.Split(c.update.Message.Text, " ")
if len(msgTXT) <= 1{
text := "command require username argument"
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
userName := msgTXT[1]
usL , err :=c.uSVC.GetAllBotsUsers(c.bot.Self.UserName)
if err != nil {
c.log.Error("geting users from bot")
return
}
for _, us := range usL{
if us.Username == userName{
tgbUS := tgbotapi.User{
UserName: us.Username,
FirstName: us.FirstName,
LastName: us.LastName,
ID: us.TguID,
}
msg := GenerateUserMessage(&tgbUS, c.update.SentFrom().ID, c.bot.Self.UserName)
c.bot.Send(msg)
}
}
}
// userIDCommand
// Render a message with user info and querys ban delete
func userIDCommand(c *command){
msgTXT := strings.Split(c.update.Message.Text, " ")
if len(msgTXT) <= 1{
text := "command require username argument"
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
userIDSTR := msgTXT[1]
userID, err := strconv.ParseInt(userIDSTR, 10, 64)
if err != nil {
c.log.Error("geting users from bot", "error", err)
text := "wrong userID argument "+ err.Error()
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
us , err :=c.uSVC.Get(userID)
if err != nil {
c.log.Error("geting user from bot", "error", err)
text := "ERROR: "+ err.Error()
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
bots , err := c.uSVC.GetBots(us.TguID)
if err != nil {
c.log.Error("geting bots for user ", "error", err)
text := "ERROR: "+ err.Error()
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
if !slices.Contains(bots, c.bot.Self.UserName){
c.log.Error("user is not part of bot list")
text := "user is not part of bot list"
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
tgbUS := tgbotapi.User{
UserName: us.Username,
FirstName: us.FirstName,
LastName: us.LastName,
ID: us.TguID,
}
msg := GenerateUserMessage(&tgbUS, c.update.SentFrom().ID, c.bot.Self.UserName)
c.bot.Send(msg)
}
// banCommand
// ban an user by its user name as param
func banCommand(c *command, msg tgbotapi.MessageConfig){
msgTXT := strings.Split(c.update.Message.Text, " ")
if len(msgTXT) <= 1{
text := "command require username argument"
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
userName := msgTXT[1]
usL , err :=c.uSVC.GetAllBotsUsers(c.bot.Self.UserName)
if err != nil {
c.log.Error("geting users from bot")
text := "ERROR: "+ err.Error()
c.bot.Send(tgbotapi.NewMessage(c.update.FromChat().ID, text))
return
}
for _, us := range usL{
if us.Username == userName{
tgbUS := tgbotapi.User{
UserName: us.Username,
FirstName: us.FirstName,
LastName: us.LastName,
ID: us.TguID,
}
msg := GenerateUserMessage(&tgbUS, c.update.SentFrom().ID, c.bot.Self.UserName)
c.bot.Send(msg)
return
}
}
msg.Text = "user not foun on bot list"
c.bot.Send(msg)
}

View File

@ -0,0 +1,7 @@
package domain
type Cedula struct {
MunCed string `json:"mun_ced"`
SeqCed string `json:"seq_ced"`
VerCed string `json:"ver_ced"`
}

View File

@ -0,0 +1,12 @@
package domain
type imagen []byte
type Foto struct {
ID int64 `json:"id"`
MunCed string `json:"mun_ced"`
SeqCed string `json:"seq_ced"`
VerCed string `json:"ver_ced"`
Sequencia int64 `json:"sequencia"`
Imagen imagen `json:"imagen"`
}

View File

@ -0,0 +1,19 @@
package domain
type Info struct {
Cedula
ID int64 `json:"id"`
Nombres string `json:"nombres"`
Apellido1 string `json:"apellido1"`
Apellido2 string `json:"apellido2"`
Sexo string `json:"sexo"`
Direccion string `json:"direccion"`
Telefono string `json:"telefono"`
FechaNac string `json:"fecha_nac"`
}
type MultipleResults struct {
Data []*Info `json:"data"`
Total int64 `json:"total"`
Page int64 `json:"page"`
}

View File

@ -0,0 +1,13 @@
package domain
type User struct {
ID int64
TguID int64
Username string `json:"username"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Subs []string
Created int64
Edited int64
Deleted int64
}

View File

@ -0,0 +1,192 @@
package message
import (
"bytes"
"context"
"fmt"
"log/slog"
"strconv"
"strings"
"sync"
"git.maximotejeda.com/maximo/cedulados-bot/internal/adapter/helper"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var ChatPool *sync.Pool
type Message struct {
bot *tgbotapi.BotAPI
update *tgbotapi.Update
msg *tgbotapi.MessageConfig
log *slog.Logger
ce ports.CeduladosService
user ports.UserService
}
// NewMessage
// Factory for message handler
func NewMessage(bot *tgbotapi.BotAPI, update *tgbotapi.Update, cSVC ports.CeduladosService, user ports.UserService) *Message {
if ChatPool == nil {
ChatPool = &sync.Pool{
New: func() any { return &Message{} },
}
for i := 0; i < 20; i++ {
ChatPool.Put(ChatPool.New())
}
}
log := slog.Default()
log = log.With("function", "message", "chat", update.Message.Chat.ID, "userid", update.Message.From.ID, "username", update.Message.From.UserName)
message := ChatPool.Get().(*Message)
message.update = update
message.bot = bot
message.log = log
message.ce = cSVC
message.user = user
return message
}
// Empty
// Returns pointer to pool
func (m *Message) Empty() {
m.update = nil
m.msg = nil
m.log = nil
m.ce = nil
m.user = nil
ChatPool.Put(m)
}
// Send
// Process message sending to bot
func (m *Message) Send() {
defer m.Empty()
m.bot.Send(m.msg)
}
func (m *Message) Handler(){
msg := tgbotapi.NewMessage(m.update.Message.Chat.ID, "")
m.msg = &msg
text := helper.RemoveAccent(m.update.Message.Text)
textList := strings.Split(text, " ")
if len(textList) >= 1 && helper.MessageChecker(text) == "digit" {
// in case of message match a cedula
ced, err := helper.NewCedula(context.Background(), text)
if err != nil {
msg.Text = "cedula no reconocida " + err.Error()
} else {
msg, photoMsg := ProcessByCedula(context.Background(), m.ce, m.user, ced)
msg.ChatID = m.update.Message.Chat.ID
if photoMsg != nil {
photoMsg.ChatID = m.update.Message.Chat.ID
m.bot.Send(photoMsg)
}
m.bot.Send(msg)
return
}
} else if len(textList) >= 2 && helper.MessageChecker(text) == "word" {
msg := ProcessByName(context.Background(), m.ce, m.user, textList)
msg.ChatID = m.update.Message.Chat.ID
m.bot.Send(msg)
}
msg.ReplyToMessageID = m.update.Message.MessageID
m.bot.Send(msg)
}
// ProcessByCedula
//
// When a text arrives the chat if it match a cedula try to query db
func ProcessByCedula(ctx context.Context, cSVC ports.CeduladosService, uSVC ports.UserService, ced *domain.Cedula) (message *tgbotapi.MessageConfig, fotoMsg *tgbotapi.PhotoConfig) {
msg := tgbotapi.NewMessage(0, "")
message = &msg
info , err := cSVC.CeduladoByCedula(context.Background(), ced)
if err != nil {
fmt.Println("error on query ", err)
}
fmt.Println("!!!!success on query", info)
if info != nil {
msg.Text = fmt.Sprintf(`
Nombres: %s
Apellidos: %s %s
Cedula: %s
Direccion: %s
Telefono: %s
`,
strings.ToLower(info.Nombres), strings.ToLower(info.Apellido1), strings.ToLower(info.Apellido2),
info.MunCed+info.SeqCed+info.VerCed, helper.RemoveSpaces(info.Direccion), info.Telefono)
}
foto, err := cSVC.QueryFotoByCedula(ctx, ced)
if err != nil {
fmt.Println("Photo not found", err.Error())
return
}
if foto != nil {
rq := tgbotapi.FileReader{Name: fmt.Sprintf("%s-%s-%s", ced.MunCed, ced.SeqCed, ced.VerCed), Reader: bytes.NewReader(foto.Imagen)}
fotost := tgbotapi.NewPhoto(msg.ChatID, rq)
fotoMsg = &fotost
}
return
}
func ProcessByName(ctx context.Context, cSVC ports.CeduladosService, uSVC ports.UserService, nameList []string) (message *tgbotapi.MessageConfig) {
var err error
page := int64(0)
// look for if the last part of the list is a number
lastItem := nameList[len(nameList)-1]
if helper.MessageChecker(lastItem) == "digit" && !strings.HasPrefix(lastItem, "0") {
pageInt, err := strconv.ParseInt(lastItem, 10, 64)
if err != nil {
fmt.Println(err)
}
nameList = nameList[:len(nameList)-1]
if pageInt < 20 {
page = pageInt
}
}
rows := &domain.MultipleResults{}
message = &tgbotapi.MessageConfig{}
text := strings.Join(nameList, " ")
res, err := cSVC.CeduladoByFTS(ctx, text, 0)
if err != nil {
fmt.Println("error oon fts", err)
}
fmt.Println("sucess", res)
rows, err = cSVC.CeduladoByFTS(ctx, text, page)
if err != nil {
message.Text = "no hubo resultados para la busqueda"
return
}
textToSend := fmt.Sprintf(`
Busqueda:
Texto: %s
Pagina: %d
resultados desde: %d A %d
De un total de: %d
`,
text,
(rows.Page),
(rows.Page*10)-9, (rows.Page)*10,
rows.Total)
for _, v := range rows.Data {
textToSend = textToSend + fmt.Sprintf("\n %s %s %s \n %s-%s-%s\n", strings.ToLower(v.Nombres), strings.ToLower(v.Apellido1), strings.ToLower(v.Apellido2), v.MunCed, v.SeqCed, v.VerCed)
}
message.Text = textToSend
return
}

View File

@ -0,0 +1,191 @@
package message
import (
"bytes"
"context"
"fmt"
"log/slog"
"strconv"
"strings"
"sync"
"git.maximotejeda.com/maximo/cedulados-bot/internal/adapter/helper"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var ChatPool *sync.Pool
type Message struct {
bot *tgbotapi.BotAPI
update *tgbotapi.Update
msg *tgbotapi.MessageConfig
log *slog.Logger
ce ports.CeduladosService
user ports.UserService
}
// NewMessage
// Factory for message handler
func NewMessage(bot *tgbotapi.BotAPI, update *tgbotapi.Update, cSVC ports.CeduladosService, user ports.UserService) *Message {
if ChatPool == nil {
ChatPool = &sync.Pool{
New: func() any { return &Message{} },
}
for i := 0; i < 20; i++ {
ChatPool.Put(ChatPool.New())
}
}
log := slog.Default()
log = log.With("function", "message", "chat", update.Message.Chat.ID, "userid", update.Message.From.ID, "username", update.Message.From.UserName)
message := ChatPool.Get().(*Message)
message.update = update
message.bot = bot
message.log = log
message.ce = cSVC
message.user = user
return message
}
// Empty
// Returns pointer to pool
func (m *Message) Empty() {
m.update = nil
m.msg = nil
m.log = nil
m.ce = nil
m.user = nil
ChatPool.Put(m)
}
// Send
// Process message sending to bot
func (m *Message) Send() {
defer m.Empty()
m.bot.Send(m.msg)
}
func (m *Message) Handler(){
msg := tgbotapi.NewMessage(m.update.Message.Chat.ID, "")
m.msg = &msg
text := helper.RemoveAccent(m.update.Message.Text)
textList := strings.Split(text, " ")
if len(textList) >= 1 && helper.MessageChecker(text) == "digit" {
// in case of message match a cedula
ced, err := helper.NewCedula(context.Background(), text)
if err != nil {
msg.Text = "cedula no reconocida " + err.Error()
} else {
msg, photoMsg := ProcessByCedula(context.Background(), m.ce, m.user, ced)
msg.ChatID = m.update.Message.Chat.ID
if photoMsg != nil {
photoMsg.ChatID = m.update.Message.Chat.ID
m.bot.Send(photoMsg)
}
m.bot.Send(msg)
return
}
} else if len(textList) >= 2 && helper.MessageChecker(text) == "word" {
msg := ProcessByName(context.Background(), m.ce, m.user, textList)
msg.ChatID = m.update.Message.Chat.ID
m.bot.Send(msg)
}
msg.ReplyToMessageID = m.update.Message.MessageID
m.bot.Send(msg)
}
// ProcessByCedula
//
// When a text arrives the chat if it match a cedula try to query db
func ProcessByCedula(ctx context.Context, cSVC ports.CeduladosService, uSVC ports.UserService, ced *domain.Cedula) (message *tgbotapi.MessageConfig, fotoMsg *tgbotapi.PhotoConfig) {
msg := tgbotapi.NewMessage(0, "")
message = &msg
info , err := cSVC.CeduladoByCedula(context.Background(), ced)
if err != nil {
fmt.Println("error on query ", err)
}
fmt.Println("!!!!success on query", info)
if info != nil {
msg.Text = fmt.Sprintf(`
Nombres: %s
Apellidos: %s %s
Cedula: %s
Direccion: %s
Telefono: %s
`,
strings.ToLower(info.Nombres), strings.ToLower(info.Apellido1), strings.ToLower(info.Apellido2),
info.MunCed+info.SeqCed+info.VerCed, helper.RemoveSpaces(info.Direccion), info.Telefono)
}
foto, err := cSVC.QueryFotoByCedula(ctx, ced)
if err != nil {
fmt.Println("Photo not found", err.Error())
return
}
if foto != nil {
rq := tgbotapi.FileReader{Name: fmt.Sprintf("%s-%s-%s", ced.MunCed, ced.SeqCed, ced.VerCed), Reader: bytes.NewReader(foto.Imagen)}
fotost := tgbotapi.NewPhoto(msg.ChatID, rq)
fotoMsg = &fotost
}
return
}
func ProcessByName(ctx context.Context, cSVC ports.CeduladosService, uSVC ports.UserService, nameList []string) (message *tgbotapi.MessageConfig) {
var err error
page := int64(0)
// look for if the last part of the list is a number
lastItem := nameList[len(nameList)-1]
if helper.MessageChecker(lastItem) == "digit" && !strings.HasPrefix(lastItem, "0") {
pageInt, err := strconv.ParseInt(lastItem, 10, 64)
if err != nil {
fmt.Println(err)
}
nameList = nameList[:len(nameList)-1]
if pageInt < 20 {
page = pageInt
}
}
rows := &domain.MultipleResults{}
message = &tgbotapi.MessageConfig{}
text := strings.Join(nameList, " ")
res, err := cSVC.CeduladoByFTS(ctx, text, 0)
if err != nil {
fmt.Println("error oon fts", err)
}
fmt.Println("sucess", res)
rows, err = cSVC.CeduladoByFTS(ctx, text, page)
if err != nil {
message.Text = "no hubo resultados para la busqueda"
return
}
textToSend := fmt.Sprintf(`
Busqueda:
Texto: %s
Pagina: %d
resultados desde: %d A %d
De un total de: %d
`,
text,
(rows.Page),
(rows.Page*10)-9, (rows.Page)*10,
rows.Total)
for _, v := range rows.Data {
textToSend = textToSend + fmt.Sprintf("\n %s %s %s \n %s-%s-%s\n", strings.ToLower(v.Nombres), strings.ToLower(v.Apellido1), strings.ToLower(v.Apellido2), v.MunCed, v.SeqCed, v.VerCed)
}
message.Text = textToSend
return
}

View File

@ -0,0 +1,167 @@
package query
import (
"fmt"
"log/slog"
"strconv"
"strings"
"sync"
"time"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/auth"
"git.maximotejeda.com/maximo/cedulados-bot/internal/ports"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var ChatPool *sync.Pool
type Query struct {
bot *tgbotapi.BotAPI
update *tgbotapi.Update
msg *tgbotapi.MessageConfig
delMSG *tgbotapi.DeleteMessageConfig
log *slog.Logger
ce ports.CeduladosService
uSVC ports.UserService
}
// NewMessage
// Factory for query handler
func NewQuery(bot *tgbotapi.BotAPI, update *tgbotapi.Update, cSVC ports.CeduladosService, user ports.UserService) *Query {
if ChatPool == nil {
ChatPool = &sync.Pool{
New: func() any { return &Query{} },
}
for i := 0; i < 20; i++ {
ChatPool.Put(ChatPool.New())
}
}
log := slog.Default()
log = log.With("function", "Query", "chat", update.CallbackQuery.From.ID, "userid", update.CallbackQuery.From.ID, "username", update.CallbackQuery.From.UserName)
query := ChatPool.Get().(*Query)
query.update = update
query.bot = bot
query.log = log
query.ce = cSVC
query.uSVC = user
return query
}
// Empty
// Returns pointer to pool
func (q *Query) Empty() {
q.update = nil
q.msg = nil
q.log = nil
q.ce = nil
q.uSVC = nil
ChatPool.Put(q)
}
// Send
// Process message sending to bot
func (q *Query) Send() {
defer q.Empty()
q.bot.Send(q.msg)
if q.delMSG != nil {
q.bot.Send(q.delMSG)
}
}
func (q *Query) Handler(){
msg := tgbotapi.NewMessage(q.update.CallbackQuery.From.ID, "")
delMSG := tgbotapi.NewDeleteMessage(q.update.CallbackQuery.From.ID, q.update.CallbackQuery.Message.MessageID)
text := ""
q.msg = &msg
q.delMSG = &delMSG
data := q.update.CallbackQuery.Data
dataList := strings.Split(data, "&")
dataMap := map[string]string{}
for _, val := range dataList{
subData := strings.Split(val, "=")
dataMap[subData[0]] = subData[1]
}
userSTR := dataMap["userID"]
userID, err := strconv.ParseInt(userSTR, 10, 64)
if err != nil {
text = fmt.Sprintf("could not parse int %s", userSTR)
q.log.Error("could not parse id", "userID", userSTR)
return
}
switch dataMap["operation"]{
case "grant":
// grant user access to a bot
if !auth.IsUserAdmin(q.update.CallbackQuery.From.ID){
text = "only admin can execute this command"
}else{
q.log.Info("inside grant")
_, err := q.uSVC.GrantAccess(userID, dataMap["bot"])
if err != nil {
q.log.Error("granting access to bot", "user", userID, "bot", dataMap["bot"])
text = fmt.Sprintf("ERROR: grant user access: %s", err)
}else {
text = fmt.Sprintf("granted access to user %s to bot %s", userSTR, dataMap["bot"])
go func (){
userMsg := tgbotapi.NewMessage(userID, "Granted access permissions!!")
q.bot.Send(userMsg)
}()
}
}
case "deny":
if !auth.IsUserAdmin(q.update.CallbackQuery.From.ID){
text = "only admin can execute this command"
}else{
// deny user access to a bot
_, err := q.uSVC.BanUser(userID, time.Now().Add(24 * 365 * time.Hour).Unix(), dataMap["bot"])
if err != nil {
q.log.Error("baning user", "user", userID, "bot", dataMap["bot"])
text = fmt.Sprintf("ERROR: banning user: %s", err)
}else {
text = fmt.Sprintf("user banned user: %s, bot: %s", userSTR, dataMap["bot"])
}
}
case "ignore":
text = fmt.Sprintf("user request ignored user: %s", userSTR)
// ignore user access request
case "ban":
if !auth.IsUserAdmin(q.update.CallbackQuery.From.ID){
text = "only admin can execute this command"
}else{
_, err := q.uSVC.BanUser(userID, time.Now().Add(24 * 365 * time.Hour).Unix(), dataMap["bot"])
if err != nil {
q.log.Error("baning user", "user", userID, "bot", dataMap["bot"])
text = fmt.Sprintf("ERROR: banning user: %s", err)
}else{
text = fmt.Sprintf("baned user: %s, bot: %s", userSTR, dataMap["bot"])
}
// TODO: ext = fmt.Sprintf("user banned user: %s, bot: %s", userSTR, dataMap["bot"])
}
case "unban":
if !auth.IsUserAdmin(q.update.CallbackQuery.From.ID){
text = "only admin can execute this command"
}else{
_, err := q.uSVC.UnBanUser(userID, dataMap["bot"])
if err != nil {
q.log.Error("baning user", "user", userID, "bot", dataMap["bot"])
text = fmt.Sprintf("ERROR: unbanning user: %s", err)
}else {
text = fmt.Sprintf("user banned user: %s, bot: %s", userSTR, dataMap["bot"])
}
}
case "delete":
if !auth.IsUserAdmin(q.update.CallbackQuery.From.ID){
text = "only admin can execute this command"
}else{
_, err := q.uSVC.DeleteBot(userID, q.bot.Self.UserName)
if err != nil {
q.log.Error("baning user", "user", userID, "bot", dataMap["bot"])
text = fmt.Sprintf("ERROR: deleted user: %s from bot %s", err, q.bot.Self.UserName)
}else {
text = fmt.Sprintf("user deleted user: %s, bot: %s", userSTR, dataMap["bot"])
}
}
}
q.msg.Text = text
q.Send()
}

View File

@ -0,0 +1,17 @@
package ports
import (
"context"
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
)
type CeduladosService interface {
CeduladoByCedula(ctx context.Context, c *domain.Cedula)(resp *domain.Info, err error)
CeduladoGetByNameLastName(ctx context.Context, params domain.Info, page int64)(info *domain.MultipleResults, err error)
CeduladoByFTS(ctx context.Context, params string, page int64)(info *domain.MultipleResults, err error)
CeduladoByNameAndLocation(ctx context.Context, params domain.Info, page int64, municipio string)(info *domain.MultipleResults, err error)
QueryFotoByCedula(ctx context.Context, c *domain.Cedula)(info *domain.Foto, err error)
QueryFotoByID(ctx context.Context, id int64)(info *domain.Foto, err error)
QueryFotoAllCedulas(ctx context.Context, ceds []*domain.Cedula)(fotos []*domain.Foto, err error)
}

7
internal/ports/tgb.go Normal file
View File

@ -0,0 +1,7 @@
package ports
type Tgb interface {
Send()
Empty()
Handler()
}

26
internal/ports/user.go Normal file
View File

@ -0,0 +1,26 @@
package ports
import (
"git.maximotejeda.com/maximo/cedulados-bot/internal/application/domain"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"git.maximotejeda.com/maximo/tgb-user/proto/golang/tgbuser"
)
type UserService interface {
Get(int64) (*domain.User, error)
Edit(*tgbotapi.User) (bool, error)
Delete(int64) (bool, error)
Create(*tgbotapi.User) (bool, error)
AddBot(int64, string) (bool, error)
GetBots(int64) ([]string, error)
DeleteBot(int64, string) (bool, error)
GetAllBotsUsers(string) ([]*domain.User, error)
CreateBot(string)(error)
CreateAccessRequest(int64, string)(bool, error)
GrantAccess(int64, string)(bool, error)
GetAllAccessRequest(string)(*tgbuser.GetAccessResponse, error)
BanUser(int64, int64, string)(bool, error)
UnBanUser(int64, string)(bool, error)
GetAllBannedUsers(string)(*tgbuser.GetBanResponse, error)
GetAccessRequest(int64) (*tgbuser.GetAccessResponse, error)
}

View File

@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: us-dop-bot
labels:
app: us-dop-bot
spec:
replicas: 1
selector:
matchLabels:
app: us-dop-bot
template:
metadata:
labels:
app: us-dop-bot
name: us-dop-bot
spec:
containers:
- name: us-dop-bot
image: localhost:32000/us-dop-bot:latest
env:
- name: DBURI
value: $DBURI
- name: NATSURI
value: "nats://nats-svc:4222"
- name: TOKEN
value: "$PRODTOKEN"
volumeMounts:
- name: database
mountPath: /app/dolardb
volumes:
- name: database
persistentVolumeClaim:
claimName: bank-crawler-pvc