add activities.

This commit is contained in:
2020-05-09 14:12:26 +02:00
parent 564ac0d24a
commit 3039dc1879
18 changed files with 388 additions and 33 deletions
+6
View File
@@ -9,6 +9,12 @@ model:
filename: graphcore/input_gen.go
package: graphcore
models:
Activity:
fields:
unitName:
resolver: true
resolver:
layout: follow-schema
dir: resolvers
+27
View File
@@ -0,0 +1,27 @@
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"
"git.aiterp.net/stufflog/server/graph/graphcore"
"git.aiterp.net/stufflog/server/models"
)
func (r *activityResolver) UnitName(ctx context.Context, obj *models.Activity) (*string, error) {
if !obj.Countable || obj.UnitIsTimeSpent {
return nil, nil
}
return &obj.UnitName, nil
}
func (r *activityResolver) Project(ctx context.Context, obj *models.Activity) (*models.Project, error) {
return r.Database.Projects().Find(ctx, obj.ProjectID)
}
// Activity returns graphcore.ActivityResolver implementation.
func (r *Resolver) Activity() graphcore.ActivityResolver { return &activityResolver{r} }
type activityResolver struct{ *Resolver }
+1
View File
@@ -5,6 +5,7 @@ package resolvers
import (
"context"
"git.aiterp.net/stufflog/server/graph/graphcore"
"git.aiterp.net/stufflog/server/graph/graphutil"
"git.aiterp.net/stufflog/server/graph/loaders"
+86 -3
View File
@@ -6,6 +6,7 @@ package resolvers
import (
"context"
"errors"
"git.aiterp.net/stufflog/server/graph/graphcore"
"git.aiterp.net/stufflog/server/internal/xlerrors"
"git.aiterp.net/stufflog/server/models"
@@ -44,6 +45,83 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input graphcore.Pr
return project, nil
}
func (r *mutationResolver) CreateActivity(ctx context.Context, input graphcore.ActivityCreateInput) (*models.Activity, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, xlerrors.PermissionDenied
}
project, err := r.Database.Projects().Find(ctx, input.ProjectID)
if err != nil {
return nil, err
}
if perm, err := r.Auth.ProjectPermission(ctx, *project); err != nil || !perm.CanManageActivities() {
return nil, xlerrors.PermissionDenied
}
activity := &models.Activity{
ProjectID: project.ID,
Name: input.Name,
Countable: input.Countable != nil && *input.Countable,
UnitIsTimeSpent: input.UnitIsTimeSpent != nil && *input.UnitIsTimeSpent,
BaseValue: input.BaseValue,
}
if !activity.UnitIsTimeSpent && activity.Countable {
if input.UnitName != nil {
activity.UnitName = *input.UnitName
} else {
return nil, errors.New("unit name is required for countable non-time-spent activities")
}
}
if activity.UnitIsTimeSpent || activity.Countable {
if input.UnitValue != nil {
activity.UnitValue = *input.UnitValue
}
}
return r.Database.Activities().Insert(ctx, *activity)
}
func (r *mutationResolver) EditActivity(ctx context.Context, input graphcore.ActivityEditInput) (*models.Activity, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, xlerrors.PermissionDenied
}
activity, err := r.Database.Activities().Find(ctx, input.ActivityID)
if err != nil {
return nil, err
}
project, err := r.Database.Projects().Find(ctx, activity.ProjectID)
if err != nil {
return nil, err
}
if perm, err := r.Auth.ProjectPermission(ctx, *project); err != nil || !perm.CanManageActivities() {
return nil, xlerrors.PermissionDenied
}
if input.SetName != nil {
activity.Name = *input.SetName
}
if input.SetBaseValue != nil {
activity.BaseValue = *input.SetBaseValue
}
if input.SetUnitName != nil {
activity.UnitName = *input.SetUnitName
}
if input.SetUnitValue != nil {
activity.UnitValue = *input.SetUnitValue
}
err = r.Database.Activities().Save(ctx, *activity)
if err != nil {
return nil, err
}
return activity, nil
}
func (r *mutationResolver) CreateIssue(ctx context.Context, input graphcore.IssueCreateInput) (*models.Issue, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
@@ -58,14 +136,19 @@ func (r *mutationResolver) CreateIssue(ctx context.Context, input graphcore.Issu
return nil, xlerrors.PermissionDenied
}
status, err := r.Database.ProjectStatuses().Find(ctx, project.ID, input.StatusName)
if err != nil {
return nil, err
}
issue := &models.Issue{
ProjectID: project.ID,
OwnerID: user.ID,
AssigneeID: "",
StatusStage: input.StatusStage,
StatusName: input.StatusName,
StatusStage: status.Stage,
StatusName: status.Name,
Name: input.Name,
Title: input.Name,
Title: input.Name, // Title set below if it's in the input.
Description: input.Description,
}
if input.Title != nil && *input.Title != "" {
+4
View File
@@ -48,6 +48,10 @@ func (r *projectResolver) Statuses(ctx context.Context, obj *models.Project, fil
return r.Database.ProjectStatuses().List(ctx, *filter)
}
func (r *projectResolver) Activities(ctx context.Context, obj *models.Project) ([]*models.Activity, error) {
return r.Database.Activities().List(ctx, models.ActivityFilter{ProjectIDs: []string{obj.ID}})
}
func (r *projectPermissionResolver) User(ctx context.Context, obj *models.ProjectPermission) (*models.User, error) {
return loaders.UserLoaderFromContext(ctx).Load(obj.UserID)
}
+5 -5
View File
@@ -8,14 +8,14 @@ import (
"errors"
"git.aiterp.net/stufflog/server/graph/graphcore"
"git.aiterp.net/stufflog/server/internal/xlerrors"
"git.aiterp.net/stufflog/server/models"
"git.aiterp.net/stufflog/server/services"
)
func (r *queryResolver) Issue(ctx context.Context, id string) (*models.Issue, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, services.ErrPermissionDenied
return nil, xlerrors.PermissionDenied
}
issue, err := r.Database.Issues().Find(ctx, id)
@@ -33,7 +33,7 @@ func (r *queryResolver) Issue(ctx context.Context, id string) (*models.Issue, er
func (r *queryResolver) Issues(ctx context.Context, filter *models.IssueFilter) ([]*models.Issue, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, services.ErrPermissionDenied
return nil, xlerrors.PermissionDenied
}
if filter == nil {
@@ -61,7 +61,7 @@ func (r *queryResolver) Issues(ctx context.Context, filter *models.IssueFilter)
func (r *queryResolver) Project(ctx context.Context, id string) (*models.Project, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, services.ErrPermissionDenied
return nil, xlerrors.PermissionDenied
}
project, err := r.Database.Projects().Find(ctx, id)
@@ -79,7 +79,7 @@ func (r *queryResolver) Project(ctx context.Context, id string) (*models.Project
func (r *queryResolver) Projects(ctx context.Context, filter *models.ProjectFilter) ([]*models.Project, error) {
user := r.Auth.UserFromContext(ctx)
if user == nil {
return nil, services.ErrPermissionDenied
return nil, xlerrors.PermissionDenied
}
skipCheck := false
+60
View File
@@ -0,0 +1,60 @@
"""
An activity is a measurable activity for a project. It can be measured.
"""
type Activity {
"The activity ID."
id: String!
"The activity name."
name: String!
"Whether the activity is countable."
countable: Boolean!
"Whether the time spent is the unit."
unitIsTimeSpent: Boolean!
"The name of the unit. If unitIsTime or countable is true, this value should be ignored."
unitName: String
"The value per unit."
unitValue: Float!
"The base score value for any performed activity. For uncountables, this is the only points scored."
baseValue: Float!
"Parent project."
project: Project!
}
"""
Input for the createActivity mutation.
"""
input ActivityCreateInput {
"Project ID to associate it with."
projectId: String!
"Proejct name."
name: String!
"The base value of any activity performed. If uncountable, this should be non-zero."
baseValue: Float!
"Whether the activity is countable. Default: false"
countable: Boolean
"Whether time spent should be the unit of this activity."
unitIsTimeSpent: Boolean
"The unit name of the activity."
unitName: String
"The per-unit value of the activity."
unitValue: Float
}
"""
Input for the editActivity mutation. Not all changes are available as they could create
some serious issues with past goals and logs.
"""
input ActivityEditInput {
"The ID of the activity to edit."
activityId: String!
"Update the name of the activity."
setName: String
"Set the base value of the activity. This has an effect on current and past goals!"
setBaseValue: Float
"Set the unit name of the activity. This is only for countable, non time spent acitivities."
setUnitName: String
"Set the unit value of the activity. This has an effect on current and past goals!"
setUnitValue: Float
}
+23 -14
View File
@@ -1,51 +1,60 @@
type Issue {
"The issue ID."
id: String!
"The time at which the issue is created."
createdTime: Time!
"The time at which the issue was updated."
updatedTime: Time!
"Optionally, when this issue is due."
dueTime: Time
"The name of the issue, used in lists."
name: String!
"The issue title. This can be longer than the name."
title: String!
"The description of the issue, in markdown."
description: String!
"Parent project."
project: Project
"The issue's owner/creator."
owner: User
"The issue assignee."
assignee: User
"The issue status."
status: ProjectStatus!
}
input IssueFilter {
"Filter by issue IDs (mostly used internally by data loaders)"
"Filter by issue IDs. (mostly used internally by data loaders)"
issueIds: [String!]
"Filter by project IDs"
"Filter by project IDs."
projectIds: [String!]
"Filter by owner IDs"
"Filter by owner IDs."
ownerIds: [String!]
"Filter by assignee IDs"
"Filter by assignee IDs."
assigneeIds: [String!]
"Text search"
"Text search."
search: String
"Earliest stage (inclusive)"
"Earliest stage. (inclusive)"
minStage: Int
"Latest stage (inclusive)"
"Latest stage. (inclusive)"
maxStage: Int
"Limit the result set"
"Limit the result set."
limit: Int
}
input IssueCreateInput {
"Project ID"
"Project ID."
projectId: String!
"Status stage"
statusStage: Int!
"Status name"
"Status name."
statusName: String!
"A name for the issue."
name: String!
"Description of the issue."
description: String!
"Assign this issue, will default to unassigned"
"Assign this issue, will default to unassigned."
assigneeId: String
"A date when this issue is due"
"A date when this issue is due."
dueTime: Time
"Optional title to use instead of the name when not in a list."
title: String
+11 -5
View File
@@ -1,19 +1,25 @@
type Mutation {
# PROJECT
"Create a new project"
"Create a new project."
createProject(input: ProjectCreateInput!): Project!
# ACTIVITY
"Create an activity."
createActivity(input: ActivityCreateInput!): Activity!
"Edit an activity."
editActivity(input: ActivityEditInput!): Activity!
# ISSUE
"Create a new issue"
"Create a new issue."
createIssue(input: IssueCreateInput!): Issue!
# USER
"Log in"
"Log in."
loginUser(input: UserLoginInput!): User!
"Log out"
"Log out."
logoutUser: User!
"Create a new user. This can only be done by administrators."
createUser(input: UserCreateInput!): User!
"Edit an existing user. This can only be done by administrators"
"Edit an existing user. This can only be done by administrators."
editUser(input: UserEditInput!): User!
}
+2
View File
@@ -16,6 +16,8 @@ type Project {
userPermissions: ProjectPermission!
"List all project statuses."
statuses(filter: ProjectStatusFilter): [ProjectStatus!]!
"The activities in this project."
activities: [Activity!]!
}
"The permissions of a user within the project."