add issue tasks.
This commit is contained in:
@@ -5,6 +5,7 @@ package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/stufflog/server/graph/graphcore"
|
||||
"git.aiterp.net/stufflog/server/models"
|
||||
)
|
||||
|
||||
@@ -76,6 +76,15 @@ func (r *issueResolver) Status(ctx context.Context, obj *models.Issue) (*models.
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (r *issueResolver) Tasks(ctx context.Context, obj *models.Issue, filter *models.IssueTaskFilter) ([]*models.IssueTask, error) {
|
||||
if filter == nil {
|
||||
filter = &models.IssueTaskFilter{}
|
||||
}
|
||||
filter.IssueTaskIDs = []string{obj.ID}
|
||||
|
||||
return r.Database.IssueTasks().List(ctx, *filter)
|
||||
}
|
||||
|
||||
// Issue returns graphcore.IssueResolver implementation.
|
||||
func (r *Resolver) Issue() graphcore.IssueResolver { return &issueResolver{r} }
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package resolvers
|
||||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/stufflog/server/graph/graphcore"
|
||||
"git.aiterp.net/stufflog/server/graph/graphutil"
|
||||
"git.aiterp.net/stufflog/server/internal/xlerrors"
|
||||
"git.aiterp.net/stufflog/server/models"
|
||||
)
|
||||
|
||||
func (r *issueTaskResolver) EstimatedUnits(ctx context.Context, obj *models.IssueTask) (*int, error) {
|
||||
// TODO: Data loader
|
||||
activity, err := r.Database.Activities().Find(ctx, obj.ActivityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !activity.Countable || activity.UnitIsTimeSpent {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &obj.EstimatedUnits, nil
|
||||
}
|
||||
|
||||
func (r *issueTaskResolver) Issue(ctx context.Context, obj *models.IssueTask) (*models.Issue, error) {
|
||||
return r.Database.Issues().Find(ctx, obj.IssueID)
|
||||
}
|
||||
|
||||
func (r *issueTaskResolver) Activity(ctx context.Context, obj *models.IssueTask) (*models.Activity, error) {
|
||||
return r.Database.Activities().Find(ctx, obj.ActivityID)
|
||||
}
|
||||
|
||||
func (r *issueTaskResolver) Status(ctx context.Context, obj *models.IssueTask) (*models.ProjectStatus, error) {
|
||||
// The project ID is always the prefix before the first dash in the issue ID.
|
||||
split := strings.SplitN(obj.IssueID, "-", 2)
|
||||
if len(split) == 0 {
|
||||
return nil, errors.New("invalid issue ID")
|
||||
}
|
||||
projectID := split[0]
|
||||
|
||||
// Shortcut: if description isn't needed, resolve this with issue's properties.
|
||||
if !graphutil.SelectsAnyField(ctx, "description") {
|
||||
return &models.ProjectStatus{
|
||||
ProjectID: projectID,
|
||||
Stage: obj.StatusStage,
|
||||
Name: obj.StatusName,
|
||||
Description: "FAKE",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Find it in the database. TODO: DataLoader
|
||||
status, err := r.Database.ProjectStatuses().Find(ctx, projectID, obj.StatusName)
|
||||
if xlerrors.IsNotFound(err) {
|
||||
return &models.ProjectStatus{
|
||||
ProjectID: projectID,
|
||||
Stage: obj.StatusStage,
|
||||
Name: obj.StatusName,
|
||||
Description: "(Deleted or unknown status)",
|
||||
}, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If the stage doesn't match, sneakily correct it for next time.
|
||||
if status.Stage != obj.StatusStage {
|
||||
updatedTask := *obj
|
||||
updatedTask.StatusStage = status.Stage
|
||||
_ = r.Database.IssueTasks().Save(ctx, updatedTask)
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (r *issueTaskResolver) RemainingTime(ctx context.Context, obj *models.IssueTask) (time.Duration, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
}
|
||||
|
||||
func (r *issueTaskResolver) RemainingUnits(ctx context.Context, obj *models.IssueTask) (*int, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
}
|
||||
|
||||
// IssueTask returns graphcore.IssueTaskResolver implementation.
|
||||
func (r *Resolver) IssueTask() graphcore.IssueTaskResolver { return &issueTaskResolver{r} }
|
||||
|
||||
type issueTaskResolver struct{ *Resolver }
|
||||
@@ -6,6 +6,7 @@ package resolvers
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/stufflog/server/graph/graphcore"
|
||||
"git.aiterp.net/stufflog/server/internal/xlerrors"
|
||||
@@ -169,6 +170,120 @@ func (r *mutationResolver) CreateIssue(ctx context.Context, input graphcore.Issu
|
||||
return issue, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateIssueTask(ctx context.Context, input graphcore.IssueTaskCreateInput) (*models.IssueTask, error) {
|
||||
user := r.Auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, xlerrors.PermissionDenied
|
||||
}
|
||||
|
||||
issue, err := r.Database.Issues().Find(ctx, input.IssueID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
|
||||
return nil, xlerrors.PermissionDenied
|
||||
}
|
||||
|
||||
status, err := r.Database.ProjectStatuses().Find(ctx, issue.ProjectID, input.StatusName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
activity, err := r.Database.Activities().Find(ctx, input.ActivityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if activity.ProjectID != issue.ProjectID {
|
||||
return nil, xlerrors.NotFound("Activity")
|
||||
}
|
||||
|
||||
issueTask := &models.IssueTask{
|
||||
IssueID: issue.ID,
|
||||
ActivityID: activity.ID,
|
||||
CreatedTime: time.Now(),
|
||||
UpdatedTime: time.Now(),
|
||||
StatusStage: status.Stage,
|
||||
StatusName: status.Name,
|
||||
Name: input.Name,
|
||||
Description: input.Description,
|
||||
PointsMultiplier: 1.0,
|
||||
}
|
||||
if input.EstimatedUnits != nil && activity.Countable && !activity.UnitIsTimeSpent {
|
||||
issueTask.EstimatedUnits = *input.EstimatedUnits
|
||||
}
|
||||
if input.PointsMultiplier != nil && *input.PointsMultiplier > 0 {
|
||||
issueTask.PointsMultiplier = *input.PointsMultiplier
|
||||
}
|
||||
|
||||
issueTask, err = r.Database.IssueTasks().Insert(ctx, *issueTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return issueTask, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditIssueTask(ctx context.Context, input graphcore.IssueTaskEditInput) (*models.IssueTask, error) {
|
||||
user := r.Auth.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return nil, xlerrors.PermissionDenied
|
||||
}
|
||||
|
||||
task, err := r.Database.IssueTasks().Find(ctx, input.IssueTaskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
issue, err := r.Database.Issues().Find(ctx, task.IssueID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if perm, err := r.Auth.IssuePermission(ctx, *issue); err != nil || !perm.CanManageOwnIssue() {
|
||||
return nil, xlerrors.PermissionDenied
|
||||
}
|
||||
|
||||
if input.SetName != nil {
|
||||
task.Name = *input.SetName
|
||||
}
|
||||
if input.SetDescription != nil {
|
||||
task.Description = *input.SetDescription
|
||||
}
|
||||
if input.SetDueTime != nil {
|
||||
task.DueTime = input.SetDueTime
|
||||
}
|
||||
if input.SetEstimatedTime != nil {
|
||||
task.EstimatedTime = *input.SetEstimatedTime
|
||||
}
|
||||
if input.SetEstimatedUnits != nil {
|
||||
activity, err := r.Database.Activities().Find(ctx, task.ActivityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if activity.Countable && !activity.UnitIsTimeSpent {
|
||||
task.EstimatedUnits = *input.SetEstimatedUnits
|
||||
}
|
||||
}
|
||||
if input.SetPointsMultiplier != nil && *input.SetPointsMultiplier > 0 {
|
||||
task.PointsMultiplier = *input.SetPointsMultiplier
|
||||
}
|
||||
if input.SetStatusName != nil {
|
||||
status, err := r.Database.ProjectStatuses().Find(ctx, issue.ProjectID, *input.SetStatusName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task.StatusName = status.Name
|
||||
task.StatusStage = status.Stage
|
||||
}
|
||||
|
||||
err = r.Database.IssueTasks().Save(ctx, *task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) LoginUser(ctx context.Context, input graphcore.UserLoginInput) (*models.User, error) {
|
||||
return r.Auth.Login(ctx, input.Username, input.Password)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user