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 { |
|||
"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! |
|||
} |
@ -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