2021-10-17 14:17:41 +00:00
|
|
|
package worker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/DataDog/datadog-go/statsd"
|
|
|
|
"github.com/adjust/rmq/v4"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
|
|
"github.com/jackc/pgx/v4/pgxpool"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
"github.com/christianselig/apollo-backend/internal/domain"
|
|
|
|
"github.com/christianselig/apollo-backend/internal/reddit"
|
|
|
|
"github.com/christianselig/apollo-backend/internal/repository"
|
|
|
|
)
|
|
|
|
|
|
|
|
type stuckNotificationsWorker struct {
|
|
|
|
logger *logrus.Logger
|
|
|
|
statsd *statsd.Client
|
|
|
|
db *pgxpool.Pool
|
|
|
|
redis *redis.Client
|
|
|
|
queue rmq.Connection
|
|
|
|
reddit *reddit.Client
|
|
|
|
|
|
|
|
consumers int
|
|
|
|
|
|
|
|
accountRepo domain.AccountRepository
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewStuckNotificationsWorker(logger *logrus.Logger, statsd *statsd.Client, db *pgxpool.Pool, redis *redis.Client, queue rmq.Connection, consumers int) Worker {
|
|
|
|
reddit := reddit.NewClient(
|
|
|
|
os.Getenv("REDDIT_CLIENT_ID"),
|
|
|
|
os.Getenv("REDDIT_CLIENT_SECRET"),
|
|
|
|
statsd,
|
2022-03-12 17:50:05 +00:00
|
|
|
redis,
|
2021-10-17 14:17:41 +00:00
|
|
|
consumers,
|
|
|
|
)
|
|
|
|
|
|
|
|
return &stuckNotificationsWorker{
|
|
|
|
logger,
|
|
|
|
statsd,
|
|
|
|
db,
|
|
|
|
redis,
|
|
|
|
queue,
|
|
|
|
reddit,
|
|
|
|
consumers,
|
|
|
|
|
|
|
|
repository.NewPostgresAccount(db),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (snw *stuckNotificationsWorker) Start() error {
|
|
|
|
queue, err := snw.queue.OpenQueue("stuck-notifications")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
snw.logger.WithFields(logrus.Fields{
|
|
|
|
"numConsumers": snw.consumers,
|
|
|
|
}).Info("starting up stuck notifications worker")
|
|
|
|
|
|
|
|
prefetchLimit := int64(snw.consumers * 2)
|
|
|
|
|
|
|
|
if err := queue.StartConsuming(prefetchLimit, pollDuration); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
host, _ := os.Hostname()
|
|
|
|
|
|
|
|
for i := 0; i < snw.consumers; i++ {
|
|
|
|
name := fmt.Sprintf("consumer %s-%d", host, i)
|
|
|
|
|
|
|
|
consumer := NewStuckNotificationsConsumer(snw, i)
|
|
|
|
if _, err := queue.AddConsumer(name, consumer); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (snw *stuckNotificationsWorker) Stop() {
|
|
|
|
<-snw.queue.StopAllConsuming() // wait for all Consume() calls to finish
|
|
|
|
}
|
|
|
|
|
|
|
|
type stuckNotificationsConsumer struct {
|
|
|
|
*stuckNotificationsWorker
|
|
|
|
tag int
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewStuckNotificationsConsumer(snw *stuckNotificationsWorker, tag int) *stuckNotificationsConsumer {
|
|
|
|
return &stuckNotificationsConsumer{
|
|
|
|
snw,
|
|
|
|
tag,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (snc *stuckNotificationsConsumer) Consume(delivery rmq.Delivery) {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#id": delivery.Payload(),
|
|
|
|
}).Debug("starting job")
|
|
|
|
|
|
|
|
id, err := strconv.ParseInt(delivery.Payload(), 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#id": delivery.Payload(),
|
|
|
|
"err": err,
|
|
|
|
}).Error("failed to parse account ID")
|
|
|
|
|
|
|
|
_ = delivery.Reject()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() { _ = delivery.Ack() }()
|
|
|
|
|
|
|
|
account, err := snc.accountRepo.GetByID(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"err": err,
|
|
|
|
}).Error("failed to fetch account from database")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if account.LastMessageID == "" {
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
}).Debug("account has no messages, returning")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-12 17:50:05 +00:00
|
|
|
rac := snc.reddit.NewAuthenticatedClient(account.AccountID, account.RefreshToken, account.AccessToken)
|
2021-10-17 14:17:41 +00:00
|
|
|
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"thing#id": account.LastMessageID,
|
|
|
|
}).Debug("fetching last thing")
|
|
|
|
|
2021-10-17 14:47:43 +00:00
|
|
|
kind := account.LastMessageID[:2]
|
|
|
|
|
|
|
|
var things *reddit.ListingResponse
|
|
|
|
if kind == "t4" {
|
2021-10-17 14:17:41 +00:00
|
|
|
snc.logger.WithFields(logrus.Fields{
|
2021-10-17 14:47:43 +00:00
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"thing#id": account.LastMessageID,
|
|
|
|
}).Debug("checking last thing via inbox")
|
2021-10-17 14:17:41 +00:00
|
|
|
|
2021-10-17 14:47:43 +00:00
|
|
|
things, err = rac.MessageInbox()
|
|
|
|
if err != nil {
|
2021-10-17 14:17:41 +00:00
|
|
|
snc.logger.WithFields(logrus.Fields{
|
2021-10-17 14:47:43 +00:00
|
|
|
"err": err,
|
|
|
|
}).Error("failed to fetch last thing via inbox")
|
2021-10-17 14:17:41 +00:00
|
|
|
return
|
|
|
|
}
|
2021-10-17 14:47:43 +00:00
|
|
|
} else {
|
|
|
|
things, err = rac.AboutInfo(account.LastMessageID)
|
|
|
|
if err != nil {
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"err": err,
|
|
|
|
}).Error("failed to fetch last thing")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 14:53:14 +00:00
|
|
|
if things.Count > 0 {
|
2021-10-17 14:47:43 +00:00
|
|
|
for _, thing := range things.Children {
|
|
|
|
if thing.FullName() != account.LastMessageID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-10-17 15:02:31 +00:00
|
|
|
if !thing.IsDeleted() {
|
2021-10-17 14:47:43 +00:00
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"thing#id": account.LastMessageID,
|
|
|
|
}).Debug("thing exists, returning")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2021-10-17 14:17:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"thing#id": account.LastMessageID,
|
2021-10-17 14:20:07 +00:00
|
|
|
}).Info("thing got deleted, resetting")
|
2021-10-17 14:17:41 +00:00
|
|
|
|
2021-10-17 14:47:43 +00:00
|
|
|
if kind != "t4" {
|
2021-10-17 15:48:41 +00:00
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
2021-10-17 15:56:23 +00:00
|
|
|
}).Debug("getting message inbox to determine last good thing")
|
2021-10-17 15:48:41 +00:00
|
|
|
|
2021-10-17 14:47:43 +00:00
|
|
|
things, err = rac.MessageInbox()
|
|
|
|
if err != nil {
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"err": err,
|
|
|
|
}).Error("failed to get message inbox")
|
|
|
|
return
|
|
|
|
}
|
2021-10-17 14:17:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
account.LastMessageID = ""
|
2021-10-17 15:48:41 +00:00
|
|
|
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
}).Debug("calculating last good thing")
|
2021-10-17 14:47:43 +00:00
|
|
|
for _, thing := range things.Children {
|
2021-10-17 15:27:52 +00:00
|
|
|
if thing.IsDeleted() {
|
2021-10-17 15:48:41 +00:00
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"thing#id": thing.FullName(),
|
|
|
|
}).Debug("thing deleted, next")
|
2021-10-17 15:27:52 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-10-17 15:48:41 +00:00
|
|
|
account.LastMessageID = thing.FullName()
|
|
|
|
break
|
2021-10-17 14:17:41 +00:00
|
|
|
}
|
|
|
|
|
2021-10-17 15:27:52 +00:00
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"thing#id": account.LastMessageID,
|
|
|
|
}).Debug("updating last good thing")
|
|
|
|
|
2021-10-17 14:17:41 +00:00
|
|
|
if err := snc.accountRepo.Update(ctx, &account); err != nil {
|
|
|
|
snc.logger.WithFields(logrus.Fields{
|
|
|
|
"account#username": account.NormalizedUsername(),
|
|
|
|
"err": err,
|
|
|
|
}).Error("failed to update account's message id")
|
|
|
|
}
|
|
|
|
}
|