package graph // 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" "database/sql" "encoding/json" "errors" "strconv" "time" mTasks "github.com/RichardKnop/machinery/v1/tasks" "github.com/go-redis/redis/v8" "github.com/google/uuid" "github.com/jinzhu/now" "github.com/jordanknott/taskcafe/internal/db" "github.com/jordanknott/taskcafe/internal/logger" log "github.com/sirupsen/logrus" ) func (r *mutationResolver) CreateTaskChecklist(ctx context.Context, input CreateTaskChecklist) (*db.TaskChecklist, error) { createdAt := time.Now().UTC() taskChecklist, err := r.Repository.CreateTaskChecklist(ctx, db.CreateTaskChecklistParams{ TaskID: input.TaskID, CreatedAt: createdAt, Name: input.Name, Position: input.Position, }) if err != nil { return &db.TaskChecklist{}, err } return &taskChecklist, nil } func (r *mutationResolver) DeleteTaskChecklist(ctx context.Context, input DeleteTaskChecklist) (*DeleteTaskChecklistPayload, error) { taskChecklist, err := r.Repository.GetTaskChecklistByID(ctx, input.TaskChecklistID) if err != nil { return &DeleteTaskChecklistPayload{Ok: false}, err } err = r.Repository.DeleteTaskChecklistByID(ctx, input.TaskChecklistID) if err != nil { return &DeleteTaskChecklistPayload{Ok: false}, err } return &DeleteTaskChecklistPayload{Ok: true, TaskChecklist: &taskChecklist}, nil } func (r *mutationResolver) UpdateTaskChecklistName(ctx context.Context, input UpdateTaskChecklistName) (*db.TaskChecklist, error) { checklist, err := r.Repository.UpdateTaskChecklistName(ctx, db.UpdateTaskChecklistNameParams{TaskChecklistID: input.TaskChecklistID, Name: input.Name}) if err != nil { return &db.TaskChecklist{}, err } return &checklist, nil } func (r *mutationResolver) CreateTaskChecklistItem(ctx context.Context, input CreateTaskChecklistItem) (*db.TaskChecklistItem, error) { createdAt := time.Now().UTC() taskChecklistItem, err := r.Repository.CreateTaskChecklistItem(ctx, db.CreateTaskChecklistItemParams{ TaskChecklistID: input.TaskChecklistID, CreatedAt: createdAt, Name: input.Name, Position: input.Position, }) if err != nil { return &db.TaskChecklistItem{}, err } return &taskChecklistItem, nil } func (r *mutationResolver) UpdateTaskChecklistLocation(ctx context.Context, input UpdateTaskChecklistLocation) (*UpdateTaskChecklistLocationPayload, error) { checklist, err := r.Repository.UpdateTaskChecklistPosition(ctx, db.UpdateTaskChecklistPositionParams{Position: input.Position, TaskChecklistID: input.TaskChecklistID}) if err != nil { return &UpdateTaskChecklistLocationPayload{}, err } return &UpdateTaskChecklistLocationPayload{Checklist: &checklist}, nil } func (r *mutationResolver) UpdateTaskChecklistItemName(ctx context.Context, input UpdateTaskChecklistItemName) (*db.TaskChecklistItem, error) { task, err := r.Repository.UpdateTaskChecklistItemName(ctx, db.UpdateTaskChecklistItemNameParams{TaskChecklistItemID: input.TaskChecklistItemID, Name: input.Name, }) if err != nil { return &db.TaskChecklistItem{}, err } return &task, nil } func (r *mutationResolver) SetTaskChecklistItemComplete(ctx context.Context, input SetTaskChecklistItemComplete) (*db.TaskChecklistItem, error) { item, err := r.Repository.SetTaskChecklistItemComplete(ctx, db.SetTaskChecklistItemCompleteParams{TaskChecklistItemID: input.TaskChecklistItemID, Complete: input.Complete}) if err != nil { return &db.TaskChecklistItem{}, err } return &item, nil } func (r *mutationResolver) DeleteTaskChecklistItem(ctx context.Context, input DeleteTaskChecklistItem) (*DeleteTaskChecklistItemPayload, error) { item, err := r.Repository.GetTaskChecklistItemByID(ctx, input.TaskChecklistItemID) if err != nil { return &DeleteTaskChecklistItemPayload{ Ok: false, TaskChecklistItem: &db.TaskChecklistItem{}, }, err } err = r.Repository.DeleteTaskChecklistItem(ctx, input.TaskChecklistItemID) if err != nil { return &DeleteTaskChecklistItemPayload{ Ok: false, TaskChecklistItem: &db.TaskChecklistItem{}, }, err } return &DeleteTaskChecklistItemPayload{ Ok: true, TaskChecklistItem: &item, }, err } func (r *mutationResolver) UpdateTaskChecklistItemLocation(ctx context.Context, input UpdateTaskChecklistItemLocation) (*UpdateTaskChecklistItemLocationPayload, error) { currentChecklistItem, err := r.Repository.GetTaskChecklistItemByID(ctx, input.TaskChecklistItemID) checklistItem, err := r.Repository.UpdateTaskChecklistItemLocation(ctx, db.UpdateTaskChecklistItemLocationParams{TaskChecklistID: input.TaskChecklistID, TaskChecklistItemID: input.TaskChecklistItemID, Position: input.Position}) if err != nil { return &UpdateTaskChecklistItemLocationPayload{}, err } return &UpdateTaskChecklistItemLocationPayload{PrevChecklistID: currentChecklistItem.TaskChecklistID, TaskChecklistID: input.TaskChecklistID, ChecklistItem: &checklistItem}, err } func (r *mutationResolver) CreateTaskComment(ctx context.Context, input *CreateTaskComment) (*CreateTaskCommentPayload, error) { userID, _ := GetUserID(ctx) createdAt := time.Now().UTC() comment, err := r.Repository.CreateTaskComment(ctx, db.CreateTaskCommentParams{ TaskID: input.TaskID, CreatedAt: createdAt, CreatedBy: userID, Message: input.Message, }) return &CreateTaskCommentPayload{Comment: &comment, TaskID: input.TaskID}, err } func (r *mutationResolver) DeleteTaskComment(ctx context.Context, input *DeleteTaskComment) (*DeleteTaskCommentPayload, error) { task, err := r.Repository.DeleteTaskCommentByID(ctx, input.CommentID) return &DeleteTaskCommentPayload{TaskID: task.TaskID, CommentID: input.CommentID}, err } func (r *mutationResolver) UpdateTaskComment(ctx context.Context, input *UpdateTaskComment) (*UpdateTaskCommentPayload, error) { updatedAt := time.Now().UTC() comment, err := r.Repository.UpdateTaskComment(ctx, db.UpdateTaskCommentParams{ TaskCommentID: input.CommentID, UpdatedAt: sql.NullTime{Valid: true, Time: updatedAt}, Message: input.Message, }) return &UpdateTaskCommentPayload{Comment: &comment}, err } func (r *mutationResolver) CreateTaskGroup(ctx context.Context, input NewTaskGroup) (*db.TaskGroup, error) { createdAt := time.Now().UTC() project, err := r.Repository.CreateTaskGroup(ctx, db.CreateTaskGroupParams{input.ProjectID, createdAt, input.Name, input.Position}) return &project, err } func (r *mutationResolver) UpdateTaskGroupLocation(ctx context.Context, input NewTaskGroupLocation) (*db.TaskGroup, error) { taskGroup, err := r.Repository.UpdateTaskGroupLocation(ctx, db.UpdateTaskGroupLocationParams{ input.TaskGroupID, input.Position, }) return &taskGroup, err } func (r *mutationResolver) UpdateTaskGroupName(ctx context.Context, input UpdateTaskGroupName) (*db.TaskGroup, error) { taskGroup, err := r.Repository.SetTaskGroupName(ctx, db.SetTaskGroupNameParams{TaskGroupID: input.TaskGroupID, Name: input.Name}) if err != nil { return &db.TaskGroup{}, err } return &taskGroup, nil } func (r *mutationResolver) DeleteTaskGroup(ctx context.Context, input DeleteTaskGroupInput) (*DeleteTaskGroupPayload, error) { deletedTasks, err := r.Repository.DeleteTasksByTaskGroupID(ctx, input.TaskGroupID) if err != nil { return &DeleteTaskGroupPayload{}, err } taskGroup, err := r.Repository.GetTaskGroupByID(ctx, input.TaskGroupID) if err != nil { return &DeleteTaskGroupPayload{}, err } deletedTaskGroups, err := r.Repository.DeleteTaskGroupByID(ctx, input.TaskGroupID) if err != nil { return &DeleteTaskGroupPayload{}, err } return &DeleteTaskGroupPayload{true, int(deletedTasks + deletedTaskGroups), &taskGroup}, nil } func (r *mutationResolver) DuplicateTaskGroup(ctx context.Context, input DuplicateTaskGroup) (*DuplicateTaskGroupPayload, error) { createdAt := time.Now().UTC() taskGroup, err := r.Repository.CreateTaskGroup(ctx, db.CreateTaskGroupParams{ProjectID: input.ProjectID, Position: input.Position, Name: input.Name, CreatedAt: createdAt}) if err != nil { return &DuplicateTaskGroupPayload{}, err } originalTasks, err := r.Repository.GetTasksForTaskGroupID(ctx, input.TaskGroupID) if err != nil && err != sql.ErrNoRows { return &DuplicateTaskGroupPayload{}, err } for _, originalTask := range originalTasks { task, err := r.Repository.CreateTaskAll(ctx, db.CreateTaskAllParams{ TaskGroupID: taskGroup.TaskGroupID, CreatedAt: createdAt, Name: originalTask.Name, Position: originalTask.Position, Complete: originalTask.Complete, DueDate: originalTask.DueDate, Description: originalTask.Description}) if err != nil { return &DuplicateTaskGroupPayload{}, err } members, err := r.Repository.GetAssignedMembersForTask(ctx, originalTask.TaskID) if err != nil { return &DuplicateTaskGroupPayload{}, err } for _, member := range members { _, err := r.Repository.CreateTaskAssigned(ctx, db.CreateTaskAssignedParams{ TaskID: task.TaskID, UserID: member.UserID, AssignedDate: member.AssignedDate}) if err != nil { return &DuplicateTaskGroupPayload{}, err } } labels, err := r.Repository.GetTaskLabelsForTaskID(ctx, originalTask.TaskID) if err != nil { return &DuplicateTaskGroupPayload{}, err } for _, label := range labels { _, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{ TaskID: task.TaskID, ProjectLabelID: label.ProjectLabelID, AssignedDate: label.AssignedDate}) if err != nil { return &DuplicateTaskGroupPayload{}, err } } checklists, err := r.Repository.GetTaskChecklistsForTask(ctx, originalTask.TaskID) if err != nil { return &DuplicateTaskGroupPayload{}, err } for _, checklist := range checklists { newChecklist, err := r.Repository.CreateTaskChecklist(ctx, db.CreateTaskChecklistParams{ TaskID: task.TaskID, Name: checklist.Name, CreatedAt: createdAt, Position: checklist.Position}) if err != nil { return &DuplicateTaskGroupPayload{}, err } checklistItems, err := r.Repository.GetTaskChecklistItemsForTaskChecklist(ctx, checklist.TaskChecklistID) if err != nil { return &DuplicateTaskGroupPayload{}, err } for _, checklistItem := range checklistItems { item, err := r.Repository.CreateTaskChecklistItem(ctx, db.CreateTaskChecklistItemParams{ TaskChecklistID: newChecklist.TaskChecklistID, CreatedAt: createdAt, Name: checklistItem.Name, Position: checklist.Position, }) if checklistItem.Complete { r.Repository.SetTaskChecklistItemComplete(ctx, db.SetTaskChecklistItemCompleteParams{TaskChecklistItemID: item.TaskChecklistItemID, Complete: true}) } if err != nil { return &DuplicateTaskGroupPayload{}, err } } } } if err != nil { return &DuplicateTaskGroupPayload{}, err } return &DuplicateTaskGroupPayload{TaskGroup: &taskGroup}, err } func (r *mutationResolver) SortTaskGroup(ctx context.Context, input SortTaskGroup) (*SortTaskGroupPayload, error) { tasks := []db.Task{} for _, task := range input.Tasks { t, err := r.Repository.UpdateTaskPosition(ctx, db.UpdateTaskPositionParams{TaskID: task.TaskID, Position: task.Position}) if err != nil { return &SortTaskGroupPayload{}, err } tasks = append(tasks, t) } return &SortTaskGroupPayload{Tasks: tasks, TaskGroupID: input.TaskGroupID}, nil } func (r *mutationResolver) DeleteTaskGroupTasks(ctx context.Context, input DeleteTaskGroupTasks) (*DeleteTaskGroupTasksPayload, error) { tasks, err := r.Repository.GetTasksForTaskGroupID(ctx, input.TaskGroupID) if err != nil && err != sql.ErrNoRows { return &DeleteTaskGroupTasksPayload{}, err } removedTasks := []uuid.UUID{} for _, task := range tasks { err = r.Repository.DeleteTaskByID(ctx, task.TaskID) if err != nil { return &DeleteTaskGroupTasksPayload{}, err } removedTasks = append(removedTasks, task.TaskID) } return &DeleteTaskGroupTasksPayload{TaskGroupID: input.TaskGroupID, Tasks: removedTasks}, nil } func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*db.Task, error) { assignedDate := time.Now().UTC() _, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{input.TaskID, input.ProjectLabelID, assignedDate}) if err != nil { return &db.Task{}, err } task, err := r.Repository.GetTaskByID(ctx, input.TaskID) return &task, nil } func (r *mutationResolver) RemoveTaskLabel(ctx context.Context, input *RemoveTaskLabelInput) (*db.Task, error) { taskLabel, err := r.Repository.GetTaskLabelByID(ctx, input.TaskLabelID) if err != nil { return &db.Task{}, err } task, err := r.Repository.GetTaskByID(ctx, taskLabel.TaskID) if err != nil { return &db.Task{}, err } err = r.Repository.DeleteTaskLabelByID(ctx, input.TaskLabelID) return &task, err } func (r *mutationResolver) ToggleTaskLabel(ctx context.Context, input ToggleTaskLabelInput) (*ToggleTaskLabelPayload, error) { task, err := r.Repository.GetTaskByID(ctx, input.TaskID) if err != nil { return &ToggleTaskLabelPayload{}, err } _, err = r.Repository.GetTaskLabelForTaskByProjectLabelID(ctx, db.GetTaskLabelForTaskByProjectLabelIDParams{TaskID: input.TaskID, ProjectLabelID: input.ProjectLabelID}) createdAt := time.Now().UTC() if err == sql.ErrNoRows { logger.New(ctx).WithFields(log.Fields{"err": err}).Warning("no rows") _, err := r.Repository.CreateTaskLabelForTask(ctx, db.CreateTaskLabelForTaskParams{ TaskID: input.TaskID, ProjectLabelID: input.ProjectLabelID, AssignedDate: createdAt, }) if err != nil { return &ToggleTaskLabelPayload{}, err } payload := ToggleTaskLabelPayload{Active: true, Task: &task} return &payload, nil } if err != nil { return &ToggleTaskLabelPayload{}, err } err = r.Repository.DeleteTaskLabelForTaskByProjectLabelID(ctx, db.DeleteTaskLabelForTaskByProjectLabelIDParams{ TaskID: input.TaskID, ProjectLabelID: input.ProjectLabelID, }) if err != nil { return &ToggleTaskLabelPayload{}, err } payload := ToggleTaskLabelPayload{Active: false, Task: &task} return &payload, nil } func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*db.Task, error) { createdAt := time.Now().UTC() logger.New(ctx).WithFields(log.Fields{"positon": input.Position, "taskGroupID": input.TaskGroupID}).Info("creating task") task, err := r.Repository.CreateTask(ctx, db.CreateTaskParams{input.TaskGroupID, createdAt, input.Name, input.Position}) if err != nil { logger.New(ctx).WithError(err).Error("issue while creating task") return &db.Task{}, err } taskGroup, err := r.Repository.GetTaskGroupByID(ctx, input.TaskGroupID) if err != nil { logger.New(ctx).WithError(err).Error("issue while creating task") return &db.Task{}, err } data := map[string]string{ "TaskGroup": taskGroup.Name, } userID, _ := GetUserID(ctx) d, err := json.Marshal(data) _, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{ TaskID: task.TaskID, Data: d, CreatedAt: createdAt, CausedBy: userID, ActivityTypeID: 1, }) if len(input.Assigned) != 0 { assignedDate := time.Now().UTC() for _, assigned := range input.Assigned { assignedTask, err := r.Repository.CreateTaskAssigned(ctx, db.CreateTaskAssignedParams{TaskID: task.TaskID, UserID: assigned, AssignedDate: assignedDate}) logger.New(ctx).WithFields(log.Fields{ "assignedUserID": assignedTask.UserID, "taskID": assignedTask.TaskID, "assignedTaskID": assignedTask.TaskAssignedID, }).Info("assigned task") if err != nil { return &db.Task{}, err } } } if err != nil { logger.New(ctx).WithError(err).Error("issue while creating task") return &db.Task{}, err } return &task, nil } func (r *mutationResolver) DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error) { logger.New(ctx).WithFields(log.Fields{ "taskID": input.TaskID, }).Info("deleting task") err := r.Repository.DeleteTaskByID(ctx, input.TaskID) if err != nil { return &DeleteTaskPayload{}, err } return &DeleteTaskPayload{input.TaskID}, nil } func (r *mutationResolver) UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*db.Task, error) { task, err := r.Repository.UpdateTaskDescription(ctx, db.UpdateTaskDescriptionParams{input.TaskID, sql.NullString{String: input.Description, Valid: true}}) return &task, err } func (r *mutationResolver) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*UpdateTaskLocationPayload, error) { userID, _ := GetUserID(ctx) previousTask, err := r.Repository.GetTaskByID(ctx, input.TaskID) if err != nil { return &UpdateTaskLocationPayload{}, err } task, _ := r.Repository.UpdateTaskLocation(ctx, db.UpdateTaskLocationParams{TaskID: input.TaskID, TaskGroupID: input.TaskGroupID, Position: input.Position}) if previousTask.TaskGroupID != input.TaskGroupID { skipAndDelete := false lastMove, err := r.Repository.GetLastMoveForTaskID(ctx, input.TaskID) if err == nil { if lastMove.Active && lastMove.PrevTaskGroupID == input.TaskGroupID.String() { skipAndDelete = true } } if skipAndDelete { _ = r.Repository.SetInactiveLastMoveForTaskID(ctx, input.TaskID) } else { prevTaskGroup, _ := r.Repository.GetTaskGroupByID(ctx, previousTask.TaskGroupID) curTaskGroup, _ := r.Repository.GetTaskGroupByID(ctx, input.TaskGroupID) data := map[string]string{ "PrevTaskGroup": prevTaskGroup.Name, "PrevTaskGroupID": prevTaskGroup.TaskGroupID.String(), "CurTaskGroup": curTaskGroup.Name, "CurTaskGroupID": curTaskGroup.TaskGroupID.String(), } createdAt := time.Now().UTC() d, _ := json.Marshal(data) _, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{ TaskID: task.TaskID, Data: d, CausedBy: userID, CreatedAt: createdAt, ActivityTypeID: 2, }) } } return &UpdateTaskLocationPayload{Task: &task, PreviousTaskGroupID: previousTask.TaskGroupID}, err } func (r *mutationResolver) UpdateTaskName(ctx context.Context, input UpdateTaskName) (*db.Task, error) { task, err := r.Repository.UpdateTaskName(ctx, db.UpdateTaskNameParams{input.TaskID, input.Name}) return &task, err } func (r *mutationResolver) SetTaskComplete(ctx context.Context, input SetTaskComplete) (*db.Task, error) { completedAt := time.Now().UTC() data := map[string]string{} activityType := TASK_MARK_INCOMPLETE if input.Complete { activityType = TASK_MARK_COMPLETE } createdAt := time.Now().UTC() userID, _ := GetUserID(ctx) d, err := json.Marshal(data) _, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{ TaskID: input.TaskID, Data: d, CausedBy: userID, CreatedAt: createdAt, ActivityTypeID: activityType, }) task, err := r.Repository.SetTaskComplete(ctx, db.SetTaskCompleteParams{TaskID: input.TaskID, Complete: input.Complete, CompletedAt: sql.NullTime{Time: completedAt, Valid: true}}) if err != nil { return &db.Task{}, err } return &task, nil } func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTaskDueDate) (*db.Task, error) { userID, _ := GetUserID(ctx) prevTask, err := r.Repository.GetTaskByID(ctx, input.TaskID) if err != nil { return &db.Task{}, err } isSame := false if prevTask.DueDate.Valid && input.DueDate != nil { if prevTask.DueDate.Time == *input.DueDate && prevTask.HasTime == input.HasTime { isSame = true } } log.WithFields(log.Fields{ "isSame": isSame, "prev": prevTask.HasTime, "new": input.HasTime, }).Info("chekcing same") data := map[string]string{} var activityType = TASK_DUE_DATE_ADDED data["HasTime"] = strconv.FormatBool(input.HasTime) if input.DueDate == nil && prevTask.DueDate.Valid { activityType = TASK_DUE_DATE_REMOVED data["PrevDueDate"] = prevTask.DueDate.Time.String() } else if prevTask.DueDate.Valid { activityType = TASK_DUE_DATE_CHANGED data["PrevDueDate"] = prevTask.DueDate.Time.String() data["CurDueDate"] = input.DueDate.String() } else if input.DueDate != nil { data["DueDate"] = input.DueDate.String() } var dueDate sql.NullTime log.WithField("dueDate", input.DueDate).Info("before ptr!") if input.DueDate == nil { dueDate = sql.NullTime{Valid: false, Time: time.Now()} } else { dueDate = sql.NullTime{Valid: true, Time: *input.DueDate} } var task db.Task if !(input.DueDate == nil && !prevTask.DueDate.Valid) { task, err = r.Repository.UpdateTaskDueDate(ctx, db.UpdateTaskDueDateParams{ TaskID: input.TaskID, DueDate: dueDate, HasTime: input.HasTime, }) reminders, err := r.Repository.GetDueDateRemindersForTaskID(ctx, input.TaskID) if err != nil { log.WithError(err).Error("error while getting due date reminders for task ID") return &db.Task{}, err } if input.DueDate != nil { for _, rem := range reminders { remindAt := now.With(*input.DueDate).BeginningOfDay() if input.HasTime { remindAt = *input.DueDate } switch rem.Duration { case "MINUTE": remindAt = remindAt.Add(time.Duration(-rem.Period) * time.Minute) break case "HOUR": remindAt = remindAt.Add(time.Duration(-rem.Period) * time.Hour) break case "DAY": remindAt = remindAt.AddDate(0, 0, int(-rem.Period)) break case "WEEK": remindAt = remindAt.AddDate(0, 0, 7*int(-rem.Period)) break } _, err := r.Repository.UpdateDueDateReminderRemindAt(ctx, db.UpdateDueDateReminderRemindAtParams{ DueDateReminderID: rem.DueDateReminderID, RemindAt: remindAt, }) if err != nil { log.WithError(err).Error("error while updating due date reminder remind at") return &db.Task{}, err } } } createdAt := time.Now().UTC() d, _ := json.Marshal(data) if !isSame { _, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{ TaskID: task.TaskID, Data: d, CausedBy: userID, CreatedAt: createdAt, ActivityTypeID: activityType, }) } } else { task, err = r.Repository.GetTaskByID(ctx, input.TaskID) } return &task, err } func (r *mutationResolver) ToggleTaskWatch(ctx context.Context, input ToggleTaskWatch) (*db.Task, error) { userID, ok := GetUserID(ctx) if !ok { log.Error("user ID is missing") return &db.Task{}, errors.New("user ID is unknown") } _, err := r.Repository.GetTaskWatcher(ctx, db.GetTaskWatcherParams{UserID: userID, TaskID: input.TaskID}) isWatching := true if err != nil { if err != sql.ErrNoRows { log.WithError(err).Error("error while getting task watcher") return &db.Task{}, err } isWatching = false } if isWatching { err := r.Repository.DeleteTaskWatcher(ctx, db.DeleteTaskWatcherParams{UserID: userID, TaskID: input.TaskID}) if err != nil { log.WithError(err).Error("error while getting deleteing task watcher") return &db.Task{}, err } } else { now := time.Now().UTC() _, err := r.Repository.CreateTaskWatcher(ctx, db.CreateTaskWatcherParams{UserID: userID, TaskID: input.TaskID, WatchedAt: now}) if err != nil { log.WithError(err).Error("error while creating task watcher") return &db.Task{}, err } } task, err := r.Repository.GetTaskByID(ctx, input.TaskID) if err != nil { log.WithError(err).Error("error while getting task by id") return &db.Task{}, err } return &task, nil } func (r *mutationResolver) AssignTask(ctx context.Context, input *AssignTaskInput) (*db.Task, error) { assignedDate := time.Now().UTC() assignedTask, err := r.Repository.CreateTaskAssigned(ctx, db.CreateTaskAssignedParams{input.TaskID, input.UserID, assignedDate}) logger.New(ctx).WithFields(log.Fields{ "assignedUserID": assignedTask.UserID, "taskID": assignedTask.TaskID, "assignedTaskID": assignedTask.TaskAssignedID, }).Info("assigned task") if err != nil { log.WithError(err).Error("error while creating task assigned") return &db.Task{}, err } _, err = r.Repository.GetTaskWatcher(ctx, db.GetTaskWatcherParams{UserID: assignedTask.UserID, TaskID: assignedTask.TaskID}) if err != nil { if err != sql.ErrNoRows { log.WithError(err).Error("error while fetching task watcher") return &db.Task{}, err } _, err = r.Repository.CreateTaskWatcher(ctx, db.CreateTaskWatcherParams{UserID: assignedTask.UserID, TaskID: assignedTask.TaskID, WatchedAt: assignedDate}) if err != nil { log.WithError(err).Error("error while creating task assigned task watcher") return &db.Task{}, err } } userID, ok := GetUserID(ctx) if !ok { log.Error("error getting user ID") return &db.Task{}, errors.New("UserID is missing") } task, err := r.Repository.GetTaskByID(ctx, input.TaskID) if err != nil { log.WithError(err).Error("error while getting task by ID") return &db.Task{}, err } if userID != assignedTask.UserID { causedBy, err := r.Repository.GetUserAccountByID(ctx, userID) if err != nil { log.WithError(err).Error("error while getting user account in assign task") return &db.Task{}, err } project, err := r.Repository.GetProjectInfoForTask(ctx, input.TaskID) if err != nil { log.WithError(err).Error("error while getting project in assign task") return &db.Task{}, err } err = r.CreateNotification(ctx, CreateNotificationParams{ ActionType: ActionTypeTaskAssigned, CausedBy: userID, NotifiedList: []uuid.UUID{assignedTask.UserID}, Data: map[string]string{ "CausedByUsername": causedBy.Username, "CausedByFullName": causedBy.FullName, "TaskID": project.TaskShortID, "TaskName": task.Name, "ProjectID": project.ProjectShortID, "ProjectName": project.Name, }, }) } if err != nil { return &task, err } // r.NotificationQueue.TaskMemberWasAdded(assignedTask.TaskID, userID, assignedTask.UserID) return &task, nil } func (r *mutationResolver) UnassignTask(ctx context.Context, input *UnassignTaskInput) (*db.Task, error) { task, err := r.Repository.GetTaskByID(ctx, input.TaskID) if err != nil { log.WithError(err).Error("error while getting task by ID") return &db.Task{}, err } log.WithFields(log.Fields{"UserID": input.UserID, "TaskID": input.TaskID}).Info("deleting task assignment") _, err = r.Repository.DeleteTaskAssignedByID(ctx, db.DeleteTaskAssignedByIDParams{TaskID: input.TaskID, UserID: input.UserID}) if err != nil && err != sql.ErrNoRows { log.WithError(err).Error("error while deleting task by ID") return &db.Task{}, err } err = r.Repository.DeleteTaskWatcher(ctx, db.DeleteTaskWatcherParams{UserID: input.UserID, TaskID: input.TaskID}) if err != nil { log.WithError(err).Error("error while creating task assigned task watcher") return &db.Task{}, err } return &task, nil } func (r *mutationResolver) CreateTaskDueDateNotifications(ctx context.Context, input []CreateTaskDueDateNotification) (*CreateTaskDueDateNotificationsResult, error) { reminders := []DueDateNotification{} if len(input) == 0 { return &CreateTaskDueDateNotificationsResult{}, nil } task, err := r.Repository.GetTaskByID(ctx, input[0].TaskID) if err != nil { log.WithError(err).Error("error while getting task by id") return &CreateTaskDueDateNotificationsResult{}, nil } for _, in := range input { remindAt := now.With(task.DueDate.Time).BeginningOfDay() if task.HasTime { remindAt = task.DueDate.Time } switch in.Duration { case "MINUTE": remindAt = remindAt.Add(time.Duration(-in.Period) * time.Minute) break case "HOUR": remindAt = remindAt.Add(time.Duration(-in.Period) * time.Hour) break case "DAY": remindAt = remindAt.AddDate(0, 0, int(-in.Period)) break case "WEEK": remindAt = remindAt.AddDate(0, 0, 7*int(-in.Period)) break } log.Info("task not found, sending task") n, err := r.Repository.CreateDueDateReminder(ctx, db.CreateDueDateReminderParams{ TaskID: in.TaskID, Period: int32(in.Period), Duration: in.Duration.String(), RemindAt: remindAt, }) signature := &mTasks.Signature{ UUID: "due_date_reminder_" + n.DueDateReminderID.String(), Name: "dueDateNotification", ETA: &remindAt, Args: []mTasks.Arg{{ Type: "string", Value: n.DueDateReminderID.String(), }, { Type: "string", Value: in.TaskID.String(), }}, } r.Job.Server.SendTask(signature) if err != nil { return &CreateTaskDueDateNotificationsResult{}, err } duration := DueDateNotificationDuration(n.Duration) if !duration.IsValid() { log.WithField("duration", n.Duration).Error("invalid duration found") return &CreateTaskDueDateNotificationsResult{}, errors.New("invalid duration") } reminders = append(reminders, DueDateNotification{ ID: n.DueDateReminderID, Period: int(n.Period), Duration: duration, }) } return &CreateTaskDueDateNotificationsResult{ Notifications: reminders, }, nil } func (r *mutationResolver) UpdateTaskDueDateNotifications(ctx context.Context, input []UpdateTaskDueDateNotification) (*UpdateTaskDueDateNotificationsResult, error) { reminders := []DueDateNotification{} if len(input) == 0 { return &UpdateTaskDueDateNotificationsResult{}, nil } for _, in := range input { task, err := r.Repository.GetTaskForDueDateReminder(ctx, in.ID) if err != nil { log.WithError(err).Error("error while getting task by id") return &UpdateTaskDueDateNotificationsResult{}, nil } current, err := r.Repository.GetDueDateReminderByID(ctx, in.ID) if err != nil { log.WithError(err).Error("error while getting task by id") return &UpdateTaskDueDateNotificationsResult{}, nil } remindAt := now.With(task.DueDate.Time).BeginningOfDay() if task.HasTime { remindAt = task.DueDate.Time } switch in.Duration { case "MINUTE": remindAt = remindAt.Add(time.Duration(-in.Period) * time.Minute) break case "HOUR": remindAt = remindAt.Add(time.Duration(-in.Period) * time.Hour) break case "DAY": remindAt = remindAt.AddDate(0, 0, int(-in.Period)) break case "WEEK": remindAt = remindAt.AddDate(0, 0, 7*int(-in.Period)) break } n, err := r.Repository.UpdateDueDateReminder(ctx, db.UpdateDueDateReminderParams{ DueDateReminderID: in.ID, Period: int32(in.Period), Duration: in.Duration.String(), RemindAt: remindAt, }) if err != nil { return &UpdateTaskDueDateNotificationsResult{}, err } etaNano := strconv.FormatInt(current.RemindAt.UnixNano(), 10) result, err := r.Redis.ZRangeByScore(ctx, "delayed_tasks", &redis.ZRangeBy{Max: etaNano, Min: etaNano}).Result() if err != nil { log.WithError(err).Error("error while getting due date reminder") } log.WithField("result", result).Info("result raw") if len(result) != 0 { r.Redis.ZRem(ctx, "delayed_tasks", result) } signature := &mTasks.Signature{ UUID: "due_date_reminder_" + n.DueDateReminderID.String(), Name: "dueDateNotification", ETA: &remindAt, Args: []mTasks.Arg{{ Type: "string", Value: n.DueDateReminderID.String(), }, { Type: "string", Value: task.TaskID.String(), }}, } r.Job.Server.SendTask(signature) duration := DueDateNotificationDuration(n.Duration) if !duration.IsValid() { log.WithField("duration", n.Duration).Error("invalid duration found") return &UpdateTaskDueDateNotificationsResult{}, errors.New("invalid duration") } reminders = append(reminders, DueDateNotification{ ID: n.DueDateReminderID, Period: int(n.Period), Duration: duration, }) } return &UpdateTaskDueDateNotificationsResult{ Notifications: reminders, }, nil } func (r *mutationResolver) DeleteTaskDueDateNotifications(ctx context.Context, input []DeleteTaskDueDateNotification) (*DeleteTaskDueDateNotificationsResult, error) { ids := []uuid.UUID{} for _, n := range input { reminder, err := r.Repository.GetDueDateReminderByID(ctx, n.ID) err = r.Repository.DeleteDueDateReminder(ctx, n.ID) if err != nil { log.WithError(err).Error("error while deleting task due date notification") return &DeleteTaskDueDateNotificationsResult{}, err } etaNano := strconv.FormatInt(reminder.RemindAt.UnixNano(), 10) result, err := r.Redis.ZRangeByScore(ctx, "delayed_tasks", &redis.ZRangeBy{Max: etaNano, Min: etaNano}).Result() if err != nil { log.WithError(err).Error("error while getting due date reminder") } log.WithField("result", result).Info("result raw") if len(result) != 0 { r.Redis.ZRem(ctx, "delayed_tasks", result) } ids = append(ids, n.ID) } return &DeleteTaskDueDateNotificationsResult{Notifications: ids}, nil } func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task, error) { var taskID uuid.UUID var err error if input.TaskID != nil { taskID = *input.TaskID } else if input.TaskShortID != nil { taskID, err = r.Repository.GetTaskIDByShortID(ctx, *input.TaskShortID) if err != nil { return &db.Task{}, err } } else { return &db.Task{}, errors.New("FindTask requires either TaskID or TaskShortID to be set") } task, err := r.Repository.GetTaskByID(ctx, taskID) return &task, err } func (r *taskResolver) ID(ctx context.Context, obj *db.Task) (uuid.UUID, error) { return obj.TaskID, nil } func (r *taskResolver) TaskGroup(ctx context.Context, obj *db.Task) (*db.TaskGroup, error) { taskGroup, err := r.Repository.GetTaskGroupByID(ctx, obj.TaskGroupID) return &taskGroup, err } func (r *taskResolver) Description(ctx context.Context, obj *db.Task) (*string, error) { task, err := r.Repository.GetTaskByID(ctx, obj.TaskID) if err != nil { return nil, err } if !task.Description.Valid { return nil, nil } return &task.Description.String, nil } func (r *taskResolver) Watched(ctx context.Context, obj *db.Task) (bool, error) { userID, ok := GetUserID(ctx) if !ok { log.Error("user ID is missing") return false, errors.New("user ID is unknown") } _, err := r.Repository.GetTaskWatcher(ctx, db.GetTaskWatcherParams{UserID: userID, TaskID: obj.TaskID}) if err != nil { if err == sql.ErrNoRows { return false, nil } log.WithError(err).Error("error while getting task watcher") return false, err } return true, nil } func (r *taskResolver) DueDate(ctx context.Context, obj *db.Task) (*DueDate, error) { nots, err := r.Repository.GetDueDateRemindersForTaskID(ctx, obj.TaskID) if err != nil { log.WithError(err).Error("error while fetching due date reminders") return &DueDate{}, err } reminders := []DueDateNotification{} for _, n := range nots { duration := DueDateNotificationDuration(n.Duration) if !duration.IsValid() { log.WithField("duration", n.Duration).Error("invalid duration found") return &DueDate{}, errors.New("invalid duration") } reminders = append(reminders, DueDateNotification{ ID: n.DueDateReminderID, Period: int(n.Period), Duration: duration, }) } var time *time.Time if obj.DueDate.Valid { time = &obj.DueDate.Time } return &DueDate{ At: time, Notifications: reminders, }, nil } func (r *taskResolver) CompletedAt(ctx context.Context, obj *db.Task) (*time.Time, error) { if obj.CompletedAt.Valid { return &obj.CompletedAt.Time, nil } return nil, nil } func (r *taskResolver) Assigned(ctx context.Context, obj *db.Task) ([]Member, error) { taskMemberLinks, err := r.Repository.GetAssignedMembersForTask(ctx, obj.TaskID) taskMembers := []Member{} if err != nil { return taskMembers, err } for _, taskMemberLink := range taskMemberLinks { user, err := r.Repository.GetUserAccountByID(ctx, taskMemberLink.UserID) if err != nil { return taskMembers, err } var url *string if user.ProfileAvatarUrl.Valid { url = &user.ProfileAvatarUrl.String } profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} projectID, err := r.Repository.GetProjectIDForTask(ctx, obj.TaskID) if err != nil { return taskMembers, err } role, err := r.Repository.GetRoleForProjectMemberByUserID(ctx, db.GetRoleForProjectMemberByUserIDParams{UserID: user.UserID, ProjectID: projectID}) if err != nil { if err == sql.ErrNoRows { role = db.Role{Code: "owner", Name: "Owner"} } else { logger.New(ctx).WithError(err).Error("get role for project member") return taskMembers, err } } taskMembers = append(taskMembers, Member{ID: taskMemberLink.UserID, FullName: user.FullName, ProfileIcon: profileIcon, Role: &role, }) } return taskMembers, nil } func (r *taskResolver) Labels(ctx context.Context, obj *db.Task) ([]db.TaskLabel, error) { return r.Repository.GetTaskLabelsForTaskID(ctx, obj.TaskID) } func (r *taskResolver) Checklists(ctx context.Context, obj *db.Task) ([]db.TaskChecklist, error) { return r.Repository.GetTaskChecklistsForTask(ctx, obj.TaskID) } func (r *taskResolver) Badges(ctx context.Context, obj *db.Task) (*TaskBadges, error) { checklists, err := r.Repository.GetTaskChecklistsForTask(ctx, obj.TaskID) if err != nil { return &TaskBadges{}, err } comments, err := r.Repository.GetCommentCountForTask(ctx, obj.TaskID) if err != nil { return &TaskBadges{}, err } complete := 0 total := 0 for _, checklist := range checklists { items, err := r.Repository.GetTaskChecklistItemsForTaskChecklist(ctx, checklist.TaskChecklistID) if err != nil { return &TaskBadges{}, err } for _, item := range items { total++ if item.Complete { complete++ } } } var taskChecklist *ChecklistBadge if total != 0 { taskChecklist = &ChecklistBadge{Total: total, Complete: complete} } var taskComments *CommentsBadge if comments != 0 { taskComments = &CommentsBadge{Total: int(comments), Unread: false} } return &TaskBadges{Checklist: taskChecklist, Comments: taskComments}, nil } func (r *taskResolver) Activity(ctx context.Context, obj *db.Task) ([]db.TaskActivity, error) { activity, err := r.Repository.GetActivityForTaskID(ctx, obj.TaskID) if err == sql.ErrNoRows { return []db.TaskActivity{}, nil } return activity, err } func (r *taskResolver) Comments(ctx context.Context, obj *db.Task) ([]db.TaskComment, error) { comments, err := r.Repository.GetCommentsForTaskID(ctx, obj.TaskID) if err == sql.ErrNoRows { return []db.TaskComment{}, nil } return comments, err } func (r *taskActivityResolver) ID(ctx context.Context, obj *db.TaskActivity) (uuid.UUID, error) { return obj.TaskActivityID, nil } func (r *taskActivityResolver) Type(ctx context.Context, obj *db.TaskActivity) (ActivityType, error) { switch obj.ActivityTypeID { case 1: return ActivityTypeTaskAdded, nil case 2: return ActivityTypeTaskMoved, nil case 3: return ActivityTypeTaskMarkedComplete, nil case 4: return ActivityTypeTaskMarkedIncomplete, nil case 5: return ActivityTypeTaskDueDateChanged, nil case 6: return ActivityTypeTaskDueDateAdded, nil case 7: return ActivityTypeTaskDueDateRemoved, nil case 8: return ActivityTypeTaskChecklistChanged, nil case 9: return ActivityTypeTaskChecklistAdded, nil case 10: return ActivityTypeTaskChecklistRemoved, nil default: return ActivityTypeTaskAdded, errors.New("unknown type") } } func (r *taskActivityResolver) Data(ctx context.Context, obj *db.TaskActivity) ([]TaskActivityData, error) { var data map[string]string _ = json.Unmarshal(obj.Data, &data) activity := []TaskActivityData{} for name, value := range data { activity = append(activity, TaskActivityData{ Name: name, Value: value, }) } return activity, nil } func (r *taskActivityResolver) CausedBy(ctx context.Context, obj *db.TaskActivity) (*CausedBy, error) { user, err := r.Repository.GetUserAccountByID(ctx, obj.CausedBy) var url *string if user.ProfileAvatarUrl.Valid { url = &user.ProfileAvatarUrl.String } profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} return &CausedBy{ ID: obj.CausedBy, FullName: user.FullName, ProfileIcon: profileIcon, }, err } func (r *taskChecklistResolver) ID(ctx context.Context, obj *db.TaskChecklist) (uuid.UUID, error) { return obj.TaskChecklistID, nil } func (r *taskChecklistResolver) Items(ctx context.Context, obj *db.TaskChecklist) ([]db.TaskChecklistItem, error) { return r.Repository.GetTaskChecklistItemsForTaskChecklist(ctx, obj.TaskChecklistID) } func (r *taskChecklistItemResolver) ID(ctx context.Context, obj *db.TaskChecklistItem) (uuid.UUID, error) { return obj.TaskChecklistItemID, nil } func (r *taskChecklistItemResolver) DueDate(ctx context.Context, obj *db.TaskChecklistItem) (*time.Time, error) { if obj.DueDate.Valid { return &obj.DueDate.Time, nil } return nil, nil } func (r *taskCommentResolver) ID(ctx context.Context, obj *db.TaskComment) (uuid.UUID, error) { return obj.TaskCommentID, nil } func (r *taskCommentResolver) UpdatedAt(ctx context.Context, obj *db.TaskComment) (*time.Time, error) { if obj.UpdatedAt.Valid { return &obj.UpdatedAt.Time, nil } return nil, nil } func (r *taskCommentResolver) CreatedBy(ctx context.Context, obj *db.TaskComment) (*CreatedBy, error) { user, err := r.Repository.GetUserAccountByID(ctx, obj.CreatedBy) var url *string if user.ProfileAvatarUrl.Valid { url = &user.ProfileAvatarUrl.String } profileIcon := &ProfileIcon{url, &user.Initials, &user.ProfileBgColor} return &CreatedBy{ ID: obj.CreatedBy, FullName: user.FullName, ProfileIcon: profileIcon, }, err } func (r *taskLabelResolver) ID(ctx context.Context, obj *db.TaskLabel) (uuid.UUID, error) { return obj.TaskLabelID, nil } func (r *taskLabelResolver) ProjectLabel(ctx context.Context, obj *db.TaskLabel) (*db.ProjectLabel, error) { projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID) return &projectLabel, err } // Task returns TaskResolver implementation. func (r *Resolver) Task() TaskResolver { return &taskResolver{r} } // TaskActivity returns TaskActivityResolver implementation. func (r *Resolver) TaskActivity() TaskActivityResolver { return &taskActivityResolver{r} } // TaskChecklist returns TaskChecklistResolver implementation. func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} } // TaskChecklistItem returns TaskChecklistItemResolver implementation. func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver { return &taskChecklistItemResolver{r} } // TaskComment returns TaskCommentResolver implementation. func (r *Resolver) TaskComment() TaskCommentResolver { return &taskCommentResolver{r} } // TaskLabel returns TaskLabelResolver implementation. func (r *Resolver) TaskLabel() TaskLabelResolver { return &taskLabelResolver{r} } type taskResolver struct{ *Resolver } type taskActivityResolver struct{ *Resolver } type taskChecklistResolver struct{ *Resolver } type taskChecklistItemResolver struct{ *Resolver } type taskCommentResolver struct{ *Resolver } type taskLabelResolver struct{ *Resolver }