add activities.
This commit is contained in:
@@ -9,6 +9,12 @@ model:
|
||||
filename: graphcore/input_gen.go
|
||||
package: graphcore
|
||||
|
||||
models:
|
||||
Activity:
|
||||
fields:
|
||||
unitName:
|
||||
resolver: true
|
||||
|
||||
resolver:
|
||||
layout: follow-schema
|
||||
dir: resolvers
|
||||
|
||||
@@ -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 }
|
||||
@@ -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"
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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!
|
||||
}
|
||||
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user