Migrate all timestamps to real iso dates (#594)
Fix query param name Add option to include null results when filtering Always set db time to gmt Fix null filter Fix timezone setting for todoist parsing Fix timezone setting for wunderlist parsing Fix import Fix caldav reminder parsing Use timezone from config Add error and test for invalid filter values Fix integration tests Remove task collection date hack Fix task filter Fix lint Fix tests and fixtures for date timezone stuff Properly set timezone Change fixtures time zone to gmt Set db timezone Set created and updated timestamps for all fixtures Fix lint Fix test fixtures Fix misspell Fix test fixtures Partially fix tests Remove timeutil package Remove adding _unix suffix hack Remove _unix suffix Move all timeutil.TimeStamp to time.Time Remove all Unix suffixes in field names Add better error messages when running migrations Make sure to not migrate 0 unix timestamps to 1970 iso dates Add migration script for sqlite Add converting sqlite values Convert 0 unix timestamps to null in postgres Convert 0 to null in timestamps Automatically rename _unix suffix Add all tables and columns for migration Fix sql migration query for mysql Fail with an error if trying to use an unsupported dbms Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/594
This commit is contained in:
@ -113,12 +113,12 @@ func (bt *BulkTask) Update() (err error) {
|
||||
Cols("title",
|
||||
"description",
|
||||
"done",
|
||||
"due_date_unix",
|
||||
"reminders_unix",
|
||||
"due_date",
|
||||
"reminders",
|
||||
"repeat_after",
|
||||
"priority",
|
||||
"start_date_unix",
|
||||
"end_date_unix").
|
||||
"start_date",
|
||||
"end_date").
|
||||
Update(oldtask)
|
||||
if err != nil {
|
||||
return sess.Rollback()
|
||||
|
@ -734,6 +734,34 @@ func (err ErrInvalidTaskFilterConcatinator) HTTPError() web.HTTPError {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrInvalidTaskFilterValue represents an error where the provided task filter value is invalid
|
||||
type ErrInvalidTaskFilterValue struct {
|
||||
Value string
|
||||
Field string
|
||||
}
|
||||
|
||||
// IsErrInvalidTaskFilterValue checks if an error is ErrInvalidTaskFilterValue.
|
||||
func IsErrInvalidTaskFilterValue(err error) bool {
|
||||
_, ok := err.(ErrInvalidTaskFilterValue)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidTaskFilterValue) Error() string {
|
||||
return fmt.Sprintf("Task filter value is invalid [Value: %s, Field: %s]", err.Value, err.Field)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidTaskFilterValue holds the unique world-error code of this error
|
||||
const ErrCodeInvalidTaskFilterValue = 4019
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidTaskFilterValue) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeInvalidTaskFilterValue,
|
||||
Message: fmt.Sprintf("The task filter value '%s' for field '%s' is invalid.", err.Value, err.Field),
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// Namespace errors
|
||||
// =================
|
||||
|
@ -18,9 +18,9 @@ package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Bucket represents a kanban bucket
|
||||
@ -35,9 +35,9 @@ type Bucket struct {
|
||||
Tasks []*Task `xorm:"-" json:"tasks"`
|
||||
|
||||
// A timestamp when this bucket was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
// A timestamp when this bucket was last updated. You cannot change this value.
|
||||
Updated timeutil.TimeStamp `xorm:"updated not null" json:"updated"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
// The user who initially created the bucket.
|
||||
CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"`
|
||||
|
@ -17,9 +17,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Label represents a label
|
||||
@ -38,9 +38,9 @@ type Label struct {
|
||||
CreatedBy *user.User `xorm:"-" json:"created_by"`
|
||||
|
||||
// A timestamp when this label was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -17,9 +17,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
@ -31,7 +31,7 @@ type LabelTask struct {
|
||||
// The label id you want to associate with a task.
|
||||
LabelID int64 `xorm:"int(11) INDEX not null" json:"label_id" param:"label"`
|
||||
// A timestamp when this task was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -2,12 +2,12 @@ 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"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
@ -17,7 +17,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
||||
ID int64
|
||||
TaskID int64
|
||||
LabelID int64
|
||||
Created timeutil.TimeStamp
|
||||
Created time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -48,11 +48,15 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
||||
Label: Label{
|
||||
ID: 4,
|
||||
Title: "Label #4 - visible via other task",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
CreatedByID: 2,
|
||||
CreatedBy: &user.User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -113,7 +117,7 @@ func TestLabelTask_Create(t *testing.T) {
|
||||
ID int64
|
||||
TaskID int64
|
||||
LabelID int64
|
||||
Created timeutil.TimeStamp
|
||||
Created time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -207,7 +211,7 @@ func TestLabelTask_Delete(t *testing.T) {
|
||||
ID int64
|
||||
TaskID int64
|
||||
LabelID int64
|
||||
Created timeutil.TimeStamp
|
||||
Created time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -18,12 +18,12 @@ 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"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
@ -36,8 +36,8 @@ func TestLabel_ReadAll(t *testing.T) {
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *user.User
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -51,6 +51,8 @@ func TestLabel_ReadAll(t *testing.T) {
|
||||
Username: "user1",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -71,6 +73,8 @@ func TestLabel_ReadAll(t *testing.T) {
|
||||
Title: "Label #1",
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -79,17 +83,23 @@ func TestLabel_ReadAll(t *testing.T) {
|
||||
Title: "Label #2",
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: Label{
|
||||
ID: 4,
|
||||
Title: "Label #4 - visible via other task",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
CreatedByID: 2,
|
||||
CreatedBy: &user.User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -138,8 +148,8 @@ func TestLabel_ReadOne(t *testing.T) {
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *user.User
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -148,6 +158,8 @@ func TestLabel_ReadOne(t *testing.T) {
|
||||
Username: "user1",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -168,6 +180,8 @@ func TestLabel_ReadOne(t *testing.T) {
|
||||
Title: "Label #1",
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
auth: &user.User{ID: 1},
|
||||
},
|
||||
@ -202,7 +216,11 @@ func TestLabel_ReadOne(t *testing.T) {
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
auth: &user.User{ID: 1},
|
||||
},
|
||||
@ -248,8 +266,8 @@ func TestLabel_Create(t *testing.T) {
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *user.User
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -308,8 +326,8 @@ func TestLabel_Update(t *testing.T) {
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *user.User
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -390,8 +408,8 @@ func TestLabel_Delete(t *testing.T) {
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *user.User
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -18,11 +18,11 @@
|
||||
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"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SharingType holds the sharing type
|
||||
@ -54,9 +54,9 @@ type LinkSharing struct {
|
||||
SharedByID int64 `xorm:"int(11) INDEX not null" json:"-"`
|
||||
|
||||
// A timestamp when this list was shared. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -19,10 +19,10 @@ 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/web"
|
||||
"strings"
|
||||
"time"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@ -58,9 +58,9 @@ type List struct {
|
||||
BackgroundInformation interface{} `xorm:"-" json:"background_information"`
|
||||
|
||||
// A timestamp when this list was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -17,8 +17,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TeamList defines the relation between a team and a list
|
||||
@ -33,9 +33,9 @@ type TeamList struct {
|
||||
Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
|
||||
// A timestamp when this relation was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -18,13 +18,13 @@ 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"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTeamList(t *testing.T) {
|
||||
@ -121,8 +121,8 @@ func TestTeamList_Update(t *testing.T) {
|
||||
TeamID int64
|
||||
ListID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -17,9 +17,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ListUser represents a list <-> user relation
|
||||
@ -36,9 +36,9 @@ type ListUser struct {
|
||||
Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
|
||||
// A timestamp when this relation was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -18,9 +18,9 @@ package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
@ -31,8 +31,8 @@ func TestListUser_CanDoSomething(t *testing.T) {
|
||||
UserID int64
|
||||
ListID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -18,12 +18,12 @@ 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"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
@ -35,8 +35,8 @@ func TestListUser_Create(t *testing.T) {
|
||||
Username string
|
||||
ListID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -136,8 +136,8 @@ func TestListUser_ReadAll(t *testing.T) {
|
||||
UserID int64
|
||||
ListID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -169,6 +169,8 @@ func TestListUser_ReadAll(t *testing.T) {
|
||||
Username: "user1",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
Right: RightRead,
|
||||
},
|
||||
@ -177,6 +179,8 @@ func TestListUser_ReadAll(t *testing.T) {
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
Right: RightRead,
|
||||
},
|
||||
@ -228,8 +232,8 @@ func TestListUser_Update(t *testing.T) {
|
||||
Username string
|
||||
ListID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -305,8 +309,8 @@ func TestListUser_Delete(t *testing.T) {
|
||||
Username string
|
||||
ListID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -20,12 +20,37 @@ import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func setupTime() {
|
||||
var err error
|
||||
loc, err := time.LoadLocation("GMT")
|
||||
if err != nil {
|
||||
fmt.Printf("Error setting up time: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
testCreatedTime, err = time.ParseInLocation(time.RFC3339Nano, "2018-12-01T15:13:12.0+00:00", loc)
|
||||
if err != nil {
|
||||
fmt.Printf("Error setting up time: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
testCreatedTime = testCreatedTime.In(loc)
|
||||
testUpdatedTime, err = time.ParseInLocation(time.RFC3339Nano, "2018-12-02T15:13:12.0+00:00", loc)
|
||||
if err != nil {
|
||||
fmt.Printf("Error setting up time: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
testUpdatedTime = testUpdatedTime.In(loc)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
setupTime()
|
||||
|
||||
// Set default config
|
||||
config.InitDefaultConfig()
|
||||
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
_ "github.com/go-sql-driver/mysql" // Because.
|
||||
_ "github.com/lib/pq" // Because.
|
||||
"time"
|
||||
"xorm.io/xorm"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3" // Because.
|
||||
@ -29,6 +30,9 @@ import (
|
||||
|
||||
var (
|
||||
x *xorm.Engine
|
||||
|
||||
testCreatedTime time.Time
|
||||
testUpdatedTime time.Time
|
||||
)
|
||||
|
||||
// GetTables returns all structs which are also a table.
|
||||
|
@ -18,7 +18,6 @@ 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"
|
||||
@ -46,9 +45,9 @@ type Namespace struct {
|
||||
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
|
||||
|
||||
// A timestamp when this namespace was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
@ -59,8 +58,8 @@ var PseudoNamespace = Namespace{
|
||||
ID: -1,
|
||||
Title: "Shared Lists",
|
||||
Description: "Lists of other users shared with you via teams or directly.",
|
||||
Created: timeutil.FromTime(time.Now()),
|
||||
Updated: timeutil.FromTime(time.Now()),
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
// TableName makes beautiful table names
|
||||
|
@ -17,8 +17,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TeamNamespace defines the relationship between a Team and a Namespace
|
||||
@ -33,9 +33,9 @@ type TeamNamespace struct {
|
||||
Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
|
||||
// A timestamp when this relation was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -18,9 +18,9 @@ package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
@ -31,8 +31,8 @@ func TestTeamNamespace_CanDoSomething(t *testing.T) {
|
||||
TeamID int64
|
||||
NamespaceID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ 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"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTeamNamespace(t *testing.T) {
|
||||
@ -113,8 +113,8 @@ func TestTeamNamespace_Update(t *testing.T) {
|
||||
TeamID int64
|
||||
NamespaceID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -17,9 +17,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NamespaceUser represents a namespace <-> user relation
|
||||
@ -35,9 +35,9 @@ type NamespaceUser struct {
|
||||
Right Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
|
||||
// A timestamp when this relation was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -18,9 +18,9 @@ package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
@ -31,8 +31,8 @@ func TestNamespaceUser_CanDoSomething(t *testing.T) {
|
||||
UserID int64
|
||||
NamespaceID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ 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"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNamespaceUser_Create(t *testing.T) {
|
||||
@ -33,8 +33,8 @@ func TestNamespaceUser_Create(t *testing.T) {
|
||||
Username string
|
||||
NamespaceID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -133,8 +133,8 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
|
||||
UserID int64
|
||||
NamespaceID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -166,6 +166,8 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
|
||||
Username: "user1",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
Right: RightRead,
|
||||
},
|
||||
@ -174,6 +176,8 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
Right: RightRead,
|
||||
},
|
||||
@ -226,8 +230,8 @@ func TestNamespaceUser_Update(t *testing.T) {
|
||||
Username string
|
||||
NamespaceID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
@ -303,8 +307,8 @@ func TestNamespaceUser_Delete(t *testing.T) {
|
||||
Username string
|
||||
NamespaceID int64
|
||||
Right Right
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -17,17 +17,17 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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 timeutil.TimeStamp `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 time.Time `xorm:"created not null"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -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 timeutil.TimeStamp `xorm:"created" json:"created"`
|
||||
Created time.Time `xorm:"created" json:"created"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
@ -149,7 +149,6 @@ func (ta *TaskAttachment) ReadAll(a web.Auth, search string, page int, perPage i
|
||||
continue
|
||||
}
|
||||
r.File = fs[r.FileID]
|
||||
r.File.Created = r.File.CreatedUnix.ToTime()
|
||||
r.CreatedBy = us[r.CreatedByID]
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,8 @@ type TaskCollection struct {
|
||||
FilterComparatorArr []string `query:"filter_comparator[]"`
|
||||
// The way all filter conditions are concatenated together, can be either "and" or "or".,
|
||||
FilterConcat string `query:"filter_concat"`
|
||||
// If set to true, the result will also include null values
|
||||
FilterIncludeNulls bool `query:"filter_include_nulls"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
@ -57,14 +59,14 @@ func validateTaskField(fieldName string) error {
|
||||
taskPropertyTitle,
|
||||
taskPropertyDescription,
|
||||
taskPropertyDone,
|
||||
taskPropertyDoneAtUnix,
|
||||
taskPropertyDueDateUnix,
|
||||
taskPropertyDoneAt,
|
||||
taskPropertyDueDate,
|
||||
taskPropertyCreatedByID,
|
||||
taskPropertyListID,
|
||||
taskPropertyRepeatAfter,
|
||||
taskPropertyPriority,
|
||||
taskPropertyStartDateUnix,
|
||||
taskPropertyEndDateUnix,
|
||||
taskPropertyStartDate,
|
||||
taskPropertyEndDate,
|
||||
taskPropertyHexColor,
|
||||
taskPropertyPercentDone,
|
||||
taskPropertyUID,
|
||||
@ -87,12 +89,13 @@ func validateTaskField(fieldName string) error {
|
||||
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @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_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`, `title`, `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 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`, `title`, `description`, `done`, `done_at`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `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 filter_by query string false "The name of the field to filter by. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
|
||||
// @Param filter_value query string false "The value to filter for."
|
||||
// @Param filter_comparator query string false "The comparator to use for a filter. Available values are `equals`, `greater`, `greater_equals`, `less` and `less_equals`. Defaults to `equals`"
|
||||
// @Param filter_concat query string false "The concatinator to use for filters. Available values are `and` or `or`. Defaults to `or`."
|
||||
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.Task "The tasks"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
@ -119,19 +122,6 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
|
||||
param.orderBy = getSortOrderFromString(tf.OrderBy[i])
|
||||
}
|
||||
|
||||
// Special case for pseudo date fields
|
||||
// FIXME: This is really dirty, to fix this properly the db fields should be renamed
|
||||
switch param.sortBy {
|
||||
case "done_at":
|
||||
param.sortBy = taskPropertyDoneAtUnix
|
||||
case "due_date":
|
||||
param.sortBy = taskPropertyDueDateUnix
|
||||
case "start_date":
|
||||
param.sortBy = taskPropertyStartDateUnix
|
||||
case "end_date":
|
||||
param.sortBy = taskPropertyEndDateUnix
|
||||
}
|
||||
|
||||
// Param validation
|
||||
if err := param.validate(); err != nil {
|
||||
return nil, 0, 0, err
|
||||
@ -140,11 +130,12 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i
|
||||
}
|
||||
|
||||
taskopts := &taskOptions{
|
||||
search: search,
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
sortby: sort,
|
||||
filterConcat: taskFilterConcatinator(tf.FilterConcat),
|
||||
search: search,
|
||||
page: page,
|
||||
perPage: perPage,
|
||||
sortby: sort,
|
||||
filterConcat: taskFilterConcatinator(tf.FilterConcat),
|
||||
filterIncludeNulls: tf.FilterIncludeNulls,
|
||||
}
|
||||
|
||||
taskopts.filters, err = getTaskFiltersByCollections(tf)
|
||||
|
@ -18,9 +18,13 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"fmt"
|
||||
"github.com/iancoleman/strcase"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
type taskFilterComparator string
|
||||
@ -85,19 +89,13 @@ func getTaskFiltersByCollections(c *TaskCollection) (filters []*taskFilter, err
|
||||
if len(c.FilterValue) > i {
|
||||
filter.value, err = getNativeValueForTaskField(filter.field, c.FilterValue[i])
|
||||
if err != nil {
|
||||
return
|
||||
return nil, ErrInvalidTaskFilterValue{
|
||||
Value: filter.field,
|
||||
Field: c.FilterValue[i],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for pseudo date fields
|
||||
// FIXME: This is really dirty, to fix this properly the db fields should be renamed
|
||||
if filter.field+"_unix" == taskPropertyDoneAtUnix ||
|
||||
filter.field+"_unix" == taskPropertyDueDateUnix ||
|
||||
filter.field+"_unix" == taskPropertyStartDateUnix ||
|
||||
filter.field+"_unix" == taskPropertyEndDateUnix {
|
||||
filter.field += "_unix"
|
||||
}
|
||||
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
|
||||
@ -153,7 +151,13 @@ func getNativeValueForTaskField(fieldName, value string) (nativeValue interface{
|
||||
nativeValue = value
|
||||
case reflect.Bool:
|
||||
nativeValue, err = strconv.ParseBool(value)
|
||||
case reflect.Struct:
|
||||
if field.Type == schemas.TimeType {
|
||||
nativeValue, err = time.Parse(time.RFC3339, value)
|
||||
nativeValue = nativeValue.(time.Time).In(config.GetTimeZone())
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("unrecognized filter type %s for field %s, value %s", field.Type.String(), fieldName, value))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -26,24 +26,24 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
taskPropertyID string = "id"
|
||||
taskPropertyTitle string = "title"
|
||||
taskPropertyDescription string = "description"
|
||||
taskPropertyDone string = "done"
|
||||
taskPropertyDoneAtUnix string = "done_at_unix"
|
||||
taskPropertyDueDateUnix string = "due_date_unix"
|
||||
taskPropertyCreatedByID string = "created_by_id"
|
||||
taskPropertyListID string = "list_id"
|
||||
taskPropertyRepeatAfter string = "repeat_after"
|
||||
taskPropertyPriority string = "priority"
|
||||
taskPropertyStartDateUnix string = "start_date_unix"
|
||||
taskPropertyEndDateUnix string = "end_date_unix"
|
||||
taskPropertyHexColor string = "hex_color"
|
||||
taskPropertyPercentDone string = "percent_done"
|
||||
taskPropertyUID string = "uid"
|
||||
taskPropertyCreated string = "created"
|
||||
taskPropertyUpdated string = "updated"
|
||||
taskPropertyPosition string = "position"
|
||||
taskPropertyID string = "id"
|
||||
taskPropertyTitle string = "title"
|
||||
taskPropertyDescription string = "description"
|
||||
taskPropertyDone string = "done"
|
||||
taskPropertyDoneAt string = "done_at"
|
||||
taskPropertyDueDate string = "due_date"
|
||||
taskPropertyCreatedByID string = "created_by_id"
|
||||
taskPropertyListID string = "list_id"
|
||||
taskPropertyRepeatAfter string = "repeat_after"
|
||||
taskPropertyPriority string = "priority"
|
||||
taskPropertyStartDate string = "start_date"
|
||||
taskPropertyEndDate string = "end_date"
|
||||
taskPropertyHexColor string = "hex_color"
|
||||
taskPropertyPercentDone string = "percent_done"
|
||||
taskPropertyUID string = "uid"
|
||||
taskPropertyCreated string = "created"
|
||||
taskPropertyUpdated string = "updated"
|
||||
taskPropertyPosition string = "position"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -46,14 +46,14 @@ func TestSortParamValidation(t *testing.T) {
|
||||
taskPropertyTitle,
|
||||
taskPropertyDescription,
|
||||
taskPropertyDone,
|
||||
taskPropertyDoneAtUnix,
|
||||
taskPropertyDueDateUnix,
|
||||
taskPropertyDoneAt,
|
||||
taskPropertyDueDate,
|
||||
taskPropertyCreatedByID,
|
||||
taskPropertyListID,
|
||||
taskPropertyRepeatAfter,
|
||||
taskPropertyPriority,
|
||||
taskPropertyStartDateUnix,
|
||||
taskPropertyEndDateUnix,
|
||||
taskPropertyStartDate,
|
||||
taskPropertyEndDate,
|
||||
taskPropertyHexColor,
|
||||
taskPropertyPercentDone,
|
||||
taskPropertyUID,
|
||||
|
@ -17,13 +17,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"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"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
@ -33,19 +34,27 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
Username: "user1",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
user2 := &user.User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
user6 := &user.User{
|
||||
ID: 6,
|
||||
Username: "user6",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
||||
loc := config.GetTimeZone()
|
||||
|
||||
// We use individual variables for the tasks here to be able to rearrange or remove ones more easily
|
||||
task1 := &Task{
|
||||
ID: 1,
|
||||
@ -63,8 +72,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
Title: "Label #4 - visible via other task",
|
||||
CreatedByID: 2,
|
||||
CreatedBy: user2,
|
||||
Updated: 0,
|
||||
Created: 0,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
},
|
||||
RelatedTasks: map[RelationKind][]*Task{
|
||||
@ -76,8 +85,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
CreatedByID: 1,
|
||||
ListID: 1,
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -88,11 +97,12 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
FileID: 1,
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Created: testCreatedTime,
|
||||
File: &files.File{
|
||||
ID: 1,
|
||||
Name: "test",
|
||||
Size: 100,
|
||||
CreatedUnix: 1570998791,
|
||||
Created: time.Unix(1570998791, 0).In(loc),
|
||||
CreatedByID: 1,
|
||||
},
|
||||
},
|
||||
@ -102,10 +112,11 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
FileID: 9999,
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Created: testCreatedTime,
|
||||
},
|
||||
},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task2 := &Task{
|
||||
ID: 2,
|
||||
@ -123,13 +134,13 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
Title: "Label #4 - visible via other task",
|
||||
CreatedByID: 2,
|
||||
CreatedBy: user2,
|
||||
Updated: 0,
|
||||
Created: 0,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
},
|
||||
},
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task3 := &Task{
|
||||
ID: 3,
|
||||
@ -140,8 +151,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
CreatedBy: user1,
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
Priority: 100,
|
||||
BucketID: 2,
|
||||
}
|
||||
@ -154,8 +165,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
CreatedBy: user1,
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
Priority: 1,
|
||||
BucketID: 2,
|
||||
}
|
||||
@ -168,9 +179,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
CreatedBy: user1,
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
DueDate: 1543636724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
DueDate: time.Unix(1543636724, 0).In(loc),
|
||||
BucketID: 2,
|
||||
}
|
||||
task6 := &Task{
|
||||
@ -182,9 +193,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
CreatedBy: user1,
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
DueDate: 1543616724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
DueDate: time.Unix(1543616724, 0).In(loc),
|
||||
BucketID: 3,
|
||||
}
|
||||
task7 := &Task{
|
||||
@ -196,9 +207,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
CreatedBy: user1,
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
StartDate: 1544600000,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
StartDate: time.Unix(1544600000, 0).In(loc),
|
||||
BucketID: 3,
|
||||
}
|
||||
task8 := &Task{
|
||||
@ -210,9 +221,9 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
CreatedBy: user1,
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
EndDate: 1544700000,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
EndDate: time.Unix(1544700000, 0).In(loc),
|
||||
BucketID: 3,
|
||||
}
|
||||
task9 := &Task{
|
||||
@ -225,10 +236,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
StartDate: 1544600000,
|
||||
EndDate: 1544700000,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
StartDate: time.Unix(1544600000, 0).In(loc),
|
||||
EndDate: time.Unix(1544700000, 0).In(loc),
|
||||
}
|
||||
task10 := &Task{
|
||||
ID: 10,
|
||||
@ -240,8 +251,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task11 := &Task{
|
||||
ID: 11,
|
||||
@ -253,8 +264,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task12 := &Task{
|
||||
ID: 12,
|
||||
@ -266,8 +277,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task15 := &Task{
|
||||
ID: 15,
|
||||
@ -279,8 +290,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 6,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 6,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task16 := &Task{
|
||||
ID: 16,
|
||||
@ -292,8 +303,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 7,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 7,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task17 := &Task{
|
||||
ID: 17,
|
||||
@ -305,8 +316,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 8,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 8,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task18 := &Task{
|
||||
ID: 18,
|
||||
@ -318,8 +329,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 9,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 9,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task19 := &Task{
|
||||
ID: 19,
|
||||
@ -331,8 +342,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 10,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 10,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task20 := &Task{
|
||||
ID: 20,
|
||||
@ -344,8 +355,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 11,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 11,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task21 := &Task{
|
||||
ID: 21,
|
||||
@ -357,8 +368,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 12,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 12,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task22 := &Task{
|
||||
ID: 22,
|
||||
@ -370,8 +381,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 13,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 13,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task23 := &Task{
|
||||
ID: 23,
|
||||
@ -383,8 +394,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 14,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 14,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task24 := &Task{
|
||||
ID: 24,
|
||||
@ -396,8 +407,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 15,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 15,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task25 := &Task{
|
||||
ID: 25,
|
||||
@ -409,8 +420,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 16,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 16,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task26 := &Task{
|
||||
ID: 26,
|
||||
@ -422,22 +433,25 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 17,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 17,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task27 := &Task{
|
||||
ID: 27,
|
||||
Title: "task #27 with reminders",
|
||||
Identifier: "test1-12",
|
||||
Index: 12,
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Reminders: []timeutil.TimeStamp{1543626724, 1543626824},
|
||||
ID: 27,
|
||||
Title: "task #27 with reminders",
|
||||
Identifier: "test1-12",
|
||||
Index: 12,
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
Reminders: []time.Time{
|
||||
time.Unix(1543626724, 0).In(loc),
|
||||
time.Unix(1543626824, 0).In(loc),
|
||||
},
|
||||
ListID: 1,
|
||||
BucketID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task28 := &Task{
|
||||
ID: 28,
|
||||
@ -450,8 +464,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
RepeatAfter: 3600,
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task29 := &Task{
|
||||
ID: 29,
|
||||
@ -470,15 +484,15 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
Index: 1,
|
||||
CreatedByID: 1,
|
||||
ListID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
BucketID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task30 := &Task{
|
||||
ID: 30,
|
||||
@ -494,8 +508,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
},
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task31 := &Task{
|
||||
ID: 31,
|
||||
@ -508,8 +522,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 1,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task32 := &Task{
|
||||
ID: 32,
|
||||
@ -521,8 +535,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
ListID: 3,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 21,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
task33 := &Task{
|
||||
ID: 33,
|
||||
@ -535,8 +549,8 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
PercentDone: 0.5,
|
||||
RelatedTasks: map[RelationKind][]*Task{},
|
||||
BucketID: 1,
|
||||
Created: 1543626724,
|
||||
Updated: 1543626724,
|
||||
Created: time.Unix(1543626724, 0).In(loc),
|
||||
Updated: time.Unix(1543626724, 0).In(loc),
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
@ -545,9 +559,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
SortBy []string // Is a string, since this is the place where a query string comes from the user
|
||||
OrderBy []string
|
||||
|
||||
FilterBy []string
|
||||
FilterValue []string
|
||||
FilterComparator []string
|
||||
FilterBy []string
|
||||
FilterValue []string
|
||||
FilterComparator []string
|
||||
FilterIncludeNulls bool
|
||||
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
@ -658,7 +673,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
name: "ReadAll Tasks with range",
|
||||
fields: fields{
|
||||
FilterBy: []string{"start_date", "end_date"},
|
||||
FilterValue: []string{"1544500000", "1544700001"},
|
||||
FilterValue: []string{"2018-12-11T03:46:40+00:00", "2018-12-13T11:20:01+00:00"},
|
||||
FilterComparator: []string{"greater", "less"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
@ -673,7 +688,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
name: "ReadAll Tasks with different range",
|
||||
fields: fields{
|
||||
FilterBy: []string{"start_date", "end_date"},
|
||||
FilterValue: []string{"1544700000", "1545000000"},
|
||||
FilterValue: []string{"2018-12-13T11:20:00+00:00", "2018-12-16T22:40:00+00:00"},
|
||||
FilterComparator: []string{"greater", "less"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
@ -687,7 +702,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
name: "ReadAll Tasks with range with start date only",
|
||||
fields: fields{
|
||||
FilterBy: []string{"start_date"},
|
||||
FilterValue: []string{"1544600000"},
|
||||
FilterValue: []string{"2018-12-12T07:33:20+00:00"},
|
||||
FilterComparator: []string{"greater"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
@ -698,7 +713,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
name: "ReadAll Tasks with range with start date only and greater equals",
|
||||
fields: fields{
|
||||
FilterBy: []string{"start_date"},
|
||||
FilterValue: []string{"1544600000"},
|
||||
FilterValue: []string{"2018-12-12T07:33:20+00:00"},
|
||||
FilterComparator: []string{"greater_equals"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
@ -777,6 +792,50 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "range with nulls",
|
||||
fields: fields{
|
||||
FilterBy: []string{"start_date", "end_date"},
|
||||
FilterValue: []string{"2018-12-11T03:46:40+00:00", "2018-12-13T11:20:01+00:00"},
|
||||
FilterComparator: []string{"greater", "less"},
|
||||
FilterIncludeNulls: true,
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{
|
||||
task1, // has nil dates
|
||||
task2, // has nil dates
|
||||
task3, // has nil dates
|
||||
task4, // has nil dates
|
||||
task5, // has nil dates
|
||||
task6, // has nil dates
|
||||
task7,
|
||||
task8,
|
||||
task9,
|
||||
task10, // has nil dates
|
||||
task11, // has nil dates
|
||||
task12, // has nil dates
|
||||
task15, // has nil dates
|
||||
task16, // has nil dates
|
||||
task17, // has nil dates
|
||||
task18, // has nil dates
|
||||
task19, // has nil dates
|
||||
task20, // has nil dates
|
||||
task21, // has nil dates
|
||||
task22, // has nil dates
|
||||
task23, // has nil dates
|
||||
task24, // has nil dates
|
||||
task25, // has nil dates
|
||||
task26, // has nil dates
|
||||
task27, // has nil dates
|
||||
task28, // has nil dates
|
||||
task29, // has nil dates
|
||||
task30, // has nil dates
|
||||
task31, // has nil dates
|
||||
task32, // has nil dates
|
||||
task33, // has nil dates
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@ -788,9 +847,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
||||
SortBy: tt.fields.SortBy,
|
||||
OrderBy: tt.fields.OrderBy,
|
||||
|
||||
FilterBy: tt.fields.FilterBy,
|
||||
FilterValue: tt.fields.FilterValue,
|
||||
FilterComparator: tt.fields.FilterComparator,
|
||||
FilterBy: tt.fields.FilterBy,
|
||||
FilterValue: tt.fields.FilterValue,
|
||||
FilterComparator: tt.fields.FilterComparator,
|
||||
FilterIncludeNulls: tt.fields.FilterIncludeNulls,
|
||||
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
|
@ -18,9 +18,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TaskComment represents a task comment
|
||||
@ -31,8 +31,8 @@ type TaskComment struct {
|
||||
Author *user.User `xorm:"-" json:"author"`
|
||||
TaskID int64 `xorm:"not null" json:"-" param:"task"`
|
||||
|
||||
Created timeutil.TimeStamp `xorm:"created" json:"created"`
|
||||
Updated timeutil.TimeStamp `xorm:"updated" json:"updated"`
|
||||
Created time.Time `xorm:"created" json:"created"`
|
||||
Updated time.Time `xorm:"updated" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -18,9 +18,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RelationKind represents a kind of relation between to tasks
|
||||
@ -88,7 +88,7 @@ type TaskRelation struct {
|
||||
CreatedBy *user.User `xorm:"-" json:"created_by"`
|
||||
|
||||
// A timestamp when this label was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"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"
|
||||
@ -44,12 +43,12 @@ type Task struct {
|
||||
// Whether a task is done or not.
|
||||
Done bool `xorm:"INDEX null" json:"done"`
|
||||
// The time when a task was marked as done.
|
||||
DoneAt timeutil.TimeStamp `xorm:"INDEX null 'done_at_unix'" json:"done_at"`
|
||||
DoneAt time.Time `xorm:"INDEX null 'done_at'" json:"done_at"`
|
||||
// The time when the task is due.
|
||||
DueDate timeutil.TimeStamp `xorm:"int(11) INDEX null 'due_date_unix'" json:"due_date"`
|
||||
DueDate time.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"`
|
||||
// An array of datetimes when the user wants to be reminded of the task.
|
||||
Reminders []timeutil.TimeStamp `xorm:"-" json:"reminder_dates"`
|
||||
CreatedByID int64 `xorm:"int(11) not null" json:"-"` // ID of the user who put that task on the list
|
||||
Reminders []time.Time `xorm:"-" json:"reminder_dates"`
|
||||
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:"list_id" 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.
|
||||
@ -59,9 +58,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.
|
||||
StartDate timeutil.TimeStamp `xorm:"int(11) INDEX null 'start_date_unix'" json:"start_date" query:"-"`
|
||||
StartDate time.Time `xorm:"DATETIME INDEX null 'start_date'" json:"start_date" query:"-"`
|
||||
// When this task ends.
|
||||
EndDate timeutil.TimeStamp `xorm:"int(11) INDEX null 'end_date_unix'" json:"end_date" query:"-"`
|
||||
EndDate time.Time `xorm:"DATETIME INDEX null 'end_date'" json:"end_date" query:"-"`
|
||||
// An array of users who are assigned to this task
|
||||
Assignees []*user.User `xorm:"-" json:"assignees"`
|
||||
// An array of labels which are associated with this task.
|
||||
@ -86,9 +85,9 @@ type Task struct {
|
||||
Attachments []*TaskAttachment `xorm:"-" json:"attachments"`
|
||||
|
||||
// A timestamp when this task was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `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"`
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
// BucketID is the ID of the kanban bucket this task belongs to.
|
||||
BucketID int64 `xorm:"int(11) null" json:"bucket_id"`
|
||||
@ -115,10 +114,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"`
|
||||
Reminder timeutil.TimeStamp `xorm:"int(11) not null INDEX 'reminder_unix'"`
|
||||
Created timeutil.TimeStamp `xorm:"created not null"`
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk"`
|
||||
TaskID int64 `xorm:"int(11) not null INDEX"`
|
||||
Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'"`
|
||||
Created time.Time `xorm:"created not null"`
|
||||
}
|
||||
|
||||
// TableName returns a pretty table name
|
||||
@ -134,12 +133,13 @@ const (
|
||||
)
|
||||
|
||||
type taskOptions struct {
|
||||
search string
|
||||
page int
|
||||
perPage int
|
||||
sortby []*sortParam
|
||||
filters []*taskFilter
|
||||
filterConcat taskFilterConcatinator
|
||||
search string
|
||||
page int
|
||||
perPage int
|
||||
sortby []*sortParam
|
||||
filters []*taskFilter
|
||||
filterConcat taskFilterConcatinator
|
||||
filterIncludeNulls bool
|
||||
}
|
||||
|
||||
// ReadAll is a dummy function to still have that endpoint documented
|
||||
@ -151,7 +151,7 @@ type taskOptions struct {
|
||||
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @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_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 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`, `due_date`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date`, `end_date`, `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 filter_by query string false "The name of the field to filter by. Accepts an array for multiple filters which will be chanied together, all supplied filter must match."
|
||||
// @Param filter_value query string false "The value to filter for."
|
||||
@ -228,13 +228,29 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resul
|
||||
case taskFilterComparatorNotEquals:
|
||||
filters = append(filters, &builder.Neq{f.field: f.value})
|
||||
case taskFilterComparatorGreater:
|
||||
filters = append(filters, builder.Or(&builder.Gt{f.field: f.value}, &builder.Eq{f.field: 0}))
|
||||
if opts.filterIncludeNulls {
|
||||
filters = append(filters, builder.Or(&builder.Gt{f.field: f.value}, &builder.IsNull{f.field}))
|
||||
} else {
|
||||
filters = append(filters, &builder.Gt{f.field: f.value})
|
||||
}
|
||||
case taskFilterComparatorGreateEquals:
|
||||
filters = append(filters, builder.Or(&builder.Gte{f.field: f.value}, &builder.Eq{f.field: 0}))
|
||||
if opts.filterIncludeNulls {
|
||||
filters = append(filters, builder.Or(&builder.Gte{f.field: f.value}, &builder.IsNull{f.field}))
|
||||
} else {
|
||||
filters = append(filters, &builder.Gte{f.field: f.value})
|
||||
}
|
||||
case taskFilterComparatorLess:
|
||||
filters = append(filters, builder.Or(&builder.Lt{f.field: f.value}, &builder.Eq{f.field: 0}))
|
||||
if opts.filterIncludeNulls {
|
||||
filters = append(filters, builder.Or(&builder.Lt{f.field: f.value}, &builder.IsNull{f.field}))
|
||||
} else {
|
||||
filters = append(filters, &builder.Lt{f.field: f.value})
|
||||
}
|
||||
case taskFilterComparatorLessEquals:
|
||||
filters = append(filters, builder.Or(&builder.Lte{f.field: f.value}, &builder.Eq{f.field: 0}))
|
||||
if opts.filterIncludeNulls {
|
||||
filters = append(filters, builder.Or(&builder.Lte{f.field: f.value}, &builder.IsNull{f.field}))
|
||||
} else {
|
||||
filters = append(filters, &builder.Lte{f.field: f.value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,9 +487,9 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
taskRemindersUnix := make(map[int64][]timeutil.TimeStamp)
|
||||
taskReminders := make(map[int64][]time.Time)
|
||||
for _, r := range reminders {
|
||||
taskRemindersUnix[r.TaskID] = append(taskRemindersUnix[r.TaskID], r.Reminder)
|
||||
taskReminders[r.TaskID] = append(taskReminders[r.TaskID], r.Reminder)
|
||||
}
|
||||
|
||||
// Get all identifiers
|
||||
@ -490,7 +506,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (err error) {
|
||||
task.CreatedBy = users[task.CreatedByID]
|
||||
|
||||
// Add the reminders
|
||||
task.Reminders = taskRemindersUnix[task.ID]
|
||||
task.Reminders = taskReminders[task.ID]
|
||||
|
||||
// Prepare the subtasks
|
||||
task.RelatedTasks = make(RelatedTaskMap)
|
||||
@ -663,7 +679,7 @@ func (t *Task) Update() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
ot.Reminders = make([]timeutil.TimeStamp, len(reminders))
|
||||
ot.Reminders = make([]time.Time, len(reminders))
|
||||
for i, r := range reminders {
|
||||
ot.Reminders[i] = r.Reminder
|
||||
}
|
||||
@ -737,20 +753,20 @@ func (t *Task) Update() (err error) {
|
||||
ot.Description = ""
|
||||
}
|
||||
// Due date
|
||||
if t.DueDate == 0 {
|
||||
ot.DueDate = 0
|
||||
if t.DueDate.IsZero() {
|
||||
ot.DueDate = time.Time{}
|
||||
}
|
||||
// Repeat after
|
||||
if t.RepeatAfter == 0 {
|
||||
ot.RepeatAfter = 0
|
||||
}
|
||||
// Start date
|
||||
if t.StartDate == 0 {
|
||||
ot.StartDate = 0
|
||||
if t.StartDate.IsZero() {
|
||||
ot.StartDate = time.Time{}
|
||||
}
|
||||
// End date
|
||||
if t.EndDate == 0 {
|
||||
ot.EndDate = 0
|
||||
if t.EndDate.IsZero() {
|
||||
ot.EndDate = time.Time{}
|
||||
}
|
||||
// Color
|
||||
if t.HexColor == "" {
|
||||
@ -773,13 +789,13 @@ func (t *Task) Update() (err error) {
|
||||
Cols("title",
|
||||
"description",
|
||||
"done",
|
||||
"due_date_unix",
|
||||
"due_date",
|
||||
"repeat_after",
|
||||
"priority",
|
||||
"start_date_unix",
|
||||
"end_date_unix",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"hex_color",
|
||||
"done_at_unix",
|
||||
"done_at",
|
||||
"percent_done",
|
||||
"list_id",
|
||||
"bucket_id",
|
||||
@ -796,7 +812,7 @@ func (t *Task) Update() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// This helper function updates the reminders, doneAtUnix, start and end dates of the *old* task
|
||||
// This helper function updates the reminders, doneAt, start and end dates of the *old* task
|
||||
// and saves the new values in the newTask object.
|
||||
// We make a few assumtions here:
|
||||
// 1. Everything in oldTask is the truth - we figure out if we update anything at all if oldTask.RepeatAfter has a value > 0
|
||||
@ -810,16 +826,16 @@ func updateDone(oldTask *Task, newTask *Task) {
|
||||
now := time.Now()
|
||||
|
||||
// assuming we'll merge the new task over the old task
|
||||
if oldTask.DueDate > 0 {
|
||||
if !oldTask.DueDate.IsZero() {
|
||||
if oldTask.RepeatFromCurrentDate {
|
||||
newTask.DueDate = timeutil.FromTime(now.Add(repeatDuration))
|
||||
newTask.DueDate = now.Add(repeatDuration)
|
||||
} else {
|
||||
// Always add one instance of the repeating interval to catch cases where a due date is already in the future
|
||||
// but not the repeating interval
|
||||
newTask.DueDate = timeutil.FromTime(oldTask.DueDate.ToTime().Add(repeatDuration))
|
||||
newTask.DueDate = oldTask.DueDate.Add(repeatDuration)
|
||||
// Add the repeating interval until the new due date is in the future
|
||||
for !newTask.DueDate.ToTime().After(now) {
|
||||
newTask.DueDate = timeutil.FromTime(newTask.DueDate.ToTime().Add(repeatDuration))
|
||||
for !newTask.DueDate.After(now) {
|
||||
newTask.DueDate = newTask.DueDate.Add(repeatDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -830,47 +846,47 @@ func updateDone(oldTask *Task, newTask *Task) {
|
||||
if len(oldTask.Reminders) > 0 {
|
||||
if oldTask.RepeatFromCurrentDate {
|
||||
sort.Slice(oldTask.Reminders, func(i, j int) bool {
|
||||
return oldTask.Reminders[i] < oldTask.Reminders[j]
|
||||
return oldTask.Reminders[i].Unix() < oldTask.Reminders[j].Unix()
|
||||
})
|
||||
first := oldTask.Reminders[0]
|
||||
for in, r := range oldTask.Reminders {
|
||||
diff := time.Duration(r-first) * time.Second
|
||||
newTask.Reminders[in] = timeutil.FromTime(now.Add(repeatDuration + diff))
|
||||
diff := r.Sub(first)
|
||||
newTask.Reminders[in] = now.Add(repeatDuration + diff)
|
||||
}
|
||||
} else {
|
||||
for in, r := range oldTask.Reminders {
|
||||
newTask.Reminders[in] = timeutil.FromTime(r.ToTime().Add(repeatDuration))
|
||||
for !newTask.Reminders[in].ToTime().After(now) {
|
||||
newTask.Reminders[in] = timeutil.FromTime(newTask.Reminders[in].ToTime().Add(repeatDuration))
|
||||
newTask.Reminders[in] = r.Add(repeatDuration)
|
||||
for !newTask.Reminders[in].After(now) {
|
||||
newTask.Reminders[in] = newTask.Reminders[in].Add(repeatDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a task has a start and end date, the end date should keep the difference to the start date when setting them as new
|
||||
if oldTask.RepeatFromCurrentDate && oldTask.StartDate > 0 && oldTask.EndDate > 0 {
|
||||
diff := time.Duration(oldTask.EndDate-oldTask.StartDate) * time.Second
|
||||
newTask.StartDate = timeutil.FromTime(now.Add(repeatDuration))
|
||||
newTask.EndDate = timeutil.FromTime(now.Add(repeatDuration + diff))
|
||||
if oldTask.RepeatFromCurrentDate && !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
|
||||
diff := oldTask.EndDate.Sub(oldTask.StartDate)
|
||||
newTask.StartDate = now.Add(repeatDuration)
|
||||
newTask.EndDate = now.Add(repeatDuration + diff)
|
||||
} else {
|
||||
if oldTask.StartDate > 0 {
|
||||
if !oldTask.StartDate.IsZero() {
|
||||
if oldTask.RepeatFromCurrentDate {
|
||||
newTask.StartDate = timeutil.FromTime(now.Add(repeatDuration))
|
||||
newTask.StartDate = now.Add(repeatDuration)
|
||||
} else {
|
||||
newTask.StartDate = timeutil.FromTime(oldTask.StartDate.ToTime().Add(repeatDuration))
|
||||
for !newTask.StartDate.ToTime().After(now) {
|
||||
newTask.StartDate = timeutil.FromTime(newTask.StartDate.ToTime().Add(repeatDuration))
|
||||
newTask.StartDate = oldTask.StartDate.Add(repeatDuration)
|
||||
for !newTask.StartDate.After(now) {
|
||||
newTask.StartDate = newTask.StartDate.Add(repeatDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if oldTask.EndDate > 0 {
|
||||
if !oldTask.EndDate.IsZero() {
|
||||
if oldTask.RepeatFromCurrentDate {
|
||||
newTask.EndDate = timeutil.FromTime(now.Add(repeatDuration))
|
||||
newTask.EndDate = now.Add(repeatDuration)
|
||||
} else {
|
||||
newTask.EndDate = timeutil.FromTime(oldTask.EndDate.ToTime().Add(repeatDuration))
|
||||
for !newTask.EndDate.ToTime().After(now) {
|
||||
newTask.EndDate = timeutil.FromTime(newTask.EndDate.ToTime().Add(repeatDuration))
|
||||
newTask.EndDate = oldTask.EndDate.Add(repeatDuration)
|
||||
for !newTask.EndDate.After(now) {
|
||||
newTask.EndDate = newTask.EndDate.Add(repeatDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -881,17 +897,17 @@ func updateDone(oldTask *Task, newTask *Task) {
|
||||
|
||||
// Update the "done at" timestamp
|
||||
if !oldTask.Done && newTask.Done {
|
||||
newTask.DoneAt = timeutil.FromTime(time.Now())
|
||||
newTask.DoneAt = time.Now()
|
||||
}
|
||||
// When unmarking a task as done, reset the timestamp
|
||||
if oldTask.Done && !newTask.Done {
|
||||
newTask.DoneAt = 0
|
||||
newTask.DoneAt = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates or deletes all necessary reminders without unneded db operations.
|
||||
// The parameter is a slice with unix dates which holds the new reminders.
|
||||
func (t *Task) updateReminders(reminders []timeutil.TimeStamp) (err error) {
|
||||
func (t *Task) updateReminders(reminders []time.Time) (err error) {
|
||||
|
||||
// Load the current reminders
|
||||
taskReminders, err := getRemindersForTasks([]int64{t.ID})
|
||||
@ -899,7 +915,7 @@ func (t *Task) updateReminders(reminders []timeutil.TimeStamp) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Reminders = make([]timeutil.TimeStamp, 0, len(taskReminders))
|
||||
t.Reminders = make([]time.Time, 0, len(taskReminders))
|
||||
for _, reminder := range taskReminders {
|
||||
t.Reminders = append(t.Reminders, reminder.Reminder)
|
||||
}
|
||||
@ -918,15 +934,15 @@ func (t *Task) updateReminders(reminders []timeutil.TimeStamp) (err error) {
|
||||
}
|
||||
|
||||
// Make a hashmap of the new reminders for easier comparison
|
||||
newReminders := make(map[timeutil.TimeStamp]*TaskReminder, len(reminders))
|
||||
newReminders := make(map[time.Time]*TaskReminder, len(reminders))
|
||||
for _, newReminder := range reminders {
|
||||
newReminders[newReminder] = &TaskReminder{Reminder: newReminder}
|
||||
}
|
||||
|
||||
// Get old reminders to delete
|
||||
var found bool
|
||||
var remindersToDelete []timeutil.TimeStamp
|
||||
oldReminders := make(map[timeutil.TimeStamp]*TaskReminder, len(t.Reminders))
|
||||
var remindersToDelete []time.Time
|
||||
oldReminders := make(map[time.Time]*TaskReminder, len(t.Reminders))
|
||||
for _, oldReminder := range t.Reminders {
|
||||
found = false
|
||||
// If a new reminder is already in the list with old reminders
|
||||
@ -944,7 +960,7 @@ func (t *Task) updateReminders(reminders []timeutil.TimeStamp) (err error) {
|
||||
|
||||
// Delete all reminders not passed
|
||||
if len(remindersToDelete) > 0 {
|
||||
_, err = x.In("reminder_unix", remindersToDelete).
|
||||
_, err = x.In("reminder", remindersToDelete).
|
||||
And("task_id = ?", t.ID).
|
||||
Delete(TaskReminder{})
|
||||
if err != nil {
|
||||
|
@ -18,7 +18,6 @@ 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"
|
||||
@ -131,54 +130,54 @@ func TestUpdateDone(t *testing.T) {
|
||||
oldTask := &Task{Done: false}
|
||||
newTask := &Task{Done: true}
|
||||
updateDone(oldTask, newTask)
|
||||
assert.NotEqual(t, timeutil.TimeStamp(0), newTask.DoneAt)
|
||||
assert.NotEqual(t, time.Time{}, newTask.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, timeutil.TimeStamp(0), newTask.DoneAt)
|
||||
assert.Equal(t, time.Time{}, newTask.DoneAt)
|
||||
})
|
||||
t.Run("repeating interval", func(t *testing.T) {
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
DueDate: timeutil.TimeStamp(1550000000),
|
||||
DueDate: time.Unix(1550000000, 0),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
var expected int64 = 1550008600
|
||||
for expected < time.Now().Unix() {
|
||||
expected += oldTask.RepeatAfter
|
||||
var expected = time.Unix(1550008600, 0)
|
||||
for time.Since(expected) > 0 {
|
||||
expected = expected.Add(time.Second * time.Duration(oldTask.RepeatAfter))
|
||||
}
|
||||
|
||||
assert.Equal(t, timeutil.TimeStamp(expected), newTask.DueDate)
|
||||
assert.Equal(t, expected, newTask.DueDate)
|
||||
})
|
||||
t.Run("don't update if due date is zero", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
DueDate: timeutil.TimeStamp(0),
|
||||
DueDate: time.Time{},
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
DueDate: timeutil.TimeStamp(1543626724),
|
||||
DueDate: time.Unix(1543626724, 0),
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
assert.Equal(t, timeutil.TimeStamp(1543626724), newTask.DueDate)
|
||||
assert.Equal(t, time.Unix(1543626724, 0), newTask.DueDate)
|
||||
})
|
||||
t.Run("update reminders", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
Reminders: []timeutil.TimeStamp{
|
||||
1550000000,
|
||||
1555000000,
|
||||
Reminders: []time.Time{
|
||||
time.Unix(1550000000, 0),
|
||||
time.Unix(1555000000, 0),
|
||||
},
|
||||
}
|
||||
newTask := &Task{
|
||||
@ -186,67 +185,67 @@ func TestUpdateDone(t *testing.T) {
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
var expected1 int64 = 1550008600
|
||||
var expected2 int64 = 1555008600
|
||||
for expected1 < time.Now().Unix() {
|
||||
expected1 += oldTask.RepeatAfter
|
||||
var expected1 = time.Unix(1550008600, 0)
|
||||
var expected2 = time.Unix(1555008600, 0)
|
||||
for time.Since(expected1) > 0 {
|
||||
expected1 = expected1.Add(time.Duration(oldTask.RepeatAfter) * time.Second)
|
||||
}
|
||||
for expected2 < time.Now().Unix() {
|
||||
expected2 += oldTask.RepeatAfter
|
||||
for time.Since(expected2) > 0 {
|
||||
expected2 = expected2.Add(time.Duration(oldTask.RepeatAfter) * time.Second)
|
||||
}
|
||||
|
||||
assert.Len(t, newTask.Reminders, 2)
|
||||
assert.Equal(t, timeutil.TimeStamp(expected1), newTask.Reminders[0])
|
||||
assert.Equal(t, timeutil.TimeStamp(expected2), newTask.Reminders[1])
|
||||
assert.Equal(t, expected1, newTask.Reminders[0])
|
||||
assert.Equal(t, expected2, newTask.Reminders[1])
|
||||
})
|
||||
t.Run("update start date", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
StartDate: timeutil.TimeStamp(1550000000),
|
||||
StartDate: time.Unix(1550000000, 0),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
var expected int64 = 1550008600
|
||||
for expected < time.Now().Unix() {
|
||||
expected += oldTask.RepeatAfter
|
||||
var expected = time.Unix(1550008600, 0)
|
||||
for time.Since(expected) > 0 {
|
||||
expected = expected.Add(time.Second * time.Duration(oldTask.RepeatAfter))
|
||||
}
|
||||
|
||||
assert.Equal(t, timeutil.TimeStamp(expected), newTask.StartDate)
|
||||
assert.Equal(t, expected, newTask.StartDate)
|
||||
})
|
||||
t.Run("update end date", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
EndDate: timeutil.TimeStamp(1550000000),
|
||||
EndDate: time.Unix(1550000000, 0),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
var expected int64 = 1550008600
|
||||
for expected < time.Now().Unix() {
|
||||
expected += oldTask.RepeatAfter
|
||||
var expected = time.Unix(1550008600, 0)
|
||||
for time.Since(expected) > 0 {
|
||||
expected = expected.Add(time.Second * time.Duration(oldTask.RepeatAfter))
|
||||
}
|
||||
|
||||
assert.Equal(t, timeutil.TimeStamp(expected), newTask.EndDate)
|
||||
assert.Equal(t, expected, newTask.EndDate)
|
||||
})
|
||||
t.Run("ensure due date is repeated even if the original one is in the future", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
DueDate: timeutil.FromTime(time.Now().Add(time.Hour)),
|
||||
DueDate: time.Now().Add(time.Hour),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
expected := int64(oldTask.DueDate) + oldTask.RepeatAfter
|
||||
assert.Equal(t, timeutil.TimeStamp(expected), newTask.DueDate)
|
||||
expected := oldTask.DueDate.Add(time.Duration(oldTask.RepeatAfter) * time.Second)
|
||||
assert.Equal(t, expected, newTask.DueDate)
|
||||
})
|
||||
t.Run("repeat from current date", func(t *testing.T) {
|
||||
t.Run("due date", func(t *testing.T) {
|
||||
@ -254,23 +253,24 @@ func TestUpdateDone(t *testing.T) {
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
RepeatFromCurrentDate: true,
|
||||
DueDate: timeutil.TimeStamp(1550000000),
|
||||
DueDate: time.Unix(1550000000, 0),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
assert.Equal(t, timeutil.FromTime(time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second)), newTask.DueDate)
|
||||
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
||||
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.DueDate.Unix())
|
||||
})
|
||||
t.Run("reminders", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
RepeatFromCurrentDate: true,
|
||||
Reminders: []timeutil.TimeStamp{
|
||||
1550000000,
|
||||
1555000000,
|
||||
Reminders: []time.Time{
|
||||
time.Unix(1550000000, 0),
|
||||
time.Unix(1555000000, 0),
|
||||
},
|
||||
}
|
||||
newTask := &Task{
|
||||
@ -278,57 +278,61 @@ func TestUpdateDone(t *testing.T) {
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
diff := time.Duration(oldTask.Reminders[1]-oldTask.Reminders[0]) * time.Second
|
||||
diff := oldTask.Reminders[1].Sub(oldTask.Reminders[0])
|
||||
|
||||
assert.Len(t, newTask.Reminders, 2)
|
||||
assert.Equal(t, timeutil.FromTime(time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second)), newTask.Reminders[0])
|
||||
assert.Equal(t, timeutil.FromTime(time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second)), newTask.Reminders[1])
|
||||
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
||||
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[0].Unix())
|
||||
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[1].Unix())
|
||||
})
|
||||
t.Run("start date", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
RepeatFromCurrentDate: true,
|
||||
StartDate: timeutil.TimeStamp(1550000000),
|
||||
StartDate: time.Unix(1550000000, 0),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
assert.Equal(t, timeutil.FromTime(time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second)), newTask.StartDate)
|
||||
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
||||
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.StartDate.Unix())
|
||||
})
|
||||
t.Run("end date", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
RepeatFromCurrentDate: true,
|
||||
EndDate: timeutil.TimeStamp(1560000000),
|
||||
EndDate: time.Unix(1560000000, 0),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
assert.Equal(t, timeutil.FromTime(time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second)), newTask.EndDate)
|
||||
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
||||
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.EndDate.Unix())
|
||||
})
|
||||
t.Run("start and end date", func(t *testing.T) {
|
||||
oldTask := &Task{
|
||||
Done: false,
|
||||
RepeatAfter: 8600,
|
||||
RepeatFromCurrentDate: true,
|
||||
StartDate: timeutil.TimeStamp(1550000000),
|
||||
EndDate: timeutil.TimeStamp(1560000000),
|
||||
StartDate: time.Unix(1550000000, 0),
|
||||
EndDate: time.Unix(1560000000, 0),
|
||||
}
|
||||
newTask := &Task{
|
||||
Done: true,
|
||||
}
|
||||
updateDone(oldTask, newTask)
|
||||
|
||||
diff := time.Duration(oldTask.EndDate-oldTask.StartDate) * time.Second
|
||||
diff := oldTask.EndDate.Sub(oldTask.StartDate)
|
||||
|
||||
assert.Equal(t, timeutil.FromTime(time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second)), newTask.StartDate)
|
||||
assert.Equal(t, timeutil.FromTime(time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second)), newTask.EndDate)
|
||||
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
||||
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.StartDate.Unix())
|
||||
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.EndDate.Unix())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -18,9 +18,9 @@ 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"
|
||||
"time"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
@ -40,9 +40,9 @@ type Team struct {
|
||||
Members []*TeamUser `xorm:"-" json:"members"`
|
||||
|
||||
// A timestamp when this relation was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created" json:"created"`
|
||||
Created time.Time `xorm:"created" json:"created"`
|
||||
// A timestamp when this relation was last updated. You cannot change this value.
|
||||
Updated timeutil.TimeStamp `xorm:"updated" json:"updated"`
|
||||
Updated time.Time `xorm:"updated" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
@ -71,7 +71,7 @@ type TeamMember struct {
|
||||
Admin bool `xorm:"null" json:"admin"`
|
||||
|
||||
// A timestamp when this relation was created. You cannot change this value.
|
||||
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
@ -18,9 +18,9 @@ package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
@ -33,8 +33,8 @@ func TestTeam_CanDoSomething(t *testing.T) {
|
||||
CreatedByID int64
|
||||
CreatedBy *user.User
|
||||
Members []*TeamUser
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
|
@ -30,17 +30,23 @@ func TestListUsersFromList(t *testing.T) {
|
||||
Username: "user1",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser2 := &user.User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser3 := &user.User{
|
||||
ID: 3,
|
||||
Username: "user3",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
PasswordResetToken: "passwordresettesttoken",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser4 := &user.User{
|
||||
ID: 4,
|
||||
@ -48,6 +54,8 @@ func TestListUsersFromList(t *testing.T) {
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: false,
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser5 := &user.User{
|
||||
ID: 5,
|
||||
@ -55,54 +63,72 @@ func TestListUsersFromList(t *testing.T) {
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: false,
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser6 := &user.User{
|
||||
ID: 6,
|
||||
Username: "user6",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser7 := &user.User{
|
||||
ID: 7,
|
||||
Username: "user7",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser8 := &user.User{
|
||||
ID: 8,
|
||||
Username: "user8",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser9 := &user.User{
|
||||
ID: 9,
|
||||
Username: "user9",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser10 := &user.User{
|
||||
ID: 10,
|
||||
Username: "user10",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser11 := &user.User{
|
||||
ID: 11,
|
||||
Username: "user11",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser12 := &user.User{
|
||||
ID: 12,
|
||||
Username: "user12",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
testuser13 := &user.User{
|
||||
ID: 13,
|
||||
Username: "user13",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
IsActive: true,
|
||||
Created: testCreatedTime,
|
||||
Updated: testUpdatedTime,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
|
Reference in New Issue
Block a user