Documentation ¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var ( Eps = []*app.Endpoint{ { Description: "Create a new task", Path: (&task.Create{}).Path(), Timeout: 500, MaxBodyBytes: app.KB, IsPrivate: false, GetDefaultArgs: func() interface{} { return &task.Create{} }, GetExampleArgs: func() interface{} { return &task.Create{ Host: app.ExampleID(), Project: app.ExampleID(), Parent: app.ExampleID(), PrevSib: ptr.ID(app.ExampleID()), Name: "do it", Description: "do the thing you're supposed to do", IsParallel: true, User: ptr.ID(app.ExampleID()), TimeEst: 40, } }, GetExampleResponse: func() interface{} { return &task.CreateRes{ Parent: ExampleTask, Task: ExampleTask, } }, Handler: func(tlbx app.Tlbx, a interface{}) interface{} { args := a.(*task.Create) me := me.AuthedGet(tlbx) args.Name = StrTrimWS(args.Name) validate.Str("name", args.Name, nameMinLen, nameMaxLen) args.Description = StrTrimWS(args.Description) validate.Str("description", args.Description, 0, descriptionMaxLen) srv := service.Get(tlbx) tx := srv.Data().BeginWrite() defer tx.Rollback() epsutil.IMustHaveAccess(tlbx, tx, args.Host, args.Project, cnsts.RoleWriter) t := &task.Task{ ID: tlbx.NewID(), Parent: &args.Parent, FirstChild: nil, NextSib: nil, User: args.User, Name: args.Name, Description: args.Description, CreatedBy: me, CreatedOn: tlbx.Start(), TimeEst: args.TimeEst, TimeInc: 0, TimeSubMin: 0, TimeSubEst: 0, TimeSubInc: 0, CostEst: args.CostEst, CostInc: 0, CostSubEst: 0, CostSubInc: 0, FileN: 0, FileSize: 0, FileSubN: 0, FileSubSize: 0, ChildN: 0, DescN: 0, IsParallel: args.IsParallel, } if args.User != nil && !args.User.Equal(me) { epsutil.MustHaveAccess(tlbx, tx, args.Host, args.Project, args.User, cnsts.RoleWriter) } epsutil.MustLockProject(tx, args.Host, args.Project) // get correct next sib value from either prevSib if // specified or parent.FirstChild otherwise. Then update prevSibs nextSib value // or parents firstChild value depending on the scenario. var prevSib *task.Task if args.PrevSib != nil { prevSib = GetOne(tx, args.Host, args.Project, *args.PrevSib) app.ReturnIf(prevSib == nil, http.StatusNotFound, "prevSib not found") app.BadReqIf(prevSib.Parent == nil || !prevSib.Parent.Equal(args.Parent), "prevSib and parent args mismatch") t.NextSib = prevSib.NextSib prevSib.NextSib = &t.ID _, err := tx.Exec(`UPDATE tasks SET nextSib=? WHERE host=? AND project=? AND id=?`, t.ID, args.Host, args.Project, prevSib.ID) PanicOn(err) } else { parent := GetOne(tx, args.Host, args.Project, args.Parent) app.ReturnIf(parent == nil, http.StatusNotFound, "parent not found") t.NextSib = parent.FirstChild _, err := tx.Exec(`UPDATE tasks SET firstChild=? WHERE host=? AND project=? AND id=?`, t.ID, args.Host, args.Project, t.Parent) PanicOn(err) } _, err := tx.Exec(Strf(`INSERT INTO tasks (host, project, %s) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, sql_task_columns), args.Host, args.Project, t.ID, t.Parent, t.FirstChild, t.NextSib, t.User, t.Name, t.Description, t.CreatedBy, t.CreatedOn, t.TimeEst, t.TimeInc, t.TimeSubMin, t.TimeSubEst, t.TimeSubInc, t.CostEst, t.CostInc, t.CostSubEst, t.CostSubInc, t.FileN, t.FileSize, t.FileSubN, t.FileSubSize, t.ChildN, t.DescN, t.IsParallel) PanicOn(err) ancestors := epsutil.SetAncestralChainAggregateValuesFromTask(tx, args.Host, args.Project, args.Parent) epsutil.LogActivity(tlbx, tx, args.Host, args.Project, t.ID, t.ID, cnsts.TypeTask, cnsts.ActionCreated, &t.Name, nil, nil, ancestors) p := GetOne(tx, args.Host, args.Project, *t.Parent) tx.Commit() return &task.CreateRes{ Parent: p, Task: t, } }, }, { Description: "Update a task", Path: (&task.Update{}).Path(), Timeout: 500, MaxBodyBytes: app.KB, IsPrivate: false, GetDefaultArgs: func() interface{} { return &task.Update{} }, GetExampleArgs: func() interface{} { return &task.Update{ Host: app.ExampleID(), Project: app.ExampleID(), ID: app.ExampleID(), Parent: &field.ID{V: app.ExampleID()}, PrevSib: &field.IDPtr{V: ptr.ID(app.ExampleID())}, Name: &field.String{V: "new name"}, Description: &field.String{V: "new description"}, IsParallel: &field.Bool{V: true}, User: &field.IDPtr{V: ptr.ID(app.ExampleID())}, TimeEst: &field.UInt64{V: 123}, CostEst: &field.UInt64{V: 123}, } }, GetExampleResponse: func() interface{} { return &task.UpdateRes{ OldParent: ExampleTask, NewParent: ExampleTask, Task: ExampleTask, } }, Handler: func(tlbx app.Tlbx, a interface{}) interface{} { args := a.(*task.Update) me := me.AuthedGet(tlbx) if args.Parent == nil && args.PrevSib == nil && args.Name == nil && args.Description == nil && args.IsParallel == nil && args.User == nil && args.TimeEst == nil && args.CostEst == nil { return nil } tx := service.Get(tlbx).Data().BeginWrite() defer tx.Rollback() if args.ID.Equal(args.Project) { app.ReturnIf(!me.Equal(args.Host), http.StatusForbidden, "only the host may edit the project root node") app.ReturnIf(args.User != nil, http.StatusForbidden, "user value is not settable on the project root node") app.ReturnIf(args.Parent != nil, http.StatusForbidden, "parent value is not settable on the project root node") app.ReturnIf(args.PrevSib != nil, http.StatusForbidden, "prevSib value is not settable on the project root node") } else { epsutil.IMustHaveAccess(tlbx, tx, args.Host, args.Project, cnsts.RoleWriter) } treeUpdateRequired := false simpleUpdateRequired := false if args.Parent != nil || args.PrevSib != nil || args.CostEst != nil || args.TimeEst != nil || args.IsParallel != nil { epsutil.MustLockProject(tx, args.Host, args.Project) } t := GetOne(tx, args.Host, args.Project, args.ID) app.ReturnIf(t == nil, http.StatusNotFound, "task not found") var newParent, newPrevSib, oldParent, oldPrevSib *task.Task if args.Parent != nil { if !args.Parent.V.Equal(*t.Parent) { var newNextSib *ID newParent = GetOne(tx, args.Host, args.Project, args.Parent.V) app.ReturnIf(newParent == nil, http.StatusNotFound, "parent not found") row := tx.QueryRow(Strf("%s SELECT COUNT(*)=1 FROM ancestors a WHERE id=?", sql_ancestors_cte), args.Host, args.Project, args.Parent.V, args.Host, args.Project, args.ID) ancestorLoopDetected := false PanicOn(row.Scan(&ancestorLoopDetected)) app.BadReqIf(ancestorLoopDetected || t.ID.Equal(args.Parent.V), "ancestor loop detected, invalid parent value") if args.PrevSib != nil && args.PrevSib.V != nil { app.BadReqIf(args.PrevSib.V.Equal(args.ID), "sib loop detected, invalid prevSib value") newPrevSib = GetOne(tx, args.Host, args.Project, *args.PrevSib.V) app.ReturnIf(newPrevSib == nil, http.StatusNotFound, "prevSib not found") app.BadReqIf(!newPrevSib.Parent.Equal(args.Parent.V), "prevSibs parent does not match the specified parent arg") newNextSib = newPrevSib.NextSib newPrevSib.NextSib = &t.ID } else { newNextSib = newParent.FirstChild newParent.FirstChild = &t.ID } if newParent.NextSib != nil && newParent.NextSib.Equal(args.ID) { oldPrevSib = newParent } else { oldPrevSib = getPrevSib(tx, args.Host, args.Project, args.ID) } if newPrevSib != nil && newPrevSib.ID.Equal(*t.Parent) { oldParent = newPrevSib } else { oldParent = GetOne(tx, args.Host, args.Project, *t.Parent) } app.ReturnIf(oldParent == nil, http.StatusNotFound, "oldParent not found") if oldPrevSib != nil { oldPrevSib.NextSib = t.NextSib } else { oldParent.FirstChild = t.NextSib } t.Parent = &newParent.ID t.NextSib = newNextSib treeUpdateRequired = true } else { args.Parent = nil } } if args.Parent == nil && args.PrevSib != nil { oldPrevSib = getPrevSib(tx, args.Host, args.Project, args.ID) if !((oldPrevSib == nil && args.PrevSib.V == nil) || (oldPrevSib != nil && args.PrevSib.V != nil && oldPrevSib.ID.Equal(*args.PrevSib.V))) { var newNextSib *ID oldParent = GetOne(tx, args.Host, args.Project, *t.Parent) PanicIf(oldParent == nil, "oldParent not found") if args.PrevSib.V != nil { if oldParent.FirstChild.Equal(t.ID) { oldParent.FirstChild = t.NextSib } else { oldParent = nil } app.BadReqIf(args.PrevSib.V.Equal(args.ID), "sib loop detected, invalid prevSib value") newPrevSib = GetOne(tx, args.Host, args.Project, *args.PrevSib.V) app.ReturnIf(newPrevSib == nil, http.StatusNotFound, "prevSib not found") app.BadReqIf(!newPrevSib.Parent.Equal(*t.Parent), "prevSibs parent does not match the current tasks parent") newNextSib = newPrevSib.NextSib newPrevSib.NextSib = &t.ID } else { newNextSib = oldParent.FirstChild oldParent.FirstChild = &t.ID } if oldPrevSib != nil { oldPrevSib.NextSib = t.NextSib } t.NextSib = newNextSib treeUpdateRequired = true } else { oldPrevSib = nil args.PrevSib = nil } } nameUpdated := false if args.Name != nil && t.Name != args.Name.V { args.Name.V = StrTrimWS(args.Name.V) validate.Str("name", args.Name.V, nameMinLen, nameMaxLen) t.Name = args.Name.V simpleUpdateRequired = true nameUpdated = true } if args.Description != nil && args.Description.V != t.Description { args.Description.V = StrTrimWS(args.Description.V) validate.Str("description", args.Description.V, 0, descriptionMaxLen) t.Description = args.Description.V simpleUpdateRequired = true } isParallelChanged := false if args.IsParallel != nil && t.IsParallel != args.IsParallel.V { t.IsParallel = args.IsParallel.V treeUpdateRequired = true isParallelChanged = true } if args.User != nil && ((args.User.V == nil && t.User != nil) || (args.User.V != nil && t.User == nil) || (args.User.V != nil && t.User != nil && !t.User.Equal(*args.User.V))) { if args.User.V != nil && !args.User.V.Equal(me) { epsutil.MustHaveAccess(tlbx, tx, args.Host, args.Project, args.User.V, cnsts.RoleWriter) } t.User = args.User.V simpleUpdateRequired = true } if args.TimeEst != nil && t.TimeEst != args.TimeEst.V { t.TimeEst = args.TimeEst.V treeUpdateRequired = true } if args.CostEst != nil && t.CostEst != args.CostEst.V { t.CostEst = args.CostEst.V treeUpdateRequired = true } update := func(ts ...*task.Task) { updated := map[string]bool{} for _, t := range ts { if t != nil { idStr := t.ID.String() if !updated[idStr] { updated[idStr] = true _, err := tx.Exec(`UPDATE tasks SET parent=?, firstChild=?, nextSib=?, name=?, description=?, isParallel=?, user=?, timeEst=?, costEst=? WHERE host=? AND project=? AND id=?`, t.Parent, t.FirstChild, t.NextSib, t.Name, t.Description, t.IsParallel, t.User, t.TimeEst, t.CostEst, args.Host, args.Project, t.ID) PanicOn(err) } } } } if simpleUpdateRequired || treeUpdateRequired { var ancestors IDs update(t, oldParent, oldPrevSib, newParent, newPrevSib) if treeUpdateRequired { if isParallelChanged { ancestors = epsutil.SetAncestralChainAggregateValuesFromTask(tx, args.Host, args.Project, t.ID) if len(ancestors) > 0 { t = GetOne(tx, args.Host, args.Project, t.ID) } } if newParent != nil { if len(ancestors) < 2 { ancestors = epsutil.SetAncestralChainAggregateValuesFromTask(tx, args.Host, args.Project, newParent.ID) } moreAncestors := epsutil.SetAncestralChainAggregateValuesFromTask(tx, args.Host, args.Project, oldParent.ID) ancestors = IDsMerge(ancestors, moreAncestors) } else if t.Parent != nil { if len(ancestors) < 2 { ancestors = epsutil.SetAncestralChainAggregateValuesFromTask(tx, args.Host, args.Project, *t.Parent) } } } if args.Name != nil { args.Name.V = StrEllipsis(args.Name.V, 50) } if args.Description != nil { args.Description.V = StrEllipsis(args.Description.V, 50) } epsutil.LogActivity(tlbx, tx, args.Host, args.Project, args.ID, args.ID, cnsts.TypeTask, cnsts.ActionUpdated, &t.Name, struct { Parent *field.ID `json:"parent,omitempty"` PrevSib *field.IDPtr `json:"prevSib,omitempty"` Name *field.String `json:"name,omitempty"` Description *field.String `json:"description,omitempty"` IsParallel *field.Bool `json:"isParallel,omitempty"` User *field.IDPtr `json:"user,omitempty"` TimeEst *field.UInt64 `json:"timeEst,omitempty"` CostEst *field.UInt64 `json:"costEst,omitempty"` }{ Parent: args.Parent, PrevSib: args.PrevSib, Name: args.Name, Description: args.Description, IsParallel: args.IsParallel, User: args.User, TimeEst: args.TimeEst, CostEst: args.CostEst, }, nil, ancestors) } if nameUpdated { epsutil.ActivityItemRename(tx, args.Host, args.Project, args.ID, t.Name, true) } res := &task.UpdateRes{ Task: t, } if args.Parent != nil { res.OldParent = GetOne(tx, args.Host, args.Project, oldParent.ID) res.NewParent = GetOne(tx, args.Host, args.Project, args.Parent.V) } else if treeUpdateRequired && t.Parent != nil { res.OldParent = GetOne(tx, args.Host, args.Project, *t.Parent) } tx.Commit() return res }, }, { Description: "Delete a task (returns the parent of the deleted task)", Path: (&task.Delete{}).Path(), Timeout: 0, MaxBodyBytes: app.KB, IsPrivate: false, GetDefaultArgs: func() interface{} { return &task.Delete{} }, GetExampleArgs: func() interface{} { return &task.Delete{ Host: app.ExampleID(), Project: app.ExampleID(), ID: app.ExampleID(), } }, GetExampleResponse: func() interface{} { return ExampleTask }, Handler: func(tlbx app.Tlbx, a interface{}) interface{} { args := a.(*task.Delete) app.BadReqIf(args.ID.Equal(args.Project), "use project delete endpoint to delete a project node") me := me.AuthedGet(tlbx) srv := service.Get(tlbx) tx := srv.Data().BeginWrite() defer tx.Rollback() role := epsutil.MustGetRole(tlbx, tx, args.Host, args.Project, me) app.ReturnIf(role == cnsts.RoleReader, http.StatusForbidden, "you don't have permission to delete a task") epsutil.MustLockProject(tx, args.Host, args.Project) t := GetOne(tx, args.Host, args.Project, args.ID) app.ReturnIf(t == nil, http.StatusNotFound, "task not found") app.BadReqIf(t.DescN > 100, "may not delete more than 100 task per delete action") app.ReturnIf(role == cnsts.RoleWriter && (!t.CreatedBy.Equal(me) || t.DescN > 0 || t.CreatedOn.Before(Now().Add(-1*time.Hour))), http.StatusForbidden, "you may only delete your own tasks within an hour of creating them and they must have no children") prevNode := getPrevSib(tx, args.Host, args.Project, args.ID) if prevNode == nil { prevNode = GetOne(tx, args.Host, args.Project, *t.Parent) PanicIf(!prevNode.FirstChild.Equal(t.ID), "invalid data detected, deleting task %s", t.ID) prevNode.FirstChild = t.NextSib } else { prevNode.NextSib = t.NextSib } tasksWithFiles := make(IDs, 0, t.DescN+1) tasksToDelete := make(IDs, 0, t.DescN+1) tx.Query(func(rows *sqlx.Rows) { for rows.Next() { i := ID{} hasFiles := false PanicOn(rows.Scan(&i, &hasFiles)) tasksToDelete = append(tasksToDelete, i) if hasFiles { tasksWithFiles = append(tasksWithFiles, i) } } }, `WITH RECURSIVE descendants (id, hasFiles) AS (SELECT id, fileN > 0 AS hasFiles FROM tasks WHERE host=? AND project=? AND id=? UNION SELECT t.id, t.fileN > 0 AS hasFiles FROM tasks t, descendants d WHERE t.host=? AND t.project=? AND t.parent=d.id) CYCLE id RESTRICT SELECT id, hasFiles FROM descendants`, args.Host, args.Project, args.ID, args.Host, args.Project) if len(tasksToDelete) > 0 { queryArgs := make([]interface{}, 0, len(tasksToDelete)+2) queryArgs = append(queryArgs, args.Host, args.Project) queryArgs = append(queryArgs, tasksToDelete.ToIs()...) _, err := tx.Exec(Strf(`DELETE FROM tasks WHERE host=? AND project=? %s`, sqlh.InCondition(true, `id`, len(tasksToDelete))), queryArgs...) PanicOn(err) _, err = tx.Exec(`UPDATE tasks SET firstChild=?, nextSib=? WHERE host=? AND project=? AND id=?`, prevNode.FirstChild, prevNode.NextSib, args.Host, args.Project, prevNode.ID) PanicOn(err) ancestors := epsutil.SetAncestralChainAggregateValuesFromTask(tx, args.Host, args.Project, *t.Parent) t.Name = StrEllipsis(t.Name, 50) t.Description = StrEllipsis(t.Description, 50) epsutil.LogActivity(tlbx, tx, args.Host, args.Project, args.ID, args.ID, cnsts.TypeTask, cnsts.ActionDeleted, &t.Name, t, nil, ancestors) sql_in_tasks := sqlh.InCondition(true, `task`, len(tasksToDelete)) _, err = tx.Exec(Strf(`DELETE FROM vitems WHERE host=? AND project=? %s`, sql_in_tasks), queryArgs...) PanicOn(err) _, err = tx.Exec(Strf(`DELETE FROM files WHERE host=? AND project=? %s`, sql_in_tasks), queryArgs...) PanicOn(err) _, err = tx.Exec(Strf(`DELETE FROM comments WHERE host=? AND project=? %s`, sql_in_tasks), queryArgs...) PanicOn(err) _, err = tx.Exec(Strf(`UPDATE activities SET taskDeleted=1, itemDeleted=1 WHERE host=? AND project=? %s`, sql_in_tasks), queryArgs...) PanicOn(err) for _, t := range tasksWithFiles { srv.Store().MustDeletePrefix(cnsts.FileBucket, epsutil.StorePrefix(args.Host, args.Project, t)) } } parent := GetOne(tx, args.Host, args.Project, *t.Parent) tx.Commit() return parent }, }, { Description: "get a task", Path: (&task.Get{}).Path(), Timeout: 500, MaxBodyBytes: app.KB, IsPrivate: false, GetDefaultArgs: func() interface{} { return &task.Get{} }, GetExampleArgs: func() interface{} { return &task.Get{ Host: app.ExampleID(), Project: app.ExampleID(), ID: app.ExampleID(), } }, GetExampleResponse: func() interface{} { return ExampleTask }, Handler: func(tlbx app.Tlbx, a interface{}) interface{} { args := a.(*task.Get) tx := service.Get(tlbx).Data().BeginRead() defer tx.Rollback() epsutil.IMustHaveAccess(tlbx, tx, args.Host, args.Project, cnsts.RoleReader) t := GetOne(tx, args.Host, args.Project, args.ID) app.ReturnIf(t == nil, http.StatusNotFound, "task not found") tx.Commit() return t }, }, { Description: "get task ancestors", Path: (&task.GetAncestors{}).Path(), Timeout: 500, MaxBodyBytes: app.KB, IsPrivate: false, GetDefaultArgs: func() interface{} { return &task.GetAncestors{ Limit: 10, } }, GetExampleArgs: func() interface{} { return &task.GetAncestors{ Host: app.ExampleID(), Project: app.ExampleID(), ID: app.ExampleID(), Limit: 20, } }, GetExampleResponse: func() interface{} { return &task.GetSetRes{ Set: []*task.Task{ExampleTask}, More: true, } }, Handler: func(tlbx app.Tlbx, a interface{}) interface{} { args := a.(*task.GetAncestors) tx := service.Get(tlbx).Data().BeginRead() defer tx.Rollback() epsutil.IMustHaveAccess(tlbx, tx, args.Host, args.Project, cnsts.RoleReader) args.Limit = sqlh.Limit100(args.Limit) res := &task.GetSetRes{ Set: make([]*task.Task, 0, args.Limit), More: false, } PanicOn(tx.Query(func(rows *sqlx.Rows) { iLimit := int(args.Limit) for rows.Next() { if len(res.Set)+1 == iLimit { res.More = true break } t, err := Scan(rows) PanicOn(err) res.Set = append(res.Set, t) } }, Strf(`%s SELECT %s FROM tasks t JOIN ancestors a ON t.id = a.id WHERE t.host=? AND t.project=? ORDER BY a.n ASC LIMIT ?`, sql_ancestors_cte, Sql_task_columns_prefixed), args.Host, args.Project, args.ID, args.Host, args.Project, args.Host, args.Project, args.Limit)) tx.Commit() return res }, }, { Description: "get task children", Path: (&task.GetChildren{}).Path(), Timeout: 500, MaxBodyBytes: app.KB, IsPrivate: false, GetDefaultArgs: func() interface{} { return &task.GetChildren{ Limit: 10, } }, GetExampleArgs: func() interface{} { return &task.GetChildren{ Host: app.ExampleID(), Project: app.ExampleID(), ID: app.ExampleID(), After: ptr.ID(app.ExampleID()), Limit: 20, } }, GetExampleResponse: func() interface{} { return &task.GetSetRes{ Set: []*task.Task{ExampleTask}, More: true, } }, Handler: func(tlbx app.Tlbx, a interface{}) interface{} { args := a.(*task.GetChildren) tx := service.Get(tlbx).Data().BeginRead() defer tx.Rollback() epsutil.IMustHaveAccess(tlbx, tx, args.Host, args.Project, cnsts.RoleReader) args.Limit = sqlh.Limit100(args.Limit) res := &task.GetSetRes{ Set: make([]*task.Task, 0, args.Limit), More: false, } var sql_filter string queryArgs := make([]interface{}, 0, 10) queryArgs = append(queryArgs, args.Host, args.Project) if args.After == nil { sql_filter = `firstChild` queryArgs = append(queryArgs, args.ID) } else { sql_filter = `nextSib` queryArgs = append(queryArgs, *args.After) } queryArgs = append(queryArgs, args.Host, args.Project, args.Host, args.Project, args.Limit) PanicOn(tx.Query(func(rows *sqlx.Rows) { iLimit := int(args.Limit) for rows.Next() { if len(res.Set)+1 == iLimit { res.More = true break } t, err := Scan(rows) PanicOn(err) res.Set = append(res.Set, t) } }, Strf(`WITH RECURSIVE sibs (n, id) AS (SELECT 0 AS n, %s AS id FROM tasks WHERE host=? AND project=? AND id=? UNION SELECT s.n + 1 AS n, t.nextSib AS id FROM tasks t, sibs s WHERE t.host=? AND t.project=? AND t.id = s.id) CYCLE id RESTRICT SELECT %s FROM tasks t JOIN sibs s ON t.id = s.id WHERE t.host=? AND t.project=? ORDER BY s.n ASC LIMIT ?`, sql_filter, Sql_task_columns_prefixed), queryArgs...)) tx.Commit() return res }, }, { Description: "get task tree", Path: (&task.GetTree{}).Path(), Timeout: 500, MaxBodyBytes: app.KB, IsPrivate: false, GetDefaultArgs: func() interface{} { return &task.GetTree{} }, GetExampleArgs: func() interface{} { return &task.GetTree{ Host: app.ExampleID(), Project: app.ExampleID(), ID: app.ExampleID(), } }, GetExampleResponse: func() interface{} { return &task.GetTreeRes{ app.ExampleID(): ExampleTask, } }, Handler: func(tlbx app.Tlbx, a interface{}) interface{} { args := a.(*task.GetTree) tx := service.Get(tlbx).Data().BeginRead() defer tx.Rollback() epsutil.IMustHaveAccess(tlbx, tx, args.Host, args.Project, cnsts.RoleReader) t := GetOne(tx, args.Host, args.Project, args.ID) app.ReturnIf(t == nil, http.StatusNotFound, "task not found") app.BadReqIf(t.DescN > 1000, "get tree may only be called on a task with descN <= 1000") res := task.GetTreeRes{ t.ID: t, } if t.DescN > 0 { queryArgs := make([]interface{}, 0, 10) queryArgs = append(queryArgs, args.Host, args.Project, args.ID, args.Host, args.Project, args.Host, args.Project) PanicOn(tx.Query(func(rows *sqlx.Rows) { for rows.Next() { t, err := Scan(rows) PanicOn(err) res[t.ID] = t } }, Strf(`WITH RECURSIVE nodes (id) AS (SELECT id FROM tasks WHERE host=? AND project=? AND parent=? UNION SELECT t.id FROM tasks t JOIN nodes n ON t.parent = n.id WHERE t.host=? AND t.project=?) CYCLE id RESTRICT SELECT %s FROM tasks t JOIN nodes n ON t.id = n.id WHERE t.host=? AND t.project=?`, Sql_task_columns_prefixed), queryArgs...)) } tx.Commit() return res }, }, } ExampleTask = &task.Task{ ID: app.ExampleID(), Parent: ptr.ID(app.ExampleID()), FirstChild: ptr.ID(app.ExampleID()), NextSib: ptr.ID(app.ExampleID()), User: ptr.ID(app.ExampleID()), Name: "do it", Description: "do that thing you're supposed to do", CreatedBy: app.ExampleID(), CreatedOn: app.ExampleTime(), TimeSubMin: 100, TimeEst: 100, TimeInc: 100, TimeSubEst: 100, TimeSubInc: 100, CostEst: 100, CostInc: 100, CostSubEst: 100, CostSubInc: 100, FileN: 100, FileSize: 100, FileSubN: 100, FileSubSize: 100, ChildN: 100, DescN: 100, IsParallel: true, } )
View Source
var (
Sql_task_columns_prefixed = `` /* 291-byte string literal not displayed */
)
Functions ¶
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.