diff --git a/config.yml.sample b/config.yml.sample index 1a3429bd9..7905cb257 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -26,6 +26,8 @@ service: enableregistration: true # Whether to enable task attachments or not enabletaskattachments: true + # The time zone all timestamps are in + timezone: GMT database: # Database type to use. Supported types are mysql and sqlite. diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md index eba75aab5..b165d8999 100644 --- a/docs/content/doc/setup/config.md +++ b/docs/content/doc/setup/config.md @@ -69,6 +69,8 @@ service: enableregistration: true # Whether to enable task attachments or not enabletaskattachments: true + # The time zone all timestamps are in + timezone: GMT database: # Database type to use. Supported types are mysql and sqlite. diff --git a/pkg/caldav/caldav.go b/pkg/caldav/caldav.go index 33dfb7167..c207e8330 100644 --- a/pkg/caldav/caldav.go +++ b/pkg/caldav/caldav.go @@ -17,6 +17,7 @@ package caldav import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "fmt" @@ -34,37 +35,37 @@ type Event struct { UID string Alarms []Alarm - TimestampUnix int64 - StartUnix int64 - EndUnix int64 + Timestamp timeutil.TimeStamp + Start timeutil.TimeStamp + End timeutil.TimeStamp } // Todo holds a single VTODO type Todo struct { // Required - TimestampUnix int64 - UID string + Timestamp timeutil.TimeStamp + UID string // Optional - Summary string - Description string - CompletedUnix int64 - Organizer *user.User - Priority int64 // 0-9, 1 is highest - RelatedToUID string + Summary string + Description string + Completed timeutil.TimeStamp + Organizer *user.User + Priority int64 // 0-9, 1 is highest + RelatedToUID string - StartUnix int64 - EndUnix int64 - DueDateUnix int64 - Duration time.Duration + Start timeutil.TimeStamp + End timeutil.TimeStamp + DueDate timeutil.TimeStamp + Duration time.Duration - CreatedUnix int64 - UpdatedUnix int64 // last-mod + Created timeutil.TimeStamp + Updated timeutil.TimeStamp // last-mod } // Alarm holds infos about an alarm from a caldav event type Alarm struct { - TimeUnix int64 + Time timeutil.TimeStamp Description string } @@ -86,7 +87,7 @@ PRODID:-//` + config.ProdID + `//EN` for _, e := range events { if e.UID == "" { - e.UID = makeCalDavTimeFromUnixTime(e.TimestampUnix) + utils.Sha256(e.Summary) + e.UID = makeCalDavTimeFromTimeStamp(e.Timestamp) + utils.Sha256(e.Summary) } caldavevents += ` @@ -94,9 +95,9 @@ BEGIN:VEVENT UID:` + e.UID + ` SUMMARY:` + e.Summary + ` DESCRIPTION:` + e.Description + ` -DTSTAMP:` + makeCalDavTimeFromUnixTime(e.TimestampUnix) + ` -DTSTART:` + makeCalDavTimeFromUnixTime(e.StartUnix) + ` -DTEND:` + makeCalDavTimeFromUnixTime(e.EndUnix) +DTSTAMP:` + makeCalDavTimeFromTimeStamp(e.Timestamp) + ` +DTSTART:` + makeCalDavTimeFromTimeStamp(e.Start) + ` +DTEND:` + makeCalDavTimeFromTimeStamp(e.End) for _, a := range e.Alarms { if a.Description == "" { @@ -105,7 +106,7 @@ DTEND:` + makeCalDavTimeFromUnixTime(e.EndUnix) caldavevents += ` BEGIN:VALARM -TRIGGER:` + calcAlarmDateFromReminder(e.StartUnix, a.TimeUnix) + ` +TRIGGER:` + calcAlarmDateFromReminder(e.Start, a.Time) + ` ACTION:DISPLAY DESCRIPTION:` + a.Description + ` END:VALARM` @@ -131,30 +132,30 @@ PRODID:-//` + config.ProdID + `//EN` for _, t := range todos { if t.UID == "" { - t.UID = makeCalDavTimeFromUnixTime(t.TimestampUnix) + utils.Sha256(t.Summary) + t.UID = makeCalDavTimeFromTimeStamp(t.Timestamp) + utils.Sha256(t.Summary) } caldavtodos += ` BEGIN:VTODO UID:` + t.UID + ` -DTSTAMP:` + makeCalDavTimeFromUnixTime(t.TimestampUnix) + ` +DTSTAMP:` + makeCalDavTimeFromTimeStamp(t.Timestamp) + ` SUMMARY:` + t.Summary - if t.StartUnix != 0 { + if t.Start != 0 { caldavtodos += ` -DTSTART: ` + makeCalDavTimeFromUnixTime(t.StartUnix) +DTSTART: ` + makeCalDavTimeFromTimeStamp(t.Start) } - if t.EndUnix != 0 { + if t.End != 0 { caldavtodos += ` -DTEND: ` + makeCalDavTimeFromUnixTime(t.EndUnix) +DTEND: ` + makeCalDavTimeFromTimeStamp(t.End) } if t.Description != "" { caldavtodos += ` DESCRIPTION:` + t.Description } - if t.CompletedUnix != 0 { + if t.Completed != 0 { caldavtodos += ` -COMPLETED: ` + makeCalDavTimeFromUnixTime(t.CompletedUnix) +COMPLETED: ` + makeCalDavTimeFromTimeStamp(t.Completed) } if t.Organizer != nil { caldavtodos += ` @@ -166,14 +167,14 @@ ORGANIZER;CN=:` + t.Organizer.Username RELATED-TO:` + t.RelatedToUID } - if t.DueDateUnix != 0 { + if t.DueDate != 0 { caldavtodos += ` -DUE:` + makeCalDavTimeFromUnixTime(t.DueDateUnix) +DUE:` + makeCalDavTimeFromTimeStamp(t.DueDate) } - if t.CreatedUnix != 0 { + if t.Created != 0 { caldavtodos += ` -CREATED:` + makeCalDavTimeFromUnixTime(t.CreatedUnix) +CREATED:` + makeCalDavTimeFromTimeStamp(t.Created) } if t.Duration != 0 { @@ -187,7 +188,7 @@ PRIORITY:` + strconv.Itoa(int(t.Priority)) } caldavtodos += ` -LAST-MODIFIED:` + makeCalDavTimeFromUnixTime(t.UpdatedUnix) +LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated) caldavtodos += ` END:VTODO` @@ -199,13 +200,12 @@ END:VCALENDAR` // Need a line break return } -func makeCalDavTimeFromUnixTime(unixtime int64) (caldavtime string) { +func makeCalDavTimeFromTimeStamp(ts timeutil.TimeStamp) (caldavtime string) { tz, _ := time.LoadLocation("UTC") - tm := time.Unix(unixtime, 0).In(tz) - return tm.Format(DateFormat) + return ts.ToTime().In(tz).Format(DateFormat) } -func calcAlarmDateFromReminder(eventStartUnix, reminderUnix int64) (alarmTime string) { +func calcAlarmDateFromReminder(eventStartUnix, reminderUnix timeutil.TimeStamp) (alarmTime string) { if eventStartUnix > reminderUnix { alarmTime += `-` } diff --git a/pkg/caldav/caldav_test.go b/pkg/caldav/caldav_test.go index 925dd4b0a..ecbf82043 100644 --- a/pkg/caldav/caldav_test.go +++ b/pkg/caldav/caldav_test.go @@ -37,26 +37,26 @@ func TestParseEvents(t *testing.T) { }, events: []*Event{ { - Summary: "Event #1", - Description: "Lorem Ipsum", - UID: "randommduid", - TimestampUnix: 1543626724, - StartUnix: 1543626724, - EndUnix: 1543627824, + Summary: "Event #1", + Description: "Lorem Ipsum", + UID: "randommduid", + Timestamp: 1543626724, + Start: 1543626724, + End: 1543627824, }, { - Summary: "Event #2", - UID: "randommduidd", - TimestampUnix: 1543726724, - StartUnix: 1543726724, - EndUnix: 1543738724, + Summary: "Event #2", + UID: "randommduidd", + Timestamp: 1543726724, + Start: 1543726724, + End: 1543738724, }, { - Summary: "Event #3 with empty uid", - UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83", - TimestampUnix: 1543726824, - StartUnix: 1543726824, - EndUnix: 1543727000, + Summary: "Event #3 with empty uid", + UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83", + Timestamp: 1543726824, + Start: 1543726824, + End: 1543727000, }, }, }, @@ -101,47 +101,47 @@ END:VCALENDAR`, }, events: []*Event{ { - Summary: "Event #1", - Description: "Lorem Ipsum", - UID: "randommduid", - TimestampUnix: 1543626724, - StartUnix: 1543626724, - EndUnix: 1543627824, + Summary: "Event #1", + Description: "Lorem Ipsum", + UID: "randommduid", + Timestamp: 1543626724, + Start: 1543626724, + End: 1543627824, Alarms: []Alarm{ - {TimeUnix: 1543626524}, - {TimeUnix: 1543626224}, - {TimeUnix: 1543626024}, + {Time: 1543626524}, + {Time: 1543626224}, + {Time: 1543626024}, }, }, { - Summary: "Event #2", - UID: "randommduidd", - TimestampUnix: 1543726724, - StartUnix: 1543726724, - EndUnix: 1543738724, + Summary: "Event #2", + UID: "randommduidd", + Timestamp: 1543726724, + Start: 1543726724, + End: 1543738724, Alarms: []Alarm{ - {TimeUnix: 1543626524}, - {TimeUnix: 1543626224}, - {TimeUnix: 1543626024}, + {Time: 1543626524}, + {Time: 1543626224}, + {Time: 1543626024}, }, }, { - Summary: "Event #3 with empty uid", - TimestampUnix: 1543726824, - StartUnix: 1543726824, - EndUnix: 1543727000, + Summary: "Event #3 with empty uid", + Timestamp: 1543726824, + Start: 1543726824, + End: 1543727000, Alarms: []Alarm{ - {TimeUnix: 1543626524}, - {TimeUnix: 1543626224}, - {TimeUnix: 1543626024}, - {TimeUnix: 1543826824}, + {Time: 1543626524}, + {Time: 1543626224}, + {Time: 1543626024}, + {Time: 1543826824}, }, }, { - Summary: "Event #4 without any", - TimestampUnix: 1543726824, - StartUnix: 1543726824, - EndUnix: 1543727000, + Summary: "Event #4 without any", + Timestamp: 1543726824, + Start: 1543726824, + End: 1543727000, }, }, }, diff --git a/pkg/config/config.go b/pkg/config/config.go index 4f2beb071..db9dff9d2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -44,6 +44,7 @@ const ( ServiceEnableLinkSharing Key = `service.enablelinksharing` ServiceEnableRegistration Key = `service.enableregistration` ServiceEnableTaskAttachments Key = `service.enabletaskattachments` + ServiceTimeZone Key = `service.timezone` DatabaseType Key = `database.type` DatabaseHost Key = `database.host` @@ -168,6 +169,7 @@ func InitDefaultConfig() { ServiceEnableLinkSharing.setDefault(true) ServiceEnableRegistration.setDefault(true) ServiceEnableTaskAttachments.setDefault(true) + ServiceTimeZone.setDefault("GMT") // Database DatabaseType.setDefault("sqlite") diff --git a/pkg/files/files.go b/pkg/files/files.go index 2791c67e2..060f6f5ce 100644 --- a/pkg/files/files.go +++ b/pkg/files/files.go @@ -18,6 +18,7 @@ package files import ( "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/web" "github.com/c2h5oh/datasize" "github.com/spf13/afero" @@ -35,8 +36,8 @@ type File struct { Created time.Time `xorm:"-" json:"created"` - CreatedUnix int64 `xorm:"created" json:"-"` - CreatedByID int64 `xorm:"int(11) not null" json:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"created" json:"-"` + CreatedByID int64 `xorm:"int(11) not null" json:"-"` File afero.File `xorm:"-" json:"-"` // This ReadCloser is only used for migration purposes. Use with care! @@ -65,7 +66,7 @@ func (f *File) LoadFileMetaByID() (err error) { if !exists { return ErrFileDoesNotExist{FileID: f.ID} } - f.Created = time.Unix(f.CreatedUnix, 0) + f.Created = f.CreatedUnix.ToTime() return } diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 374d693af..3cd8c4f00 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -95,33 +95,33 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) t.Run("by priority desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) + assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":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) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) // should equal duedate asc t.Run("by duedate", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":null,"dueDate":"2018-11-30T22:25:24Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":null,"dueDate":"2018-12-01T03:58:44Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) t.Run("by duedate desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":null,"dueDate":"2018-12-01T03:58:44Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":6,"text":"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_unix"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":null,"dueDate":"2018-11-30T22:25:24Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":null,"dueDate":"2018-12-01T03:58:44Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) t.Run("invalid sort parameter", func(t *testing.T) { _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams) @@ -249,33 +249,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) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) t.Run("by priority desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) + assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":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) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":null,"dueDate":null,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) // should equal duedate asc t.Run("by duedate", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":null,"dueDate":"2018-11-30T22:25:24Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":null,"dueDate":"2018-12-01T03:58:44Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) t.Run("by duedate desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":null,"dueDate":"2018-12-01T03:58:44Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":6,"text":"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_unix"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":null,"dueDate":"2018-11-30T22:25:24Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":null,"dueDate":"2018-12-01T03:58:44Z","reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":null,"endDate":null,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":null,"updated":null}}]`) }) t.Run("invalid parameter", func(t *testing.T) { // Invalid parameter should not sort at all diff --git a/pkg/integrations/task_test.go b/pkg/integrations/task_test.go index 41c8b61bd..89de665ab 100644 --- a/pkg/integrations/task_test.go +++ b/pkg/integrations/task_test.go @@ -67,21 +67,21 @@ func TestTask(t *testing.T) { assert.NotContains(t, rec.Body.String(), `"done":true`) }) t.Run("Due date", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"dueDate": 123456}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"dueDate": "2020-02-10T10:00:00Z"}`) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"dueDate":123456`) + assert.Contains(t, rec.Body.String(), `"dueDate":"2020-02-10T10:00:00Z"`) assert.NotContains(t, rec.Body.String(), `"dueDate":0`) }) t.Run("Due date unset", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "5"}, `{"dueDate": 0}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "5"}, `{"dueDate": null}`) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"dueDate":0`) - assert.NotContains(t, rec.Body.String(), `"dueDate":1543636724`) + assert.Contains(t, rec.Body.String(), `"dueDate":null`) + assert.NotContains(t, rec.Body.String(), `"dueDate":"2020-02-10T10:00:00Z"`) }) t.Run("Reminders", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminderDates": [1555508227,1555511000]}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminderDates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"reminderDates":[1555508227,1555511000]`) + assert.Contains(t, rec.Body.String(), `"reminderDates":["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]`) assert.NotContains(t, rec.Body.String(), `"reminderDates": null`) }) t.Run("Reminders unset to empty array", func(t *testing.T) { @@ -145,28 +145,28 @@ func TestTask(t *testing.T) { assert.NotContains(t, rec.Body.String(), `"priority":100`) }) t.Run("Start date", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"startDate":1234567}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"startDate":"2020-02-10T10:00:00Z"}`) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"startDate":1234567`) + assert.Contains(t, rec.Body.String(), `"startDate":"2020-02-10T10:00:00Z"`) assert.NotContains(t, rec.Body.String(), `"startDate":0`) }) t.Run("Start date unset", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "7"}, `{"startDate":0}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "7"}, `{"startDate":null}`) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"startDate":0`) - assert.NotContains(t, rec.Body.String(), `"startDate":1544600000`) + assert.Contains(t, rec.Body.String(), `"startDate":null`) + assert.NotContains(t, rec.Body.String(), `"startDate":"2020-02-10T10:00:00Z"`) }) t.Run("End date", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"endDate":123456}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"endDate":"2020-02-10T12:00:00Z"}`) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"endDate":123456`) - assert.NotContains(t, rec.Body.String(), `"endDate":0`) + assert.Contains(t, rec.Body.String(), `"endDate":"2020-02-10T12:00:00Z"`) + assert.NotContains(t, rec.Body.String(), `"endDate":""`) }) t.Run("End date unset", func(t *testing.T) { - rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "8"}, `{"endDate":0}`) + rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "8"}, `{"endDate":null}`) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `"endDate":0`) - assert.NotContains(t, rec.Body.String(), `"endDate":1544700000`) + assert.Contains(t, rec.Body.String(), `"endDate":null`) + assert.NotContains(t, rec.Body.String(), `"endDate":"2020-02-10T10:00:00Z"`) }) t.Run("Color", func(t *testing.T) { rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"hexColor":"f0f0f0"}`) diff --git a/pkg/models/label.go b/pkg/models/label.go index e47283493..bfdcd2c77 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "time" @@ -37,10 +38,10 @@ type Label struct { // The user who created this label CreatedBy *user.User `xorm:"-" json:"created_by"` - // A unix timestamp when this label was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this label was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this label was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this label was last updated. You cannot change this value. + Updated timeutil.TimeStamp `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 c9bbbcacf..aa38e40a9 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/go-xorm/builder" @@ -29,8 +30,8 @@ type LabelTask struct { TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"` // The label id you want to associate with a task. LabelID int64 `xorm:"int(11) INDEX not null" json:"label_id" param:"label"` - // A unix timestamp when this task was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` + // A timestamp when this task was created. You cannot change this value. + Created timeutil.TimeStamp `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 cd497e859..2cd0d56b4 100644 --- a/pkg/models/label_task_test.go +++ b/pkg/models/label_task_test.go @@ -2,6 +2,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "gopkg.in/d4l3k/messagediff.v1" "reflect" @@ -16,7 +17,7 @@ func TestLabelTask_ReadAll(t *testing.T) { ID int64 TaskID int64 LabelID int64 - Created int64 + Created timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -113,7 +114,7 @@ func TestLabelTask_Create(t *testing.T) { ID int64 TaskID int64 LabelID int64 - Created int64 + Created timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -207,7 +208,7 @@ func TestLabelTask_Delete(t *testing.T) { ID int64 TaskID int64 LabelID int64 - Created int64 + Created timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/label_test.go b/pkg/models/label_test.go index f8f3fdf40..79b8045e0 100644 --- a/pkg/models/label_test.go +++ b/pkg/models/label_test.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "gopkg.in/d4l3k/messagediff.v1" "reflect" @@ -34,8 +35,8 @@ func TestLabel_ReadAll(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -138,8 +139,8 @@ func TestLabel_ReadOne(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -250,8 +251,8 @@ func TestLabel_Create(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -310,8 +311,8 @@ func TestLabel_Update(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -392,8 +393,8 @@ func TestLabel_Delete(t *testing.T) { HexColor string CreatedByID int64 CreatedBy *user.User - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index 115e3405c..7501872ad 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -18,6 +18,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" @@ -52,10 +53,10 @@ type LinkSharing struct { SharedBy *user.User `xorm:"-" json:"shared_by"` SharedByID int64 `xorm:"int(11) INDEX not null" json:"-"` - // A unix timestamp when this list was shared. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this share was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this list was shared. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this share was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/list.go b/pkg/models/list.go index fc98780c7..9ddde6a04 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -42,10 +43,10 @@ type List struct { // Deprecated: you should use the dedicated task list endpoint because it has support for pagination and filtering Tasks []*Task `xorm:"-" json:"-"` - // A unix timestamp when this list was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this list was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this list was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this list was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/list_team.go b/pkg/models/list_team.go index 6fa5c5883..f3896fe51 100644 --- a/pkg/models/list_team.go +++ b/pkg/models/list_team.go @@ -16,7 +16,10 @@ package models -import "code.vikunja.io/web" +import ( + "code.vikunja.io/api/pkg/timeutil" + "code.vikunja.io/web" +) // TeamList defines the relation between a team and a list type TeamList struct { @@ -29,10 +32,10 @@ type TeamList struct { // The right this team has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` - // A unix timestamp when this relation was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this relation was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this relation was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this relation was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/list_team_test.go b/pkg/models/list_team_test.go index 3c0207a5c..90e445a09 100644 --- a/pkg/models/list_team_test.go +++ b/pkg/models/list_team_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/stretchr/testify/assert" @@ -120,8 +121,8 @@ func TestTeamList_Update(t *testing.T) { TeamID int64 ListID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/list_users.go b/pkg/models/list_users.go index dc770b0c1..fb3edda33 100644 --- a/pkg/models/list_users.go +++ b/pkg/models/list_users.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -34,10 +35,10 @@ type ListUser struct { // The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` - // A unix timestamp when this relation was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this relation was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this relation was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this relation was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/list_users_rights_test.go b/pkg/models/list_users_rights_test.go index 262bebe69..56ff1e511 100644 --- a/pkg/models/list_users_rights_test.go +++ b/pkg/models/list_users_rights_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "testing" @@ -30,8 +31,8 @@ func TestListUser_CanDoSomething(t *testing.T) { UserID int64 ListID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/list_users_test.go b/pkg/models/list_users_test.go index dca18f4ae..ed7c4bf49 100644 --- a/pkg/models/list_users_test.go +++ b/pkg/models/list_users_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "gopkg.in/d4l3k/messagediff.v1" "reflect" @@ -34,8 +35,8 @@ func TestListUser_Create(t *testing.T) { Username string ListID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -135,8 +136,8 @@ func TestListUser_ReadAll(t *testing.T) { UserID int64 ListID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -229,8 +230,8 @@ func TestListUser_Update(t *testing.T) { Username string ListID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -306,8 +307,8 @@ func TestListUser_Delete(t *testing.T) { Username string ListID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index b8eae354b..1bcf9eab9 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/imdario/mergo" @@ -37,10 +38,10 @@ type Namespace struct { // The user who owns this namespace Owner *user.User `xorm:"-" json:"owner" valid:"-"` - // A unix timestamp when this namespace was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this namespace was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this namespace was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this namespace was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` @@ -51,8 +52,8 @@ var PseudoNamespace = Namespace{ ID: -1, Name: "Shared Lists", Description: "Lists of other users shared with you via teams or directly.", - Created: time.Now().Unix(), - Updated: time.Now().Unix(), + Created: timeutil.FromTime(time.Now()), + Updated: timeutil.FromTime(time.Now()), } // TableName makes beautiful table names diff --git a/pkg/models/namespace_team.go b/pkg/models/namespace_team.go index dbee61fa4..3a498cd2d 100644 --- a/pkg/models/namespace_team.go +++ b/pkg/models/namespace_team.go @@ -16,7 +16,10 @@ package models -import "code.vikunja.io/web" +import ( + "code.vikunja.io/api/pkg/timeutil" + "code.vikunja.io/web" +) // TeamNamespace defines the relationship between a Team and a Namespace type TeamNamespace struct { @@ -29,10 +32,10 @@ type TeamNamespace struct { // The right this team has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` - // A unix timestamp when this relation was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this relation was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this relation was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this relation was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/namespace_team_rights_test.go b/pkg/models/namespace_team_rights_test.go index e8664c487..eb628f594 100644 --- a/pkg/models/namespace_team_rights_test.go +++ b/pkg/models/namespace_team_rights_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "testing" @@ -30,8 +31,8 @@ func TestTeamNamespace_CanDoSomething(t *testing.T) { TeamID int64 NamespaceID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/namespace_team_test.go b/pkg/models/namespace_team_test.go index 811ebb14f..a8b109cb1 100644 --- a/pkg/models/namespace_team_test.go +++ b/pkg/models/namespace_team_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/stretchr/testify/assert" @@ -112,8 +113,8 @@ func TestTeamNamespace_Update(t *testing.T) { TeamID int64 NamespaceID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/namespace_users.go b/pkg/models/namespace_users.go index 4c3e45b6b..1f988ad3f 100644 --- a/pkg/models/namespace_users.go +++ b/pkg/models/namespace_users.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" user2 "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -33,10 +34,10 @@ type NamespaceUser struct { // The right this user has. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details. Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"` - // A unix timestamp when this relation was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this relation was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this relation was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this relation was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/namespace_users_rights_test.go b/pkg/models/namespace_users_rights_test.go index 5807e349e..dc82183a0 100644 --- a/pkg/models/namespace_users_rights_test.go +++ b/pkg/models/namespace_users_rights_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "testing" @@ -30,8 +31,8 @@ func TestNamespaceUser_CanDoSomething(t *testing.T) { UserID int64 NamespaceID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/namespace_users_test.go b/pkg/models/namespace_users_test.go index 4e6c1c4ac..a4931b1a2 100644 --- a/pkg/models/namespace_users_test.go +++ b/pkg/models/namespace_users_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "gopkg.in/d4l3k/messagediff.v1" @@ -32,8 +33,8 @@ func TestNamespaceUser_Create(t *testing.T) { Username string NamespaceID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -132,8 +133,8 @@ func TestNamespaceUser_ReadAll(t *testing.T) { UserID int64 NamespaceID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -227,8 +228,8 @@ func TestNamespaceUser_Update(t *testing.T) { Username string NamespaceID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } @@ -304,8 +305,8 @@ func TestNamespaceUser_Delete(t *testing.T) { Username string NamespaceID int64 Right Right - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/models/task_assignees.go b/pkg/models/task_assignees.go index e7d43eeff..ba3e9be0a 100644 --- a/pkg/models/task_assignees.go +++ b/pkg/models/task_assignees.go @@ -17,16 +17,17 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) // TaskAssginee represents an assignment of a user to a task type TaskAssginee struct { - ID int64 `xorm:"int(11) autoincr not null unique pk" json:"-"` - TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"` - UserID int64 `xorm:"int(11) INDEX not null" json:"user_id" param:"user"` - Created int64 `xorm:"created not null"` + ID int64 `xorm:"int(11) autoincr not null unique pk" json:"-"` + TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"` + UserID int64 `xorm:"int(11) INDEX not null" json:"user_id" param:"user"` + Created timeutil.TimeStamp `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 03088b66d..b351b6bc5 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -18,10 +18,10 @@ package models import ( "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "io" - "time" ) // TaskAttachment is the definition of a task attachment @@ -35,7 +35,7 @@ type TaskAttachment struct { File *files.File `xorm:"-" json:"file"` - Created int64 `xorm:"created" json:"created"` + Created timeutil.TimeStamp `xorm:"created" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` @@ -145,7 +145,7 @@ func (ta *TaskAttachment) ReadAll(a web.Auth, search string, page int, perPage i continue } r.File = fs[r.FileID] - r.File.Created = time.Unix(r.File.CreatedUnix, 0) + r.File.Created = r.File.CreatedUnix.ToTime() r.CreatedBy = us[r.CreatedByID] } diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index de19f6462..30955053a 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -53,8 +53,8 @@ type TaskCollection struct { // @Param s query string false "Search tasks by task text." // @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `text`, `description`, `done`, `done_at_unix`, `due_date_unix`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date_unix`, `end_date_unix`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`." // @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`." -// @Param startdate query int false "The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the current time." -// @Param enddate query int false "The end date parameter to filter by. Expects a unix timestamp. If no start date, but an end date is specified, the start date is set to the current time." +// @Param startdate query int false "The start date parameter to filter by. Expects a timestamp. If no end date, but a start date is specified, the end date is set to the current time." +// @Param enddate query int false "The end date parameter to filter by. Expects a timestamp. If no start date, but an end date is specified, the start date is set to the current time." // @Security JWTKeyAuth // @Success 200 {array} models.Task "The tasks" // @Failure 500 {object} models.Message "Internal error" diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index d1d314b47..a668e0886 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -17,6 +17,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "fmt" "reflect" "sort" @@ -117,6 +118,13 @@ func mustMakeComparator(fieldName string) taskComparator { return reflect.ValueOf(task).Elem().FieldByIndex(field.Index).Interface() } + // Special case for handling TimeStamp types + if field.Type.Name() == "TimeStamp" { + return func(lhs, rhs *Task) int64 { + return int64(extractProp(lhs).(timeutil.TimeStamp)) - int64(extractProp(rhs).(timeutil.TimeStamp)) + } + } + switch field.Type.Kind() { case reflect.Int64: return func(lhs, rhs *Task) int64 { @@ -165,14 +173,14 @@ var propertyComparators = map[sortProperty]taskComparator{ taskPropertyText: mustMakeComparator("Text"), taskPropertyDescription: mustMakeComparator("Description"), taskPropertyDone: mustMakeComparator("Done"), - taskPropertyDoneAtUnix: mustMakeComparator("DoneAtUnix"), - taskPropertyDueDateUnix: mustMakeComparator("DueDateUnix"), + taskPropertyDoneAtUnix: mustMakeComparator("DoneAt"), + taskPropertyDueDateUnix: mustMakeComparator("DueDate"), taskPropertyCreatedByID: mustMakeComparator("CreatedByID"), taskPropertyListID: mustMakeComparator("ListID"), taskPropertyRepeatAfter: mustMakeComparator("RepeatAfter"), taskPropertyPriority: mustMakeComparator("Priority"), - taskPropertyStartDateUnix: mustMakeComparator("StartDateUnix"), - taskPropertyEndDateUnix: mustMakeComparator("EndDateUnix"), + taskPropertyStartDateUnix: mustMakeComparator("StartDate"), + taskPropertyEndDateUnix: mustMakeComparator("EndDate"), taskPropertyHexColor: mustMakeComparator("HexColor"), taskPropertyPercentDone: mustMakeComparator("PercentDone"), taskPropertyUID: mustMakeComparator("UID"), diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 1efcdec72..8efe6bc6d 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -99,29 +99,29 @@ var ( Text: "aaa", Description: "Lorem Ipsum", Done: true, - DoneAtUnix: 1543626000, + DoneAt: 1543626000, ListID: 1, UID: "JywtBPCESImlyKugvaZWrxmXAFAWXFISMeXYImEh", Created: 1543626724, Updated: 1543626724, } task2 = &Task{ - ID: 2, - Text: "bbb", - Description: "Arem Ipsum", - Done: true, - DoneAtUnix: 1543626724, - CreatedByID: 1, - ListID: 2, - PercentDone: 0.3, - StartDateUnix: 1543626724, - Created: 1553626724, - Updated: 1553626724, + ID: 2, + Text: "bbb", + Description: "Arem Ipsum", + Done: true, + DoneAt: 1543626724, + CreatedByID: 1, + ListID: 2, + PercentDone: 0.3, + StartDate: 1543626724, + Created: 1553626724, + Updated: 1553626724, } task3 = &Task{ ID: 3, Text: "ccc", - DueDateUnix: 1583626724, + DueDate: 1583626724, Priority: 100, ListID: 3, HexColor: "000000", @@ -129,49 +129,49 @@ var ( Updated: 1555555555, } task4 = &Task{ - ID: 4, - Text: "ddd", - Priority: 1, - StartDateUnix: 1643626724, - ListID: 1, + ID: 4, + Text: "ddd", + Priority: 1, + StartDate: 1643626724, + ListID: 1, } task5 = &Task{ - ID: 5, - Text: "eef", - Priority: 50, - UID: "shggzCHQWLhGNMNsOGOCOjcVkInOYjTAnORqTkdL", - DueDateUnix: 1543636724, - Updated: 1565555555, + ID: 5, + Text: "eef", + Priority: 50, + UID: "shggzCHQWLhGNMNsOGOCOjcVkInOYjTAnORqTkdL", + DueDate: 1543636724, + Updated: 1565555555, } task6 = &Task{ ID: 6, Text: "eef", - DueDateUnix: 1543616724, + DueDate: 1543616724, RepeatAfter: 6400, CreatedByID: 2, HexColor: "ffffff", } task7 = &Task{ - ID: 7, - Text: "mmmn", - Description: "Zoremis", - StartDateUnix: 1544600000, - EndDateUnix: 1584600000, - UID: "tyzCZuLMSKhwclJOsDyDcUdyVAPBDOPHNTBOLTcW", + ID: 7, + Text: "mmmn", + Description: "Zoremis", + StartDate: 1544600000, + EndDate: 1584600000, + UID: "tyzCZuLMSKhwclJOsDyDcUdyVAPBDOPHNTBOLTcW", } task8 = &Task{ - ID: 8, - Text: "b123", - EndDateUnix: 1544700000, + ID: 8, + Text: "b123", + EndDate: 1544700000, } task9 = &Task{ - ID: 9, - Done: true, - DoneAtUnix: 1573626724, - Text: "a123", - RepeatAfter: 86000, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, + ID: 9, + Done: true, + DoneAt: 1573626724, + Text: "a123", + RepeatAfter: 86000, + StartDate: 1544600000, + EndDate: 1544700000, } task10 = &Task{ ID: 10, diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 15c617be1..02d0b7f0c 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -19,6 +19,7 @@ package models import ( "code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "gopkg.in/d4l3k/messagediff.v1" @@ -167,7 +168,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { RelatedTasks: map[RelationKind][]*Task{}, Created: 1543626724, Updated: 1543626724, - DueDateUnix: 1543636724, + DueDate: 1543636724, } task6 := &Task{ ID: 6, @@ -180,20 +181,20 @@ func TestTaskCollection_ReadAll(t *testing.T) { RelatedTasks: map[RelationKind][]*Task{}, Created: 1543626724, Updated: 1543626724, - DueDateUnix: 1543616724, + DueDate: 1543616724, } task7 := &Task{ - ID: 7, - Text: "task #7 with start date", - Identifier: "test1-7", - Index: 7, - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, + ID: 7, + Text: "task #7 with start date", + Identifier: "test1-7", + Index: 7, + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDate: 1544600000, } task8 := &Task{ ID: 8, @@ -206,21 +207,21 @@ func TestTaskCollection_ReadAll(t *testing.T) { RelatedTasks: map[RelationKind][]*Task{}, Created: 1543626724, Updated: 1543626724, - EndDateUnix: 1544700000, + EndDate: 1544700000, } task9 := &Task{ - ID: 9, - Text: "task #9 with start and end date", - Identifier: "test1-9", - Index: 9, - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, + ID: 9, + Text: "task #9 with start and end date", + Identifier: "test1-9", + Index: 9, + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDate: 1544600000, + EndDate: 1544700000, } task10 := &Task{ ID: 10, @@ -403,17 +404,17 @@ func TestTaskCollection_ReadAll(t *testing.T) { Updated: 1543626724, } task27 := &Task{ - ID: 27, - Text: "task #27 with reminders", - Identifier: "test1-12", - Index: 12, - CreatedByID: 1, - CreatedBy: user1, - RemindersUnix: []int64{1543626724, 1543626824}, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, + ID: 27, + Text: "task #27 with reminders", + Identifier: "test1-12", + Index: 12, + CreatedByID: 1, + CreatedBy: user1, + Reminders: []timeutil.TimeStamp{1543626724, 1543626824}, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, } task28 := &Task{ ID: 28, diff --git a/pkg/models/task_relation.go b/pkg/models/task_relation.go index 75b8b4fe1..f410c6606 100644 --- a/pkg/models/task_relation.go +++ b/pkg/models/task_relation.go @@ -18,6 +18,7 @@ package models import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" ) @@ -86,8 +87,8 @@ type TaskRelation struct { // The user who created this relation CreatedBy *user.User `xorm:"-" json:"created_by"` - // A unix timestamp when this label was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` + // A timestamp when this label was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 327978f27..ffad55b33 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -19,6 +19,7 @@ package models import ( "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" @@ -38,13 +39,13 @@ type Task struct { Description string `xorm:"longtext null" json:"description"` // Whether a task is done or not. Done bool `xorm:"INDEX null" json:"done"` - // The unix timestamp when a task was marked as done. - DoneAtUnix int64 `xorm:"INDEX null" json:"doneAt"` - // A unix timestamp when the task is due. - DueDateUnix int64 `xorm:"int(11) INDEX null" json:"dueDate"` - // An array of unix timestamps when the user wants to be reminded of the task. - RemindersUnix []int64 `xorm:"-" json:"reminderDates"` - CreatedByID int64 `xorm:"int(11) not null" json:"-"` // ID of the user who put that task on the list + // The time when a task was marked as done. + DoneAt timeutil.TimeStamp `xorm:"INDEX null 'done_at_unix'" json:"doneAt"` + // The time when the task is due. + DueDate timeutil.TimeStamp `xorm:"int(11) INDEX null 'due_date_unix'" json:"dueDate"` + // An array of datetimes when the user wants to be reminded of the task. + Reminders []timeutil.TimeStamp `xorm:"-" json:"reminderDates"` + CreatedByID int64 `xorm:"int(11) not null" json:"-"` // ID of the user who put that task on the list // The list this task belongs to. ListID int64 `xorm:"int(11) INDEX not null" json:"listID" param:"list"` // An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount. @@ -52,9 +53,9 @@ type Task struct { // The task priority. Can be anything you want, it is possible to sort by this later. Priority int64 `xorm:"int(11) null" json:"priority"` // When this task starts. - StartDateUnix int64 `xorm:"int(11) INDEX null" json:"startDate" query:"-"` + StartDate timeutil.TimeStamp `xorm:"int(11) INDEX null 'start_date_unix'" json:"startDate" query:"-"` // When this task ends. - EndDateUnix int64 `xorm:"int(11) INDEX null" json:"endDate" query:"-"` + EndDate timeutil.TimeStamp `xorm:"int(11) INDEX null 'end_date_unix'" json:"endDate" 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. @@ -72,20 +73,16 @@ type Task struct { // The UID is currently not used for anything other than caldav, which is why we don't expose it over json UID string `xorm:"varchar(250) null" json:"-"` - Sorting string `xorm:"-" json:"-" query:"sort"` // Parameter to sort by - StartDateSortUnix int64 `xorm:"-" json:"-" query:"startdate"` - EndDateSortUnix int64 `xorm:"-" json:"-" query:"enddate"` - // All related tasks, grouped by their relation kind RelatedTasks RelatedTaskMap `xorm:"-" json:"related_tasks"` // All attachments this task has Attachments []*TaskAttachment `xorm:"-" json:"attachments"` - // A unix timestamp when this task was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this task was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this task was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this task was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` // The user who initially created the task. CreatedBy *user.User `xorm:"-" json:"createdBy" valid:"-"` @@ -101,10 +98,10 @@ func (Task) TableName() string { // TaskReminder holds a reminder on a task type TaskReminder struct { - ID int64 `xorm:"int(11) autoincr not null unique pk"` - TaskID int64 `xorm:"int(11) not null INDEX"` - ReminderUnix int64 `xorm:"int(11) not null INDEX"` - Created int64 `xorm:"created not null"` + ID int64 `xorm:"int(11) autoincr not null unique pk"` + TaskID int64 `xorm:"int(11) not null INDEX"` + Reminder timeutil.TimeStamp `xorm:"int(11) not null INDEX 'reminder_unix'"` + Created timeutil.TimeStamp `xorm:"created not null"` } // TableName returns a pretty table name @@ -131,8 +128,8 @@ type taskOptions struct { // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search tasks by task text." // @Param sort query string false "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc." -// @Param startdate query int false "The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the current time." -// @Param enddate query int false "The end date parameter to filter by. Expects a unix timestamp. If no start date, but an end date is specified, the start date is set to the current time." +// @Param startdate query int false "The start date parameter to filter by. Expects a timestamp. If no end date, but a start date is specified, the end date is set to the current time." +// @Param enddate query int false "The end date parameter to filter by. Expects a timestamp. If no start date, but an end date is specified, the start date is set to the current time." // @Security JWTKeyAuth // @Success 200 {array} models.Task "The tasks" // @Failure 500 {object} models.Message "Internal error" @@ -292,7 +289,7 @@ func GetTasksByUIDs(uids []string) (tasks []*Task, err error) { func getRemindersForTasks(taskIDs []int64) (reminders []*TaskReminder, err error) { reminders = []*TaskReminder{} - err = x.Table("task_reminders").In("task_id", taskIDs).Find(&reminders) + err = x.In("task_id", taskIDs).Find(&reminders) return } @@ -390,9 +387,9 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { return } - taskRemindersUnix := make(map[int64][]int64) + taskRemindersUnix := make(map[int64][]timeutil.TimeStamp) for _, r := range reminders { - taskRemindersUnix[r.TaskID] = append(taskRemindersUnix[r.TaskID], r.ReminderUnix) + taskRemindersUnix[r.TaskID] = append(taskRemindersUnix[r.TaskID], r.Reminder) } // Get all identifiers @@ -409,7 +406,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { task.CreatedBy = users[task.CreatedByID] // Add the reminders - task.RemindersUnix = taskRemindersUnix[task.ID] + task.Reminders = taskRemindersUnix[task.ID] // Prepare the subtasks task.RelatedTasks = make(RelatedTaskMap) @@ -518,7 +515,7 @@ func (t *Task) Create(a web.Auth) (err error) { } // Update the reminders - if err := t.updateReminders(t.RemindersUnix); err != nil { + if err := t.updateReminders(t.Reminders); err != nil { return err } @@ -558,7 +555,7 @@ func (t *Task) Update() (err error) { } // Update the reminders - if err := ot.updateReminders(t.RemindersUnix); err != nil { + if err := ot.updateReminders(t.Reminders); err != nil { return err } @@ -603,20 +600,20 @@ func (t *Task) Update() (err error) { ot.Description = "" } // Due date - if t.DueDateUnix == 0 { - ot.DueDateUnix = 0 + if t.DueDate == 0 { + ot.DueDate = 0 } // Repeat after if t.RepeatAfter == 0 { ot.RepeatAfter = 0 } // Start date - if t.StartDateUnix == 0 { - ot.StartDateUnix = 0 + if t.StartDate == 0 { + ot.StartDate = 0 } // End date - if t.EndDateUnix == 0 { - ot.EndDateUnix = 0 + if t.EndDate == 0 { + ot.EndDate = 0 } // Color if t.HexColor == "" { @@ -653,10 +650,10 @@ func (t *Task) Update() (err error) { // with updated values into the db) func updateDone(oldTask *Task, newTask *Task) { if !oldTask.Done && newTask.Done && oldTask.RepeatAfter > 0 { - oldTask.DueDateUnix = oldTask.DueDateUnix + oldTask.RepeatAfter // assuming we'll save the old task (merged) + oldTask.DueDate = timeutil.FromTime(oldTask.DueDate.ToTime().Add(time.Duration(oldTask.RepeatAfter) * time.Second)) // assuming we'll save the old task (merged) - for in, r := range oldTask.RemindersUnix { - oldTask.RemindersUnix[in] = r + oldTask.RepeatAfter + for in, r := range oldTask.Reminders { + oldTask.Reminders[in] = timeutil.FromTime(r.ToTime().Add(time.Duration(oldTask.RepeatAfter) * time.Second)) } newTask.Done = false @@ -664,17 +661,17 @@ func updateDone(oldTask *Task, newTask *Task) { // Update the "done at" timestamp if !oldTask.Done && newTask.Done { - oldTask.DoneAtUnix = time.Now().Unix() + oldTask.DoneAt = timeutil.FromTime(time.Now()) } // When unmarking a task as done, reset the timestamp if oldTask.Done && !newTask.Done { - oldTask.DoneAtUnix = 0 + oldTask.DoneAt = 0 } } // Creates or deletes all necessary remindes without unneded db operations. // The parameter is a slice with unix dates which holds the new reminders. -func (t *Task) updateReminders(reminders []int64) (err error) { +func (t *Task) updateReminders(reminders []timeutil.TimeStamp) (err error) { // Load the current reminders taskReminders, err := getRemindersForTasks([]int64{t.ID}) @@ -682,35 +679,35 @@ func (t *Task) updateReminders(reminders []int64) (err error) { return err } - t.RemindersUnix = make([]int64, 0, len(taskReminders)) + t.Reminders = make([]timeutil.TimeStamp, 0, len(taskReminders)) for _, reminder := range taskReminders { - t.RemindersUnix = append(t.RemindersUnix, reminder.ReminderUnix) + t.Reminders = append(t.Reminders, reminder.Reminder) } // If we're removing everything, delete all reminders right away - if len(reminders) == 0 && len(t.RemindersUnix) > 0 { + if len(reminders) == 0 && len(t.Reminders) > 0 { _, err = x.Where("task_id = ?", t.ID). Delete(TaskReminder{}) - t.RemindersUnix = nil + t.Reminders = nil return err } // If we didn't change anything (from 0 to zero) don't do anything. - if len(reminders) == 0 && len(t.RemindersUnix) == 0 { + if len(reminders) == 0 && len(t.Reminders) == 0 { return nil } // Make a hashmap of the new reminders for easier comparison - newReminders := make(map[int64]*TaskReminder, len(reminders)) + newReminders := make(map[timeutil.TimeStamp]*TaskReminder, len(reminders)) for _, newReminder := range reminders { - newReminders[newReminder] = &TaskReminder{ReminderUnix: newReminder} + newReminders[newReminder] = &TaskReminder{Reminder: newReminder} } // Get old reminders to delete var found bool - var remindersToDelete []int64 - oldReminders := make(map[int64]*TaskReminder, len(t.RemindersUnix)) - for _, oldReminder := range t.RemindersUnix { + var remindersToDelete []timeutil.TimeStamp + oldReminders := make(map[timeutil.TimeStamp]*TaskReminder, len(t.Reminders)) + for _, oldReminder := range t.Reminders { found = false // If a new reminder is already in the list with old reminders if newReminders[oldReminder] != nil { @@ -722,7 +719,7 @@ func (t *Task) updateReminders(reminders []int64) (err error) { remindersToDelete = append(remindersToDelete, oldReminder) } - oldReminders[oldReminder] = &TaskReminder{ReminderUnix: oldReminder} + oldReminders[oldReminder] = &TaskReminder{Reminder: oldReminder} } // Delete all reminders not passed @@ -744,15 +741,15 @@ func (t *Task) updateReminders(reminders []int64) (err error) { } // Add the new reminder - _, err = x.Insert(TaskReminder{TaskID: t.ID, ReminderUnix: r}) + _, err = x.Insert(TaskReminder{TaskID: t.ID, Reminder: r}) if err != nil { return err } } - t.RemindersUnix = reminders + t.Reminders = reminders if len(reminders) == 0 { - t.RemindersUnix = nil + t.Reminders = nil } err = updateListLastUpdated(&List{ID: t.ListID}) diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index f46256c0a..701128417 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "github.com/stretchr/testify/assert" "testing" @@ -127,14 +128,14 @@ func TestUpdateDone(t *testing.T) { oldTask := &Task{Done: false} newTask := &Task{Done: true} updateDone(oldTask, newTask) - assert.NotEqual(t, int64(0), oldTask.DoneAtUnix) + assert.NotEqual(t, timeutil.TimeStamp(0), oldTask.DoneAt) }) t.Run("unmarking a task as done", func(t *testing.T) { db.LoadAndAssertFixtures(t) oldTask := &Task{Done: true} newTask := &Task{Done: false} updateDone(oldTask, newTask) - assert.Equal(t, int64(0), oldTask.DoneAtUnix) + assert.Equal(t, timeutil.TimeStamp(0), oldTask.DoneAt) }) } diff --git a/pkg/models/teams.go b/pkg/models/teams.go index e69f4c695..5858c3f0f 100644 --- a/pkg/models/teams.go +++ b/pkg/models/teams.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/web" "github.com/go-xorm/builder" @@ -38,10 +39,10 @@ type Team struct { // An array of all members in this team. Members []*TeamUser `xorm:"-" json:"members"` - // A unix timestamp when this relation was created. You cannot change this value. - Created int64 `xorm:"created" json:"created"` - // A unix timestamp when this relation was last updated. You cannot change this value. - Updated int64 `xorm:"updated" json:"updated"` + // A timestamp when this relation was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created" json:"created"` + // A timestamp when this relation was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated" json:"updated"` web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` @@ -69,8 +70,8 @@ type TeamMember struct { // Whether or not the member is an admin of the team. See the docs for more about what a team admin can do Admin bool `xorm:"tinyint(1) INDEX null" json:"admin"` - // A unix timestamp when this relation was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` + // A timestamp when this relation was created. You cannot change this value. + Created timeutil.TimeStamp `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 6241c35d2..4ed8aa6aa 100644 --- a/pkg/models/teams_rights_test.go +++ b/pkg/models/teams_rights_test.go @@ -18,6 +18,7 @@ package models import ( "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "testing" @@ -32,8 +33,8 @@ func TestTeam_CanDoSomething(t *testing.T) { CreatedByID int64 CreatedBy *user.User Members []*TeamUser - Created int64 - Updated int64 + Created timeutil.TimeStamp + Updated timeutil.TimeStamp CRUDable web.CRUDable Rights web.Rights } diff --git a/pkg/modules/migration/migration_status.go b/pkg/modules/migration/migration_status.go index 92010bcd4..ae25016cf 100644 --- a/pkg/modules/migration/migration_status.go +++ b/pkg/modules/migration/migration_status.go @@ -17,15 +17,16 @@ package migration import ( + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" ) // Status represents this migration status type Status struct { - ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"` - UserID int64 `xorm:"int(11) not null" json:"-"` - MigratorName string `xorm:"varchar(255)" json:"migrator_name"` - CreatedUnix int64 `xorm:"created not null" json:"time_unix"` + ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"` + UserID int64 `xorm:"int(11) not null" json:"-"` + MigratorName string `xorm:"varchar(255)" json:"migrator_name"` + Created timeutil.TimeStamp `xorm:"created not null 'created_unix'" json:"time_unix"` } // TableName holds the table name for the migration status table diff --git a/pkg/modules/migration/wunderlist/wunderlist.go b/pkg/modules/migration/wunderlist/wunderlist.go index 1a239631a..b802fcfc2 100644 --- a/pkg/modules/migration/wunderlist/wunderlist.go +++ b/pkg/modules/migration/wunderlist/wunderlist.go @@ -23,6 +23,7 @@ import ( "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/migration" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/utils" "encoding/json" @@ -144,7 +145,7 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) ( l := &models.List{ Title: list.Title, - Created: list.CreatedAt.Unix(), + Created: timeutil.FromTime(list.CreatedAt), } // Find all tasks belonging to this list and put them in @@ -152,13 +153,13 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) ( if t.ListID == listID { newTask := &models.Task{ Text: t.Title, - Created: t.CreatedAt.Unix(), + Created: timeutil.FromTime(t.CreatedAt), Done: t.Completed, } // Set Done At if newTask.Done { - newTask.DoneAtUnix = t.CompletedAt.Unix() + newTask.DoneAt = timeutil.FromTime(t.CompletedAt) } // Parse the due date @@ -167,7 +168,7 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) ( if err != nil { return nil, err } - newTask.DueDateUnix = dueDate.Unix() + newTask.DueDate = timeutil.FromTime(dueDate) } // Find related notes @@ -198,13 +199,13 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) ( Mime: f.ContentType, Size: uint64(f.FileSize), Created: f.CreatedAt, - CreatedUnix: f.CreatedAt.Unix(), + CreatedUnix: timeutil.FromTime(f.CreatedAt), // We directly pass the file contents here to have a way to link the attachment to the file later. // Because we don't have an ID for our task at this point of the migration, we cannot just throw all // attachments in a slice and do the work of downloading and properly storing them later. FileContent: buf.Bytes(), }, - Created: f.CreatedAt.Unix(), + Created: timeutil.FromTime(f.CreatedAt), }) } } @@ -224,7 +225,7 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) ( // Reminders for _, r := range content.reminders { if r.TaskID == t.ID { - newTask.RemindersUnix = append(newTask.RemindersUnix, r.Date.Unix()) + newTask.Reminders = append(newTask.Reminders, timeutil.FromTime(r.Date)) } } @@ -247,8 +248,8 @@ func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierach namespace := &models.NamespaceWithLists{ Namespace: models.Namespace{ Name: folder.Title, - Created: folder.CreatedAt.Unix(), - Updated: folder.UpdatedAt.Unix(), + Created: timeutil.FromTime(folder.CreatedAt), + Updated: timeutil.FromTime(folder.UpdatedAt), }, } diff --git a/pkg/modules/migration/wunderlist/wunderlist_test.go b/pkg/modules/migration/wunderlist/wunderlist_test.go index 35f0a9558..405d6150b 100644 --- a/pkg/modules/migration/wunderlist/wunderlist_test.go +++ b/pkg/modules/migration/wunderlist/wunderlist_test.go @@ -20,6 +20,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/timeutil" "github.com/stretchr/testify/assert" "gopkg.in/d4l3k/messagediff.v1" "io/ioutil" @@ -192,18 +193,18 @@ func TestWunderlistParsing(t *testing.T) { { Namespace: models.Namespace{ Name: "Lorem Ipsum", - Created: time1.Unix(), - Updated: time2.Unix(), + Created: timeutil.FromTime(time1), + Updated: timeutil.FromTime(time2), }, Lists: []*models.List{ { - Created: time1.Unix(), + Created: timeutil.FromTime(time1), Title: "Lorem1", Tasks: []*models.Task{ { Text: "Ipsum1", - DueDateUnix: 1378339200, - Created: time1.Unix(), + DueDate: 1378339200, + Created: timeutil.FromTime(time1), Description: "Lorem Ipsum dolor sit amet", Attachments: []*models.TaskAttachment{ { @@ -212,18 +213,18 @@ func TestWunderlistParsing(t *testing.T) { Mime: "text/plain", Size: 12345, Created: time2, - CreatedUnix: time2.Unix(), + CreatedUnix: timeutil.FromTime(time2), FileContent: exampleFile, }, - Created: time2.Unix(), + Created: timeutil.FromTime(time2), }, }, - RemindersUnix: []int64{time4.Unix()}, + Reminders: []timeutil.TimeStamp{timeutil.FromTime(time4)}, }, { Text: "Ipsum2", - DueDateUnix: 1378339200, - Created: time1.Unix(), + DueDate: 1378339200, + Created: timeutil.FromTime(time1), Description: "Lorem Ipsum dolor sit amet", RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindSubtask: { @@ -239,15 +240,15 @@ func TestWunderlistParsing(t *testing.T) { }, }, { - Created: time1.Unix(), + Created: timeutil.FromTime(time1), Title: "Lorem2", Tasks: []*models.Task{ { Text: "Ipsum3", Done: true, - DoneAtUnix: time1.Unix(), - DueDateUnix: 1378339200, - Created: time1.Unix(), + DoneAt: timeutil.FromTime(time1), + DueDate: 1378339200, + Created: timeutil.FromTime(time1), Description: "Lorem Ipsum dolor sit amet", Attachments: []*models.TaskAttachment{ { @@ -256,18 +257,18 @@ func TestWunderlistParsing(t *testing.T) { Mime: "text/plain", Size: 12345, Created: time3, - CreatedUnix: time3.Unix(), + CreatedUnix: timeutil.FromTime(time3), FileContent: exampleFile, }, - Created: time3.Unix(), + Created: timeutil.FromTime(time3), }, }, }, { - Text: "Ipsum4", - DueDateUnix: 1378339200, - Created: time1.Unix(), - RemindersUnix: []int64{time3.Unix()}, + Text: "Ipsum4", + DueDate: 1378339200, + Created: timeutil.FromTime(time1), + Reminders: []timeutil.TimeStamp{timeutil.FromTime(time3)}, RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindSubtask: { { @@ -279,52 +280,52 @@ func TestWunderlistParsing(t *testing.T) { }, }, { - Created: time1.Unix(), + Created: timeutil.FromTime(time1), Title: "Lorem3", Tasks: []*models.Task{ { - Text: "Ipsum5", - DueDateUnix: 1378339200, - Created: time1.Unix(), + Text: "Ipsum5", + DueDate: 1378339200, + Created: timeutil.FromTime(time1), }, { - Text: "Ipsum6", - DueDateUnix: 1378339200, - Created: time1.Unix(), - Done: true, - DoneAtUnix: time1.Unix(), + Text: "Ipsum6", + DueDate: 1378339200, + Created: timeutil.FromTime(time1), + Done: true, + DoneAt: timeutil.FromTime(time1), }, { - Text: "Ipsum7", - DueDateUnix: 1378339200, - Created: time1.Unix(), - Done: true, - DoneAtUnix: time1.Unix(), + Text: "Ipsum7", + DueDate: 1378339200, + Created: timeutil.FromTime(time1), + Done: true, + DoneAt: timeutil.FromTime(time1), }, { - Text: "Ipsum8", - DueDateUnix: 1378339200, - Created: time1.Unix(), + Text: "Ipsum8", + DueDate: 1378339200, + Created: timeutil.FromTime(time1), }, }, }, { - Created: time1.Unix(), + Created: timeutil.FromTime(time1), Title: "Lorem4", Tasks: []*models.Task{ { - Text: "Ipsum9", - DueDateUnix: 1378339200, - Created: time1.Unix(), - Done: true, - DoneAtUnix: time1.Unix(), + Text: "Ipsum9", + DueDate: 1378339200, + Created: timeutil.FromTime(time1), + Done: true, + DoneAt: timeutil.FromTime(time1), }, { - Text: "Ipsum10", - DueDateUnix: 1378339200, - Created: time1.Unix(), - Done: true, - DoneAtUnix: time1.Unix(), + Text: "Ipsum10", + DueDate: 1378339200, + Created: timeutil.FromTime(time1), + Done: true, + DoneAt: timeutil.FromTime(time1), }, }, }, @@ -336,7 +337,7 @@ func TestWunderlistParsing(t *testing.T) { }, Lists: []*models.List{ { - Created: time4.Unix(), + Created: timeutil.FromTime(time4), Title: "List without a namespace", }, }, diff --git a/pkg/routes/caldav/listStorageProvider.go b/pkg/routes/caldav/listStorageProvider.go index 054193993..394009daa 100644 --- a/pkg/routes/caldav/listStorageProvider.go +++ b/pkg/routes/caldav/listStorageProvider.go @@ -342,12 +342,12 @@ func (vlra *VikunjaListResourceAdapter) CalculateEtag() string { // Return the etag of a task if we have one if vlra.task != nil { - return `"` + strconv.FormatInt(vlra.task.ID, 10) + `-` + strconv.FormatInt(vlra.task.Updated, 10) + `"` + return `"` + strconv.FormatInt(vlra.task.ID, 10) + `-` + strconv.FormatInt(vlra.task.Updated.ToTime().Unix(), 10) + `"` } // This also returns the etag of the list, and not of the task, // which becomes problematic because the client uses this etag (= the one from the list) to make // Requests to update a task. These do not match and thus updating a task fails. - return `"` + strconv.FormatInt(vlra.list.ID, 10) + `-` + strconv.FormatInt(vlra.list.Updated, 10) + `"` + return `"` + strconv.FormatInt(vlra.list.ID, 10) + `-` + strconv.FormatInt(vlra.list.Updated.ToTime().Unix(), 10) + `"` } // GetContent returns the content string of a resource (a task in our case) @@ -372,11 +372,11 @@ func (vlra *VikunjaListResourceAdapter) GetContentSize() int64 { // GetModTime returns when the resource was last modified func (vlra *VikunjaListResourceAdapter) GetModTime() time.Time { if vlra.task != nil { - return time.Unix(vlra.task.Updated, 0) + return time.Unix(vlra.task.Updated.ToTime().Unix(), 0) } if vlra.list != nil { - return time.Unix(vlra.list.Updated, 0) + return time.Unix(vlra.list.Updated.ToTime().Unix(), 0) } return time.Time{} diff --git a/pkg/routes/caldav/parsing.go b/pkg/routes/caldav/parsing.go index 1cab4363b..886d6c4fa 100644 --- a/pkg/routes/caldav/parsing.go +++ b/pkg/routes/caldav/parsing.go @@ -20,6 +20,7 @@ import ( "code.vikunja.io/api/pkg/caldav" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/timeutil" "github.com/laurent22/ical-go" "strconv" "time" @@ -31,23 +32,22 @@ func getCaldavTodosForTasks(list *models.List) string { var caldavtodos []*caldav.Todo for _, t := range list.Tasks { - durationString := t.EndDateUnix - t.StartDateUnix - duration, _ := time.ParseDuration(strconv.FormatInt(durationString, 10) + `s`) + duration := t.EndDate.ToTime().Sub(t.StartDate.ToTime()) caldavtodos = append(caldavtodos, &caldav.Todo{ - TimestampUnix: t.Updated, - UID: t.UID, - Summary: t.Text, - Description: t.Description, - CompletedUnix: t.DoneAtUnix, + Timestamp: t.Updated, + UID: t.UID, + Summary: t.Text, + Description: t.Description, + Completed: t.DoneAt, // Organizer: &t.CreatedBy, // Disabled until we figure out how this works - Priority: t.Priority, - StartUnix: t.StartDateUnix, - EndUnix: t.EndDateUnix, - CreatedUnix: t.Created, - UpdatedUnix: t.Updated, - DueDateUnix: t.DueDateUnix, - Duration: duration, + Priority: t.Priority, + Start: t.StartDate, + End: t.EndDate, + Created: t.Created, + Updated: t.Updated, + DueDate: t.DueDate, + Duration: duration, }) } @@ -90,36 +90,36 @@ func parseTaskFromVTODO(content string) (vTask *models.Task, err error) { duration, _ := time.ParseDuration(task["DURATION"]) vTask = &models.Task{ - UID: task["UID"], - Text: task["SUMMARY"], - Description: task["DESCRIPTION"], - Priority: priority, - DueDateUnix: caldavTimeToUnixTimestamp(task["DUE"]), - Updated: caldavTimeToUnixTimestamp(task["DTSTAMP"]), - StartDateUnix: caldavTimeToUnixTimestamp(task["DTSTART"]), - DoneAtUnix: caldavTimeToUnixTimestamp(task["COMPLETED"]), + UID: task["UID"], + Text: task["SUMMARY"], + Description: task["DESCRIPTION"], + Priority: priority, + DueDate: caldavTimeToTimestamp(task["DUE"]), + Updated: caldavTimeToTimestamp(task["DTSTAMP"]), + StartDate: caldavTimeToTimestamp(task["DTSTART"]), + DoneAt: caldavTimeToTimestamp(task["COMPLETED"]), } if task["STATUS"] == "COMPLETED" { vTask.Done = true } - if duration > 0 && vTask.StartDateUnix > 0 { - vTask.EndDateUnix = vTask.StartDateUnix + int64(duration.Seconds()) + if duration > 0 && vTask.StartDate > 0 { + vTask.EndDate = timeutil.FromTime(vTask.StartDate.ToTime().Add(duration)) } return } -func caldavTimeToUnixTimestamp(tstring string) int64 { +func caldavTimeToTimestamp(tstring string) timeutil.TimeStamp { if tstring == "" { return 0 } t, err := time.Parse(caldav.DateFormat, tstring) if err != nil { - log.Warningf("Error while parsing caldav time %s to unix time: %s", tstring, err) + log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err) return 0 } - return t.Unix() + return timeutil.FromTime(t) } diff --git a/pkg/timeutil/time.go b/pkg/timeutil/time.go new file mode 100644 index 000000000..363640ea6 --- /dev/null +++ b/pkg/timeutil/time.go @@ -0,0 +1,79 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2018-2020 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 General Public License 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package timeutil + +import ( + "code.vikunja.io/api/pkg/config" + "encoding/json" + "time" +) + +// TimeStamp is a type which holds a unix timestamp, but becomes a RFC3339 date when parsed to json. +// This allows us to save the time as a unix timestamp into the database while returning it as an iso +// date to the api client. +type TimeStamp int64 + +// ToTime returns a time.Time from a TimeStamp +func (ts *TimeStamp) ToTime() time.Time { + return time.Unix(int64(*ts), 0) +} + +// FromTime converts a time.Time to a TimeStamp +func FromTime(t time.Time) TimeStamp { + return TimeStamp(t.Unix()) +} + +// MarshalJSON converts a TimeStamp to a json string +func (ts *TimeStamp) MarshalJSON() ([]byte, error) { + if int64(*ts) == 0 { + return []byte("null"), nil + } + + loc, err := time.LoadLocation(config.ServiceTimeZone.GetString()) + if err != nil { + return nil, err + } + + s := `"` + ts.ToTime().In(loc).Format(time.RFC3339) + `"` + return []byte(s), nil +} + +// UnmarshalJSON converts an iso date string from json to a TimeStamp +func (ts *TimeStamp) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + if s == "" { + *ts = FromTime(time.Unix(0, 0)) + return nil + } + + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return err + } + + loc, err := time.LoadLocation(config.ServiceTimeZone.GetString()) + if err != nil { + return err + } + + *ts = TimeStamp(t.In(loc).Unix()) + return nil +} diff --git a/pkg/user/user.go b/pkg/user/user.go index 42cfc33d3..9c9be5442 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -21,6 +21,7 @@ import ( "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/mail" "code.vikunja.io/api/pkg/metrics" + "code.vikunja.io/api/pkg/timeutil" "code.vikunja.io/api/pkg/utils" "code.vikunja.io/web" "fmt" @@ -54,10 +55,10 @@ type User struct { PasswordResetToken string `xorm:"varchar(450) null" json:"-"` EmailConfirmToken string `xorm:"varchar(450) null" json:"-"` - // A unix timestamp when this task was created. You cannot change this value. - Created int64 `xorm:"created not null" json:"created"` - // A unix timestamp when this task was last updated. You cannot change this value. - Updated int64 `xorm:"updated not null" json:"updated"` + // A timestamp when this task was created. You cannot change this value. + Created timeutil.TimeStamp `xorm:"created not null" json:"created"` + // A timestamp when this task was last updated. You cannot change this value. + Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"` web.Auth `xorm:"-" json:"-"` }