0

I am creating GO rest APIs. We are using AWS server. I want to send push notification to mobile. Then I used

https://pkg.go.dev/github.com/robfig/cron (https://github.com/robfig/cron )

for creating cron job.

We are using 2 version of API, V1(old one) and V1.1(new one)

we have more than 1 environment dev,QA,preproduction,production

in our go lang code I created a cron job for sending push notification to mobile. and the function called inside main().

But we are getting 2 notification each interval.

I didn't understand why 2 one is getting at a time

I am attaching my code.

const title = "This Week’s Activity"

func NotifyWeeklyActivity(db *sql.DB, logger *zap.Logger) {
    logger.Info("NotifyWeeklyActivity- start")
    c := cron.New()
    c.AddFunc("*/5 * * * *", func() {
        lastweekTime := CurrentUTC().AddDate(0, 0, -7)

        type PostCount struct {
            HiveID               uint64      `json:"hive_id"`
            Post                 uint64      `json:"post"`
            NotificationTopicArn null.String `json:"notification_topic_arn"`
        }
        var posts []PostCount
        err := queries.Raw(`
            select count(post_id) as post , post.hive_id as hive_id , hive.notification_topic_arn
            from post
            join hive on post.hive_id=hive.hive_id and hive.deleted_at is null
            where post.deleted_at is null
            and hive.deleted_at is null
            and post.created_at between ? and ?
            group by hive_id
            having count(post_id)>3 ;
    `, lastweekTime, CurrentUTC()).Bind(context.TODO(), db, &posts)

        if err != nil {
            logger.Error("error while fetching data ", zap.Error(err))
            // return err
        }
        cfg, _ := config.GetImpart()
        if cfg.Env != config.Local {
            notification := NewImpartNotificationService(db, string(cfg.Env), cfg.Region, cfg.IOSNotificationARN, logger)
            logger.Info("Notification- fetching complted")
            for _, hive := range posts {
                pushNotification := Alert{
                    Title: aws.String(title),
                    Body: aws.String(
                        fmt.Sprintf("Check out %d new posts in your Hive this week", hive.Post),
                    ),
                }
                additionalData := NotificationData{
                    EventDatetime: CurrentUTC(),
                    HiveID:        hive.HiveID,
                }
                Logger.Info("Notification",
                    zap.Any("pushNotification", pushNotification),
                    zap.Any("additionalData", additionalData),
                    zap.Any("hive", hive),
                )
                err = notification.NotifyTopic(context.Background(), additionalData, pushNotification, hive.NotificationTopicArn.String)
                if err != nil {
                    logger.Error("error sending notification to topic", zap.Error(err))
                }

            }
        }
    })
    c.Start()
} 




func NewImpartNotificationService(db *sql.DB, stage, region, platformApplicationARN string, logger *zap.Logger) NotificationService {

    //SNS not available in us-east-2
    if strings.EqualFold(region, "us-east-2") {
        region = "us-east-1"
    }
    sess, err := session.NewSession(&aws.Config{
        Region:     aws.String(region),
        HTTPClient: NewHttpClient(10 * time.Second),
    })
    if err != nil {
        logger.Fatal("unable to create aws session", zap.Error(err))
    }

    snsAppleNotificationService := &snsAppleNotificationService{
        stage:                  stage,
        Logger:                 logger,
        SNS:                    sns.New(sess),
        platformApplicationARN: platformApplicationARN,
        db:                     db,
    }

    logger.Debug("created new NotificationService",
        zap.String("stage", stage),
        zap.String("arn", platformApplicationARN))

    return snsAppleNotificationService
}

Why I am getting 2 notification at a time ? How can I Solve this

func (ns *snsAppleNotificationService) NotifyTopic(ctx context.Context, data NotificationData, alert Alert, topicARN string) error {
    var b []byte
    var err error
    if strings.TrimSpace(topicARN) == "" {
        return nil
    }

    ns.Logger.Debug("sending push notification",
        zap.Any("data", data),
        zap.Any("msg", alert),
        zap.String("platformEndpoint", topicARN),
        zap.String("arn", ns.platformApplicationARN))

    if b, err = json.Marshal(apnsMessageWrapper{
        APNSData: APNSMessage{
            Alert: alert,
            Sound: aws.String("default"),
            Data:  data,
            Badge: aws.Int(0),
        },
    }); err != nil {
        return err
    }

    msg := awsSNSMessage{Default: *alert.Body}

    msg.APNS = string(b)
    msg.APNSSandbox = string(b)

    if b, err = json.Marshal(msg); err != nil {
        return err
    }

    input := &sns.PublishInput{
        Message:          aws.String(string(b)),
        MessageStructure: aws.String("json"),
        TopicArn:         aws.String(topicARN),
    }
    // print()
    _, err = ns.Publish(input)
    if err != nil {
        ns.Logger.Error("push-notification : After publish input",
            zap.Any("topicARN", topicARN),
            zap.Error(err),
        )
    }
    return err
}

main fuction

func main() {
    logger, err := zap.NewProduction()
    if err != nil {
        log.Fatal(err)
    }
    cfg, err := config.GetImpart()
    if err != nil {
        logger.Fatal("error parsing config", zap.Error(err))
    }
    if cfg == nil {
        logger.Fatal("nil config")
        return
    }

    if cfg.Debug {
        gin.SetMode(gin.DebugMode)
        //boil.DebugMode = true
        boil.WithDebugWriter(context.TODO(), &config.ZapBoilWriter{Logger: logger})
        logger, _ = zap.NewDevelopment()
        if cfg.Env == config.Local || cfg.Env == config.Development {
            logger.Debug("config startup", zap.Any("config", *cfg))
        }
    } else {
        gin.SetMode(gin.ReleaseMode)
    }

    //init the sentry logger ,either debug
    logger, err = impart.InitSentryLogger(cfg, logger, cfg.Debug)
    if err != nil {
        logger.Error("error on sentry init", zap.Any("error", err))
    }

    migrationDB, err := cfg.GetMigrationDBConnection()
    if err != nil {
        logger.Fatal("unable to connect to DB", zap.Error(err))
    }

    //Trap sigterm during migraitons
    migrationsDoneChan := make(chan bool)
    shutdownMigrationsChan := make(chan bool)
    sigc := make(chan os.Signal, 1)
    signal.Notify(sigc,
        syscall.SIGINT,
        syscall.SIGTERM,
        syscall.SIGQUIT)
    go func() {
        select {
        case <-sigc:
            logger.Info("received a shutdown request during migrations, sending shutdown signal")
            shutdownMigrationsChan <- true
        case <-migrationsDoneChan:
            logger.Info("migrations complete, no longer waiting for sig int")
            return
        }
    }()

    err = migrater.RunMigrationsUp(migrationDB, cfg.MigrationsPath, logger, shutdownMigrationsChan)
    if err != nil {
        logger.Fatal("error running migrations", zap.Error(err))
    }
    migrationsDoneChan <- true
    if err := migrationDB.Close(); err != nil {
        logger.Fatal("error closing migrations DB connection", zap.Error(err))
    }

    boil.SetLocation(time.UTC)
    db, err := cfg.GetDBConnection()
    if err != nil {
        logger.Fatal("unable to connect to DB", zap.Error(err))
    }
    defer db.Close()
    defer logger.Sync()

    // if err := migrater.BootStrapAdminUsers(db, cfg.Env, logger); err != nil {
    //  logger.Fatal("unable to bootstrap user", zap.Error(err))
    // }

    // if err := migrater.BootStrapTopicHive(db, cfg.Env, logger); err != nil {
    //  logger.Fatal("unable to bootstrap user", zap.Error(err))
    // }

    // initiate global profanity detector
    impart.InitProfanityDetector(db, logger)
    impart.NotifyWeeklyActivity(db, logger)

    services := setupServices(cfg, db, logger)

    r := gin.New()
    r.Use(CORS)
    r.Use(secure.Secure(secure.Options{
        //AllowedHosts:          []string{"*"},
        // AllowedHosts: []string{"localhost:3000", "ssl.example.com"},
        //SSLRedirect: true,
        // SSLHost:               "*",
        SSLProxyHeaders:       map[string]string{"X-Forwarded-Proto": "https"},
        STSIncludeSubdomains:  true,
        FrameDeny:             true,
        ContentTypeNosniff:    true,
        BrowserXssFilter:      true,
        ContentSecurityPolicy: "default-src 'self'",
    }))
    r.RedirectTrailingSlash = true
    r.Use(ginzap.RecoveryWithZap(logger, true))      // panics don't stop server
    r.Use(ginzap.Ginzap(logger, time.RFC3339, true)) // logs all requests

    r.NoRoute(noRouteFunc)
    r.GET("/ping", func(ctx *gin.Context) {
        _, err := dbmodels.Pings(dbmodels.PingWhere.Ok.EQ(true)).One(ctx, db)
        if err != nil {
            ctx.AbortWithStatus(http.StatusInternalServerError)
        }
        ctx.String(http.StatusOK, "pong")
    })
    var v1Route string
    var v2Route string
    if cfg.Env == config.Production || cfg.Env == config.Local {
        v1Route = "v1"
        v2Route = "v1.1"
    } else {
        v1Route = fmt.Sprintf("%s/v1", cfg.Env)
        v2Route = fmt.Sprintf("%s/v1.1", cfg.Env)
    }
    err = mailchimp.SetKey(impart.MailChimpApiKey)
    if err != nil {
        logger.Info("Error connecting Mailchimp", zap.Error(err),
            zap.Any("MailchimpApikey", cfg.MailchimpApikey))
    }

    v1 := r.Group(v1Route)
    setRouter(v1, services, logger, db)

    v2 := r.Group(v2Route)
    setRouter(v2, services, logger, db)

    server := cfg.GetHttpServer()
    server.Handler = r
    logger.Info("Impart backend started.", zap.Int("port", cfg.Port), zap.String("env", string(cfg.Env)))
    if err := graceful.Graceful(server.ListenAndServe, server.Shutdown); err != nil {
        logger.Fatal("error serving", zap.Error(err))
    }
    logger.Info("done serving")
}

publish

// See also, https://docs.aws.amazon.com/goto/WebAPI/sns-2010-03-31/Publish
func (c *SNS) Publish(input *PublishInput) (*PublishOutput, error) {
    req, out := c.PublishRequest(input)
    return out, req.Send()
}
Mia Mia
  • 143
  • 12
  • Is the function called twice by the cron package? You should be able to see this from the log. If this is not the case then you likely have 2 elements in `posts` since you send a notification for each element of `posts`. – Dylan Reimerink Nov 04 '21 at 09:49
  • @caveman can we get any special log for cron differtely ? if there is any log how can I get ? if there are two elements in posts , they are having different NotificationTopicArn ( topicArn filed in notify function). I will update the notify topic fucntion also – Mia Mia Nov 04 '21 at 10:04
  • What I mean is, your code is the thing that actually sends notifications. In there you have a log message `logger.Info("Notification- fetching complted")` if the cron package calls your function twice I expect to see this message twice in your log. If we only see this message once the issue must be somewhere else. Trying to make the search space of a bug smaller by using process of elimination – Dylan Reimerink Nov 04 '21 at 10:24
  • @MiaMia the zap log from within the loop, do you see it once or do you see it twice? – mkopriva Nov 04 '21 at 10:26
  • @MiaMia how identical is the code in the question to your actual code? The reason I'm asking is that the behaviour you describe resembles a common issue caused by using goroutines and/or closures inside a loop. Does your actual loop have a closure or a goroutine? If yes, then add it to the question's code. – mkopriva Nov 04 '21 at 10:29
  • @mkopriva that is coming only 1 in each 5 mint. But in IOS device getting 2 notificaiton – Mia Mia Nov 04 '21 at 10:30
  • @MiaMia if you're seeing the log only once in the 5min interval, then the problem has nothing to do with cron. – mkopriva Nov 04 '21 at 10:31
  • @mkopriva first i used v1 version of the package, Then i got ony one notification. THen i changed the time, after that i am getting 2 notifcation – Mia Mia Nov 04 '21 at 10:33
  • @mkopriva after that i start using v3 version of robfig/cron package – Mia Mia Nov 04 '21 at 10:34
  • @MiaMia i'll say it again, if you see the log statement, the one right above the `notification.NotifyTopic` call, only once per 5min, then cron is not the issue, the version is irrelevant. cron doesn't pick and choose individual statements from the given function's body and then invokes them at random. If there is only one log, then there is only one call to `NotifyTopic`. – mkopriva Nov 04 '21 at 10:38
  • @caveman only one log comming – Mia Mia Nov 04 '21 at 10:39
  • @mkopriva . Do you have any idea what will be the issue ? – Mia Mia Nov 04 '21 at 10:41
  • @MiaMia yes, the issue is in the code that you have not shared. – mkopriva Nov 04 '21 at 10:41
  • @mkopriva I added my main function also – Mia Mia Nov 04 '21 at 10:45
  • @mkopriva , code updated.. All other functions are already attached in qustion – Mia Mia Nov 04 '21 at 10:47
  • @MiaMia `main` is even more irrelevant to the problem than cron. Instead, try showing the `Publish` method. – mkopriva Nov 04 '21 at 10:50
  • @mkopriva publish is default of aws package.. I am attaching that one – Mia Mia Nov 04 '21 at 10:53
  • 1
    @MiaMia are you sure that you are running only one instance of the app? You don't have some orphaned, old version process running somewhere? And, also, what about the other environments, could it be that some of them are also running and talking to the same database to fetch the posts? – mkopriva Nov 04 '21 at 12:00
  • @mkopriva no for each environment only different database using . Old versions I. I removed the code from all environment and start keep one environment.. But 2 notification is getting – Mia Mia Nov 05 '21 at 07:38
  • @mkopriva I just checked for using another package for cron but basically most of them forked from this one – Mia Mia Nov 05 '21 at 07:39
  • @mkopriva all cron codes, packages removed and added as fresh one but again getting 2 notification – Mia Mia Nov 05 '21 at 07:40
  • 1
    @mkopriva thankyou for your support.. In AWS 2 instance is running. That is the reason. Thankyou – Mia Mia Nov 06 '21 at 13:34

0 Answers0