Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions internal/apis/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
ID string `json:"id"`
Title string `json:"title" binding:"required"`
NextDueDate string `json:"next_due_date"`
EndDate string `json:"end_date"`
IsRolling bool `json:"is_rolling"`
Frequency models.Frequency `json:"frequency"`
Notification models.NotificationTriggerOptions `json:"notification"`
Expand Down Expand Up @@ -114,7 +115,6 @@
}

var dueDate *time.Time

if TaskReq.NextDueDate != "" {
rawDueDate, err := time.Parse(time.RFC3339, TaskReq.NextDueDate)
if err != nil {
Expand All @@ -129,10 +129,26 @@
dueDate = &rawDueDate
}

var endDate *time.Time
if TaskReq.EndDate != "" {
rawEndDate, err := time.Parse(time.RFC3339, TaskReq.EndDate)
if err != nil {
log.Errorf("error parsing end date: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": "End date must be in UTC format",
})
return
}

Check warning on line 141 in internal/apis/task.go

View check run for this annotation

Codecov / codecov/patch

internal/apis/task.go#L132-L141

Added lines #L132 - L141 were not covered by tests

rawEndDate = rawEndDate.UTC()
endDate = &rawEndDate

Check warning on line 144 in internal/apis/task.go

View check run for this annotation

Codecov / codecov/patch

internal/apis/task.go#L143-L144

Added lines #L143 - L144 were not covered by tests
}

createdTask := &models.Task{
Title: TaskReq.Title,
Frequency: TaskReq.Frequency,
NextDueDate: dueDate,
EndDate: endDate,

Check warning on line 151 in internal/apis/task.go

View check run for this annotation

Codecov / codecov/patch

internal/apis/task.go#L151

Added line #L151 was not covered by tests
CreatedBy: currentIdentity.UserID,
IsRolling: TaskReq.IsRolling,
IsActive: true,
Expand Down Expand Up @@ -180,7 +196,6 @@
}

var dueDate *time.Time

if TaskReq.NextDueDate != "" {
rawDueDate, err := time.Parse(time.RFC3339, TaskReq.NextDueDate)
if err != nil {
Expand All @@ -195,6 +210,21 @@
dueDate = &rawDueDate
}

var endDate *time.Time
if TaskReq.EndDate != "" {
rawEndDate, err := time.Parse(time.RFC3339, TaskReq.EndDate)
if err != nil {
log.Errorf("error parsing end date: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": "End date must be in UTC format",
})
return
}

Check warning on line 222 in internal/apis/task.go

View check run for this annotation

Codecov / codecov/patch

internal/apis/task.go#L213-L222

Added lines #L213 - L222 were not covered by tests

rawEndDate = rawEndDate.UTC()
endDate = &rawEndDate

Check warning on line 225 in internal/apis/task.go

View check run for this annotation

Codecov / codecov/patch

internal/apis/task.go#L224-L225

Added lines #L224 - L225 were not covered by tests
}

taskId, err := strconv.Atoi(TaskReq.ID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
Expand Down Expand Up @@ -232,6 +262,7 @@
Title: TaskReq.Title,
Frequency: TaskReq.Frequency,
NextDueDate: dueDate,
EndDate: endDate,

Check warning on line 265 in internal/apis/task.go

View check run for this annotation

Codecov / codecov/patch

internal/apis/task.go#L265

Added line #L265 was not covered by tests
CreatedBy: currentIdentity.UserID,
IsRolling: TaskReq.IsRolling,
Notification: TaskReq.Notification,
Expand Down
1 change: 1 addition & 0 deletions internal/models/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Task struct {
Title string `json:"title" gorm:"column:title;not null"`
Frequency Frequency `json:"frequency" gorm:"embedded;embeddedPrefix:frequency_"`
NextDueDate *time.Time `json:"next_due_date" gorm:"column:next_due_date;index"`
EndDate *time.Time `json:"end_date" gorm:"column:end_date;default:NULL"`
IsRolling bool `json:"is_rolling" gorm:"column:is_rolling;default:false"`
CreatedBy int `json:"-" gorm:"column:created_by;not null"`
IsActive bool `json:"-" gorm:"column:is_active;default:true"`
Expand Down
4 changes: 4 additions & 0 deletions internal/repos/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,9 @@ func ScheduleNextDueDate(task *models.Task, completedDate time.Time) (*time.Time
}
}

if task.EndDate != nil && nextDueDate.After(*task.EndDate) {
return nil, nil
}

return &nextDueDate, nil
}
45 changes: 45 additions & 0 deletions internal/repos/task/task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,39 @@ func (s *TaskTestSuite) TestCreateTask() {
s.Equal(string(models.RepeatOnce), string(savedTask.Frequency.Type))
}

func (s *TaskTestSuite) TestCreateTaskWithEndDate() {
ctx := context.Background()
dueDate := time.Now().Add(24 * time.Hour)
endDate := time.Now().Add(30 * 24 * time.Hour) // 30 days from now

task := &models.Task{
Title: "Recurring Task with End Date",
CreatedBy: s.testUser.ID,
NextDueDate: &dueDate,
EndDate: &endDate,
IsRolling: false,
IsActive: true,
Frequency: models.Frequency{
Type: models.RepeatWeekly,
},
}

id, err := s.repo.CreateTask(ctx, task)
s.Require().NoError(err)
s.Require().Greater(id, 0)

var savedTask models.Task
err = s.DB.First(&savedTask, id).Error
s.Require().NoError(err)
s.Equal("Recurring Task with End Date", savedTask.Title)
s.Equal(s.testUser.ID, savedTask.CreatedBy)
s.WithinDuration(*savedTask.NextDueDate, dueDate, time.Second)
s.WithinDuration(*savedTask.EndDate, endDate, time.Second)
s.Equal(false, savedTask.IsRolling)
s.Equal(true, savedTask.IsActive)
s.Equal(string(models.RepeatWeekly), string(savedTask.Frequency.Type))
}

func (s *TaskTestSuite) TestUpsertTask() {
ctx := context.Background()
dueDate := time.Now().Add(24 * time.Hour)
Expand Down Expand Up @@ -475,6 +508,18 @@ func (s *TaskTestSuite) TestScheduleNextDueDate() {
expectedType: "time",
expectedDelta: 7 * 24 * time.Hour, // Should be 7 days from completion
},
{
name: "End date restriction",
task: &models.Task{
NextDueDate: &now,
EndDate: &now, // End date is today
Frequency: models.Frequency{
Type: models.RepeatDaily,
},
},
completedDate: now,
expectedType: "nil", // Should not calculate a next due date
},
}

for _, tc := range testCases {
Expand Down