diff --git a/frontend/src/helpers/parseDateOrNull.ts b/frontend/src/helpers/parseDateOrNull.ts index 680a5bb25..1f00b3a3b 100644 --- a/frontend/src/helpers/parseDateOrNull.ts +++ b/frontend/src/helpers/parseDateOrNull.ts @@ -1,7 +1,7 @@ /** * Make date objects from timestamps */ -export function parseDateOrNull(date: string | Date) { +export function parseDateOrNull(date: string | Date | null) { if (date instanceof Date) { return date } diff --git a/pkg/caldav/parsing.go b/pkg/caldav/parsing.go index 497b2b957..363e07de2 100644 --- a/pkg/caldav/parsing.go +++ b/pkg/caldav/parsing.go @@ -17,6 +17,7 @@ package caldav import ( + "code.vikunja.io/api/pkg/modules" "errors" "strconv" "strings" @@ -198,7 +199,7 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT var alarms []Alarm for _, reminder := range t.Reminders { alarms = append(alarms, Alarm{ - Time: reminder.Reminder, + Time: reminder.Reminder.Time(), Duration: time.Duration(reminder.RelativePeriod) * time.Second, RelativeTo: reminder.RelativeTo, }) @@ -215,19 +216,19 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT } caldavtodos = append(caldavtodos, &Todo{ - Timestamp: t.Updated, + Timestamp: t.Updated.Time(), UID: t.UID, Summary: t.Title, Description: t.Description, - Completed: t.DoneAt, + Completed: t.DoneAt.Time(), // Organizer: &t.CreatedBy, // Disabled until we figure out how this works Categories: categories, Priority: t.Priority, - Start: t.StartDate, - End: t.EndDate, - Created: t.Created, - Updated: t.Updated, - DueDate: t.DueDate, + Start: t.StartDate.Time(), + End: t.EndDate.Time(), + Created: t.Created.Time(), + Updated: t.Updated.Time(), + DueDate: t.DueDate.Time(), Duration: duration, RepeatAfter: t.RepeatAfter, RepeatMode: t.RepeatMode, @@ -437,10 +438,10 @@ func contains(array []string, str string) bool { } // https://tools.ietf.org/html/rfc5545#section-3.3.5 -func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time { +func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) modules.Time { tstring := ianaProperty.Value if tstring == "" { - return time.Time{} + return modules.Time{} } format := DateFormat @@ -453,7 +454,6 @@ func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time { format = `20060102` } - var t time.Time var err error tzParameter := ianaProperty.ICalParameters["TZID"] if len(tzParameter) > 0 { @@ -461,19 +461,18 @@ func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time { if err != nil { log.Warningf("Error while parsing caldav timezone %s: %s", tzParameter[0], err) } else { - t, err = time.ParseInLocation(format, tstring, loc) + tt, err := time.ParseInLocation(format, tstring, loc) if err != nil { log.Warningf("Error while parsing caldav time %s to TimeStamp: %s at location %s", tstring, loc, err) } else { - t = t.In(config.GetTimeZone()) - return t + return modules.Time(tt.In(config.GetTimeZone())) } } } - t, err = time.Parse(format, tstring) + tt, err := time.Parse(format, tstring) if err != nil { log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err) - return time.Time{} + return modules.Time{} } - return t + return modules.Time(tt) } diff --git a/pkg/caldav/parsing_test.go b/pkg/caldav/parsing_test.go index 2ec4591ea..e72ba8497 100644 --- a/pkg/caldav/parsing_test.go +++ b/pkg/caldav/parsing_test.go @@ -17,6 +17,7 @@ package caldav import ( + "code.vikunja.io/api/pkg/modules" "testing" "time" @@ -56,7 +57,7 @@ END:VCALENDAR`, Title: "Todo #1", UID: "randomuid", Description: "Lorem Ipsum", - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), }, }, { @@ -82,7 +83,7 @@ END:VCALENDAR`, UID: "randomuid", Description: "Lorem Ipsum", Priority: 1, - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), }, }, { @@ -115,7 +116,7 @@ END:VCALENDAR`, Title: "cat2", }, }, - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), }, }, { @@ -144,10 +145,10 @@ END:VCALENDAR`, Description: "Lorem Ipsum", Reminders: []*models.TaskReminder{ { - Reminder: time.Date(2018, 12, 1, 1, 12, 10, 0, config.GetTimeZone()), + Reminder: modules.Time(time.Date(2018, 12, 1, 1, 12, 10, 0, config.GetTimeZone())), }, }, - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), }, }, { @@ -192,8 +193,8 @@ END:VCALENDAR`, Title: "Todo #1", UID: "randomuid", Description: "Lorem Ipsum", - StartDate: time.Date(2023, 2, 28, 17, 0, 0, 0, config.GetTimeZone()), - DueDate: time.Date(2023, 3, 4, 15, 0, 0, 0, config.GetTimeZone()), + StartDate: modules.Time(time.Date(2023, 2, 28, 17, 0, 0, 0, config.GetTimeZone())), + DueDate: modules.Time(time.Date(2023, 3, 4, 15, 0, 0, 0, config.GetTimeZone())), Reminders: []*models.TaskReminder{ { RelativeTo: models.ReminderRelationStartDate, @@ -216,7 +217,7 @@ END:VCALENDAR`, RelativePeriod: -1800, }, }, - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), }, }, { @@ -241,7 +242,7 @@ END:VCALENDAR`, Title: "SubTask #1", UID: "randomuid", Description: "Lorem Ipsum", - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindParenttask: { { @@ -273,7 +274,7 @@ END:VCALENDAR`, Title: "Parent", UID: "randomuid", Description: "Lorem Ipsum", - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindSubtask: { { @@ -331,7 +332,7 @@ END:VTIMEZONE END:VCALENDAR`, }, wantVTask: &models.Task{ - Updated: time.Date(2023, 4, 2, 7, 41, 58, 0, config.GetTimeZone()), + Updated: modules.Time(time.Date(2023, 4, 2, 7, 41, 58, 0, config.GetTimeZone())), UID: "4290517349243274514", Title: "Test with tasks.org", Priority: 1, @@ -340,15 +341,15 @@ END:VCALENDAR`, Title: "Vikunja", }, }, - DueDate: time.Date(2023, 4, 2, 15, 0, 1, 0, config.GetTimeZone()), - StartDate: time.Date(2023, 4, 1, 7, 0, 0, 0, config.GetTimeZone()), + DueDate: modules.Time(time.Date(2023, 4, 2, 15, 0, 1, 0, config.GetTimeZone())), + StartDate: modules.Time(time.Date(2023, 4, 1, 7, 0, 0, 0, config.GetTimeZone())), Reminders: []*models.TaskReminder{ { RelativeTo: models.ReminderRelationDueDate, RelativePeriod: 0, }, { - Reminder: time.Date(2023, 4, 2, 10, 0, 0, 0, config.GetTimeZone()), + Reminder: modules.Time(time.Date(2023, 4, 2, 10, 0, 0, 0, config.GetTimeZone())), }, }, }, @@ -529,12 +530,12 @@ func TestGetCaldavTodosForTasks(t *testing.T) { UID: "randomuid", Description: "Description", Priority: 3, - Created: time.Unix(1543626721, 0).In(config.GetTimeZone()), - DueDate: time.Unix(1543626722, 0).In(config.GetTimeZone()), - StartDate: time.Unix(1543626723, 0).In(config.GetTimeZone()), - EndDate: time.Unix(1543626724, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()), - DoneAt: time.Unix(1543626726, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626721, 0).In(config.GetTimeZone())), + DueDate: modules.Time(time.Unix(1543626722, 0).In(config.GetTimeZone())), + StartDate: modules.Time(time.Unix(1543626723, 0).In(config.GetTimeZone())), + EndDate: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626725, 0).In(config.GetTimeZone())), + DoneAt: modules.Time(time.Unix(1543626726, 0).In(config.GetTimeZone())), RepeatAfter: 86400, Labels: []*models.Label{ { @@ -548,10 +549,10 @@ func TestGetCaldavTodosForTasks(t *testing.T) { }, Reminders: []*models.TaskReminder{ { - Reminder: time.Unix(1543626730, 0).In(config.GetTimeZone()), + Reminder: modules.Time(time.Unix(1543626730, 0).In(config.GetTimeZone())), }, { - Reminder: time.Unix(1543626731, 0).In(config.GetTimeZone()), + Reminder: modules.Time(time.Unix(1543626731, 0).In(config.GetTimeZone())), RelativePeriod: -3600, RelativeTo: models.ReminderRelationDueDate, }, @@ -609,23 +610,23 @@ END:VCALENDAR`, UID: "randomuid_parent", Description: "A parent task", Priority: 3, - Created: time.Unix(1543626721, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626721, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626725, 0).In(config.GetTimeZone())), RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindSubtask: { { Title: "Subtask 1", UID: "randomuid_child_1", Description: "The first child task", - Created: time.Unix(1543626724, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), }, { Title: "Subtask 2", UID: "randomuid_child_2", Description: "The second child task", - Created: time.Unix(1543626724, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), }, }, }, @@ -636,8 +637,8 @@ END:VCALENDAR`, Title: "Subtask 1", UID: "randomuid_child_1", Description: "The first child task", - Created: time.Unix(1543626724, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindParenttask: { { @@ -645,8 +646,8 @@ END:VCALENDAR`, UID: "randomuid_parent", Description: "A parent task", Priority: 3, - Created: time.Unix(1543626721, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626721, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626725, 0).In(config.GetTimeZone())), }, }, }, @@ -657,8 +658,8 @@ END:VCALENDAR`, Title: "Subtask 2", UID: "randomuid_child_2", Description: "The second child task", - Created: time.Unix(1543626724, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626724, 0).In(config.GetTimeZone())), RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindParenttask: { { @@ -666,8 +667,8 @@ END:VCALENDAR`, UID: "randomuid_parent", Description: "A parent task", Priority: 3, - Created: time.Unix(1543626721, 0).In(config.GetTimeZone()), - Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()), + Created: modules.Time(time.Unix(1543626721, 0).In(config.GetTimeZone())), + Updated: modules.Time(time.Unix(1543626725, 0).In(config.GetTimeZone())), }, }, }, diff --git a/pkg/files/files.go b/pkg/files/files.go index 695ec052d..531dad9be 100644 --- a/pkg/files/files.go +++ b/pkg/files/files.go @@ -22,12 +22,12 @@ import ( "os" "path/filepath" "strconv" - "time" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/modules/keyvalue" "code.vikunja.io/api/pkg/web" @@ -43,8 +43,8 @@ type File struct { Mime string `xorm:"text null" json:"mime"` Size uint64 `xorm:"bigint not null" json:"size"` - Created time.Time `xorm:"created" json:"created"` - CreatedByID int64 `xorm:"bigint not null" json:"-"` + Created modules.Time `xorm:"created" json:"created"` + CreatedByID int64 `xorm:"bigint not null" json:"-"` File afero.File `xorm:"-" json:"-"` // This ReadCloser is only used for migration purposes. Use with care! diff --git a/pkg/initialize/events.go b/pkg/initialize/events.go index d5cd7996b..70d35b184 100644 --- a/pkg/initialize/events.go +++ b/pkg/initialize/events.go @@ -16,7 +16,9 @@ package initialize -import "time" +import ( + "time" +) // BootedEvent represents a BootedEvent event type BootedEvent struct { diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index d550d7e56..1a0faeec3 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -115,49 +115,49 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by priority desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) + assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) }) t.Run("by priority asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) // should equal duedate asc t.Run("by due_date", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) + assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) }) t.Run("by duedate desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) }) // Due date without unix suffix t.Run("by duedate asc without suffix", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) + assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) }) t.Run("by due_date without suffix", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) + assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) }) t.Run("by duedate desc without suffix", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) }) t.Run("by duedate asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) + assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) }) t.Run("invalid sort parameter", func(t *testing.T) { _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams) @@ -235,7 +235,7 @@ func TestTaskCollection(t *testing.T) { urlParams, ) require.NoError(t, err) - // If no start date but an end date is specified, this should be null + // If no start date but an end date is specified, this should benull // since we don't have any tasks in the fixtures with an end date > // the current date. assert.Equal(t, "[]\n", rec.Body.String()) @@ -358,33 +358,33 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},{"id":5,"title":"Label #5","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},{"id":5,"title":"Label #5","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by priority desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) + assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) }) t.Run("by priority asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},{"id":5,"title":"Label #5","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},{"id":5,"title":"Label #5","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":null,"due_date":null,"reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) // should equal duedate asc t.Run("by due_date", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) + assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) }) t.Run("by duedate desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) }) t.Run("by duedate asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) + assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) }) t.Run("invalid parameter", func(t *testing.T) { // Invalid parameter should not sort at all @@ -452,7 +452,7 @@ func TestTaskCollection(t *testing.T) { nil, ) require.NoError(t, err) - // If no start date but an end date is specified, this should be null + // If no start date but an end date is specified, this should benull // since we don't have any tasks in the fixtures with an end date > // the current date. assert.Equal(t, "[]\n", rec.Body.String()) diff --git a/pkg/integrations/task_test.go b/pkg/integrations/task_test.go index 7e77020d0..93c8df234 100644 --- a/pkg/integrations/task_test.go +++ b/pkg/integrations/task_test.go @@ -91,9 +91,9 @@ func TestTask(t *testing.T) { assert.NotContains(t, rec.Body.String(), `"due_date":0`) }) t.Run("Due date unset", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "5"}, `{"due_date": null}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "5"}, `{"due_date":null}`) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"due_date":"0001-01-01T00:00:00Z"`) + assert.Contains(t, rec.Body.String(), `"due_date":null`) assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`) }) t.Run("Reminders", func(t *testing.T) { @@ -110,8 +110,8 @@ func TestTask(t *testing.T) { assert.Contains(t, rec.Body.String(), `"reminders":null`) assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`) }) - t.Run("Reminders unset to null", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminders": null}`) + t.Run("Reminders unset tonull", func(t *testing.T) { + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminders":null}`) require.NoError(t, err) assert.Contains(t, rec.Body.String(), `"reminders":null`) assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`) @@ -146,7 +146,7 @@ func TestTask(t *testing.T) { assert.Contains(t, rec.Body.String(), `"assignees":null`) assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`) }) - t.Run("Removing Assignees null", func(t *testing.T) { + t.Run("Removing Assigneesnull", func(t *testing.T) { rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "30"}, `{"assignees":null}`) require.NoError(t, err) assert.Contains(t, rec.Body.String(), `"assignees":null`) @@ -171,9 +171,9 @@ func TestTask(t *testing.T) { assert.NotContains(t, rec.Body.String(), `"start_date":0`) }) t.Run("Start date unset", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "7"}, `{"start_date":"0001-01-01T00:00:00Z"}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "7"}, `{"start_date":null}`) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"start_date":"0001-01-01T00:00:00Z"`) + assert.Contains(t, rec.Body.String(), `"start_date":null`) assert.NotContains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`) }) t.Run("End date", func(t *testing.T) { @@ -183,9 +183,9 @@ func TestTask(t *testing.T) { assert.NotContains(t, rec.Body.String(), `"end_date":""`) }) t.Run("End date unset", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "8"}, `{"end_date":"0001-01-01T00:00:00Z"}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "8"}, `{"end_date":null}`) require.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"end_date":"0001-01-01T00:00:00Z"`) + assert.Contains(t, rec.Body.String(), `"end_date":null`) assert.NotContains(t, rec.Body.String(), `"end_date":"2020-02-10T10:00:00Z"`) }) t.Run("Color", func(t *testing.T) { diff --git a/pkg/metrics/active_users.go b/pkg/metrics/active_users.go index aa6a08a2d..9ec593951 100644 --- a/pkg/metrics/active_users.go +++ b/pkg/metrics/active_users.go @@ -21,8 +21,10 @@ import ( "time" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/modules/keyvalue" "code.vikunja.io/api/pkg/web" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) @@ -34,7 +36,7 @@ const activeLinkSharesKey = `active_link_shares` // ActiveAuthenticable defines an active user or link share type ActiveAuthenticable struct { ID int64 - LastSeen time.Time + LastSeen modules.Time } type activeUsersMap map[int64]*ActiveAuthenticable @@ -82,7 +84,7 @@ func setupActiveUsersMetric() { } count := 0 for _, u := range allActiveUsers { - if time.Since(u.LastSeen) < secondsUntilInactive*time.Second { + if time.Since(u.LastSeen.Time()) < secondsUntilInactive*time.Second { count++ } } @@ -109,7 +111,7 @@ func setupActiveLinkSharesMetric() { } count := 0 for _, u := range allActiveLinkShares { - if time.Since(u.LastSeen) < secondsUntilInactive*time.Second { + if time.Since(u.LastSeen.Time()) < secondsUntilInactive*time.Second { count++ } } @@ -126,7 +128,7 @@ func SetUserActive(a web.Auth) (err error) { defer activeUsers.mutex.Unlock() activeUsers.users[a.GetID()] = &ActiveAuthenticable{ ID: a.GetID(), - LastSeen: time.Now(), + LastSeen: modules.Time(time.Now()), } return keyvalue.Put(activeUsersKey, activeUsers.users) @@ -138,7 +140,7 @@ func SetLinkShareActive(a web.Auth) (err error) { defer activeLinkShares.mutex.Unlock() activeLinkShares.shares[a.GetID()] = &ActiveAuthenticable{ ID: a.GetID(), - LastSeen: time.Now(), + LastSeen: modules.Time(time.Now()), } return keyvalue.Put(activeLinkSharesKey, activeLinkShares.shares) diff --git a/pkg/models/api_tokens.go b/pkg/models/api_tokens.go index b15abafaf..59d97331d 100644 --- a/pkg/models/api_tokens.go +++ b/pkg/models/api_tokens.go @@ -20,14 +20,13 @@ import ( "crypto/sha256" "crypto/subtle" "encoding/hex" - "time" - "xorm.io/builder" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/utils" - "code.vikunja.io/api/pkg/web" + "golang.org/x/crypto/pbkdf2" "xorm.io/xorm" ) @@ -48,10 +47,10 @@ type APIToken struct { // The permissions this token has. Possible values are available via the /routes endpoint and consist of the keys of the list from that endpoint. For example, if the token should be able to read all tasks as well as update existing tasks, you should add `{"tasks":["read_all","update"]}`. Permissions APIPermissions `xorm:"json not null" json:"permissions" valid:"required"` // The date when this key expires. - ExpiresAt time.Time `xorm:"not null" json:"expires_at" valid:"required"` + ExpiresAt modules.Time `xorm:"not null" json:"expires_at" valid:"required"` // A timestamp when this api key was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` OwnerID int64 `xorm:"bigint not null" json:"-"` diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 2f0f112ea..c26ef8a6b 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "strconv" "strings" "time" @@ -50,9 +51,9 @@ type Bucket struct { Position float64 `xorm:"double null" json:"position"` // A timestamp when this bucket was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this bucket was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` // The user who initially created the bucket. CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"` @@ -175,8 +176,8 @@ func GetTasksInBucketsForView(s *xorm.Session, view *ProjectView, projects []*Pr ProjectViewID: view.ID, Position: float64(id), CreatedByID: auth.GetID(), - Created: time.Now(), - Updated: time.Now(), + Created: modules.Time(time.Now()), + Updated: modules.Time(time.Now()), }) } } diff --git a/pkg/models/kanban_task_bucket.go b/pkg/models/kanban_task_bucket.go index 1777be217..36d94a28d 100644 --- a/pkg/models/kanban_task_bucket.go +++ b/pkg/models/kanban_task_bucket.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "time" "code.vikunja.io/api/pkg/events" @@ -152,9 +153,9 @@ func (b *TaskBucket) Update(s *xorm.Session, a web.Auth) (err error) { if doneChanged { if task.Done { - task.DoneAt = time.Now() + task.DoneAt = modules.Time(time.Now()) } else { - task.DoneAt = time.Time{} + task.DoneAt = modules.Time{} } _, err = s.Where("id = ?", task.ID). Cols( diff --git a/pkg/models/label.go b/pkg/models/label.go index fe6d279c8..af2355af5 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -17,8 +17,7 @@ package models import ( - "time" - + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" @@ -42,9 +41,9 @@ type Label struct { CreatedBy *user.User `xorm:"-" json:"created_by"` // A timestamp when this label was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this label was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/label_task.go b/pkg/models/label_task.go index 08303f713..329bf928e 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -17,14 +17,13 @@ package models import ( - "strconv" - "strings" - "time" - "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" + "strconv" + "strings" "xorm.io/builder" "xorm.io/xorm" @@ -38,7 +37,7 @@ type LabelTask struct { // The label id you want to associate with a task. LabelID int64 `xorm:"bigint INDEX not null" json:"label_id" param:"label"` // A timestamp when this task was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/label_task_test.go b/pkg/models/label_task_test.go index dc57ddfa3..0e16b5eaf 100644 --- a/pkg/models/label_task_test.go +++ b/pkg/models/label_task_test.go @@ -17,14 +17,13 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/user" + "gopkg.in/d4l3k/messagediff.v1" "reflect" "runtime" "testing" - "time" - - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/user" - "gopkg.in/d4l3k/messagediff.v1" "code.vikunja.io/api/pkg/web" ) @@ -54,7 +53,7 @@ func TestLabelTask_ReadAll(t *testing.T) { ID int64 TaskID int64 LabelID int64 - Created time.Time + Created modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -160,7 +159,7 @@ func TestLabelTask_Create(t *testing.T) { ID int64 TaskID int64 LabelID int64 - Created time.Time + Created modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -264,7 +263,7 @@ func TestLabelTask_Delete(t *testing.T) { ID int64 TaskID int64 LabelID int64 - Created time.Time + Created modules.Time CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/label_test.go b/pkg/models/label_test.go index 47d8a3169..7aa71c3ee 100644 --- a/pkg/models/label_test.go +++ b/pkg/models/label_test.go @@ -17,14 +17,13 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/web" "reflect" "runtime" "testing" - "time" - - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/user" - "code.vikunja.io/api/pkg/web" "gopkg.in/d4l3k/messagediff.v1" ) @@ -37,8 +36,8 @@ func TestLabel_ReadAll(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -173,8 +172,8 @@ func TestLabel_ReadOne(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -303,8 +302,8 @@ func TestLabel_Create(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -373,8 +372,8 @@ func TestLabel_Update(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -463,8 +462,8 @@ func TestLabel_Delete(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index ad166b8cc..cc9f7195c 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -17,10 +17,9 @@ package models import ( - "errors" - "time" - "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" + "errors" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" @@ -66,9 +65,9 @@ type LinkSharing struct { SharedByID int64 `xorm:"bigint INDEX not null" json:"-"` // A timestamp when this project was shared. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this share was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/listeners.go b/pkg/models/listeners.go index 4333d1f27..ff3bb2176 100644 --- a/pkg/models/listeners.go +++ b/pkg/models/listeners.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "context" "encoding/json" "strconv" @@ -830,9 +831,9 @@ func (wl *WebhookListener) Name() string { } type WebhookPayload struct { - EventName string `json:"event_name"` - Time time.Time `json:"time"` - Data interface{} `json:"data"` + EventName string `json:"event_name"` + Time modules.Time `json:"time"` + Data interface{} `json:"data"` } func getIDAsInt64(id interface{}) int64 { @@ -964,7 +965,7 @@ func (wl *WebhookListener) Handle(msg *message.Message) (err error) { for _, webhook := range matchingWebhooks { err = webhook.sendWebhookPayload(&WebhookPayload{ EventName: wl.EventName, - Time: time.Now(), + Time: modules.Time(time.Now()), Data: event, }) if err != nil { diff --git a/pkg/models/main_test.go b/pkg/models/main_test.go index 6ff126080..d48526b61 100644 --- a/pkg/models/main_test.go +++ b/pkg/models/main_test.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "fmt" "os" "testing" @@ -35,13 +36,15 @@ func setupTime() { fmt.Printf("Error setting up time: %s", err) os.Exit(1) } - testCreatedTime, err = time.ParseInLocation(time.RFC3339Nano, "2018-12-01T15:13:12.0+00:00", loc) + testCreatedTimeRaw, err := time.ParseInLocation(time.RFC3339Nano, "2018-12-01T15:13:12.0+00:00", loc) + testCreatedTime = modules.Time(testCreatedTimeRaw) if err != nil { fmt.Printf("Error setting up time: %s", err) os.Exit(1) } testCreatedTime = testCreatedTime.In(loc) - testUpdatedTime, err = time.ParseInLocation(time.RFC3339Nano, "2018-12-02T15:13:12.0+00:00", loc) + testUpdatedTimeRaw, err := time.ParseInLocation(time.RFC3339Nano, "2018-12-02T15:13:12.0+00:00", loc) + testUpdatedTime = modules.Time(testUpdatedTimeRaw) if err != nil { fmt.Printf("Error setting up time: %s", err) os.Exit(1) diff --git a/pkg/models/models.go b/pkg/models/models.go index a9d5d388d..4e653610e 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -17,23 +17,22 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" - _ "github.com/go-sql-driver/mysql" // Because. - _ "github.com/lib/pq" // Because. - "xorm.io/xorm" + "code.vikunja.io/api/pkg/modules" - _ "github.com/mattn/go-sqlite3" // Because. + _ "github.com/go-sql-driver/mysql" // imported for side effects + _ "github.com/lib/pq" // imported for side effects + _ "github.com/mattn/go-sqlite3" // imported for side effects + "xorm.io/xorm" ) var ( x *xorm.Engine - testCreatedTime time.Time - testUpdatedTime time.Time + testCreatedTime modules.Time + testUpdatedTime modules.Time ) // GetTables returns all structs which are also a table. diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go index 96442e59c..214ee3d7b 100644 --- a/pkg/models/notifications.go +++ b/pkg/models/notifications.go @@ -225,7 +225,7 @@ type UndoneTaskOverdueNotification struct { // ToMail returns the mail notification for UndoneTaskOverdueNotification func (n *UndoneTaskOverdueNotification) ToMail() *notifications.Mail { - until := time.Until(n.Task.DueDate).Round(1*time.Hour) * -1 + until := time.Until(n.Task.DueDate.Time()).Round(1*time.Hour) * -1 return notifications.NewMail(). IncludeLinkToSettings(). Subject(`Task "`+n.Task.Title+`" (`+n.Project.Title+`) is overdue`). @@ -261,12 +261,12 @@ func (n *UndoneTasksOverdueNotification) ToMail() *notifications.Mail { } sort.Slice(sortedTasks, func(i, j int) bool { - return sortedTasks[i].DueDate.Before(sortedTasks[j].DueDate) + return sortedTasks[i].DueDate.Time().Before(sortedTasks[j].DueDate.Time()) }) overdueLine := "" for _, task := range sortedTasks { - until := time.Until(task.DueDate).Round(1*time.Hour) * -1 + until := time.Until(task.DueDate.Time()).Round(1*time.Hour) * -1 overdueLine += `* [` + task.Title + `](` + config.ServicePublicURL.GetString() + "tasks/" + strconv.FormatInt(task.ID, 10) + `) (` + n.Projects[task.ProjectID].Title + `), ` + getOverdueSinceString(until) + "\n" } diff --git a/pkg/models/project.go b/pkg/models/project.go index f7596757e..db0b0cbdf 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "fmt" "math" "strconv" @@ -81,9 +82,9 @@ type Project struct { MaxRight Right `xorm:"-" json:"max_right"` // A timestamp when this project was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this project was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` @@ -154,8 +155,8 @@ var FavoritesPseudoProject = Project{ }, }, - Created: time.Now(), - Updated: time.Now(), + Created: modules.Time(time.Now()), + Updated: modules.Time(time.Now()), } // ReadAll gets all projects a user has access to diff --git a/pkg/models/project_team.go b/pkg/models/project_team.go index 7811b015b..20fdb236d 100644 --- a/pkg/models/project_team.go +++ b/pkg/models/project_team.go @@ -17,9 +17,8 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/events" @@ -39,9 +38,9 @@ type TeamProject struct { Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this relation was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/project_team_test.go b/pkg/models/project_team_test.go index 99b2f3a43..875233593 100644 --- a/pkg/models/project_team_test.go +++ b/pkg/models/project_team_test.go @@ -17,14 +17,13 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/web" "reflect" "runtime" "testing" - "time" - - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/user" - "code.vikunja.io/api/pkg/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -212,8 +211,8 @@ func TestTeamProject_Update(t *testing.T) { TeamID int64 ProjectID int64 Right Right - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/project_users.go b/pkg/models/project_users.go index 1c67e9eb9..272f08729 100644 --- a/pkg/models/project_users.go +++ b/pkg/models/project_users.go @@ -17,9 +17,8 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/events" @@ -42,9 +41,9 @@ type ProjectUser struct { Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this relation was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/project_users_rights_test.go b/pkg/models/project_users_rights_test.go index e94d5afe8..336a6884a 100644 --- a/pkg/models/project_users_rights_test.go +++ b/pkg/models/project_users_rights_test.go @@ -17,11 +17,10 @@ package models import ( - "testing" - "time" - "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" + "testing" "code.vikunja.io/api/pkg/web" ) @@ -32,8 +31,8 @@ func TestProjectUser_CanDoSomething(t *testing.T) { UserID int64 ProjectID int64 Right Right - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/project_users_test.go b/pkg/models/project_users_test.go index 1c8b4a4ac..ff51a3528 100644 --- a/pkg/models/project_users_test.go +++ b/pkg/models/project_users_test.go @@ -17,14 +17,13 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/web" "reflect" "runtime" "testing" - "time" - - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/user" - "code.vikunja.io/api/pkg/web" "github.com/stretchr/testify/require" "gopkg.in/d4l3k/messagediff.v1" @@ -37,8 +36,8 @@ func TestProjectUser_Create(t *testing.T) { Username string ProjectID int64 Right Right - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -179,8 +178,8 @@ func TestProjectUser_ReadAll(t *testing.T) { UserID int64 ProjectID int64 Right Right - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -271,8 +270,8 @@ func TestProjectUser_Update(t *testing.T) { Username string ProjectID int64 Right Right - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } @@ -361,8 +360,8 @@ func TestProjectUser_Delete(t *testing.T) { UserID int64 ProjectID int64 Right Right - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/project_view.go b/pkg/models/project_view.go index 99eac034a..3a86a9670 100644 --- a/pkg/models/project_view.go +++ b/pkg/models/project_view.go @@ -17,11 +17,10 @@ package models import ( + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/web" "encoding/json" "fmt" - "time" - - "code.vikunja.io/api/pkg/web" "xorm.io/xorm" ) @@ -145,9 +144,9 @@ type ProjectView struct { DoneBucketID int64 `xorm:"bigint INDEX null" json:"done_bucket_id"` // A timestamp when this view was updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` // A timestamp when this reaction was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/reaction.go b/pkg/models/reaction.go index fc38786a2..93d31bfda 100644 --- a/pkg/models/reaction.go +++ b/pkg/models/reaction.go @@ -17,8 +17,7 @@ package models import ( - "time" - + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/web" "xorm.io/builder" "xorm.io/xorm" @@ -51,7 +50,7 @@ type Reaction struct { Value string `xorm:"varchar(20) not null INDEX" json:"value" valid:"required"` // A timestamp when this reaction was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/saved_filters.go b/pkg/models/saved_filters.go index d9ee2226c..5c976db35 100644 --- a/pkg/models/saved_filters.go +++ b/pkg/models/saved_filters.go @@ -17,12 +17,11 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/cron" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" @@ -49,9 +48,9 @@ type SavedFilter struct { IsFavorite bool `xorm:"default false" json:"is_favorite"` // A timestamp when this filter was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this filter was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/subscription.go b/pkg/models/subscription.go index 6b5da305c..7c9b0bd27 100644 --- a/pkg/models/subscription.go +++ b/pkg/models/subscription.go @@ -17,13 +17,12 @@ package models import ( - "encoding/json" - "strconv" - "time" - + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/web" + "encoding/json" + "strconv" "xorm.io/xorm" ) @@ -107,7 +106,7 @@ type Subscription struct { UserID int64 `xorm:"bigint index not null" json:"-"` // A timestamp when this subscription was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/task_assignees.go b/pkg/models/task_assignees.go index 4fc370fa9..6d116705f 100644 --- a/pkg/models/task_assignees.go +++ b/pkg/models/task_assignees.go @@ -17,10 +17,9 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" @@ -30,10 +29,10 @@ import ( // TaskAssginee represents an assignment of a user to a task type TaskAssginee struct { - ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` - TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"projecttask"` - UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user"` - Created time.Time `xorm:"created not null"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` + TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"projecttask"` + UserID int64 `xorm:"bigint INDEX not null" json:"user_id" param:"user"` + Created modules.Time `xorm:"created not null"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index 622e26f85..70aef2eb5 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -18,18 +18,17 @@ package models import ( "bytes" + "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/modules/keyvalue" + "code.vikunja.io/api/pkg/user" + "code.vikunja.io/api/pkg/web" "image" "image/png" "io" "strconv" - "time" - - "code.vikunja.io/api/pkg/events" - "code.vikunja.io/api/pkg/files" - "code.vikunja.io/api/pkg/log" - "code.vikunja.io/api/pkg/modules/keyvalue" - "code.vikunja.io/api/pkg/user" - "code.vikunja.io/api/pkg/web" "github.com/disintegration/imaging" "xorm.io/xorm" @@ -46,7 +45,7 @@ type TaskAttachment struct { File *files.File `xorm:"-" json:"file"` - Created time.Time `xorm:"created" json:"created"` + Created modules.Time `xorm:"created" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/task_collection_filter.go b/pkg/models/task_collection_filter.go index a85a8337c..ebf959ded 100644 --- a/pkg/models/task_collection_filter.go +++ b/pkg/models/task_collection_filter.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "fmt" "reflect" "regexp" @@ -31,7 +32,6 @@ import ( "github.com/iancoleman/strcase" "github.com/jszwedko/go-datemath" "xorm.io/builder" - "xorm.io/xorm/schemas" ) type taskFilterComparator string @@ -62,13 +62,14 @@ type taskFilter struct { join taskFilterConcatinator } -func parseTimeFromUserInput(timeString string) (value time.Time, err error) { - value, err = time.Parse(time.RFC3339, timeString) +func parseTimeFromUserInput(timeString string) (value modules.Time, err error) { + var timeValue time.Time + timeValue, err = time.Parse(time.RFC3339, timeString) if err != nil { - value, err = time.Parse(safariDateAndTime, timeString) + timeValue, err = time.Parse(safariDateAndTime, timeString) } if err != nil { - value, err = time.Parse(safariDate, timeString) + timeValue, err = time.Parse(safariDate, timeString) } if err != nil { // Here we assume a date like 2022-11-1 and try to parse it manually @@ -88,10 +89,10 @@ func parseTimeFromUserInput(timeString string) (value time.Time, err error) { if err != nil { return value, err } - value = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) - return value.In(config.GetTimeZone()), nil + timeValue = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) + return modules.Time(timeValue.In(config.GetTimeZone())), nil } - return value.In(config.GetTimeZone()), err + return modules.Time(timeValue.In(config.GetTimeZone())), nil } func parseFilterFromExpression(f fexpr.ExprGroup, loc *time.Location) (filter *taskFilter, err error) { @@ -286,12 +287,12 @@ func getValueForField(field reflect.StructField, rawValue string, loc *time.Loca case reflect.Bool: value, err = strconv.ParseBool(rawValue) case reflect.Struct: - if field.Type == schemas.TimeType { + if field.Type == reflect.TypeOf((*modules.Time)(nil)).Elem() { var t datemath.Expression - var tt time.Time + var tt modules.Time t, err = datemath.Parse(rawValue) if err == nil { - tt = t.Time(datemath.WithLocation(config.GetTimeZone())).In(loc) + tt = modules.Time(t.Time(datemath.WithLocation(config.GetTimeZone())).In(loc)) } else { tt, err = parseTimeFromUserInput(rawValue) } @@ -300,8 +301,8 @@ func getValueForField(field reflect.StructField, rawValue string, loc *time.Loca } // Mysql/Mariadb does not support date values where the year < 1. To make this edge-case work, // we're setting the year to 1 in that case. - if db.GetDialect() == builder.MYSQL && tt.Year() < 1 { - tt = tt.AddDate(1-tt.Year(), 0, 0) + if db.GetDialect() == builder.MYSQL && tt.Time().Year() < 1 { + tt = modules.Time(tt.Time().AddDate(1-tt.Time().Year(), 0, 0)) } value = tt } diff --git a/pkg/models/task_collection_filter_test.go b/pkg/models/task_collection_filter_test.go index 4d2e5eb6b..0f84a7573 100644 --- a/pkg/models/task_collection_filter_test.go +++ b/pkg/models/task_collection_filter_test.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "testing" "time" @@ -115,7 +116,7 @@ func TestParseFilter(t *testing.T) { require.Len(t, result, 1) assert.Equal(t, "due_date", result[0].field) in30Days := time.Now().Add(time.Hour * 24 * 30) - assert.Equal(t, in30Days.Unix(), result[0].value.(time.Time).Unix()) + assert.Equal(t, in30Days.Unix(), result[0].value.(modules.Time).Unix()) }) t.Run("date math strings with quotes", func(t *testing.T) { result, err := getTaskFiltersFromFilterString("due_date < 'now+30d'", "UTC") @@ -124,7 +125,7 @@ func TestParseFilter(t *testing.T) { require.Len(t, result, 1) assert.Equal(t, "due_date", result[0].field) in30Days := time.Now().Add(time.Hour * 24 * 30) - assert.Equal(t, in30Days.Unix(), result[0].value.(time.Time).Unix()) + assert.Equal(t, in30Days.Unix(), result[0].value.(modules.Time).Unix()) }) t.Run("string values with single quotes", func(t *testing.T) { result, err := getTaskFiltersFromFilterString("title = 'foo bar'", "UTC") @@ -246,7 +247,7 @@ func TestParseFilter(t *testing.T) { assert.Equal(t, "start_date", result[0].field) assert.Equal(t, taskFilterComparatorEquals, result[0].comparator) expectedDate, _ := time.Parse("2006-01-02", "2023-06-15") - assert.Equal(t, expectedDate, result[0].value) + assert.Equal(t, expectedDate.Unix(), result[0].value.(modules.Time).Unix()) }) t.Run("in query with multiple values", func(t *testing.T) { result, err := getTaskFiltersFromFilterString("priority in 1,3,5", "UTC") @@ -268,18 +269,18 @@ func TestParseFilter(t *testing.T) { assert.Equal(t, "done_at", result[0].field) assert.Equal(t, taskFilterComparatorGreater, result[0].comparator) sevenDaysAgo := time.Now().Add(-7 * 24 * time.Hour) - assert.Equal(t, sevenDaysAgo.Unix(), result[0].value.(time.Time).Unix()) + assert.Equal(t, sevenDaysAgo.Unix(), result[0].value.(modules.Time).Unix()) }) t.Run("date filter with 0000-01-01", func(t *testing.T) { result, err := getTaskFiltersFromFilterString("due_date > 0000-01-01", "UTC") require.NoError(t, err) require.Len(t, result, 1) - date := result[0].value.(time.Time) + date := result[0].value.(modules.Time) if db.GetDialect() == builder.MYSQL { - assert.Equal(t, 1, date.Year()) + assert.Equal(t, 1, date.Time().Year()) } else { - assert.Equal(t, 0, date.Year()) + assert.Equal(t, 0, date.Time().Year()) } }) } diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 2ddf7f4d9..b88c1e167 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -24,6 +24,7 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" @@ -116,8 +117,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { Index: 14, CreatedByID: 1, ProjectID: 1, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), }, }, }, @@ -133,7 +134,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 1, Name: "test", Size: 100, - Created: time.Unix(1570998791, 0).In(loc), + Created: modules.Time(time.Unix(1570998791, 0).In(loc)), CreatedByID: 1, }, }, @@ -156,13 +157,13 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 1, Name: "test", Size: 100, - Created: time.Unix(1570998791, 0).In(loc), + Created: modules.Time(time.Unix(1570998791, 0).In(loc)), CreatedByID: 1, }, }, }, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } var task1WithReaction = &Task{} *task1WithReaction = *task1 @@ -186,12 +187,12 @@ func TestTaskCollection_ReadAll(t *testing.T) { { ID: 3, TaskID: 2, - Reminder: time.Unix(1543626824, 0).In(loc), - Created: time.Unix(1543626724, 0).In(loc), + Reminder: modules.Time(time.Unix(1543626824, 0).In(loc)), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), }, }, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task3 := &Task{ ID: 3, @@ -202,8 +203,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), Priority: 100, } task4 := &Task{ @@ -215,8 +216,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), Priority: 1, } task5 := &Task{ @@ -228,9 +229,9 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), - DueDate: time.Unix(1543636724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), + DueDate: modules.Time(time.Unix(1543636724, 0).In(loc)), } task6 := &Task{ ID: 6, @@ -241,9 +242,9 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), - DueDate: time.Unix(1543616724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), + DueDate: modules.Time(time.Unix(1543616724, 0).In(loc)), } task7 := &Task{ ID: 7, @@ -254,9 +255,9 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), - StartDate: time.Unix(1544600000, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), + StartDate: modules.Time(time.Unix(1544600000, 0).In(loc)), } task8 := &Task{ ID: 8, @@ -267,9 +268,9 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), - EndDate: time.Unix(1544700000, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), + EndDate: modules.Time(time.Unix(1544700000, 0).In(loc)), } task9 := &Task{ ID: 9, @@ -280,10 +281,10 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), - StartDate: time.Unix(1544600000, 0).In(loc), - EndDate: time.Unix(1544700000, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), + StartDate: modules.Time(time.Unix(1544600000, 0).In(loc)), + EndDate: modules.Time(time.Unix(1544700000, 0).In(loc)), } task10 := &Task{ ID: 10, @@ -294,8 +295,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task11 := &Task{ ID: 11, @@ -306,8 +307,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task12 := &Task{ ID: 12, @@ -318,8 +319,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task15 := &Task{ ID: 15, @@ -331,8 +332,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { ProjectID: 6, IsFavorite: true, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task16 := &Task{ ID: 16, @@ -343,8 +344,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 7, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task17 := &Task{ ID: 17, @@ -355,8 +356,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 8, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task18 := &Task{ ID: 18, @@ -367,8 +368,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 9, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task19 := &Task{ ID: 19, @@ -379,8 +380,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 10, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task20 := &Task{ ID: 20, @@ -391,8 +392,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 11, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task21 := &Task{ ID: 21, @@ -403,8 +404,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 32, // parent project is shared to user 1 via direct share RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task22 := &Task{ ID: 22, @@ -415,8 +416,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 33, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task23 := &Task{ ID: 23, @@ -427,8 +428,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 34, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task24 := &Task{ ID: 24, @@ -439,8 +440,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 15, // parent project is shared to user 1 via team RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task25 := &Task{ ID: 25, @@ -451,8 +452,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 16, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task26 := &Task{ ID: 26, @@ -463,8 +464,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user6, ProjectID: 17, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task27 := &Task{ ID: 27, @@ -477,23 +478,23 @@ func TestTaskCollection_ReadAll(t *testing.T) { { ID: 1, TaskID: 27, - Reminder: time.Unix(1543626724, 0).In(loc), - Created: time.Unix(1543626724, 0).In(loc), + Reminder: modules.Time(time.Unix(1543626724, 0).In(loc)), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), }, { ID: 2, TaskID: 27, - Reminder: time.Unix(1543626824, 0).In(loc), - Created: time.Unix(1543626724, 0).In(loc), + Reminder: modules.Time(time.Unix(1543626824, 0).In(loc)), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), RelativePeriod: -3600, RelativeTo: "start_date", }, }, - StartDate: time.Unix(1543616724, 0).In(loc), + StartDate: modules.Time(time.Unix(1543616724, 0).In(loc)), ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task28 := &Task{ ID: 28, @@ -505,8 +506,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, RepeatAfter: 3600, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task29 := &Task{ ID: 29, @@ -526,13 +527,13 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedByID: 1, ProjectID: 1, IsFavorite: true, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), }, }, }, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task30 := &Task{ ID: 30, @@ -547,8 +548,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { user2, }, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task31 := &Task{ ID: 31, @@ -560,8 +561,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 1, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task32 := &Task{ ID: 32, @@ -572,8 +573,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 3, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task33 := &Task{ ID: 33, @@ -585,8 +586,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { ProjectID: 1, PercentDone: 0.5, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task35 := &Task{ ID: 35, @@ -613,8 +614,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedByID: 1, ProjectID: 1, IsFavorite: true, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), }, { ID: 1, @@ -624,13 +625,13 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedByID: 1, ProjectID: 1, IsFavorite: true, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), }, }, }, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } task39 := &Task{ ID: 39, @@ -640,8 +641,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ProjectID: 25, RelatedTasks: map[RelationKind][]*Task{}, - Created: time.Unix(1543626724, 0).In(loc), - Updated: time.Unix(1543626724, 0).In(loc), + Created: modules.Time(time.Unix(1543626724, 0).In(loc)), + Updated: modules.Time(time.Unix(1543626724, 0).In(loc)), } type fields struct { @@ -955,7 +956,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { wantErr: false, }, { - name: "range with nulls", + name: "range withnulls", fields: fields{ FilterIncludeNulls: true, Filter: "start_date > '2018-12-11T03:46:40+00:00' || end_date < '2018-12-13T11:20:01+00:00'", diff --git a/pkg/models/task_comments.go b/pkg/models/task_comments.go index 21b92b034..c22fe6b1c 100644 --- a/pkg/models/task_comments.go +++ b/pkg/models/task_comments.go @@ -17,9 +17,8 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/events" "code.vikunja.io/api/pkg/user" @@ -39,8 +38,8 @@ type TaskComment struct { Reactions ReactionMap `xorm:"-" json:"reactions"` - Created time.Time `xorm:"created" json:"created"` - Updated time.Time `xorm:"updated" json:"updated"` + Created modules.Time `xorm:"created" json:"created"` + Updated modules.Time `xorm:"updated" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` @@ -67,8 +66,8 @@ func (tc *TaskComment) TableName() string { func (tc *TaskComment) Create(s *xorm.Session, a web.Auth) (err error) { tc.ID = 0 - tc.Created = time.Time{} - tc.Updated = time.Time{} + tc.Created = modules.Time{} + tc.Updated = modules.Time{} return tc.CreateWithTimestamps(s, a) } @@ -86,7 +85,7 @@ func (tc *TaskComment) CreateWithTimestamps(s *xorm.Session, a web.Auth) (err er } tc.AuthorID = tc.Author.ID - if !tc.Created.IsZero() && !tc.Updated.IsZero() { + if !tc.Created.Time().IsZero() && !tc.Updated.Time().IsZero() { _, err = s.NoAutoTime().Insert(tc) if err != nil { return diff --git a/pkg/models/task_overdue_reminder.go b/pkg/models/task_overdue_reminder.go index 07e76da93..9a476ab56 100644 --- a/pkg/models/task_overdue_reminder.go +++ b/pkg/models/task_overdue_reminder.go @@ -87,7 +87,7 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i overdueMailTime := time.Date(now.Year(), now.Month(), now.Day(), tm.Hour(), tm.Minute(), 0, 0, tz) isTimeForReminder := overdueMailTime.After(now) || overdueMailTime.Equal(now.In(tz)) wasTimeForReminder := overdueMailTime.Before(nextMinute) - taskIsOverdueInUserTimezone := overdueMailTime.After(t.Task.DueDate.In(tz)) + taskIsOverdueInUserTimezone := overdueMailTime.After(t.Task.DueDate.Time().In(tz)) if isTimeForReminder && wasTimeForReminder && taskIsOverdueInUserTimezone { _, exists := uts[t.User.ID] if !exists { diff --git a/pkg/models/task_relation.go b/pkg/models/task_relation.go index 0ad9f64a2..612c4c98b 100644 --- a/pkg/models/task_relation.go +++ b/pkg/models/task_relation.go @@ -17,9 +17,8 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/events" + "code.vikunja.io/api/pkg/modules" "xorm.io/builder" "xorm.io/xorm" @@ -93,7 +92,7 @@ type TaskRelation struct { CreatedBy *user.User `xorm:"-" json:"created_by"` // A timestamp when this label was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/task_reminder.go b/pkg/models/task_reminder.go index 6f24ef3e9..fc87c910f 100644 --- a/pkg/models/task_reminder.go +++ b/pkg/models/task_reminder.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "time" "code.vikunja.io/api/pkg/utils" @@ -50,8 +51,8 @@ type TaskReminder struct { ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"` TaskID int64 `xorm:"bigint not null INDEX" json:"-"` // The absolute time when the user wants to be reminded of the task. - Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'" json:"reminder"` - Created time.Time `xorm:"created not null" json:"-"` + Reminder modules.Time `xorm:"DATETIME not null INDEX 'reminder'" json:"reminder"` + Created modules.Time `xorm:"created not null" json:"-"` // A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date. Default: 0, tiggers when RelativeTo is due. RelativePeriod int64 `xorm:"bigint null" json:"relative_period"` // The name of the date field to which the relative period refers to. @@ -243,7 +244,7 @@ func getTasksWithRemindersDueAndTheirUsers(s *xorm.Session, now time.Time) (remi tzs[u.User.Timezone] = tz } - actualReminder := r.Reminder.In(tz) + actualReminder := r.Reminder.Time().In(tz) if (actualReminder.After(now) && actualReminder.Before(now.Add(time.Minute))) || actualReminder.Equal(now) { reminderNotifications = append(reminderNotifications, &ReminderDueNotification{ User: u.User, diff --git a/pkg/models/task_search.go b/pkg/models/task_search.go index f289c1d99..b02653dc6 100644 --- a/pkg/models/task_search.go +++ b/pkg/models/task_search.go @@ -17,16 +17,16 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/web" "context" "fmt" "strconv" "strings" "time" - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/log" - "code.vikunja.io/api/pkg/web" - "github.com/typesense/typesense-go/v2/typesense/api" "github.com/typesense/typesense-go/v2/typesense/api/pointer" "xorm.io/builder" @@ -464,6 +464,8 @@ func convertFilterValues(value interface{}) string { } return "false" + case modules.Time: + return strconv.FormatInt(v.Time().Unix(), 10) case time.Time: return strconv.FormatInt(v.Unix(), 10) default: diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 238bb8baa..636b39ef1 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "errors" "math" "regexp" @@ -60,9 +61,9 @@ type Task struct { // Whether a task is done or not. Done bool `xorm:"INDEX null" json:"done"` // The time when a task was marked as done. - DoneAt time.Time `xorm:"INDEX null 'done_at'" json:"done_at"` + DoneAt modules.Time `xorm:"INDEX null 'done_at'" json:"done_at"` // The time when the task is due. - DueDate time.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"` + DueDate modules.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"` // An array of reminders that are associated with this task. Reminders []*TaskReminder `xorm:"-" json:"reminders"` // The project this task belongs to. @@ -74,9 +75,9 @@ type Task struct { // The task priority. Can be anything you want, it is possible to sort by this later. Priority int64 `xorm:"bigint null" json:"priority"` // When this task starts. - StartDate time.Time `xorm:"DATETIME INDEX null 'start_date'" json:"start_date" query:"-"` + StartDate modules.Time `xorm:"DATETIME INDEX null 'start_date'" json:"start_date" query:"-"` // When this task ends. - EndDate time.Time `xorm:"DATETIME INDEX null 'end_date'" json:"end_date" query:"-"` + EndDate modules.Time `xorm:"DATETIME INDEX null 'end_date'" json:"end_date" query:"-"` // An array of users who are assigned to this task Assignees []*user.User `xorm:"-" json:"assignees"` // An array of labels which are associated with this task. This property is read-only, you must use the separate endpoint to add labels to a task. @@ -111,9 +112,9 @@ type Task struct { Subscription *Subscription `xorm:"-" json:"subscription,omitempty"` // A timestamp when this task was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this task was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` // The bucket id. Will only be populated when the task is accessed via a view with buckets. // Can be used to move a task between buckets. In that case, the new bucket must be in the same view as the old one. @@ -1252,20 +1253,20 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { ot.Description = "" } // Due date - if t.DueDate.IsZero() { - ot.DueDate = time.Time{} + if t.DueDate.Time().IsZero() { + ot.DueDate = modules.Time{} } // Repeat after if t.RepeatAfter == 0 { ot.RepeatAfter = 0 } // Start date - if t.StartDate.IsZero() { - ot.StartDate = time.Time{} + if t.StartDate.Time().IsZero() { + ot.StartDate = modules.Time{} } // End date - if t.EndDate.IsZero() { - ot.EndDate = time.Time{} + if t.EndDate.Time().IsZero() { + ot.EndDate = modules.Time{} } // Color if t.HexColor == "" { @@ -1317,8 +1318,9 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { return updateProjectLastUpdated(s, &Project{ID: t.ProjectID}) } -func addOneMonthToDate(d time.Time) time.Time { - return time.Date(d.Year(), d.Month()+1, d.Day(), d.Hour(), d.Minute(), d.Second(), d.Nanosecond(), config.GetTimeZone()) +func addOneMonthToDate(dd modules.Time) modules.Time { + d := dd.Time() + return modules.Time(time.Date(d.Year(), d.Month()+1, d.Day(), d.Hour(), d.Minute(), d.Second(), d.Nanosecond(), config.GetTimeZone())) } func setTaskDatesDefault(oldTask, newTask *Task) { @@ -1327,12 +1329,12 @@ func setTaskDatesDefault(oldTask, newTask *Task) { } // Current time in an extra variable to base all calculations on the same time - now := time.Now() + now := modules.Time(time.Now()) repeatDuration := time.Duration(oldTask.RepeatAfter) * time.Second // assuming we'll merge the new task over the old task - if !oldTask.DueDate.IsZero() { + if !oldTask.DueDate.Time().IsZero() { // Always add one instance of the repeating interval to catch cases where a due date is already in the future // but not the repeating interval newTask.DueDate = oldTask.DueDate.Add(repeatDuration) @@ -1407,7 +1409,7 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) { } // Current time in an extra variable to base all calculations on the same time - now := time.Now() + now := modules.Time(time.Now()) repeatDuration := time.Duration(oldTask.RepeatAfter) * time.Second @@ -1485,12 +1487,12 @@ func updateDone(oldTask *Task, newTask *Task) { setTaskDatesDefault(oldTask, newTask) } - newTask.DoneAt = time.Now() + newTask.DoneAt = modules.Time(time.Now()) } // When unmarking a task as done, reset the timestamp if oldTask.Done && !newTask.Done { - newTask.DoneAt = time.Time{} + newTask.DoneAt = modules.Time{} } } @@ -1499,7 +1501,7 @@ func updateRelativeReminderDates(task *Task) (err error) { for _, reminder := range task.Reminders { relativeDuration := time.Duration(reminder.RelativePeriod) * time.Second if reminder.RelativeTo != "" { - reminder.Reminder = time.Time{} + reminder.Reminder = modules.Time{} } switch reminder.RelativeTo { case ReminderRelationDueDate: @@ -1547,7 +1549,7 @@ func (t *Task) updateReminders(s *xorm.Session, task *Task) (err error) { // Resolve duplicates and sort them reminderMap := make(map[int64]*TaskReminder, len(task.Reminders)) for _, reminder := range task.Reminders { - reminderMap[reminder.Reminder.UTC().Unix()] = reminder + reminderMap[reminder.Reminder.Unix()] = reminder } t.Reminders = make([]*TaskReminder, 0, len(reminderMap)) diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index 3eaa6361b..fd5036775 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "testing" "time" @@ -81,9 +82,9 @@ func TestTask_Create(t *testing.T) { Title: "Lorem", Description: "Lorem Ipsum Dolor", ProjectID: 1, - DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local), - StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local), - EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local), + DueDate: modules.Time(time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local)), + StartDate: modules.Time(time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local)), + EndDate: modules.Time(time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local)), Reminders: []*TaskReminder{ { RelativeTo: "due_date", @@ -98,7 +99,7 @@ func TestTask_Create(t *testing.T) { RelativePeriod: -1, }, { - Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local), + Reminder: modules.Time(time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local)), }, }} err := task.Create(s, usr) @@ -359,9 +360,9 @@ func TestTask_Update(t *testing.T) { ID: 1, ProjectID: 1, Title: "test", - DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local), - StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local), - EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local), + DueDate: modules.Time(time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local)), + StartDate: modules.Time(time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local)), + EndDate: modules.Time(time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local)), Reminders: []*TaskReminder{ { RelativeTo: "due_date", @@ -376,7 +377,7 @@ func TestTask_Update(t *testing.T) { RelativePeriod: -1, }, { - Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local), + Reminder: modules.Time(time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local)), }, }} err := task.Update(s, u) @@ -403,10 +404,10 @@ func TestTask_Update(t *testing.T) { Title: "test", Reminders: []*TaskReminder{ { - Reminder: time.Unix(1674745156, 0), + Reminder: modules.Time(time.Unix(1674745156, 0)), }, { - Reminder: time.Unix(1674745156, 223), + Reminder: modules.Time(time.Unix(1674745156, 223)), }, }, ProjectID: 1, @@ -426,7 +427,7 @@ func TestTask_Update(t *testing.T) { taskBefore := &Task{ Title: "test", ProjectID: 1, - StartDate: time.Date(2022, time.March, 8, 8, 5, 20, 0, time.Local), + StartDate: modules.Time(time.Date(2022, time.March, 8, 8, 5, 20, 0, time.Local)), Reminders: []*TaskReminder{ { RelativeTo: "start_date", @@ -441,7 +442,7 @@ func TestTask_Update(t *testing.T) { // when start_date is modified task := taskBefore - task.StartDate = time.Date(2023, time.March, 8, 8, 5, 0, 0, time.Local) + task.StartDate = modules.Time(time.Date(2023, time.March, 8, 8, 5, 0, 0, time.Local)) err = task.Update(s, u) require.NoError(t, err) @@ -481,7 +482,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{Done: false} newTask := &Task{Done: true} updateDone(oldTask, newTask) - assert.NotEqual(t, time.Time{}, newTask.DoneAt) + assert.NotEqual(t, modules.Time{}, newTask.DoneAt) }) t.Run("unmarking a task as done", func(t *testing.T) { db.LoadAndAssertFixtures(t) @@ -491,10 +492,10 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{Done: true} newTask := &Task{Done: false} updateDone(oldTask, newTask) - assert.Equal(t, time.Time{}, newTask.DoneAt) + assert.Equal(t, modules.Time{}, newTask.DoneAt) }) t.Run("no interval set, default repeat mode", func(t *testing.T) { - dueDate := time.Unix(1550000000, 0) + dueDate := modules.Time(time.Unix(1550000000, 0)) oldTask := &Task{ Done: false, RepeatAfter: 0, @@ -515,7 +516,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatAfter: 8600, - DueDate: time.Unix(1550000000, 0), + DueDate: modules.Time(time.Unix(1550000000, 0)), } newTask := &Task{ Done: true, @@ -534,11 +535,11 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatAfter: 8600, - DueDate: time.Time{}, + DueDate: modules.Time{}, } newTask := &Task{ Done: true, - DueDate: time.Unix(1543626724, 0), + DueDate: modules.Time(time.Unix(1543626724, 0)), } updateDone(oldTask, newTask) assert.Equal(t, time.Unix(1543626724, 0), newTask.DueDate) @@ -550,10 +551,10 @@ func TestUpdateDone(t *testing.T) { RepeatAfter: 8600, Reminders: []*TaskReminder{ { - Reminder: time.Unix(1550000000, 0), + Reminder: modules.Time(time.Unix(1550000000, 0)), }, { - Reminder: time.Unix(1555000000, 0), + Reminder: modules.Time(time.Unix(1555000000, 0)), }, }, } @@ -580,7 +581,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatAfter: 8600, - StartDate: time.Unix(1550000000, 0), + StartDate: modules.Time(time.Unix(1550000000, 0)), } newTask := &Task{ Done: true, @@ -599,7 +600,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatAfter: 8600, - EndDate: time.Unix(1550000000, 0), + EndDate: modules.Time(time.Unix(1550000000, 0)), } newTask := &Task{ Done: true, @@ -618,7 +619,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatAfter: 8600, - DueDate: time.Now().Add(time.Hour), + DueDate: modules.Time(time.Now().Add(time.Hour)), } newTask := &Task{ Done: true, @@ -634,14 +635,14 @@ func TestUpdateDone(t *testing.T) { Done: false, RepeatAfter: 8600, RepeatMode: TaskRepeatModeFromCurrentDate, - DueDate: time.Unix(1550000000, 0), + DueDate: modules.Time(time.Unix(1550000000, 0)), } newTask := &Task{ Done: true, } updateDone(oldTask, newTask) - // Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value + // Only comparing unix timestamps because Time use nanoseconds which can't ever possibly have the same value assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.DueDate.Unix()) assert.False(t, newTask.Done) }) @@ -652,10 +653,10 @@ func TestUpdateDone(t *testing.T) { RepeatMode: TaskRepeatModeFromCurrentDate, Reminders: []*TaskReminder{ { - Reminder: time.Unix(1550000000, 0), + Reminder: modules.Time(time.Unix(1550000000, 0)), }, { - Reminder: time.Unix(1555000000, 0), + Reminder: modules.Time(time.Unix(1555000000, 0)), }, }} newTask := &Task{ @@ -666,7 +667,7 @@ func TestUpdateDone(t *testing.T) { diff := oldTask.Reminders[1].Reminder.Sub(oldTask.Reminders[0].Reminder) assert.Len(t, newTask.Reminders, 2) - // Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value + // Only comparing unix timestamps because Time use nanoseconds which can't ever possibly have the same value assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[0].Reminder.Unix()) assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[1].Reminder.Unix()) assert.False(t, newTask.Done) @@ -676,14 +677,14 @@ func TestUpdateDone(t *testing.T) { Done: false, RepeatAfter: 8600, RepeatMode: TaskRepeatModeFromCurrentDate, - StartDate: time.Unix(1550000000, 0), + StartDate: modules.Time(time.Unix(1550000000, 0)), } newTask := &Task{ Done: true, } updateDone(oldTask, newTask) - // Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value + // Only comparing unix timestamps because Time use nanoseconds which can't ever possibly have the same value assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.StartDate.Unix()) assert.False(t, newTask.Done) }) @@ -692,14 +693,14 @@ func TestUpdateDone(t *testing.T) { Done: false, RepeatAfter: 8600, RepeatMode: TaskRepeatModeFromCurrentDate, - EndDate: time.Unix(1560000000, 0), + EndDate: modules.Time(time.Unix(1560000000, 0)), } newTask := &Task{ Done: true, } updateDone(oldTask, newTask) - // Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value + // Only comparing unix timestamps because Time use nanoseconds which can't ever possibly have the same value assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.EndDate.Unix()) assert.False(t, newTask.Done) }) @@ -708,8 +709,8 @@ func TestUpdateDone(t *testing.T) { Done: false, RepeatAfter: 8600, RepeatMode: TaskRepeatModeFromCurrentDate, - StartDate: time.Unix(1550000000, 0), - EndDate: time.Unix(1560000000, 0), + StartDate: modules.Time(time.Unix(1550000000, 0)), + EndDate: modules.Time(time.Unix(1560000000, 0)), } newTask := &Task{ Done: true, @@ -718,7 +719,7 @@ func TestUpdateDone(t *testing.T) { diff := oldTask.EndDate.Sub(oldTask.StartDate) - // Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value + // Only comparing unix timestamps because Time use nanoseconds which can't ever possibly have the same value assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.StartDate.Unix()) assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.EndDate.Unix()) assert.False(t, newTask.Done) @@ -729,7 +730,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatMode: TaskRepeatModeMonth, - DueDate: time.Unix(1550000000, 0), + DueDate: modules.Time(time.Unix(1550000000, 0)), } newTask := &Task{ Done: true, @@ -748,16 +749,16 @@ func TestUpdateDone(t *testing.T) { RepeatMode: TaskRepeatModeMonth, Reminders: []*TaskReminder{ { - Reminder: time.Unix(1550000000, 0), + Reminder: modules.Time(time.Unix(1550000000, 0)), }, { - Reminder: time.Unix(1555000000, 0), + Reminder: modules.Time(time.Unix(1555000000, 0)), }, }} newTask := &Task{ Done: true, } - oldReminders := make([]time.Time, len(oldTask.Reminders)) + oldReminders := make([]modules.Time, len(oldTask.Reminders)) for i, r := range newTask.Reminders { oldReminders[i] = r.Reminder } @@ -775,7 +776,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatMode: TaskRepeatModeMonth, - StartDate: time.Unix(1550000000, 0), + StartDate: modules.Time(time.Unix(1550000000, 0)), } newTask := &Task{ Done: true, @@ -792,7 +793,7 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatMode: TaskRepeatModeMonth, - EndDate: time.Unix(1560000000, 0), + EndDate: modules.Time(time.Unix(1560000000, 0)), } newTask := &Task{ Done: true, @@ -809,8 +810,8 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{ Done: false, RepeatMode: TaskRepeatModeMonth, - StartDate: time.Unix(1550000000, 0), - EndDate: time.Unix(1560000000, 0), + StartDate: modules.Time(time.Unix(1550000000, 0)), + EndDate: modules.Time(time.Unix(1560000000, 0)), } newTask := &Task{ Done: true, diff --git a/pkg/models/teams.go b/pkg/models/teams.go index 34c2c8255..23338935c 100644 --- a/pkg/models/teams.go +++ b/pkg/models/teams.go @@ -17,10 +17,9 @@ package models import ( - "time" - "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/events" "code.vikunja.io/api/pkg/user" @@ -50,9 +49,9 @@ type Team struct { Members []*TeamUser `xorm:"-" json:"members"` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created" json:"created"` + Created modules.Time `xorm:"created" json:"created"` // A timestamp when this relation was last updated. You cannot change this value. - Updated time.Time `xorm:"updated" json:"updated"` + Updated modules.Time `xorm:"updated" json:"updated"` // Defines wether the team should be publicly discoverable when sharing a project IsPublic bool `xorm:"not null default false" json:"is_public"` @@ -83,7 +82,7 @@ type TeamMember struct { Admin bool `xorm:"null" json:"admin"` // A timestamp when this relation was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/teams_rights_test.go b/pkg/models/teams_rights_test.go index 8f32c0f9c..66bcd9727 100644 --- a/pkg/models/teams_rights_test.go +++ b/pkg/models/teams_rights_test.go @@ -17,11 +17,10 @@ package models import ( - "testing" - "time" - "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/user" + "testing" "code.vikunja.io/api/pkg/web" ) @@ -34,8 +33,8 @@ func TestTeam_CanDoSomething(t *testing.T) { CreatedByID int64 CreatedBy *user.User Members []*TeamUser - Created time.Time - Updated time.Time + Created modules.Time + Updated modules.Time CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/typesense.go b/pkg/models/typesense.go index 770bf5552..b71c00efd 100644 --- a/pkg/models/typesense.go +++ b/pkg/models/typesense.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "context" "fmt" "strconv" @@ -366,10 +367,10 @@ func indexDummyTask() (err error) { { ID: -10, TaskID: -100, - Reminder: time.Now(), + Reminder: modules.Time(time.Now()), RelativePeriod: 10, RelativeTo: ReminderRelationDueDate, - Created: time.Now(), + Created: modules.Time(time.Now()), }, }, Assignees: []*user.User{ @@ -378,8 +379,8 @@ func indexDummyTask() (err error) { Username: "dummy", Name: "dummy", Email: "dummy@vikunja", - Created: time.Now(), - Updated: time.Now(), + Created: modules.Time(time.Now()), + Updated: modules.Time(time.Now()), }, }, Labels: []*Label{ @@ -388,30 +389,30 @@ func indexDummyTask() (err error) { Title: "dummylabel", Description: "Lorem Ipsum Dummy", HexColor: "000000", - Created: time.Now(), - Updated: time.Now(), + Created: modules.Time(time.Now()), + Updated: modules.Time(time.Now()), }, }, Attachments: []*TaskAttachment{ { ID: -120, TaskID: -100, - Created: time.Now(), + Created: modules.Time(time.Now()), }, }, Comments: []*TaskComment{ { ID: -220, Comment: "Lorem Ipsum Dummy", - Created: time.Now(), - Updated: time.Now(), + Created: modules.Time(time.Now()), + Updated: modules.Time(time.Now()), Author: &user.User{ ID: -100, Username: "dummy", Name: "dummy", Email: "dummy@vikunja", - Created: time.Now(), - Updated: time.Now(), + Created: modules.Time(time.Now()), + Updated: modules.Time(time.Now()), }, }, }, @@ -476,22 +477,22 @@ func convertTaskToTypesenseTask(task *Task, positions []*TaskPositionWithView, b Title: task.Title, Description: task.Description, Done: task.Done, - DoneAt: pointer.Int64(task.DoneAt.UTC().Unix()), - DueDate: pointer.Int64(task.DueDate.UTC().Unix()), + DoneAt: pointer.Int64(task.DoneAt.Unix()), + DueDate: pointer.Int64(task.DueDate.Unix()), ProjectID: task.ProjectID, RepeatAfter: task.RepeatAfter, RepeatMode: int(task.RepeatMode), Priority: task.Priority, - StartDate: pointer.Int64(task.StartDate.UTC().Unix()), - EndDate: pointer.Int64(task.EndDate.UTC().Unix()), + StartDate: pointer.Int64(task.StartDate.Unix()), + EndDate: pointer.Int64(task.EndDate.Unix()), HexColor: task.HexColor, PercentDone: task.PercentDone, Identifier: task.Identifier, Index: task.Index, UID: task.UID, CoverImageAttachmentID: task.CoverImageAttachmentID, - Created: task.Created.UTC().Unix(), - Updated: task.Updated.UTC().Unix(), + Created: task.Created.Unix(), + Updated: task.Updated.Unix(), CreatedByID: task.CreatedByID, Reminders: task.Reminders, Assignees: task.Assignees, diff --git a/pkg/models/user_delete.go b/pkg/models/user_delete.go index 56bb1983e..3778dee67 100644 --- a/pkg/models/user_delete.go +++ b/pkg/models/user_delete.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/modules" "time" "code.vikunja.io/api/pkg/cron" @@ -59,7 +60,7 @@ func deleteUsers() { now := time.Now() for _, u := range users { - if !u.DeletionScheduledAt.Before(now) { + if !u.DeletionScheduledAt.Before(modules.Time(now)) { log.Debugf("User %d is not yet scheduled for deletion. Scheduled at %s, now is %s", u.ID, u.DeletionScheduledAt, now) continue } diff --git a/pkg/models/webhooks.go b/pkg/models/webhooks.go index def553e97..2ba05eba4 100644 --- a/pkg/models/webhooks.go +++ b/pkg/models/webhooks.go @@ -18,6 +18,7 @@ package models import ( "bytes" + "code.vikunja.io/api/pkg/modules" "context" "crypto/hmac" "crypto/sha256" @@ -61,9 +62,9 @@ type Webhook struct { CreatedByID int64 `xorm:"bigint not null" json:"-"` // A timestamp when this webhook target was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this webhook target was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/modules/migration/microsoft-todo/microsoft_todo.go b/pkg/modules/migration/microsoft-todo/microsoft_todo.go index 838d4f7e2..0eec93fef 100644 --- a/pkg/modules/migration/microsoft-todo/microsoft_todo.go +++ b/pkg/modules/migration/microsoft-todo/microsoft_todo.go @@ -18,6 +18,7 @@ package microsofttodo import ( "bytes" + "code.vikunja.io/api/pkg/modules" "context" "encoding/json" "fmt" @@ -54,8 +55,8 @@ type task struct { IsReminderOn bool `json:"isReminderOn"` Status string `json:"status"` Title string `json:"title"` - CreatedDateTime time.Time `json:"createdDateTime"` - LastModifiedDateTime time.Time `json:"lastModifiedDateTime"` + CreatedDateTime modules.Time `json:"createdDateTime"` + LastModifiedDateTime modules.Time `json:"lastModifiedDateTime"` ID string `json:"id"` Body *body `json:"body"` DueDateTime *dateTimeTimeZone `json:"dueDateTime"` @@ -113,13 +114,14 @@ type projectsResponse struct { Value []*project `json:"value"` } -func (dtt *dateTimeTimeZone) toTime() (t time.Time, err error) { +func (dtt *dateTimeTimeZone) toTime() (t modules.Time, err error) { loc, err := time.LoadLocation(dtt.TimeZone) if err != nil { return t, err } - return time.ParseInLocation(time.RFC3339Nano, dtt.DateTime+"Z", loc) + tt, err := time.ParseInLocation(time.RFC3339Nano, dtt.DateTime+"Z", loc) + return modules.Time(tt), err } // AuthURL returns the url users need to authenticate against diff --git a/pkg/modules/migration/microsoft-todo/microsoft_todo_test.go b/pkg/modules/migration/microsoft-todo/microsoft_todo_test.go index eee93af44..ba1de26b4 100644 --- a/pkg/modules/migration/microsoft-todo/microsoft_todo_test.go +++ b/pkg/modules/migration/microsoft-todo/microsoft_todo_test.go @@ -21,6 +21,7 @@ import ( "time" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules" "github.com/d4l3k/messagediff" "github.com/stretchr/testify/assert" @@ -34,8 +35,9 @@ func TestConverting(t *testing.T) { TimeZone: "UTC", } - testtimeTime, err := time.Parse(time.RFC3339Nano, "2020-12-18T03:00:00.4770000Z") + testTime, err := time.Parse(time.RFC3339Nano, "2020-12-18T03:00:00.4770000Z") require.NoError(t, err) + testtimeTime := modules.Time(testTime) microsoftTodoData := []*project{ { diff --git a/pkg/modules/migration/ticktick/ticktick.go b/pkg/modules/migration/ticktick/ticktick.go index 33a24d975..bc431d1e6 100644 --- a/pkg/modules/migration/ticktick/ticktick.go +++ b/pkg/modules/migration/ticktick/ticktick.go @@ -26,6 +26,7 @@ import ( "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" @@ -62,16 +63,20 @@ type tickTickTask struct { } type tickTickTime struct { - time.Time + modules.Time } func (date *tickTickTime) UnmarshalCSV(csv string) (err error) { - date.Time = time.Time{} + date.Time = modules.Time{} if csv == "" { return nil } - date.Time, err = time.Parse(timeISO, csv) - return err + tt, err := time.Parse(timeISO, csv) + if err != nil { + return err + } + date.Time = modules.Time(tt) + return nil } func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.ProjectWithTasksAndBuckets) { @@ -110,11 +115,11 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.ProjectWi ID: t.TaskID, Title: t.Title, Description: t.Content, - StartDate: t.StartDate.Time, - EndDate: t.DueDate.Time, - DueDate: t.DueDate.Time, + StartDate: modules.Time(t.StartDate.Time), + EndDate: modules.Time(t.DueDate.Time), + DueDate: modules.Time(t.DueDate.Time), Done: t.Status == "1", - DoneAt: t.CompletedTime.Time, + DoneAt: modules.Time(t.CompletedTime.Time), Position: t.Order, Labels: labels, }, diff --git a/pkg/modules/migration/ticktick/ticktick_test.go b/pkg/modules/migration/ticktick/ticktick_test.go index f77fc549e..a6f5fdd6b 100644 --- a/pkg/modules/migration/ticktick/ticktick_test.go +++ b/pkg/modules/migration/ticktick/ticktick_test.go @@ -17,6 +17,7 @@ package ticktick import ( + "code.vikunja.io/api/pkg/modules" "testing" "time" @@ -28,13 +29,13 @@ import ( func TestConvertTicktickTasksToVikunja(t *testing.T) { t1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z") require.NoError(t, err) - time1 := tickTickTime{Time: t1} + time1 := tickTickTime{Time: modules.Time(t1)} t2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z") require.NoError(t, err) - time2 := tickTickTime{Time: t2} + time2 := tickTickTime{Time: modules.Time(t2)} t3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z") require.NoError(t, err) - time3 := tickTickTime{Time: t3} + time3 := tickTickTime{Time: modules.Time(t3)} duration, err := time.ParseDuration("24h") require.NoError(t, err) diff --git a/pkg/modules/migration/todoist/todoist.go b/pkg/modules/migration/todoist/todoist.go index ff1b96351..6992c378d 100644 --- a/pkg/modules/migration/todoist/todoist.go +++ b/pkg/modules/migration/todoist/todoist.go @@ -18,6 +18,7 @@ package todoist import ( "bytes" + "code.vikunja.io/api/pkg/modules" "encoding/json" "fmt" "net/http" @@ -77,24 +78,24 @@ type dueDate struct { } type item struct { - ID string `json:"id"` - UserID string `json:"user_id"` - ProjectID string `json:"project_id"` - Content string `json:"content"` - Priority int64 `json:"priority"` - Due *dueDate `json:"due"` - ParentID string `json:"parent_id"` - ChildOrder int64 `json:"child_order"` - SectionID string `json:"section_id"` - Children interface{} `json:"children"` - Labels []string `json:"labels"` - AddedByUID string `json:"added_by_uid"` - AssignedByUID string `json:"assigned_by_uid"` - ResponsibleUID string `json:"responsible_uid"` - Checked bool `json:"checked"` - DateAdded time.Time `json:"added_at"` - HasMoreNotes bool `json:"has_more_notes"` - DateCompleted time.Time `json:"completed_at"` + ID string `json:"id"` + UserID string `json:"user_id"` + ProjectID string `json:"project_id"` + Content string `json:"content"` + Priority int64 `json:"priority"` + Due *dueDate `json:"due"` + ParentID string `json:"parent_id"` + ChildOrder int64 `json:"child_order"` + SectionID string `json:"section_id"` + Children interface{} `json:"children"` + Labels []string `json:"labels"` + AddedByUID string `json:"added_by_uid"` + AssignedByUID string `json:"assigned_by_uid"` + ResponsibleUID string `json:"responsible_uid"` + Checked bool `json:"checked"` + DateAdded modules.Time `json:"added_at"` + HasMoreNotes bool `json:"has_more_notes"` + DateCompleted modules.Time `json:"completed_at"` } type itemWrapper struct { @@ -102,11 +103,11 @@ type itemWrapper struct { } type doneItem struct { - CompletedDate time.Time `json:"completed_at"` - Content string `json:"content"` - ID string `json:"id"` - ProjectID string `json:"project_id"` - TaskID string `json:"task_id"` + CompletedDate modules.Time `json:"completed_at"` + Content string `json:"content"` + ID string `json:"id"` + ProjectID string `json:"project_id"` + TaskID string `json:"task_id"` } type doneItemSync struct { @@ -128,14 +129,14 @@ type note struct { ItemID string `json:"item_id"` Content string `json:"content"` FileAttachment *fileAttachment `json:"file_attachment"` - Posted time.Time `json:"posted_at"` + Posted modules.Time `json:"posted_at"` } type projectNote struct { Content string `json:"content"` FileAttachment *fileAttachment `json:"file_attachment"` ID string `json:"id"` - Posted time.Time `json:"posted"` + Posted modules.Time `json:"posted"` ProjectID string `json:"project_id"` } @@ -148,12 +149,12 @@ type reminder struct { } type section struct { - ID string `json:"id"` - DateAdded time.Time `json:"added_at"` - IsDeleted bool `json:"is_deleted"` - Name string `json:"name"` - ProjectID string `json:"project_id"` - SectionOrder int64 `json:"section_order"` + ID string `json:"id"` + DateAdded modules.Time `json:"added_at"` + IsDeleted bool `json:"is_deleted"` + Name string `json:"name"` + ProjectID string `json:"project_id"` + SectionOrder int64 `json:"section_order"` } type sync struct { @@ -224,25 +225,26 @@ func (m *Migration) AuthURL() string { "&state=" + utils.MakeRandomString(32) } -func parseDate(dateString string) (date time.Time, err error) { +func parseDate(dateString string) (date modules.Time, err error) { if len(dateString) == 10 { // We're probably dealing with a date in the form of 2021-11-23 without a time - date, err = time.Parse("2006-01-02", dateString) + tdate, err := time.Parse("2006-01-02", dateString) if err == nil { // round the day to eod + date = modules.Time(tdate) return date.Add(time.Hour*23 + time.Minute*59), nil } } - date, err = time.Parse("2006-01-02T15:04:05Z", dateString) + tdate, err := time.Parse("2006-01-02T15:04:05Z", dateString) if err != nil { - date, err = time.Parse("2006-01-02T15:04:05", dateString) + tdate, err = time.Parse("2006-01-02T15:04:05", dateString) } if err != nil { - date, err = time.Parse("2006-01-02", dateString) + tdate, err = time.Parse("2006-01-02", dateString) } - return date, err + return modules.Time(tdate), err } func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVikunjaHierachie []*models.ProjectWithTasksAndBuckets, err error) { diff --git a/pkg/modules/migration/todoist/todoist_test.go b/pkg/modules/migration/todoist/todoist_test.go index d94cfac30..092269776 100644 --- a/pkg/modules/migration/todoist/todoist_test.go +++ b/pkg/modules/migration/todoist/todoist_test.go @@ -17,6 +17,7 @@ package todoist import ( + "code.vikunja.io/api/pkg/modules" "os" "testing" "time" @@ -34,20 +35,17 @@ func TestConvertTodoistToVikunja(t *testing.T) { config.InitConfig() - time1, err := time.Parse(time.RFC3339Nano, "2014-09-26T08:25:05Z") - require.NoError(t, err) - time1 = time1.In(config.GetTimeZone()) - time3, err := time.Parse(time.RFC3339Nano, "2014-10-21T08:25:05Z") - require.NoError(t, err) - time3 = time3.In(config.GetTimeZone()) - dueTime, err := time.Parse(time.RFC3339Nano, "2020-05-31T23:59:00Z") - require.NoError(t, err) - dueTime = dueTime.In(config.GetTimeZone()) - dueTimeWithTime, err := time.Parse(time.RFC3339Nano, "2021-01-31T19:00:00Z") - require.NoError(t, err) - dueTimeWithTime = dueTimeWithTime.In(config.GetTimeZone()) - nilTime, err := time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00Z") - require.NoError(t, err) + parseAndConvertToTimeZone := func(timeStr string) modules.Time { + parsedTime, err := time.Parse(time.RFC3339Nano, timeStr) + require.NoError(t, err) + return modules.Time(parsedTime.In(config.GetTimeZone())) + } + + time1 := parseAndConvertToTimeZone("2014-09-26T08:25:05Z") + time3 := parseAndConvertToTimeZone("2014-10-21T08:25:05Z") + dueTime := parseAndConvertToTimeZone("2020-05-31T23:59:00Z") + dueTimeWithTime := parseAndConvertToTimeZone("2021-01-31T19:00:00Z") + nilTime := parseAndConvertToTimeZone("0001-01-01T00:00:00Z") exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg") require.NoError(t, err) @@ -394,8 +392,8 @@ func TestConvertTodoistToVikunja(t *testing.T) { Done: false, Created: time1, Reminders: []*models.TaskReminder{ - {Reminder: time.Date(2020, time.June, 15, 23, 59, 0, 0, time.UTC).In(config.GetTimeZone())}, - {Reminder: time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())}, + {Reminder: modules.Time(time.Date(2020, time.June, 15, 23, 59, 0, 0, time.UTC).In(config.GetTimeZone()))}, + {Reminder: modules.Time(time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()))}, }, }, }, @@ -413,7 +411,7 @@ func TestConvertTodoistToVikunja(t *testing.T) { Done: false, Created: time1, Reminders: []*models.TaskReminder{ - {Reminder: time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())}, + {Reminder: modules.Time(time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()))}, }, }, }, @@ -427,7 +425,7 @@ func TestConvertTodoistToVikunja(t *testing.T) { DoneAt: time3, Labels: vikunjaLabels, Reminders: []*models.TaskReminder{ - {Reminder: time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())}, + {Reminder: modules.Time(time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()))}, }, }, }, @@ -447,7 +445,7 @@ func TestConvertTodoistToVikunja(t *testing.T) { Created: time1, DoneAt: time3, Reminders: []*models.TaskReminder{ - {Reminder: time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())}, + {Reminder: modules.Time(time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()))}, }, }, }, @@ -539,7 +537,7 @@ func TestConvertTodoistToVikunja(t *testing.T) { Done: false, Created: time1, Reminders: []*models.TaskReminder{ - {Reminder: time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone())}, + {Reminder: modules.Time(time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()))}, }, }, }, diff --git a/pkg/modules/migration/trello/trello.go b/pkg/modules/migration/trello/trello.go index eaa6eaf0d..bc7f2819e 100644 --- a/pkg/modules/migration/trello/trello.go +++ b/pkg/modules/migration/trello/trello.go @@ -18,6 +18,7 @@ package trello import ( "bytes" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/files" @@ -296,7 +297,7 @@ func convertTrelloDataToVikunja(organizationName string, trelloData []*trello.Bo } if card.Due != nil { - task.DueDate = *card.Due + task.DueDate = modules.Time(*card.Due) } // Checklists (as markdown in description) @@ -405,8 +406,8 @@ func convertTrelloDataToVikunja(organizationName string, trelloData []*trello.Bo comment := &models.TaskComment{ Comment: action.Data.Text, - Created: action.Date, - Updated: action.Date, + Created: modules.Time(action.Date), + Updated: modules.Time(action.Date), } if currentMember == nil || action.IDMemberCreator != currentMember.ID { diff --git a/pkg/modules/migration/trello/trello_test.go b/pkg/modules/migration/trello/trello_test.go index 6da52c61c..f7606730b 100644 --- a/pkg/modules/migration/trello/trello_test.go +++ b/pkg/modules/migration/trello/trello_test.go @@ -25,6 +25,7 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules" "github.com/adlio/trello" "github.com/d4l3k/messagediff" @@ -32,7 +33,7 @@ import ( "github.com/stretchr/testify/require" ) -func getTestBoard(t *testing.T) ([]*trello.Board, time.Time) { +func getTestBoard(t *testing.T) ([]*trello.Board, modules.Time) { config.InitConfig() @@ -234,7 +235,7 @@ func getTestBoard(t *testing.T) ([]*trello.Board, time.Time) { } trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up - return trelloData, time1 + return trelloData, modules.Time(time1) } func TestConvertTrelloToVikunja(t *testing.T) { diff --git a/pkg/modules/time.go b/pkg/modules/time.go new file mode 100644 index 000000000..c4de840db --- /dev/null +++ b/pkg/modules/time.go @@ -0,0 +1,81 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package modules + +import "time" + +type Time time.Time + +func (t Time) MarshalJSON() ([]byte, error) { + if time.Time(t).IsZero() { + return []byte("null"), nil + } + return []byte(`"` + time.Time(t).Format(time.RFC3339) + `"`), nil +} + +func (t *Time) UnmarshalJSON(data []byte) error { + if string(data) == "null" { + *t = Time{} + return nil + } + parsedTime, err := time.Parse(`"`+time.RFC3339+`"`, string(data)) + if err != nil { + return err + } + *t = Time(parsedTime) + return nil +} + +func (t Time) Time() time.Time { + return time.Time(t) +} + +func (t Time) IsZero() bool { + return t.Time().IsZero() +} + +func (t Time) Add(d time.Duration) Time { + return Time(t.Time().Add(d)) +} + +func (t Time) After(u Time) bool { + return t.Time().After(u.Time()) +} + +func (t Time) Before(u Time) bool { + return t.Time().Before(u.Time()) +} + +func (t Time) Sub(u Time) time.Duration { + return t.Time().Sub(u.Time()) +} + +func (t Time) Unix() int64 { + return t.Time().Unix() +} + +func (t Time) In(loc *time.Location) Time { + return Time(t.Time().In(loc)) +} + +func (t Time) Format(layout string) string { + return t.Time().Format(layout) +} + +func (t Time) Month() time.Month { + return t.Time().Month() +} diff --git a/pkg/notifications/database.go b/pkg/notifications/database.go index fe92bc5df..fbddf89cb 100644 --- a/pkg/notifications/database.go +++ b/pkg/notifications/database.go @@ -19,6 +19,8 @@ package notifications import ( "time" + "code.vikunja.io/api/pkg/modules" + "xorm.io/xorm" ) @@ -37,10 +39,10 @@ type DatabaseNotification struct { SubjectID int64 `xorm:"bigint null" json:"-"` // When this notification is marked as read, this will be updated with the current timestamp. - ReadAt time.Time `xorm:"datetime null" json:"read_at"` + ReadAt modules.Time `xorm:"datetime null" json:"read_at"` // A timestamp when this notification was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` } // TableName resolves to a better table name for notifications @@ -86,9 +88,9 @@ func CanMarkNotificationAsRead(s *xorm.Session, notification *DatabaseNotificati // MarkNotificationAsRead marks a notification as read. It should be called only after CanMarkNotificationAsRead has // been called. func MarkNotificationAsRead(s *xorm.Session, notification *DatabaseNotification, read bool) (err error) { - notification.ReadAt = time.Time{} + notification.ReadAt = modules.Time{} if read { - notification.ReadAt = time.Now() + notification.ReadAt = modules.Time(time.Now()) } _, err = s. @@ -102,6 +104,8 @@ func MarkAllNotificationsAsRead(s *xorm.Session, userID int64) (err error) { _, err = s. Where("notifiable_id = ?", userID). Cols("read_at"). - Update(&DatabaseNotification{ReadAt: time.Now()}) + Update(&DatabaseNotification{ + ReadAt: modules.Time(time.Now()), + }) return } diff --git a/pkg/routes/api/v1/task_attachment.go b/pkg/routes/api/v1/task_attachment.go index dd72afe5a..e8739e40f 100644 --- a/pkg/routes/api/v1/task_attachment.go +++ b/pkg/routes/api/v1/task_attachment.go @@ -191,6 +191,6 @@ func GetTaskAttachment(c echo.Context) error { } } - http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File) + http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created.Time(), taskAttachment.File.File) return nil } diff --git a/pkg/routes/api/v1/user_export.go b/pkg/routes/api/v1/user_export.go index 444d8b269..3877392cc 100644 --- a/pkg/routes/api/v1/user_export.go +++ b/pkg/routes/api/v1/user_export.go @@ -136,6 +136,6 @@ func DownloadUserDataExport(c echo.Context) error { return handler.HandleHTTPError(err) } - http.ServeContent(c.Response(), c.Request(), exportFile.Name, exportFile.Created, exportFile.File) + http.ServeContent(c.Response(), c.Request(), exportFile.Name, exportFile.Created.Time(), exportFile.File) return nil } diff --git a/pkg/routes/api/v1/user_show.go b/pkg/routes/api/v1/user_show.go index f5f714b38..c2f955297 100644 --- a/pkg/routes/api/v1/user_show.go +++ b/pkg/routes/api/v1/user_show.go @@ -18,25 +18,22 @@ package v1 import ( "net/http" - "time" - - "code.vikunja.io/api/pkg/modules/auth/openid" - - "code.vikunja.io/api/pkg/user" - - "code.vikunja.io/api/pkg/models" - "code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/db" - + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules" + "code.vikunja.io/api/pkg/modules/auth" + "code.vikunja.io/api/pkg/modules/auth/openid" + "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web/handler" + "github.com/labstack/echo/v4" ) type UserWithSettings struct { user.User Settings *UserSettings `json:"settings"` - DeletionScheduledAt time.Time `json:"deletion_scheduled_at"` + DeletionScheduledAt modules.Time `json:"deletion_scheduled_at"` IsLocalUser bool `json:"is_local_user"` AuthProvider string `json:"auth_provider"` } diff --git a/pkg/routes/api_tokens.go b/pkg/routes/api_tokens.go index 4220dbcf0..35868a633 100644 --- a/pkg/routes/api_tokens.go +++ b/pkg/routes/api_tokens.go @@ -73,8 +73,8 @@ func checkAPITokenAndPutItInContext(tokenHeaderValue string, c echo.Context) err return echo.NewHTTPError(http.StatusInternalServerError).SetInternal(err) } - if time.Now().After(token.ExpiresAt) { - log.Debugf("[auth] Tried authenticating with token %d but it expired on %s", token.ID, token.ExpiresAt.String()) + if time.Now().After(token.ExpiresAt.Time()) { + log.Debugf("[auth] Tried authenticating with token %d but it expired on %s", token.ID, token.ExpiresAt.Time().String()) return echo.NewHTTPError(http.StatusUnauthorized) } diff --git a/pkg/routes/caldav/listStorageProvider.go b/pkg/routes/caldav/listStorageProvider.go index 2fb6cec67..f41eb75e6 100644 --- a/pkg/routes/caldav/listStorageProvider.go +++ b/pkg/routes/caldav/listStorageProvider.go @@ -28,6 +28,7 @@ import ( "code.vikunja.io/api/pkg/models" user2 "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/web" + "github.com/samedi/caldav-go/data" "github.com/samedi/caldav-go/errs" "xorm.io/xorm" @@ -620,11 +621,11 @@ func (vlra *VikunjaProjectResourceAdapter) GetContentSize() int64 { // GetModTime returns when the resource was last modified func (vlra *VikunjaProjectResourceAdapter) GetModTime() time.Time { if vlra.task != nil { - return vlra.task.Updated + return vlra.task.Updated.Time() } if vlra.project != nil { - return vlra.project.Updated + return vlra.project.Updated.Time() } return time.Time{} diff --git a/pkg/user/delete.go b/pkg/user/delete.go index b1380ec76..78ae65809 100644 --- a/pkg/user/delete.go +++ b/pkg/user/delete.go @@ -22,6 +22,7 @@ import ( "code.vikunja.io/api/pkg/cron" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/notifications" "xorm.io/builder" @@ -52,15 +53,15 @@ func notifyUsersScheduledForDeletion() { log.Debugf("Found %d users scheduled for deletion to notify", len(users)) for _, user := range users { - if time.Since(user.DeletionLastReminderSent) < time.Hour*24 { + if time.Since(user.DeletionLastReminderSent.Time()) < time.Hour*24 { continue } var number = 2 - if user.DeletionLastReminderSent.IsZero() { + if user.DeletionLastReminderSent.Time().IsZero() { number = 3 } - if user.DeletionScheduledAt.Sub(user.DeletionLastReminderSent) < time.Hour*24 { + if user.DeletionScheduledAt.Time().Sub(user.DeletionLastReminderSent.Time()) < time.Hour*24 { number = 1 } @@ -75,7 +76,7 @@ func notifyUsersScheduledForDeletion() { continue } - user.DeletionLastReminderSent = time.Now() + user.DeletionLastReminderSent = modules.Time(time.Now()) _, err = s.Where("id = ?", user.ID). Cols("deletion_last_reminder_sent"). Update(user) @@ -115,7 +116,7 @@ func ConfirmDeletion(s *xorm.Session, user *User, token string) (err error) { return err } - user.DeletionScheduledAt = time.Now().Add(3 * 24 * time.Hour) + user.DeletionScheduledAt = modules.Time(time.Now().Add(3 * 24 * time.Hour)) _, err = s.Where("id = ?", user.ID). Cols("deletion_scheduled_at"). Update(user) @@ -124,8 +125,8 @@ func ConfirmDeletion(s *xorm.Session, user *User, token string) (err error) { // CancelDeletion cancels the deletion of a user func CancelDeletion(s *xorm.Session, user *User) (err error) { - user.DeletionScheduledAt = time.Time{} - user.DeletionLastReminderSent = time.Time{} + user.DeletionScheduledAt = modules.Time{} + user.DeletionLastReminderSent = modules.Time{} _, err = s.Where("id = ?", user.ID). Cols("deletion_scheduled_at", "deletion_last_reminder_sent"). Update(user) diff --git a/pkg/user/token.go b/pkg/user/token.go index 85915640c..f23ec6f47 100644 --- a/pkg/user/token.go +++ b/pkg/user/token.go @@ -22,6 +22,7 @@ import ( "code.vikunja.io/api/pkg/cron" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/utils" "xorm.io/xorm" ) @@ -41,12 +42,12 @@ const ( // Token is a token a user can use to do things like verify their email or resetting their password type Token struct { - ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` - UserID int64 `xorm:"not null" json:"-"` - Token string `xorm:"varchar(450) not null index" json:"-"` - ClearTextToken string `xorm:"-" json:"token"` - Kind TokenKind `xorm:"not null" json:"-"` - Created time.Time `xorm:"created not null" json:"created"` + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"` + UserID int64 `xorm:"not null" json:"-"` + Token string `xorm:"varchar(450) not null index" json:"-"` + ClearTextToken string `xorm:"-" json:"token"` + Kind TokenKind `xorm:"not null" json:"-"` + Created modules.Time `xorm:"created not null" json:"created"` } // TableName returns the real table name for user tokens diff --git a/pkg/user/user.go b/pkg/user/user.go index 1733e7f75..f9f25c389 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -27,10 +27,11 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules" "code.vikunja.io/api/pkg/modules/keyvalue" "code.vikunja.io/api/pkg/notifications" - "code.vikunja.io/api/pkg/web" + "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v4" "golang.org/x/crypto/bcrypt" @@ -102,17 +103,17 @@ type User struct { Language string `xorm:"varchar(50) null" json:"-"` Timezone string `xorm:"varchar(255) null" json:"-"` - DeletionScheduledAt time.Time `xorm:"datetime null" json:"-"` - DeletionLastReminderSent time.Time `xorm:"datetime null" json:"-"` + DeletionScheduledAt modules.Time `xorm:"datetime null" json:"-"` + DeletionLastReminderSent modules.Time `xorm:"datetime null" json:"-"` FrontendSettings interface{} `xorm:"json null" json:"-"` ExportFileID int64 `xorm:"bigint null" json:"-"` // A timestamp when this task was created. You cannot change this value. - Created time.Time `xorm:"created not null" json:"created"` + Created modules.Time `xorm:"created not null" json:"created"` // A timestamp when this task was last updated. You cannot change this value. - Updated time.Time `xorm:"updated not null" json:"updated"` + Updated modules.Time `xorm:"updated not null" json:"updated"` web.Auth `xorm:"-" json:"-"` }