-- envio de email al admini en caso de error
This commit is contained in:
parent
bbc517d256
commit
e4f4c2294d
11
.env
11
.env
@ -48,3 +48,14 @@ AUTH_METHOD=POST
|
|||||||
TM_HEADER_ORIGIN=https://azure-function.timemanagerweb.com
|
TM_HEADER_ORIGIN=https://azure-function.timemanagerweb.com
|
||||||
TM_HEADER_TENANT_NAME=pruebas-dos
|
TM_HEADER_TENANT_NAME=pruebas-dos
|
||||||
TM_HEADER_USER_AGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
TM_HEADER_USER_AGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
||||||
|
|
||||||
|
|
||||||
|
# Configuración de correo SMTP
|
||||||
|
EMAIL_HOST=mail.myapps.bo
|
||||||
|
EMAIL_PORT=465
|
||||||
|
EMAIL_USER=noreply@myapps.bo
|
||||||
|
EMAIL_PASSWORD=sb6+3vchj11e
|
||||||
|
EMAIL_FROM=noreply@myapps.bo
|
||||||
|
EMAIL_ADMIN=sureflatron3@gmail.com
|
||||||
|
|
||||||
|
APP_NAME=Rendición de Gastos
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -11,7 +11,7 @@
|
|||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
*.log
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
@ -29,6 +29,8 @@ go.work
|
|||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
# User-specific stuff
|
# User-specific stuff
|
||||||
|
.idea/**
|
||||||
|
.logs/*.log
|
||||||
.idea/**/workspace.xml
|
.idea/**/workspace.xml
|
||||||
.idea/**/tasks.xml
|
.idea/**/tasks.xml
|
||||||
.idea/**/usage.statistics.xml
|
.idea/**/usage.statistics.xml
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/tuusuario/go-sync-service/internal/config"
|
"github.com/tuusuario/go-sync-service/internal/config"
|
||||||
|
email "github.com/tuusuario/go-sync-service/internal/email"
|
||||||
"github.com/tuusuario/go-sync-service/internal/scheduler"
|
"github.com/tuusuario/go-sync-service/internal/scheduler"
|
||||||
"github.com/tuusuario/go-sync-service/metrics"
|
"github.com/tuusuario/go-sync-service/metrics"
|
||||||
)
|
)
|
||||||
@ -26,6 +27,8 @@ func main() {
|
|||||||
config.InitLogger(conf)
|
config.InitLogger(conf)
|
||||||
config.Log.Info("🚀 Iniciando servicio: go-sync-service")
|
config.Log.Info("🚀 Iniciando servicio: go-sync-service")
|
||||||
|
|
||||||
|
emailService := email.NewSMTPEmailService(conf)
|
||||||
|
|
||||||
// Conexión a Redis
|
// Conexión a Redis
|
||||||
redisClient := config.GetRedisClient(conf)
|
redisClient := config.GetRedisClient(conf)
|
||||||
if err := redisClient.Ping(context.Background()).Err(); err != nil {
|
if err := redisClient.Ping(context.Background()).Err(); err != nil {
|
||||||
@ -44,7 +47,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
config.Log.Info("✅ Conexión a base de datos establecida")
|
config.Log.Info("✅ Conexión a base de datos establecida")
|
||||||
|
|
||||||
scheduler.Start(context.Background(), redisClient, redisConfigProvider, database)
|
scheduler.Start(context.Background(), redisClient, redisConfigProvider, database, *emailService)
|
||||||
config.Log.Info("✅ Scheduler en ejecución y escuchando recargas")
|
config.Log.Info("✅ Scheduler en ejecución y escuchando recargas")
|
||||||
|
|
||||||
//metrics Grafana
|
//metrics Grafana
|
||||||
|
|||||||
17
go.mod
17
go.mod
@ -27,8 +27,8 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/sync v0.13.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gorm.io/gorm v1.30.0
|
gorm.io/gorm v1.30.0
|
||||||
)
|
)
|
||||||
@ -41,9 +41,9 @@ require (
|
|||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
golang.org/x/crypto v0.42.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
)
|
)
|
||||||
@ -52,9 +52,16 @@ require github.com/elastic/go-elasticsearch v0.0.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.28.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.62.0 // indirect
|
github.com/prometheus/common v0.62.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
24
go.sum
24
go.sum
@ -18,6 +18,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||||
|
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
@ -44,6 +52,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
@ -92,22 +102,36 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
|||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@ -55,6 +55,16 @@ type Config struct {
|
|||||||
TM_HEADER_ORIGIN string `mapstructure:"TM_HEADER_ORIGIN"`
|
TM_HEADER_ORIGIN string `mapstructure:"TM_HEADER_ORIGIN"`
|
||||||
TM_HEADER_TENANT_NAME string `mapstructure:"TM_HEADER_TENANT_NAME"`
|
TM_HEADER_TENANT_NAME string `mapstructure:"TM_HEADER_TENANT_NAME"`
|
||||||
TM_HEADER_USER_AGENT string `mapstructure:"TM_HEADER_USER_AGENT"`
|
TM_HEADER_USER_AGENT string `mapstructure:"TM_HEADER_USER_AGENT"`
|
||||||
|
|
||||||
|
// Email
|
||||||
|
EmailHost string `mapstructure:"EMAIL_HOST"`
|
||||||
|
EmailPort int `mapstructure:"EMAIL_PORT"`
|
||||||
|
EmailUser string `mapstructure:"EMAIL_USER"`
|
||||||
|
EmailPassword string `mapstructure:"EMAIL_PASSWORD"`
|
||||||
|
EmailFrom string `mapstructure:"EMAIL_FROM"`
|
||||||
|
EmailAdmin string `mapstructure:"EMAIL_ADMIN"`
|
||||||
|
|
||||||
|
AppName string `mapstructure:"APP_NAME"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var GlobalConfig *Config
|
var GlobalConfig *Config
|
||||||
|
|||||||
139
internal/email/email_service.go
Normal file
139
internal/email/email_service.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/tuusuario/go-sync-service/internal/config"
|
||||||
|
"github.com/tuusuario/go-sync-service/internal/domain/ports"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"gopkg.in/gomail.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmailService define la interfaz para el envío de correos
|
||||||
|
type EmailService interface {
|
||||||
|
SendEmail(email EmailRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMTPEmailService implementa EmailService con SMTP
|
||||||
|
type SMTPEmailService struct {
|
||||||
|
Config *config.Config
|
||||||
|
Validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMTPEmailService retorna una nueva instancia del servicio de email con validación
|
||||||
|
func NewSMTPEmailService(cfg *config.Config) *SMTPEmailService {
|
||||||
|
return &SMTPEmailService{
|
||||||
|
Config: cfg,
|
||||||
|
Validator: validator.New(), // Inicializa el validador
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailRequest estructura para la validación del email
|
||||||
|
type EmailRequest struct {
|
||||||
|
To string `json:"correoElectronico" validate:"required,email"`
|
||||||
|
Subject string `json:"asunto" validate:"required,min=3,max=100"`
|
||||||
|
Body string `json:"cuerpo" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEmail valida y envía un correo electrónico usando SMTP
|
||||||
|
func (s *SMTPEmailService) SendEmail(email EmailRequest) error {
|
||||||
|
// Validar la estructura antes de enviarla
|
||||||
|
/*
|
||||||
|
err := s.Validator.Struct(email)
|
||||||
|
if err != nil {
|
||||||
|
for _, err := range err.(validator.ValidationErrors) {
|
||||||
|
log.Printf("❌ Error de validación en %s: %s", err.Field(), err.Tag())
|
||||||
|
}
|
||||||
|
return fmt.Errorf("❌ Error en la validación del email: %v", err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
smtpHost := s.Config.EmailHost
|
||||||
|
smtpPort := s.Config.EmailPort
|
||||||
|
smtpUser := s.Config.EmailUser
|
||||||
|
smtpPass := s.Config.EmailPassword
|
||||||
|
emailFrom := s.Config.EmailFrom
|
||||||
|
|
||||||
|
if smtpHost == "" || smtpUser == "" || smtpPass == "" || emailFrom == "" || smtpPort == 0 {
|
||||||
|
log.Println("❌ ERROR: Faltan variables de entorno para SMTP")
|
||||||
|
return fmt.Errorf("faltan variables de entorno para el envío de correos")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("📧 Enviando correo desde: %s", emailFrom)
|
||||||
|
|
||||||
|
mail := gomail.NewMessage()
|
||||||
|
mail.SetHeader("From", emailFrom)
|
||||||
|
mail.SetHeader("To", email.To)
|
||||||
|
mail.SetHeader("Subject", email.Subject)
|
||||||
|
mail.SetBody("text/html", email.Body)
|
||||||
|
dialer := gomail.NewDialer(smtpHost, smtpPort, smtpUser, smtpPass)
|
||||||
|
err := dialer.DialAndSend(mail)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Error enviando correo a %s: %s", email.To, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("✅ Correo enviado con éxito a %s", email.To)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPEmailService) PrepareEmail(asunto string, mensaje string, cfg ports.RedisConfigProvider) error {
|
||||||
|
// Validar la estructura antes de enviarla
|
||||||
|
branding := LoadBrandingAsignation(cfg, s.Config.AppName)
|
||||||
|
|
||||||
|
// Cargar destinatarios del entorno
|
||||||
|
destinos := s.LoadDestinosCorreo(cfg)
|
||||||
|
|
||||||
|
// Cargar datos para la plantilla
|
||||||
|
data := TaskTplData{
|
||||||
|
AppName: branding.AppName,
|
||||||
|
Nombre: "Administrador",
|
||||||
|
TareaNombre: asunto, // Ejemplo
|
||||||
|
Descripcion: mensaje, // Ejemplo
|
||||||
|
FechaError: time.Now().Format("02/01/2006 15:04"), // Ejemplo
|
||||||
|
ErrorDescripcion: mensaje,
|
||||||
|
LinkTarea: "",
|
||||||
|
LogoURL: branding.LogoURL,
|
||||||
|
Direccion: branding.Direccion,
|
||||||
|
Telefono: branding.Telefono,
|
||||||
|
EmailContacto: branding.EmailContacto,
|
||||||
|
Year: branding.Year,
|
||||||
|
ColorHeaderBG: branding.ColorHeaderBG,
|
||||||
|
ColorAccent: branding.ColorAccent,
|
||||||
|
ColorFooterText: branding.ColorFooterText,
|
||||||
|
ColorFooterMuted: branding.ColorFooterMuted,
|
||||||
|
ColorBodyBG: branding.ColorBodyBG,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderizar el correo
|
||||||
|
body, err := RenderTaskEmailHTML("", data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error al renderizar la plantilla de tarea: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear el correo
|
||||||
|
subject := BuildTaskSubject(branding.AppName)
|
||||||
|
reqMail := EmailRequest{
|
||||||
|
To: *destinos, // Usamos la lista de destinos cargada
|
||||||
|
Subject: subject,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enviar el correo
|
||||||
|
if err := s.SendEmail(reqMail); err != nil {
|
||||||
|
return fmt.Errorf("error al enviar el correo de notificación de tarea: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cargar los correos electrónicos desde la configuración
|
||||||
|
func (s *SMTPEmailService) LoadDestinosCorreo(cfg ports.RedisConfigProvider) *string {
|
||||||
|
destinos, err := cfg.GetString("SYNCRONIZADOR_EMAIL_DESTINOS") // Asumiendo que usas RedisConfigProvider
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(" ERROR no se pudo cargar la lista de destinatarios: %v", err)
|
||||||
|
destinos = s.Config.EmailAdmin
|
||||||
|
}
|
||||||
|
return &destinos
|
||||||
|
}
|
||||||
155
internal/email/task_asignation.go
Normal file
155
internal/email/task_asignation.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/tuusuario/go-sync-service/internal/domain/ports"
|
||||||
|
"html/template"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TaskTplData contiene los datos que se inyectarán en la plantilla HTML de la notificación de tarea.
|
||||||
|
// Se usan como {{.Campo}} dentro del template.
|
||||||
|
type TaskTplData struct {
|
||||||
|
AppName string
|
||||||
|
Nombre string
|
||||||
|
TareaNombre string
|
||||||
|
Descripcion string
|
||||||
|
FechaVencimiento string
|
||||||
|
ErrorDescripcion string
|
||||||
|
FechaError string
|
||||||
|
LinkTarea string
|
||||||
|
LogoURL string
|
||||||
|
Direccion string
|
||||||
|
Telefono string
|
||||||
|
EmailContacto string
|
||||||
|
Year int
|
||||||
|
ColorHeaderBG string
|
||||||
|
ColorAccent string
|
||||||
|
ColorFooterText string
|
||||||
|
ColorFooterMuted string
|
||||||
|
ColorBodyBG string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Branding struct {
|
||||||
|
AppName string
|
||||||
|
LogoURL string
|
||||||
|
ColorHeaderBG string
|
||||||
|
ColorAccent string
|
||||||
|
ColorFooterText string
|
||||||
|
ColorFooterMuted string
|
||||||
|
ColorBodyBG string
|
||||||
|
Direccion string
|
||||||
|
Telefono string
|
||||||
|
EmailContacto string
|
||||||
|
Year int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Código del parámetro en tu tabla `parametros` que guarda la PLANTILLA HTML para la tarea.
|
||||||
|
// Ej: SELECT valor FROM parametros WHERE codigo = 'EMAIL_TEMPLATE_TASK';
|
||||||
|
const ParamEmailTaskTemplate = "EMAIL_TEMPLATE_TASK"
|
||||||
|
|
||||||
|
// BuildTaskSubject arma el asunto con el nombre de la app.
|
||||||
|
func BuildTaskSubject(appName string) string {
|
||||||
|
appName = strings.TrimSpace(appName)
|
||||||
|
if appName == "" {
|
||||||
|
return "Tarea asignada"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%s] Tarea asignada", appName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderTaskEmailHTML recibe la plantilla HTML (tplStr) y los datos (data) para la notificación de tarea.
|
||||||
|
// Si tplStr viene vacío, usa DefaultTaskTemplate como fallback.
|
||||||
|
func RenderTaskEmailHTML(tplStr string, data TaskTplData) (string, error) {
|
||||||
|
if strings.TrimSpace(tplStr) == "" {
|
||||||
|
tplStr = DefaultErrorSyncTemplate
|
||||||
|
}
|
||||||
|
tpl, err := template.New("email_task").Parse(tplStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("parse template: %w", err)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tpl.Execute(&buf, data); err != nil {
|
||||||
|
return "", fmt.Errorf("execute template: %w", err)
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadBrandingAsignation(cfg ports.RedisConfigProvider, appName string) Branding {
|
||||||
|
get := func(key, def string) string {
|
||||||
|
if cfg == nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
v, _ := cfg.GetString("parametros:" + key)
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(appName) == "" {
|
||||||
|
appName = get("EMAIL_APP_NAME", appName)
|
||||||
|
}
|
||||||
|
return Branding{
|
||||||
|
AppName: appName,
|
||||||
|
LogoURL: get("EMAIL_LOGO_URL", ""),
|
||||||
|
ColorHeaderBG: get("EMAIL_COLOR_HEADER_BG", "#0e1a2b"),
|
||||||
|
ColorAccent: get("EMAIL_COLOR_ACCENT", "#CBA135"),
|
||||||
|
ColorFooterText: get("EMAIL_COLOR_FOOTER_TEXT", "#ffffff"),
|
||||||
|
ColorFooterMuted: get("EMAIL_COLOR_FOOTER_MUTED", "#bfc6d1"),
|
||||||
|
ColorBodyBG: get("EMAIL_COLOR_BODY_BG", "#f4f6f8"),
|
||||||
|
Direccion: get("EMAIL_FOOTER_ADDRESS", ""),
|
||||||
|
Telefono: get("EMAIL_FOOTER_PHONE", ""),
|
||||||
|
EmailContacto: get("EMAIL_FOOTER_EMAIL", ""),
|
||||||
|
Year: time.Now().Year(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Plantilla por defecto para la notificación de tarea (Go template con {{.Campo}}) ---
|
||||||
|
const DefaultErrorSyncTemplate = `<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>{{.AppName}} – Error de Sincronización</title>
|
||||||
|
<style>@media (max-width:620px){.container{width:100% !important}.p-24{padding:20px !important}.h1{font-size:22px !important;line-height:28px !important}}</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:0;background:#f4f6f8;color:#1c1c1c;">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background:#f4f6f8;">
|
||||||
|
<tr><td align="center" style="padding:24px;">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600" class="container" style="width:600px;max-width:600px;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 4px 18px rgba(0,0,0,.06);">
|
||||||
|
<tr><td style="background:{{if .ColorHeaderBG}}{{.ColorHeaderBG}}{{else}}#0e1a2b{{end}};padding:20px 24px;" align="left">
|
||||||
|
{{if .LogoURL}}<img src="{{.LogoURL}}" width="140" alt="{{.AppName}}" style="display:block;border:0;max-width:100%">{{else}}<div style="font-family:Arial,Helvetica,sans-serif;font-size:16px;font-weight:bold;color:#fff;">{{.AppName}}</div>{{end}}
|
||||||
|
</td></tr>
|
||||||
|
<tr><td class="p-24" style="padding:28px 32px 8px 32px;">
|
||||||
|
<h1 class="h1" style="margin:0 0 8px 0;font-family:Arial,Helvetica,sans-serif;font-size:24px;line-height:32px;color:#0e1a2b;">
|
||||||
|
Error de sincronización en {{.AppName}}
|
||||||
|
</h1>
|
||||||
|
<p style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:22px;color:#4a4a4a;">
|
||||||
|
Hola <strong>{{.Nombre}}</strong>, hemos detectado un problema al intentar sincronizar los datos. Aquí están los detalles:
|
||||||
|
</p>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td class="p-24" style="padding:8px 32px 0 32px;">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="background:#f9fafb;border:1px solid #e6e8eb;border-radius:8px;">
|
||||||
|
<tr><td style="padding:16px 20px;">
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
|
<tr><td style="font-family:Arial,Helvetica,sans-serif;font-size:13px;color:#6b7280;padding:4px 0;width:160px;">Descripción del Error</td>
|
||||||
|
<td style="font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#111827;padding:4px 0;"><strong>{{.ErrorDescripcion}}</strong></td></tr>
|
||||||
|
<tr><td style="font-family:Arial,Helvetica,sans-serif;font-size:13px;color:#6b7280;padding:4px 0;width:160px;">Hora del Error</td>
|
||||||
|
<td style="font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#111827;padding:4px 0;"><strong>{{.FechaError}}</strong></td></tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
<br/><br/><br/><br/>
|
||||||
|
<tr><td style="background:{{if .ColorHeaderBG}}{{.ColorHeaderBG}}{{else}}#0e1a2b{{end}};padding:18px 24px;" align="left">
|
||||||
|
<p style="margin:0 0 6px 0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:18px;color:#ffffff;">{{.AppName}}</p>
|
||||||
|
<p style="margin:0;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:18px;color:#bfc6d1;">
|
||||||
|
{{.Direccion}}{{if .Telefono}} · Tel. {{.Telefono}}{{end}}{{if .EmailContacto}} · {{.EmailContacto}}{{end}} · © {{.Year}} {{.AppName}}
|
||||||
|
</p>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</body></html>`
|
||||||
@ -2,6 +2,7 @@ package scheduler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/tuusuario/go-sync-service/internal/email"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"github.com/tuusuario/go-sync-service/internal/config"
|
"github.com/tuusuario/go-sync-service/internal/config"
|
||||||
@ -9,7 +10,7 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func listenCronReload(ctx context.Context, redisClient *redis.Client, cfg ports.RedisConfigProvider, dbConn *gorm.DB) {
|
func listenCronReload(ctx context.Context, redisClient *redis.Client, cfg ports.RedisConfigProvider, dbConn *gorm.DB, email email.SMTPEmailService) {
|
||||||
pubsub := redisClient.Subscribe(ctx, config.GlobalConfig.RedisSubscribe)
|
pubsub := redisClient.Subscribe(ctx, config.GlobalConfig.RedisSubscribe)
|
||||||
ch := pubsub.Channel()
|
ch := pubsub.Channel()
|
||||||
|
|
||||||
@ -18,8 +19,10 @@ func listenCronReload(ctx context.Context, redisClient *redis.Client, cfg ports.
|
|||||||
for msg := range ch {
|
for msg := range ch {
|
||||||
if msg.Payload == "reload" {
|
if msg.Payload == "reload" {
|
||||||
config.Log.Info("🔄 Recargando configuración de cron...")
|
config.Log.Info("🔄 Recargando configuración de cron...")
|
||||||
if err := loadAndStartJobs(ctx, cfg, dbConn); err != nil {
|
if err := loadAndStartJobs(ctx, cfg, dbConn, email); err != nil {
|
||||||
config.Log.Errorf("❌ Error al recargar cron jobs: %v", err)
|
config.Log.Errorf("❌ Error al recargar cron jobs: %v", err)
|
||||||
|
_ = email.PrepareEmail("ERROR AL OBTENER CRONS EN EL REOLAD", err.Error(), cfg)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package scheduler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
email "github.com/tuusuario/go-sync-service/internal/email"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
@ -17,16 +18,17 @@ import (
|
|||||||
|
|
||||||
var currentCron *cron.Cron
|
var currentCron *cron.Cron
|
||||||
|
|
||||||
func Start(ctx context.Context, redisClient *redis.Client, cfg ports.RedisConfigProvider, database *gorm.DB) {
|
func Start(ctx context.Context, redisClient *redis.Client, cfg ports.RedisConfigProvider, database *gorm.DB, email email.SMTPEmailService) {
|
||||||
config.Log.Info("🚀 Iniciando Scheduler...")
|
config.Log.Info("🚀 Iniciando Scheduler...")
|
||||||
|
|
||||||
if err := loadAndStartJobs(ctx, cfg, database); err != nil {
|
if err := loadAndStartJobs(ctx, cfg, database, email); err != nil {
|
||||||
config.Log.Errorf("❌ Error inicializando jobs: %v", err)
|
config.Log.Errorf("❌ Error inicializando jobs: %v", err)
|
||||||
|
_ = email.PrepareEmail("ERROR AL OBTENER CRONS", err.Error(), cfg)
|
||||||
}
|
}
|
||||||
go listenCronReload(ctx, redisClient, cfg, database)
|
go listenCronReload(ctx, redisClient, cfg, database, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAndStartJobs(ctx context.Context, cfg ports.RedisConfigProvider, dbConn *gorm.DB) error {
|
func loadAndStartJobs(ctx context.Context, cfg ports.RedisConfigProvider, dbConn *gorm.DB, email email.SMTPEmailService) error {
|
||||||
lista, err := utils.CargarDesdeRedis[dto.CronConfigList](cfg, config.CronConfig)
|
lista, err := utils.CargarDesdeRedis[dto.CronConfigList](cfg, config.CronConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -48,10 +50,11 @@ func loadAndStartJobs(ctx context.Context, cfg ports.RedisConfigProvider, dbConn
|
|||||||
_, err := newCron.AddFunc(job.Configuracion.Ejecucion, func() {
|
_, err := newCron.AddFunc(job.Configuracion.Ejecucion, func() {
|
||||||
config.Log.Infof("🚀 Ejecutando job: %s", job.Nombre)
|
config.Log.Infof("🚀 Ejecutando job: %s", job.Nombre)
|
||||||
//cargar Job
|
//cargar Job
|
||||||
fetcher.SyncData(cfg, dbConn, job)
|
fetcher.SyncData(cfg, dbConn, job, email)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.Log.Errorf("❌ Error registrando job %s: %v", job.Nombre, err)
|
config.Log.Errorf("❌ Error registrando job %s: %v", job.Nombre, err)
|
||||||
|
_ = email.PrepareEmail("ERROR AL REGISTRAR EL JOB: "+job.Nombre, err.Error(), cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,9 @@ package fetcher
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/tuusuario/go-sync-service/internal/email"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tuusuario/go-sync-service/internal/config"
|
"github.com/tuusuario/go-sync-service/internal/config"
|
||||||
@ -16,13 +18,14 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SyncData(redis ports.RedisConfigProvider, database *gorm.DB, job dto.CronJob) {
|
func SyncData(redis ports.RedisConfigProvider, database *gorm.DB, job dto.CronJob, email email.SMTPEmailService) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
logPrefix := fmt.Sprintf("[🧩 Job: %s] ", job.Nombre)
|
logPrefix := fmt.Sprintf("[🧩 Job: %s] ", job.Nombre)
|
||||||
config.Log.Printf("%s Iniciando sincronización...", logPrefix)
|
config.Log.Printf("%s Iniciando sincronización...", logPrefix)
|
||||||
|
|
||||||
var dbcore ports.Database = db.NewGormDatabase(database)
|
var dbcore ports.Database = db.NewGormDatabase(database)
|
||||||
var hasError bool
|
var hasError bool
|
||||||
|
var errorMessages []string // Collects all error messages
|
||||||
|
|
||||||
http.InitClient()
|
http.InitClient()
|
||||||
|
|
||||||
@ -37,6 +40,7 @@ func SyncData(redis ports.RedisConfigProvider, database *gorm.DB, job dto.CronJo
|
|||||||
jobIndividual, err := utils.CargarDesdeRedis[dto.JobConfig](redis, proceso)
|
jobIndividual, err := utils.CargarDesdeRedis[dto.JobConfig](redis, proceso)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.Log.Printf(logPrefix+" ❌ Error al obtener configuración del proceso: %v", err)
|
config.Log.Printf(logPrefix+" ❌ Error al obtener configuración del proceso: %v", err)
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("Error en proceso %s: %v", proceso, err))
|
||||||
hasError = true
|
hasError = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -44,11 +48,13 @@ func SyncData(redis ports.RedisConfigProvider, database *gorm.DB, job dto.CronJo
|
|||||||
session, err := http.GetSession(redis, job, jobIndividual.Auth, dbcore, logPrefix)
|
session, err := http.GetSession(redis, job, jobIndividual.Auth, dbcore, logPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.Log.Println(logPrefix + " ❌ No se pudo obtener sesión")
|
config.Log.Println(logPrefix + " ❌ No se pudo obtener sesión")
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("❌ No se pudo obtener sesión para proceso %s: %v", proceso, err))
|
||||||
hasError = true
|
hasError = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if session == nil || session.Headers == nil {
|
if session == nil || session.Headers == nil {
|
||||||
config.Log.Println(logPrefix + " ❌ Sesión inválida o vacía")
|
config.Log.Println(logPrefix + " ❌ Sesión inválida o vacía")
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("❌ Sesión inválida o vacía para proceso %s", proceso))
|
||||||
hasError = true
|
hasError = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -63,6 +69,7 @@ func SyncData(redis ports.RedisConfigProvider, database *gorm.DB, job dto.CronJo
|
|||||||
response, err := FetchAllPaginatedManual[map[string]interface{}](session.EndPoint, jobIndividual.Service, logPrefix)
|
response, err := FetchAllPaginatedManual[map[string]interface{}](session.EndPoint, jobIndividual.Service, logPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.Log.Printf(logPrefix+" ❌ Error al obtener data: %v", err)
|
config.Log.Printf(logPrefix+" ❌ Error al obtener data: %v", err)
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("❌ Error al obtener data para proceso %s: %v", proceso, err))
|
||||||
hasError = true
|
hasError = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -72,6 +79,7 @@ func SyncData(redis ports.RedisConfigProvider, database *gorm.DB, job dto.CronJo
|
|||||||
err = dbcore.SyncRows(jobIndividual.Persistencia, response, job.UnidadNegocio.CompanyDB)
|
err = dbcore.SyncRows(jobIndividual.Persistencia, response, job.UnidadNegocio.CompanyDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.Log.Printf(logPrefix+" ❌ Error al guardar en base de datos: %v", err)
|
config.Log.Printf(logPrefix+" ❌ Error al guardar en base de datos: %v", err)
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("❌ Error al guardar en base de datos para proceso %s: %v", proceso, err))
|
||||||
hasError = true
|
hasError = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +91,9 @@ END:
|
|||||||
metrics.CronDuration.WithLabelValues(jobName).Observe(duration)
|
metrics.CronDuration.WithLabelValues(jobName).Observe(duration)
|
||||||
|
|
||||||
if hasError {
|
if hasError {
|
||||||
|
errMessage := strings.Join(errorMessages, "\n") // Combine all errors into one message
|
||||||
|
_ = email.PrepareEmail("ERROR AL EJECUTAR EL JOB: "+job.Nombre, errMessage, redis)
|
||||||
|
|
||||||
metrics.CronError.WithLabelValues(jobName).Inc()
|
metrics.CronError.WithLabelValues(jobName).Inc()
|
||||||
metrics.CronLastError.WithLabelValues(jobName).Set(float64(time.Now().Unix()))
|
metrics.CronLastError.WithLabelValues(jobName).Set(float64(time.Now().Unix()))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user