forked from vikunja/vikunja
Compare commits
3 Commits
main
...
caldav-sub
Author | SHA1 | Date | |
---|---|---|---|
476bf072b1 | |||
d74334a590 | |||
c06f35e6a0 |
1
go.mod
1
go.mod
|
@ -65,6 +65,7 @@ require (
|
||||||
github.com/wneessen/go-mail v0.3.8
|
github.com/wneessen/go-mail v0.3.8
|
||||||
github.com/yuin/goldmark v1.5.4
|
github.com/yuin/goldmark v1.5.4
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.7.0
|
||||||
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
|
||||||
golang.org/x/image v0.6.0
|
golang.org/x/image v0.6.0
|
||||||
golang.org/x/oauth2 v0.6.0
|
golang.org/x/oauth2 v0.6.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.1.0
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -782,6 +782,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||||
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
|
|
@ -51,20 +51,20 @@ type Todo struct {
|
||||||
UID string
|
UID string
|
||||||
|
|
||||||
// Optional
|
// Optional
|
||||||
Summary string
|
Summary string
|
||||||
Description string
|
Description string
|
||||||
Completed time.Time
|
Completed time.Time
|
||||||
Organizer *user.User
|
Organizer *user.User
|
||||||
Priority int64 // 0-9, 1 is highest
|
Priority int64 // 0-9, 1 is highest
|
||||||
RelatedToUID string
|
RelatedToParentUID string
|
||||||
Color string
|
Color string
|
||||||
Categories []string
|
Categories []string
|
||||||
Start time.Time
|
Start time.Time
|
||||||
End time.Time
|
End time.Time
|
||||||
DueDate time.Time
|
DueDate time.Time
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
RepeatAfter int64
|
RepeatAfter int64
|
||||||
RepeatMode models.TaskRepeatMode
|
RepeatMode models.TaskRepeatMode
|
||||||
|
|
||||||
Created time.Time
|
Created time.Time
|
||||||
Updated time.Time // last-mod
|
Updated time.Time // last-mod
|
||||||
|
@ -209,9 +209,9 @@ STATUS:COMPLETED`
|
||||||
ORGANIZER;CN=:` + t.Organizer.Username
|
ORGANIZER;CN=:` + t.Organizer.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.RelatedToUID != "" {
|
if t.RelatedToParentUID != "" {
|
||||||
caldavtodos += `
|
caldavtodos += `
|
||||||
RELATED-TO:` + t.RelatedToUID
|
RELATED-TO;RELTYPE=PARENT:` + t.RelatedToParentUID
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.DueDate.Unix() > 0 {
|
if t.DueDate.Unix() > 0 {
|
||||||
|
|
|
@ -520,6 +520,45 @@ X-FUNAMBOL-COLOR:#affffeFF
|
||||||
CATEGORIES:label1,label2
|
CATEGORIES:label1,label2
|
||||||
LAST-MODIFIED:00010101T000000Z
|
LAST-MODIFIED:00010101T000000Z
|
||||||
END:VTODO
|
END:VTODO
|
||||||
|
END:VCALENDAR`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with parent task",
|
||||||
|
args: args{
|
||||||
|
config: &Config{
|
||||||
|
Name: "test",
|
||||||
|
ProdID: "RandomProdID which is not random",
|
||||||
|
Color: "ffffff",
|
||||||
|
},
|
||||||
|
todos: []*Todo{
|
||||||
|
{
|
||||||
|
Summary: "Todo #1",
|
||||||
|
UID: "randommduid",
|
||||||
|
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
Color: "affffe",
|
||||||
|
RelatedToParentUID: "another_random_uid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:test
|
||||||
|
PRODID:-//RandomProdID which is not random//EN
|
||||||
|
X-APPLE-CALENDAR-COLOR:#ffffffFF
|
||||||
|
X-OUTLOOK-COLOR:#ffffffFF
|
||||||
|
X-FUNAMBOL-COLOR:#ffffffFF
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:randommduid
|
||||||
|
DTSTAMP:20181201T011204Z
|
||||||
|
SUMMARY:Todo #1
|
||||||
|
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||||
|
X-OUTLOOK-COLOR:#affffeFF
|
||||||
|
X-FUNAMBOL-COLOR:#affffeFF
|
||||||
|
RELATED-TO;RELTYPE=PARENT:another_random_uid
|
||||||
|
LAST-MODIFIED:00010101T000000Z
|
||||||
|
END:VTODO
|
||||||
END:VCALENDAR`,
|
END:VCALENDAR`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,12 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
|
||||||
categories = append(categories, label.Title)
|
categories = append(categories, label.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the UID of the parent task, if it exists:
|
||||||
|
var parentTaskUID string
|
||||||
|
if parentTasks, ok := t.RelatedTasks[models.RelationKindParenttask]; ok {
|
||||||
|
parentTaskUID = parentTasks[0].UID
|
||||||
|
}
|
||||||
|
|
||||||
caldavtodos = append(caldavtodos, &Todo{
|
caldavtodos = append(caldavtodos, &Todo{
|
||||||
Timestamp: t.Updated,
|
Timestamp: t.Updated,
|
||||||
UID: t.UID,
|
UID: t.UID,
|
||||||
|
@ -46,16 +52,17 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
|
||||||
Description: t.Description,
|
Description: t.Description,
|
||||||
Completed: t.DoneAt,
|
Completed: t.DoneAt,
|
||||||
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
|
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
|
||||||
Categories: categories,
|
Categories: categories,
|
||||||
Priority: t.Priority,
|
Priority: t.Priority,
|
||||||
Start: t.StartDate,
|
RelatedToParentUID: parentTaskUID,
|
||||||
End: t.EndDate,
|
Start: t.StartDate,
|
||||||
Created: t.Created,
|
End: t.EndDate,
|
||||||
Updated: t.Updated,
|
Created: t.Created,
|
||||||
DueDate: t.DueDate,
|
Updated: t.Updated,
|
||||||
Duration: duration,
|
DueDate: t.DueDate,
|
||||||
RepeatAfter: t.RepeatAfter,
|
Duration: duration,
|
||||||
RepeatMode: t.RepeatMode,
|
RepeatAfter: t.RepeatAfter,
|
||||||
|
RepeatMode: t.RepeatMode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,9 +80,11 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var parsedProperties = parsed.Components[0].UnknownPropertiesIANAProperties()
|
||||||
|
|
||||||
// We put the task details in a map to be able to handle them more easily
|
// We put the task details in a map to be able to handle them more easily
|
||||||
task := make(map[string]string)
|
task := make(map[string]string)
|
||||||
for _, c := range parsed.Components[0].UnknownPropertiesIANAProperties() {
|
for _, c := range parsedProperties {
|
||||||
task[c.IANAToken] = c.Value
|
task[c.IANAToken] = c.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +99,26 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||||
priority = parseVTODOPriority(priorityParsed)
|
priority = parseVTODOPriority(priorityParsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the task has a parent:
|
||||||
|
var parentTaskUID string
|
||||||
|
for _, c := range parsedProperties {
|
||||||
|
// Check if the entry is a relation:
|
||||||
|
if c.IANAToken != "RELATED-TO" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the relation has a type:
|
||||||
|
if _, ok := c.ICalParameters["RELTYPE"]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check that the type is "PARENT":
|
||||||
|
if len(c.ICalParameters["RELTYPE"]) != 1 || c.ICalParameters["RELTYPE"][0] != "PARENT" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have the id of the parent task:
|
||||||
|
parentTaskUID = c.Value
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the enddate
|
// Parse the enddate
|
||||||
duration, _ := time.ParseDuration(task["DURATION"])
|
duration, _ := time.ParseDuration(task["DURATION"])
|
||||||
|
|
||||||
|
@ -123,6 +152,19 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||||
vTask.Done = true
|
vTask.Done = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the task has a parent and create a dummy relation if yes:
|
||||||
|
if parentTaskUID != "" {
|
||||||
|
var parentTaskUID = parentTaskUID
|
||||||
|
|
||||||
|
if vTask.RelatedTasks == nil {
|
||||||
|
vTask.RelatedTasks = make(models.RelatedTaskMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
vTask.RelatedTasks[models.RelationKindParenttask] = append(vTask.RelatedTasks[models.RelationKindParenttask], &models.Task{
|
||||||
|
UID: parentTaskUID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if duration > 0 && !vTask.StartDate.IsZero() {
|
if duration > 0 && !vTask.StartDate.IsZero() {
|
||||||
vTask.EndDate = vTask.StartDate.Add(duration)
|
vTask.EndDate = vTask.StartDate.Add(duration)
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,63 @@ END:VCALENDAR`,
|
||||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "With parent",
|
||||||
|
args: args{content: `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:test
|
||||||
|
PRODID:-//RandomProdID which is not random//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:randomuid
|
||||||
|
DTSTAMP:20181201T011204
|
||||||
|
SUMMARY:SubTask #1
|
||||||
|
DESCRIPTION:Lorem Ipsum
|
||||||
|
RELATED-TO;RELTYPE=PARENT:randomuid_parent
|
||||||
|
LAST-MODIFIED:00010101T000000
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`,
|
||||||
|
},
|
||||||
|
wantVTask: &models.Task{
|
||||||
|
Title: "SubTask #1",
|
||||||
|
UID: "randomuid",
|
||||||
|
Description: "Lorem Ipsum",
|
||||||
|
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||||
|
models.RelationKindParenttask: {
|
||||||
|
{
|
||||||
|
UID: "randomuid_parent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "With non-parent relation we ignore",
|
||||||
|
args: args{content: `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:test
|
||||||
|
PRODID:-//RandomProdID which is not random//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:randomuid
|
||||||
|
DTSTAMP:20181201T011204
|
||||||
|
SUMMARY:Parent task
|
||||||
|
DESCRIPTION:Lorem Ipsum
|
||||||
|
RELATED-TO;RELTYPE=CHILD:randomuid_child
|
||||||
|
LAST-MODIFIED:00010101T000000
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`,
|
||||||
|
},
|
||||||
|
wantVTask: &models.Task{
|
||||||
|
Title: "Parent task",
|
||||||
|
UID: "randomuid",
|
||||||
|
Description: "Lorem Ipsum",
|
||||||
|
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -135,8 +192,8 @@ END:VCALENDAR`,
|
||||||
|
|
||||||
func TestGetCaldavTodosForTasks(t *testing.T) {
|
func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
list *models.ProjectWithTasksAndBuckets
|
project *models.ProjectWithTasksAndBuckets
|
||||||
tasks []*models.TaskWithComments
|
tasks []*models.TaskWithComments
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -146,9 +203,9 @@ func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Format single Task as Caldav",
|
name: "Format single Task as Caldav",
|
||||||
args: args{
|
args: args{
|
||||||
list: &models.ProjectWithTasksAndBuckets{
|
project: &models.ProjectWithTasksAndBuckets{
|
||||||
Project: models.Project{
|
Project: models.Project{
|
||||||
Title: "List title",
|
Title: "Project title",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tasks: []*models.TaskWithComments{
|
tasks: []*models.TaskWithComments{
|
||||||
|
@ -183,7 +240,7 @@ func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
METHOD:PUBLISH
|
METHOD:PUBLISH
|
||||||
X-PUBLISHED-TTL:PT4H
|
X-PUBLISHED-TTL:PT4H
|
||||||
X-WR-CALNAME:List title
|
X-WR-CALNAME:Project title
|
||||||
PRODID:-//Vikunja Todo App//EN
|
PRODID:-//Vikunja Todo App//EN
|
||||||
BEGIN:VTODO
|
BEGIN:VTODO
|
||||||
UID:randomuid
|
UID:randomuid
|
||||||
|
@ -201,12 +258,126 @@ RRULE:FREQ=SECONDLY;INTERVAL=86400
|
||||||
CATEGORIES:label1,label2
|
CATEGORIES:label1,label2
|
||||||
LAST-MODIFIED:20181201T011205Z
|
LAST-MODIFIED:20181201T011205Z
|
||||||
END:VTODO
|
END:VTODO
|
||||||
|
END:VCALENDAR`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Format tasks with relationship as Caldav",
|
||||||
|
args: args{
|
||||||
|
project: &models.ProjectWithTasksAndBuckets{
|
||||||
|
Project: models.Project{
|
||||||
|
Title: "Project title",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tasks: []*models.TaskWithComments{
|
||||||
|
{
|
||||||
|
Task: models.Task{
|
||||||
|
Title: "Parent task",
|
||||||
|
UID: "randomuid_parent",
|
||||||
|
Description: "This is a parent task",
|
||||||
|
Priority: 3,
|
||||||
|
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||||
|
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
|
||||||
|
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||||
|
models.RelationKindSubtask: {
|
||||||
|
{
|
||||||
|
Title: "Subtask 1",
|
||||||
|
UID: "randomuid_child_1",
|
||||||
|
Description: "This is the first child task",
|
||||||
|
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Title: "Subtask 2",
|
||||||
|
UID: "randomuid_child_2",
|
||||||
|
Description: "This is the second child task",
|
||||||
|
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Task: models.Task{
|
||||||
|
Title: "Subtask 1",
|
||||||
|
UID: "randomuid_child_1",
|
||||||
|
Description: "This is the first child task",
|
||||||
|
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||||
|
models.RelationKindParenttask: {
|
||||||
|
{
|
||||||
|
Title: "Parent task",
|
||||||
|
UID: "randomuid_parent",
|
||||||
|
Description: "This is a parent task",
|
||||||
|
Priority: 3,
|
||||||
|
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Task: models.Task{
|
||||||
|
Title: "Subtask 2",
|
||||||
|
UID: "randomuid_child_2",
|
||||||
|
Description: "This is the second child task",
|
||||||
|
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||||
|
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||||
|
models.RelationKindParenttask: {
|
||||||
|
{
|
||||||
|
Title: "Parent task",
|
||||||
|
UID: "randomuid_parent",
|
||||||
|
Description: "This is a parent task",
|
||||||
|
Priority: 3,
|
||||||
|
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantCaldav: `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project title
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:randomuid_parent
|
||||||
|
DTSTAMP:20181201T011205Z
|
||||||
|
SUMMARY:Parent task
|
||||||
|
DESCRIPTION:This is a parent task
|
||||||
|
CREATED:20181201T011201Z
|
||||||
|
PRIORITY:3
|
||||||
|
LAST-MODIFIED:20181201T011205Z
|
||||||
|
END:VTODO
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:randomuid_child_1
|
||||||
|
DTSTAMP:20181201T011204Z
|
||||||
|
SUMMARY:Subtask 1
|
||||||
|
DESCRIPTION:This is the first child task
|
||||||
|
RELATED-TO;RELTYPE=PARENT:randomuid_parent
|
||||||
|
CREATED:20181201T011204Z
|
||||||
|
LAST-MODIFIED:20181201T011204Z
|
||||||
|
END:VTODO
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:randomuid_child_2
|
||||||
|
DTSTAMP:20181201T011204Z
|
||||||
|
SUMMARY:Subtask 2
|
||||||
|
DESCRIPTION:This is the second child task
|
||||||
|
RELATED-TO;RELTYPE=PARENT:randomuid_parent
|
||||||
|
CREATED:20181201T011204Z
|
||||||
|
LAST-MODIFIED:20181201T011204Z
|
||||||
|
END:VTODO
|
||||||
END:VCALENDAR`,
|
END:VCALENDAR`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := GetCaldavTodosForTasks(tt.args.list, tt.args.tasks)
|
got := GetCaldavTodosForTasks(tt.args.project, tt.args.tasks)
|
||||||
if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal {
|
if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal {
|
||||||
t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff)
|
t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff)
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,3 +224,9 @@
|
||||||
created_by_id: 15
|
created_by_id: 15
|
||||||
created: 2020-04-18 21:13:52
|
created: 2020-04-18 21:13:52
|
||||||
updated: 2020-04-18 21:13:52
|
updated: 2020-04-18 21:13:52
|
||||||
|
- id: 37
|
||||||
|
title: testbucket37
|
||||||
|
project_id: 27
|
||||||
|
created_by_id: 15
|
||||||
|
created: 2020-04-18 21:13:52
|
||||||
|
updated: 2020-04-18 21:13:52
|
||||||
|
|
|
@ -255,7 +255,7 @@
|
||||||
created: 2018-12-01 15:13:12
|
created: 2018-12-01 15:13:12
|
||||||
-
|
-
|
||||||
id: 26
|
id: 26
|
||||||
title: List 26 for Caldav tests
|
title: Project 26 for Caldav tests
|
||||||
description: Lorem Ipsum
|
description: Lorem Ipsum
|
||||||
identifier: test26
|
identifier: test26
|
||||||
owner_id: 15
|
owner_id: 15
|
||||||
|
@ -263,3 +263,14 @@
|
||||||
position: 1
|
position: 1
|
||||||
updated: 2018-12-02 15:13:12
|
updated: 2018-12-02 15:13:12
|
||||||
created: 2018-12-01 15:13:12
|
created: 2018-12-01 15:13:12
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 27
|
||||||
|
title: Project 27 for Caldav tests
|
||||||
|
description: Lorem Ipsum
|
||||||
|
identifier: test27
|
||||||
|
owner_id: 15
|
||||||
|
namespace_id: 18
|
||||||
|
position: 2
|
||||||
|
updated: 2018-12-02 15:13:12
|
||||||
|
created: 2018-12-01 15:13:12
|
||||||
|
|
|
@ -52,3 +52,9 @@
|
||||||
right: 0
|
right: 0
|
||||||
updated: 2018-12-02 15:13:12
|
updated: 2018-12-02 15:13:12
|
||||||
created: 2018-12-01 15:13:12
|
created: 2018-12-01 15:13:12
|
||||||
|
- id: 10
|
||||||
|
user_id: 15
|
||||||
|
project_id: 27
|
||||||
|
right: 0
|
||||||
|
updated: 2018-12-02 15:13:12
|
||||||
|
created: 2018-12-01 15:13:12
|
||||||
|
|
|
@ -59,7 +59,7 @@ func InitTestFileFixtures(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitTests handles the actual bootstrapping of the test env
|
// InitTests handles the actual bootstrapping of the test env
|
||||||
func InitTests() {
|
func InitTests(loadFixtures bool) {
|
||||||
var err error
|
var err error
|
||||||
x, err = db.CreateTestEngine()
|
x, err = db.CreateTestEngine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,9 +71,11 @@ func InitTests() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.InitTestFixtures("files")
|
if loadFixtures {
|
||||||
if err != nil {
|
err = db.InitTestFixtures("files")
|
||||||
log.Fatal(err)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InitTestFileHandler()
|
InitTestFileHandler()
|
||||||
|
|
|
@ -23,6 +23,6 @@ import (
|
||||||
|
|
||||||
// TestMain is the main test function used to bootstrap the test env
|
// TestMain is the main test function used to bootstrap the test env
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
InitTests()
|
InitTests(true)
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ const vtodo = `BEGIN:VCALENDAR
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
METHOD:PUBLISH
|
METHOD:PUBLISH
|
||||||
X-PUBLISHED-TTL:PT4H
|
X-PUBLISHED-TTL:PT4H
|
||||||
X-WR-CALNAME:List 26 for Caldav tests
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
PRODID:-//Vikunja Todo App//EN
|
PRODID:-//Vikunja Todo App//EN
|
||||||
BEGIN:VTODO
|
BEGIN:VTODO
|
||||||
UID:uid
|
UID:uid
|
||||||
|
@ -42,22 +42,22 @@ END:VCALENDAR`
|
||||||
|
|
||||||
func TestCaldav(t *testing.T) {
|
func TestCaldav(t *testing.T) {
|
||||||
t.Run("Delivers VTODO for project", func(t *testing.T) {
|
t.Run("Delivers VTODO for project", func(t *testing.T) {
|
||||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "26"})
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "26"}, true)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
|
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
|
||||||
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:List 26 for Caldav tests")
|
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:Project 26 for Caldav tests")
|
||||||
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
|
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
|
||||||
assert.Contains(t, rec.Body.String(), "END:VTODO")
|
assert.Contains(t, rec.Body.String(), "END:VTODO")
|
||||||
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
|
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
|
||||||
})
|
})
|
||||||
t.Run("Import VTODO", func(t *testing.T) {
|
t.Run("Import VTODO", func(t *testing.T) {
|
||||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "26", "task": "uid"})
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "26", "task": "uid"}, true)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
})
|
})
|
||||||
t.Run("Export VTODO", func(t *testing.T) {
|
t.Run("Export VTODO", func(t *testing.T) {
|
||||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid-caldav-test"})
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid-caldav-test"}, true)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
assert.Contains(t, rec.Body.String(), "SUMMARY:Title Caldav Test")
|
assert.Contains(t, rec.Body.String(), "SUMMARY:Title Caldav Test")
|
||||||
|
@ -67,3 +67,366 @@ func TestCaldav(t *testing.T) {
|
||||||
assert.Contains(t, rec.Body.String(), "CATEGORIES:Label #4")
|
assert.Contains(t, rec.Body.String(), "CATEGORIES:Label #4")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here we check that the CALDAV implementation correctly supports subtasks:
|
||||||
|
func TestCaldavSubtasks(t *testing.T) {
|
||||||
|
const vtodoParentTask = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_parent_task
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav parent task
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
const vtodoChildTask1 = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_child1
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav child task 1
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
const vtodoChildTask2 = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_child2
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav child task 2
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
const vtodoGrandChildTask = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_grand_child
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav grand child task
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_child1
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
t.Run("Import parent task", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoParentTask, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Import children tasks", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask1, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask2, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Import grand child task", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoGrandChildTask, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check the relationship between all the tasks by fetching them one by one", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_parent_task")
|
||||||
|
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_child1")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_child2")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child1")
|
||||||
|
})
|
||||||
|
|
||||||
|
const vtodoEditedGrandChildTask = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_grand_child
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav grand child task edited
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_child1
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
t.Run("Update the grand child task again and check that the relation is still there", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoEditedGrandChildTask, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||||
|
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav grand child task edited")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child1")
|
||||||
|
})
|
||||||
|
|
||||||
|
const vtodoChildTask2WithoutRelation = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_child2
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav child task 2
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
t.Run("Remove the relation from the second child", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask2WithoutRelation, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
|
||||||
|
// Check that the relation was removed from the DB, and isn't returned anymore:
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_child2")
|
||||||
|
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||||
|
})
|
||||||
|
|
||||||
|
const vtodoGrandChildTaskNewParent = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_grand_child
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav grand child task edited
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
t.Run("Update the grand child task again and change its parent", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoGrandChildTaskNewParent, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we check that the CALDAV implementation correctly supports task relations from different lists:
|
||||||
|
func TestCaldavSubtasksDifferentLists(t *testing.T) {
|
||||||
|
const vtodoParentTask = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_parent_task
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav parent task
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
const vtodoChildTask1 = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 27 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_child1
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav child task 1
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
t.Run("Import parent task", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoParentTask, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Import child tasks into a different list", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask1, nil, map[string]string{"project": "27", "task": "uid_child1"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check the relationship between all the tasks by fetching them one by one", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_parent_task")
|
||||||
|
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "27", "task": "uid_child1"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_child1")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we check that subtasks are handled properly even if the children tasks are created before the parent tasks
|
||||||
|
func TestCaldavSubtasksInverseOrder(t *testing.T) {
|
||||||
|
const vtodoGrandChildTask = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_grand_child
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav grand child task
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_child1
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
const vtodoChildTask1 = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_child1
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav child task 1
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
const vtodoParentTask = `BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
METHOD:PUBLISH
|
||||||
|
X-PUBLISHED-TTL:PT4H
|
||||||
|
X-WR-CALNAME:Project 26 for Caldav tests
|
||||||
|
PRODID:-//Vikunja Todo App//EN
|
||||||
|
BEGIN:VTODO
|
||||||
|
UID:uid_parent_task
|
||||||
|
DTSTAMP:20230301T073337Z
|
||||||
|
SUMMARY:Caldav parent task
|
||||||
|
CREATED:20230301T073337Z
|
||||||
|
LAST-MODIFIED:20230301T073337Z
|
||||||
|
END:VTODO
|
||||||
|
END:VCALENDAR`
|
||||||
|
|
||||||
|
t.Run("Import grand child task", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoGrandChildTask, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Import children tasks", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask1, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Import parent task", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoParentTask, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check the relationship between all the tasks by fetching them one by one", func(t *testing.T) {
|
||||||
|
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_parent_task")
|
||||||
|
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav parent task")
|
||||||
|
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_child1")
|
||||||
|
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav child task 1")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||||
|
|
||||||
|
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||||
|
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||||
|
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||||
|
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav grand child task")
|
||||||
|
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child1")
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -58,20 +58,22 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestEnv() (e *echo.Echo, err error) {
|
func setupTestEnv(loadFixtures bool) (e *echo.Echo, err error) {
|
||||||
config.InitDefaultConfig()
|
config.InitDefaultConfig()
|
||||||
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
||||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||||
// Some tests use the file engine, so we'll need to initialize that
|
// Some tests use the file engine, so we'll need to initialize that
|
||||||
files.InitTests()
|
files.InitTests(loadFixtures)
|
||||||
user.InitTests()
|
user.InitTests()
|
||||||
models.SetupTests()
|
models.SetupTests()
|
||||||
events.Fake()
|
events.Fake()
|
||||||
keyvalue.InitStorage()
|
keyvalue.InitStorage()
|
||||||
|
|
||||||
err = db.LoadFixtures()
|
if loadFixtures {
|
||||||
if err != nil {
|
err = db.LoadFixtures()
|
||||||
return
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e = routes.NewEcho()
|
e = routes.NewEcho()
|
||||||
|
@ -79,9 +81,9 @@ func setupTestEnv() (e *echo.Echo, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func bootstrapTestRequest(t *testing.T, method string, payload string, queryParam url.Values) (c echo.Context, rec *httptest.ResponseRecorder) {
|
func bootstrapTestRequest(t *testing.T, method string, payload string, queryParam url.Values, loadFixtures bool) (c echo.Context, rec *httptest.ResponseRecorder) {
|
||||||
// Setup
|
// Setup
|
||||||
e, err := setupTestEnv()
|
e, err := setupTestEnv(loadFixtures)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Do the actual request
|
// Do the actual request
|
||||||
|
@ -95,7 +97,7 @@ func bootstrapTestRequest(t *testing.T, method string, payload string, queryPara
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) error, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) error, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, true)
|
||||||
err = handler(c)
|
err = handler(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -124,8 +126,8 @@ func addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c echo.
|
||||||
c.Set("user", tken)
|
c.Set("user", tken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRequestSetup(t *testing.T, method string, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, c echo.Context) {
|
func testRequestSetup(t *testing.T, method string, payload string, queryParams url.Values, urlParams map[string]string, loadFixtures bool) (rec *httptest.ResponseRecorder, c echo.Context) {
|
||||||
c, rec = bootstrapTestRequest(t, method, payload, queryParams)
|
c, rec = bootstrapTestRequest(t, method, payload, queryParams, loadFixtures)
|
||||||
|
|
||||||
var paramNames []string
|
var paramNames []string
|
||||||
var paramValues []string
|
var paramValues []string
|
||||||
|
@ -139,21 +141,21 @@ func testRequestSetup(t *testing.T, method string, payload string, queryParams u
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, true)
|
||||||
addUserTokenToContext(t, user, c)
|
addUserTokenToContext(t, user, c)
|
||||||
err = handler(c)
|
err = handler(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestRequestWithLinkShare(t *testing.T, method string, handler echo.HandlerFunc, share *models.LinkSharing, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
func newTestRequestWithLinkShare(t *testing.T, method string, handler echo.HandlerFunc, share *models.LinkSharing, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, true)
|
||||||
addLinkShareTokenToContext(t, share, c)
|
addLinkShareTokenToContext(t, share, c)
|
||||||
err = handler(c)
|
err = handler(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCaldavTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
func newCaldavTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string, loadFixtures bool) (rec *httptest.ResponseRecorder, err error) {
|
||||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, loadFixtures)
|
||||||
c.Request().Header.Set(echo.HeaderContentType, echo.MIMETextPlain)
|
c.Request().Header.Set(echo.HeaderContentType, echo.MIMETextPlain)
|
||||||
|
|
||||||
result, _ := caldav.BasicAuth(user.Username, "1234", c)
|
result, _ := caldav.BasicAuth(user.Username, "1234", c)
|
||||||
|
|
|
@ -59,7 +59,7 @@ func TestMain(m *testing.M) {
|
||||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||||
|
|
||||||
// Some tests use the file engine, so we'll need to initialize that
|
// Some tests use the file engine, so we'll need to initialize that
|
||||||
files.InitTests()
|
files.InitTests(true)
|
||||||
|
|
||||||
user.InitTests()
|
user.InitTests()
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import (
|
||||||
// TestMain is the main test function used to bootstrap the test env
|
// TestMain is the main test function used to bootstrap the test env
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
user.InitTests()
|
user.InitTests()
|
||||||
files.InitTests()
|
files.InitTests(true)
|
||||||
models.SetupTests()
|
models.SetupTests()
|
||||||
events.Fake()
|
events.Fake()
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
|
|
|
@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
|
||||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||||
|
|
||||||
// Some tests use the file engine, so we'll need to initialize that
|
// Some tests use the file engine, so we'll need to initialize that
|
||||||
files.InitTests()
|
files.InitTests(true)
|
||||||
user.InitTests()
|
user.InitTests()
|
||||||
models.SetupTests()
|
models.SetupTests()
|
||||||
events.Fake()
|
events.Fake()
|
||||||
|
|
|
@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
|
||||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||||
|
|
||||||
// Some tests use the file engine, so we'll need to initialize that
|
// Some tests use the file engine, so we'll need to initialize that
|
||||||
files.InitTests()
|
files.InitTests(true)
|
||||||
user.InitTests()
|
user.InitTests()
|
||||||
models.SetupTests()
|
models.SetupTests()
|
||||||
events.Fake()
|
events.Fake()
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package caldav
|
package caldav
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -294,6 +295,13 @@ func (vcls *VikunjaCaldavProjectStorage) CreateResource(rpath, content string) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vcls.task.ProjectID = vcls.project.ID
|
||||||
|
err = persistRelations(s, vcls.user, vcls.task, vTask.RelatedTasks)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.Rollback()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Commit(); err != nil {
|
if err := s.Commit(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -318,6 +326,10 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
|
||||||
// At this point, we already have the right task in vcls.task, so we can use that ID directly
|
// At this point, we already have the right task in vcls.task, so we can use that ID directly
|
||||||
vTask.ID = vcls.task.ID
|
vTask.ID = vcls.task.ID
|
||||||
|
|
||||||
|
// Explicitely set the ProjectID in case the task now belongs to a different project:
|
||||||
|
vTask.ProjectID = vcls.project.ID
|
||||||
|
vcls.task.ProjectID = vcls.project.ID
|
||||||
|
|
||||||
s := db.NewSession()
|
s := db.NewSession()
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
|
@ -345,6 +357,12 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = persistRelations(s, vcls.user, vcls.task, vTask.RelatedTasks)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.Rollback()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Commit(); err != nil {
|
if err := s.Commit(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -432,6 +450,88 @@ func persistLabels(s *xorm.Session, a web.Auth, task *models.Task, labels []*mod
|
||||||
return task.UpdateTaskLabels(s, a, labels)
|
return task.UpdateTaskLabels(s, a, labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When a VTODO entry doesn't have a parent anymore, but we do, we need to remove it as well.
|
||||||
|
func removeLegacyParentRelations(s *xorm.Session, a web.Auth, task *models.Task, newRelations map[models.RelationKind][]*models.Task) (err error) {
|
||||||
|
|
||||||
|
// Get the existing task with details:
|
||||||
|
existingTask := &models.Task{ID: task.ID}
|
||||||
|
err = existingTask.ReadOne(s, a)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through all the existing task's parent relationships:
|
||||||
|
if _, ok := existingTask.RelatedTasks[models.RelationKindParenttask]; ok {
|
||||||
|
for _, parentTask := range existingTask.RelatedTasks[models.RelationKindParenttask] {
|
||||||
|
|
||||||
|
// Check if the existing parent relation is in the new list:
|
||||||
|
parentRelationInNewList := slices.ContainsFunc(newRelations[models.RelationKindParenttask], func(newRelation *models.Task) bool { return newRelation.UID == parentTask.UID })
|
||||||
|
|
||||||
|
// Remove the relations if it's not there in the new list anymore:
|
||||||
|
if !parentRelationInNewList {
|
||||||
|
rel := models.TaskRelation{
|
||||||
|
TaskID: task.ID,
|
||||||
|
OtherTaskID: parentTask.ID,
|
||||||
|
RelationKind: models.RelationKindParenttask,
|
||||||
|
}
|
||||||
|
err = rel.Delete(s, a)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist new relations provided by the VTODO entry:
|
||||||
|
func persistRelations(s *xorm.Session, a web.Auth, task *models.Task, newRelations map[models.RelationKind][]*models.Task) (err error) {
|
||||||
|
|
||||||
|
// Remove existing "parent" relations that are not present in the new list:
|
||||||
|
err = removeLegacyParentRelations(s, a, task, newRelations)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the current relations exist:
|
||||||
|
for relationType, relatedTasks := range newRelations {
|
||||||
|
|
||||||
|
// Persist each relation independently:
|
||||||
|
for _, relatedTask := range relatedTasks {
|
||||||
|
|
||||||
|
// Get the task from the DB:
|
||||||
|
has, err := s.Get(relatedTask)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the related task doesn't exist, create a dummy one now in the same list.
|
||||||
|
// It'll probably be populated right after in a following request:
|
||||||
|
if !has {
|
||||||
|
relatedTask.ProjectID = task.ProjectID
|
||||||
|
relatedTask.Title = "UID-" + relatedTask.UID
|
||||||
|
err = relatedTask.Create(s, a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the relation:
|
||||||
|
rel := models.TaskRelation{
|
||||||
|
TaskID: task.ID,
|
||||||
|
OtherTaskID: relatedTask.ID,
|
||||||
|
RelationKind: relationType,
|
||||||
|
}
|
||||||
|
err = rel.Create(s, a)
|
||||||
|
if err != nil && !models.IsErrRelationAlreadyExists(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// VikunjaProjectResourceAdapter holds the actual resource
|
// VikunjaProjectResourceAdapter holds the actual resource
|
||||||
type VikunjaProjectResourceAdapter struct {
|
type VikunjaProjectResourceAdapter struct {
|
||||||
project *models.ProjectWithTasksAndBuckets
|
project *models.ProjectWithTasksAndBuckets
|
||||||
|
|
Loading…
Reference in New Issue
Block a user