tuna-timer/tuna-timer-api

View on GitHub
data/timer_service.go

Summary

Maintainability
A
0 mins
Test Coverage
package data

import (
    "github.com/cleverua/tuna-timer-api/models"
    "github.com/cleverua/tuna-timer-api/utils"
    "gopkg.in/mgo.v2"
    "log"
    "time"
    "errors"
)

const maxDaysCount  = 31

// TimerService - the structure of the service
type TimerService struct {
    repository *TimerRepository
}

// NewTimerService constructs an instance of the service
func NewTimerService(session *mgo.Session) *TimerService {
    return &TimerService{
        repository: NewTimerRepository(session),
    }
}

// GetActiveTimer returns a timer the user is currently working on
func (s *TimerService) GetActiveTimer(teamID, userID string) (*models.Timer, error) {
    timer, err := s.repository.findActiveByTeamAndUser(teamID, userID)
    return timer, err
}

func (s *TimerService) FindByID(id string)  (*models.Timer, error) {
    return s.repository.findByID(id)
}

// StopTimer stops the timer and updates its Minutes field
func (s *TimerService) StopTimer(timer *models.Timer) error {
    now := time.Now()
    timer.ActualMinutes = s.CalculateMinutesForActiveTimer(timer)
    timer.Minutes = timer.ActualMinutes
    timer.FinishedAt = &now
    return s.repository.update(timer)
}

// StartTimer creates a new timer
func (s *TimerService) StartTimer(teamID string, project *models.Project, user *models.TeamUser, taskName string) (*models.Timer, error) {
    return s.repository.create(teamID, project, user, taskName)
}

// TotalMinutesForTaskToday calculates the total number of minutes the user was/is working on particular task today
func (s *TimerService) TotalMinutesForTaskToday(timer *models.Timer) int {
    endDate := time.Now()
    startDate := time.Now().Truncate(24 * time.Hour)

    result := s.repository.totalMinutesForTaskAndUser(
        timer.TaskHash, timer.TeamUserID, startDate, endDate)

    if timer.FinishedAt == nil {
        result += s.CalculateMinutesForActiveTimer(timer)
    }

    return result
}

// UserTotalMinutesForToday calculates the total number of minute this user contributed to any project today
func (s *TimerService) TotalCompletedMinutesForDay(year int, month time.Month, day int, user *models.TeamUser) int {

    //log.Printf("TotalUserMinutesForDay, Year: %d, Month: %d, Day: %d", year, month, day)

    tzOffset := user.SlackUserInfo.TZOffset
    //log.Printf("TotalUserMinutesForDay, tzOffset: %d", tzOffset)

    startDate := time.Date(year, month, day, 0, 0, 0, 0, time.UTC).Add(time.Duration(tzOffset) * time.Second * -1)
    endDate := time.Date(year, month, day, 23, 59, 59, 0, time.UTC).Add(time.Duration(tzOffset) * time.Second * -1)

    return s.repository.totalMinutesForUser(user.ID.Hex(), startDate, endDate)
}

// GetCompletedTasksForDay - returns the list of tasks the user had completed during given work day by his/her timezone
// - year, month, day - is the day to get the list of completed tasks for
// - user - whose tasks the viewer is interested in
func (s *TimerService) GetCompletedTasksForDay(year int, month time.Month, day int, user *models.TeamUser) ([]*models.TaskAggregation, error) {
    //log.Printf("GetCompletedTasksForDay, Year: %d, Month: %d, Day: %d", year, month, day)

    tzOffset := user.SlackUserInfo.TZOffset
    //log.Printf("GetCompletedTasksForDay, tzOffset: %d", tzOffset)

    startDate := time.Date(year, month, day, 0, 0, 0, 0, time.UTC).Add(time.Duration(tzOffset) * time.Second * -1)
    endDate := time.Date(year, month, day, 23, 59, 59, 0, time.UTC).Add(time.Duration(tzOffset) * time.Second * -1)

    //log.Printf("GetCompletedTasksForDay, startDate: %+v", startDate)
    //log.Printf("GetCompletedTasksForDay, endDate: %+v", endDate)

    tasks, err := s.repository.completedTasksForUser(user.ID.Hex(), startDate, endDate)

    if err != nil {
        return nil, err
    }

    return tasks, nil
}

func (s *TimerService) CompleteActiveTimersAtMidnight(utcNow *time.Time) error {
    timezoneOffset := utils.WhichTimezoneIsMidnightAt(utcNow.Hour(), utcNow.Minute())
    timers, err := s.repository.findActiveByTimezoneOffset(timezoneOffset)
    if err != nil {
        return err
    }

    log.Printf("Found %d timer(s) to complete", len(timers))

    for _, timer := range timers {
        log.Printf("Completing %s timer", timer.TaskName)

        endDate := time.Date(timer.CreatedAt.Year(), timer.CreatedAt.Month(), timer.CreatedAt.Day(), utcNow.Hour()-1, 59, 59, 0, time.UTC)
        timer.FinishedAt = &endDate

        timer.Minutes = int(endDate.Sub(timer.CreatedAt).Minutes())
        err = s.repository.update(timer)
        if err != nil {
            return err
        }
    }

    return nil
}

func (s *TimerService) CalculateMinutesForActiveTimer(timer *models.Timer) int {
    duration := time.Since(timer.CreatedAt)
    return int(duration.Minutes())
}

// Returns all user tasks for range(startDate...endDate).
// Range couldn't be more than 31 day
func (s *TimerService) GetUserTimersByRange(startDate, endDate string, user *models.TeamUser) ([]*models.Timer, error) {
    // Decide what timezone to use: user or tz from frontend request? todo
    tzOffset := user.SlackUserInfo.TZOffset
    layout := "2006-1-2 15:04:05"

    startDateParse, err := time.Parse(layout, startDate + " 00:00:00")
    if err != nil {
        return nil, err
    }

    endDateTParse, err := time.Parse(layout, endDate + " 23:59:59")
    if err != nil {
        return nil, err
    }

    startTime := startDateParse.Add(time.Duration(tzOffset) * time.Second * -1)
    endTime := endDateTParse.Add(time.Duration(tzOffset) * time.Second * -1)

    if endTime.Sub(startTime).Hours() > maxDaysCount * 24 {
        return nil, errors.New("Too much days in range")
    }

    return s.repository.findUserTasksByRange(user.ID.Hex(), startTime, endTime)
}

func (s *TimerService) UpdateUserTimer(user *models.TeamUser, timer *models.Timer, newData *models.Timer) error {
    if user.ID.Hex() != timer.TeamUserID && !(user.SlackUserInfo.IsOwner && user.TeamID == timer.TeamID) {
        //TODO move all errors into separate package
        return errors.New("update forbidden")
    }

    // Allowed parameters: TaskName, ProjectID, ProjectExternalID, ProjectExternalName, Edits
    timer.TaskName = newData.TaskName
    timer.ProjectID = newData.ProjectID
    timer.ProjectExternalID = newData.ProjectExternalID
    timer.ProjectExternalName = newData.ProjectExternalName
    timer.Edits = newData.Edits

    var count int = 0
    for _, te := range newData.Edits {
        count += te.Minutes
    }
    timer.Minutes = timer.ActualMinutes + count

    return s.repository.update(timer)
}

func (s *TimerService) DeleteUserTimer(user *models.TeamUser, timer *models.Timer) error {
    if user.ID.Hex() != timer.TeamUserID {
        //TODO move all errors into separate package
        return errors.New("delete forbidden")
    }

    now := time.Now()
    timer.DeletedAt = &now

    if timer.FinishedAt == nil {
        return s.StopTimer(timer)
    }
    return s.repository.update(timer)
}

func (s *TimerService) UserMonthStatistics(user *models.TeamUser, date string) ([]*models.UserStatisticsAggregation, error) {
    layout := "2006-1-2 15:04:05"

    startOfMonth, err := time.Parse(layout, date + " 00:00:00")
    if err != nil {
        return nil, err
    }
    endOfMonth := startOfMonth.AddDate(0, 1, 0).Add(-1 * time.Second)

    return s.repository.userStatistics(user, startOfMonth, endOfMonth)
}