<commit_before>package dailyemail

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/workers/email/activityemail/models"
	"socialapi/workers/email/emailmodels"
	"socialapi/workers/helper"
	notificationmodels "socialapi/workers/notification/models"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/koding/logging"
	"github.com/robfig/cron"
)

const (
	DAY           = 24 * time.Hour
	TIMEFORMAT    = "20060102"
	DATEFORMAT    = "Jan 02"
	CACHEPREFIX   = "dailymail"
	RECIPIENTSKEY = "recipients"
	SCHEDULE      = "0 * * * * *"

	Subject = "[Koding] Your Koding Activity for today: %s"
)

type Controller struct {
	log      logging.Logger
	settings *emailmodels.EmailSettings
}

var ObsoleteActivity = errors.New("obsolete activity")

var (
	cronJob *cron.Cron
)

func New(log logging.Logger, es *emailmodels.EmailSettings) (*Controller, error) {

	c := &Controller{
		log:      log,
		settings: es,
	}

	return c, c.initDailyEmailCron()
}

func (n *Controller) initDailyEmailCron() error {
	cronJob = cron.New()
	err := cronJob.AddFunc(SCHEDULE, n.sendDailyMails)
	if err != nil {
		return err
	}
	cronJob.Start()

	return nil
}

func (n *Controller) Shutdown() {
	cronJob.Stop()
}

func (n *Controller) sendDailyMails() {
	redisConn := helper.MustGetRedisConn()
	for {
		key := prepareRecipientsCacheKey()
		reply, err := redisConn.PopSetMember(key)
		if err == redis.ErrNil {
			n.log.Info("all daily mails are sent")
			return
		}
		if err != nil {
			n.log.Error("Could not fetch recipient %s", err)
			return
		}

		accountId, err := strconv.ParseInt(reply, 10, 64)
		if err != nil {
			n.log.Error("Could not cast recipient id: %s", err)
			continue
		}

		if err := n.prepareDailyEmail(accountId); err != nil {
			n.log.Error("error occurred: %s", err)
		}
	}
}

func (n *Controller) prepareDailyEmail(accountId int64) error {
	uc, err := emailmodels.FetchUserContactWithToken(accountId)
	if err != nil {
		return err
	}

	// notifications are disabled
	if val := uc.EmailSettings.Global; !val {
		return nil
	}

	activityIds, err := n.getDailyActivityIds(accountId)
	if err != nil {
		return fmt.Errorf("Could not fetch activity ids: %s", err)
	}

	if len(activityIds) == 0 {
		return nil
	}

	containers := make([]*models.MailerContainer, 0)
	for _, activityId := range activityIds {
		container, err := buildContainerForDailyMail(accountId, activityId)
		if err != nil {
			if err != ObsoleteActivity {
				n.log.Error("error occurred while sending activity: %s ", err)
			}
			continue
		}

		containers = append(containers, container)
	}

	if len(containers) == 0 {
		return nil
	}

	tp := models.NewTemplateParser()
	tp.UserContact = uc
	body, err := tp.RenderDailyTemplate(containers)
	if err != nil {
		return fmt.Errorf("an error occurred while preparing notification email: %s", err)
	}

	mailer := emailmodels.Mailer{
		EmailSettings: n.settings,
		UserContact:   uc,
	}

	subject := fmt.Sprintf(Subject, time.Now().Format(DATEFORMAT))

	return mailer.SendMail("daily", body, subject)
}

func (n *Controller) getDailyActivityIds(accountId int64) ([]int64, error) {
	redisConn := helper.MustGetRedisConn()
	members, err := redisConn.GetSetMembers(prepareDailyActivitiesCacheKey(accountId))
	if err != nil {
		return nil, err
	}

	activityIds := make([]int64, len(members))
	for i, member := range members {<commit_msg>email: render daily email date depending on user's last login timezone<commit_after>package dailyemail

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/workers/email/activityemail/models"
	"socialapi/workers/email/emailmodels"
	"socialapi/workers/helper"
	notificationmodels "socialapi/workers/notification/models"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/koding/logging"
	"github.com/robfig/cron"
)

const (
	DAY           = 24 * time.Hour
	TIMEFORMAT    = "20060102"
	DATEFORMAT    = "Jan 02"
	CACHEPREFIX   = "dailymail"
	RECIPIENTSKEY = "recipients"
	SCHEDULE      = "0 * * * * *"

	Subject = "[Koding] Your Koding Activity for today: %s"
)

type Controller struct {
	log      logging.Logger
	settings *emailmodels.EmailSettings
}

var ObsoleteActivity = errors.New("obsolete activity")

var (
	cronJob *cron.Cron
)

func New(log logging.Logger, es *emailmodels.EmailSettings) (*Controller, error) {

	c := &Controller{
		log:      log,
		settings: es,
	}

	return c, c.initDailyEmailCron()
}

func (n *Controller) initDailyEmailCron() error {
	cronJob = cron.New()
	err := cronJob.AddFunc(SCHEDULE, n.sendDailyMails)
	if err != nil {
		return err
	}
	cronJob.Start()

	return nil
}

func (n *Controller) Shutdown() {
	cronJob.Stop()
}

func (n *Controller) sendDailyMails() {
	redisConn := helper.MustGetRedisConn()
	for {
		key := prepareRecipientsCacheKey()
		reply, err := redisConn.PopSetMember(key)
		if err == redis.ErrNil {
			n.log.Info("all daily mails are sent")
			return
		}
		if err != nil {
			n.log.Error("Could not fetch recipient %s", err)
			return
		}

		accountId, err := strconv.ParseInt(reply, 10, 64)
		if err != nil {
			n.log.Error("Could not cast recipient id: %s", err)
			continue
		}

		if err := n.prepareDailyEmail(accountId); err != nil {
			n.log.Error("error occurred: %s", err)
		}
	}
}

func (n *Controller) prepareDailyEmail(accountId int64) error {
	uc, err := emailmodels.FetchUserContactWithToken(accountId)
	if err != nil {
		return err
	}

	// notifications are disabled
	if val := uc.EmailSettings.Global; !val {
		return nil
	}

	activityIds, err := n.getDailyActivityIds(accountId)
	if err != nil {
		return fmt.Errorf("Could not fetch activity ids: %s", err)
	}

	if len(activityIds) == 0 {
		return nil
	}

	containers := make([]*models.MailerContainer, 0)
	for _, activityId := range activityIds {
		container, err := buildContainerForDailyMail(accountId, activityId)
		if err != nil {
			if err != ObsoleteActivity {
				n.log.Error("error occurred while sending activity: %s ", err)
			}
			continue
		}

		containers = append(containers, container)
	}

	if len(containers) == 0 {
		return nil
	}

	tp := models.NewTemplateParser()
	tp.UserContact = uc
	body, err := tp.RenderDailyTemplate(containers)
	if err != nil {
		return fmt.Errorf("an error occurred while preparing notification email: %s", err)
	}

	mailer := emailmodels.Mailer{
		EmailSettings: n.settings,
		UserContact:   uc,
	}

	var loc *time.Location
	if uc.LastLoginTimezone != "" {
		loc, _ = time.LoadLocation(uc.LastLoginTimezone)
	}

	today := time.Now()
	if loc != nil {
		today = today.In(loc)
	}

	subject := fmt.Sprintf(Subject, today.Format(DATEFORMAT))

	return mailer.SendMail("daily", body, subject)
}

func (n *Controller) getDailyActivityIds(accountId int64) ([]int64, error) {
	redisConn := helper.MustGetRedisConn()
	members, err := redisConn.GetSetMembers(prepareDailyActivitiesCacheKey(accountId))
	if err != nil {
		return nil, err
	}

	activityIds := make([]int64, len(members))
	for i, member := range members {
{'fraction_non_alphanumeric': 0.13042853034224908, 'fraction_numerical': 0.00805291918320391, 'mean_word_length': 3.936124911284599, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5217463', 'n_tokens_mistral': 2627, 'n_tokens_neox': 2406, 'n_words': 588}