Gisle Aune
5 years ago
18 changed files with 388 additions and 33 deletions
-
1database/database.go
-
100database/drivers/mysqldriver/activities.go
-
7database/drivers/mysqldriver/db.go
-
14database/repositories/activityrepository.go
-
6graph/gqlgen.yml
-
27graph/resolvers/activity.resolvers.go
-
1graph/resolvers/issue.resolvers.go
-
89graph/resolvers/mutation.resolvers.go
-
4graph/resolvers/project.resolvers.go
-
10graph/resolvers/query.resolvers.go
-
60graph/schema/activity.gql
-
37graph/schema/issue.gql
-
16graph/schema/mutation.gql
-
2graph/schema/project.gql
-
4internal/generate/ids.go
-
20migrations/mysql/20200508155557_create_table_activity.sql
-
19models/activity.go
-
4models/projectpermission.go
@ -0,0 +1,100 @@ |
|||||
|
package mysqldriver |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"database/sql" |
||||
|
"errors" |
||||
|
"git.aiterp.net/stufflog/server/internal/generate" |
||||
|
"git.aiterp.net/stufflog/server/internal/xlerrors" |
||||
|
"git.aiterp.net/stufflog/server/models" |
||||
|
sq "github.com/Masterminds/squirrel" |
||||
|
"github.com/jmoiron/sqlx" |
||||
|
) |
||||
|
|
||||
|
type activityRepository struct { |
||||
|
db *sqlx.DB |
||||
|
} |
||||
|
|
||||
|
func (r *activityRepository) Find(ctx context.Context, id string) (*models.Activity, error) { |
||||
|
activity := models.Activity{} |
||||
|
err := r.db.GetContext(ctx, &activity, "SELECT * FROM activity WHERE activity_id=?", id) |
||||
|
if err != nil { |
||||
|
if err == sql.ErrNoRows { |
||||
|
return nil, xlerrors.NotFound("Activity") |
||||
|
} |
||||
|
|
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return &activity, nil |
||||
|
} |
||||
|
|
||||
|
func (r *activityRepository) List(ctx context.Context, filter models.ActivityFilter) ([]*models.Activity, error) { |
||||
|
q := sq.Select("*").From("activity") |
||||
|
if len(filter.ActivityIDs) > 0 { |
||||
|
q = q.Where(sq.Eq{"activity_id": filter.ActivityIDs}) |
||||
|
} |
||||
|
if len(filter.ProjectIDs) > 0 { |
||||
|
q = q.Where(sq.Eq{"project_id": filter.ProjectIDs}) |
||||
|
} |
||||
|
|
||||
|
query, args, err := q.ToSql() |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
results := make([]*models.Activity, 0, 16) |
||||
|
err = r.db.SelectContext(ctx, &results, query, args...) |
||||
|
if err != nil { |
||||
|
if err == sql.ErrNoRows { |
||||
|
return []*models.Activity{}, nil |
||||
|
} |
||||
|
|
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return results, nil |
||||
|
} |
||||
|
|
||||
|
func (r *activityRepository) Insert(ctx context.Context, activity models.Activity) (*models.Activity, error) { |
||||
|
if activity.ProjectID == "" { |
||||
|
return nil, errors.New("missing project id") |
||||
|
} |
||||
|
|
||||
|
activity.ID = generate.ActivityID() |
||||
|
|
||||
|
_, err := r.db.NamedExecContext(ctx, ` |
||||
|
INSERT INTO activity ( |
||||
|
activity_id, project_id, name, countable, |
||||
|
unit_is_time, unit_name, unit_value, base_value |
||||
|
) VALUES ( |
||||
|
:activity_id, :project_id, :name, :countable, |
||||
|
:unit_is_time, :unit_name, :unit_value, :base_value |
||||
|
) |
||||
|
`, activity) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return &activity, nil |
||||
|
} |
||||
|
|
||||
|
func (r *activityRepository) Save(ctx context.Context, activity models.Activity) error { |
||||
|
_, err := r.db.NamedExecContext(ctx, ` |
||||
|
UPDATE activity SET |
||||
|
name=:name, |
||||
|
countable=:countable, |
||||
|
unit_is_time=:unit_is_time, |
||||
|
unit_name=:unit_name, |
||||
|
unit_value=:unit_value, |
||||
|
base_value=:base_value |
||||
|
WHERE activity_id=:activity_id |
||||
|
`, activity) |
||||
|
|
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
func (r *activityRepository) Delete(ctx context.Context, activity models.Activity) error { |
||||
|
_, err := r.db.ExecContext(ctx, "DELETE FROM activity WHERE activity_id=? LIMIT 1;", activity.ID) |
||||
|
return err |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
package repositories |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"git.aiterp.net/stufflog/server/models" |
||||
|
) |
||||
|
|
||||
|
type ActivityRepository interface { |
||||
|
Find(ctx context.Context, id string) (*models.Activity, error) |
||||
|
List(ctx context.Context, filter models.ActivityFilter) ([]*models.Activity, error) |
||||
|
Insert(ctx context.Context, activity models.Activity) (*models.Activity, error) |
||||
|
Save(ctx context.Context, activity models.Activity) error |
||||
|
Delete(ctx context.Context, activity models.Activity) error |
||||
|
} |
@ -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 } |
@ -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 |
||||
|
} |
@ -1,51 +1,60 @@ |
|||||
type Issue { |
type Issue { |
||||
|
"The issue ID." |
||||
id: String! |
id: String! |
||||
|
"The time at which the issue is created." |
||||
createdTime: Time! |
createdTime: Time! |
||||
|
"The time at which the issue was updated." |
||||
updatedTime: Time! |
updatedTime: Time! |
||||
|
"Optionally, when this issue is due." |
||||
dueTime: Time |
dueTime: Time |
||||
|
"The name of the issue, used in lists." |
||||
name: String! |
name: String! |
||||
|
"The issue title. This can be longer than the name." |
||||
title: String! |
title: String! |
||||
|
"The description of the issue, in markdown." |
||||
description: String! |
description: String! |
||||
|
|
||||
|
"Parent project." |
||||
project: Project |
project: Project |
||||
|
"The issue's owner/creator." |
||||
owner: User |
owner: User |
||||
|
"The issue assignee." |
||||
assignee: User |
assignee: User |
||||
|
"The issue status." |
||||
status: ProjectStatus! |
status: ProjectStatus! |
||||
} |
} |
||||
|
|
||||
input IssueFilter { |
input IssueFilter { |
||||
"Filter by issue IDs (mostly used internally by data loaders)" |
|
||||
|
"Filter by issue IDs. (mostly used internally by data loaders)" |
||||
issueIds: [String!] |
issueIds: [String!] |
||||
"Filter by project IDs" |
|
||||
|
"Filter by project IDs." |
||||
projectIds: [String!] |
projectIds: [String!] |
||||
"Filter by owner IDs" |
|
||||
|
"Filter by owner IDs." |
||||
ownerIds: [String!] |
ownerIds: [String!] |
||||
"Filter by assignee IDs" |
|
||||
|
"Filter by assignee IDs." |
||||
assigneeIds: [String!] |
assigneeIds: [String!] |
||||
"Text search" |
|
||||
|
"Text search." |
||||
search: String |
search: String |
||||
"Earliest stage (inclusive)" |
|
||||
|
"Earliest stage. (inclusive)" |
||||
minStage: Int |
minStage: Int |
||||
"Latest stage (inclusive)" |
|
||||
|
"Latest stage. (inclusive)" |
||||
maxStage: Int |
maxStage: Int |
||||
"Limit the result set" |
|
||||
|
"Limit the result set." |
||||
limit: Int |
limit: Int |
||||
} |
} |
||||
|
|
||||
input IssueCreateInput { |
input IssueCreateInput { |
||||
"Project ID" |
|
||||
|
"Project ID." |
||||
projectId: String! |
projectId: String! |
||||
"Status stage" |
|
||||
statusStage: Int! |
|
||||
"Status name" |
|
||||
|
"Status name." |
||||
statusName: String! |
statusName: String! |
||||
"A name for the issue." |
"A name for the issue." |
||||
name: String! |
name: String! |
||||
"Description of the issue." |
"Description of the issue." |
||||
description: String! |
description: String! |
||||
"Assign this issue, will default to unassigned" |
|
||||
|
"Assign this issue, will default to unassigned." |
||||
assigneeId: String |
assigneeId: String |
||||
"A date when this issue is due" |
|
||||
|
"A date when this issue is due." |
||||
dueTime: Time |
dueTime: Time |
||||
"Optional title to use instead of the name when not in a list." |
"Optional title to use instead of the name when not in a list." |
||||
title: String |
title: String |
@ -1,19 +1,25 @@ |
|||||
type Mutation { |
type Mutation { |
||||
# PROJECT |
# PROJECT |
||||
"Create a new project" |
|
||||
|
"Create a new project." |
||||
createProject(input: ProjectCreateInput!): Project! |
createProject(input: ProjectCreateInput!): Project! |
||||
|
|
||||
|
# ACTIVITY |
||||
|
"Create an activity." |
||||
|
createActivity(input: ActivityCreateInput!): Activity! |
||||
|
"Edit an activity." |
||||
|
editActivity(input: ActivityEditInput!): Activity! |
||||
|
|
||||
# ISSUE |
# ISSUE |
||||
"Create a new issue" |
|
||||
|
"Create a new issue." |
||||
createIssue(input: IssueCreateInput!): Issue! |
createIssue(input: IssueCreateInput!): Issue! |
||||
|
|
||||
# USER |
# USER |
||||
"Log in" |
|
||||
|
"Log in." |
||||
loginUser(input: UserLoginInput!): User! |
loginUser(input: UserLoginInput!): User! |
||||
"Log out" |
|
||||
|
"Log out." |
||||
logoutUser: User! |
logoutUser: User! |
||||
"Create a new user. This can only be done by administrators." |
"Create a new user. This can only be done by administrators." |
||||
createUser(input: UserCreateInput!): User! |
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! |
editUser(input: UserEditInput!): User! |
||||
} |
} |
@ -0,0 +1,20 @@ |
|||||
|
-- +goose Up |
||||
|
-- +goose StatementBegin |
||||
|
CREATE TABLE activity ( |
||||
|
activity_id CHAR(16) PRIMARY KEY, |
||||
|
project_id CHAR(16) NOT NULL, |
||||
|
name VARCHAR(255) NOT NULL, |
||||
|
countable BOOLEAN NOT NULL, |
||||
|
unit_is_time BOOLEAN NOT NULL, |
||||
|
unit_name VARCHAR(255) NOT NULL, |
||||
|
unit_value FLOAT NOT NULL, |
||||
|
base_value FLOAT NOT NULL, |
||||
|
|
||||
|
INDEX(Project_id) |
||||
|
); |
||||
|
-- +goose StatementEnd |
||||
|
|
||||
|
-- +goose Down |
||||
|
-- +goose StatementBegin |
||||
|
DROP TABLE activity; |
||||
|
-- +goose StatementEnd |
Write
Preview
Loading…
Cancel
Save
Reference in new issue