22 changed files with 883 additions and 30 deletions
-
1database/database.go
-
7database/drivers/mysqldriver/db.go
-
281database/drivers/mysqldriver/logs.go
-
14database/repositories/logrepository.go
-
3go.mod
-
11go.sum
-
11graph/resolvers/issue.resolvers.go
-
44graph/resolvers/log.resolvers.go
-
156graph/resolvers/mutation.resolvers.go
-
40graph/resolvers/query.resolvers.go
-
4graph/schema/issue.gql
-
110graph/schema/log.gql
-
6graph/schema/mutation.gql
-
5graph/schema/query.gql
-
4internal/generate/ids.go
-
17main.go
-
16migrations/mysql/20200517111706_create_table_issue_item.sql
-
17migrations/mysql/20200523151259_create_table_log.sql
-
18migrations/mysql/20200523151309_create_table_log_item.sql
-
19migrations/mysql/20200524122544_create_table_log_task.sql
-
39models/log.go
-
90services/auth.go
@ -0,0 +1,281 @@ |
|||
package mysqldriver |
|||
|
|||
import ( |
|||
"context" |
|||
"database/sql" |
|||
"git.aiterp.net/stufflog/server/internal/generate" |
|||
"git.aiterp.net/stufflog/server/internal/slerrors" |
|||
"git.aiterp.net/stufflog/server/models" |
|||
sq "github.com/Masterminds/squirrel" |
|||
"github.com/jmoiron/sqlx" |
|||
) |
|||
|
|||
type logRepository struct { |
|||
db *sqlx.DB |
|||
} |
|||
|
|||
func (r *logRepository) Find(ctx context.Context, id string) (*models.Log, error) { |
|||
log := models.Log{} |
|||
err := r.db.GetContext(ctx, &log, "SELECT * FROM log WHERE log_id=?", id) |
|||
if err != nil { |
|||
if err == sql.ErrNoRows { |
|||
return nil, slerrors.NotFound("Log") |
|||
} |
|||
|
|||
return nil, err |
|||
} |
|||
|
|||
err = r.db.SelectContext(ctx, &log.Items, "SELECT * FROM log_item WHERE log_id=?", id) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
err = r.db.SelectContext(ctx, &log.Tasks, "SELECT * FROM log_task WHERE log_id=?", id) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return &log, nil |
|||
} |
|||
|
|||
func (r *logRepository) List(ctx context.Context, filter models.LogFilter) ([]*models.Log, error) { |
|||
q := sq.Select("log.*").From("log").GroupBy("log.log_id").OrderBy("log.date") |
|||
if len(filter.IssueItemIDs) > 0 || len(filter.IssueIDs) > 0 { |
|||
q = q.LeftJoin("log_item ON log_item.log_id = log.log_id") |
|||
} |
|||
if len(filter.IssueTaskIDs) > 0 || len(filter.IssueIDs) > 0 { |
|||
q = q.LeftJoin("log_task ON log_task.log_id = log.log_id") |
|||
} |
|||
if len(filter.IssueIDs) > 0 { |
|||
q = q.Where(sq.Or{ |
|||
sq.Eq{"log_task.issue_id": filter.IssueIDs}, |
|||
sq.Eq{"log_item.issue_id": filter.IssueIDs}, |
|||
}) |
|||
} |
|||
if len(filter.IssueItemIDs) > 0 { |
|||
q = q.Where(sq.Eq{"log_item.issue_item_id": filter.IssueItemIDs}) |
|||
} |
|||
if len(filter.IssueTaskIDs) > 0 { |
|||
q = q.Where(sq.Eq{"log_task.issue_task_id": filter.IssueTaskIDs}) |
|||
} |
|||
if len(filter.LogIDs) > 0 { |
|||
q = q.Where(sq.Eq{"log.log_id": filter.LogIDs}) |
|||
} |
|||
if len(filter.UserIDs) > 0 { |
|||
q = q.Where(sq.Eq{"log.user_id": filter.UserIDs}) |
|||
} |
|||
if filter.FromDate != nil { |
|||
q = q.Where(sq.GtOrEq{"log.date": *filter.FromDate}) |
|||
} |
|||
if filter.ToDate != nil { |
|||
q = q.Where(sq.LtOrEq{"log.date": *filter.ToDate}) |
|||
} |
|||
|
|||
query, args, err := q.ToSql() |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
results := make([]*models.Log, 0, 16) |
|||
err = r.db.SelectContext(ctx, &results, query, args...) |
|||
if err != nil { |
|||
if err == sql.ErrNoRows { |
|||
return []*models.Log{}, nil |
|||
} |
|||
|
|||
return nil, err |
|||
} |
|||
|
|||
err = r.fill(ctx, results) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return results, nil |
|||
} |
|||
|
|||
func (r *logRepository) Insert(ctx context.Context, log models.Log) (*models.Log, error) { |
|||
tx, err := r.db.BeginTxx(ctx, nil) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
log.ID = generate.LogID() |
|||
|
|||
_, err = tx.NamedExecContext(ctx, ` |
|||
INSERT INTO log ( |
|||
log_id, user_id, date, description |
|||
) VALUES ( |
|||
:log_id, :user_id, :date, :description |
|||
) |
|||
`, log) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return nil, err |
|||
} |
|||
|
|||
for _, item := range log.Items { |
|||
item.LogID = log.ID |
|||
_, err = tx.NamedExecContext(ctx, ` |
|||
INSERT INTO log_item ( |
|||
log_id, issue_id, issue_item_id, amount |
|||
) VALUES ( |
|||
:log_id, :issue_id, :issue_item_id, :amount |
|||
) |
|||
`, item) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return nil, err |
|||
} |
|||
} |
|||
for _, task := range log.Tasks { |
|||
task.LogID = log.ID |
|||
_, err = tx.NamedExecContext(ctx, ` |
|||
INSERT INTO log_task ( |
|||
log_id, issue_id, issue_task_id, units, duration |
|||
) VALUES ( |
|||
:log_id, :issue_id, :issue_task_id, :units, :duration |
|||
) |
|||
`, task) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return nil, err |
|||
} |
|||
} |
|||
|
|||
err = tx.Commit() |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return nil, err |
|||
} |
|||
|
|||
return &log, nil |
|||
} |
|||
|
|||
func (r *logRepository) Save(ctx context.Context, log models.Log) error { |
|||
tx, err := r.db.BeginTxx(ctx, nil) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
_, err = tx.NamedExecContext(ctx, ` |
|||
UPDATE log SET |
|||
date=:date, |
|||
description=:description |
|||
WHERE log_id=:log_id |
|||
`, log) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
|
|||
_, err = tx.ExecContext(ctx, "DELETE FROM log_item WHERE log_id=?", log.ID) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
_, err = tx.ExecContext(ctx, "DELETE FROM log_task WHERE log_id=?", log.ID) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
|
|||
for _, item := range log.Items { |
|||
_, err = tx.NamedExecContext(ctx, ` |
|||
INSERT INTO log_item ( |
|||
log_id, issue_id, issue_item_id, amount |
|||
) VALUES ( |
|||
:log_id, :issue_id, :issue_item_id, :amount |
|||
) |
|||
`, item) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
} |
|||
for _, task := range log.Tasks { |
|||
_, err = tx.NamedExecContext(ctx, ` |
|||
INSERT INTO log_task ( |
|||
log_id, issue_id, issue_task_id, units, duration |
|||
) VALUES ( |
|||
:log_id, :issue_id, :issue_task_id, :units, :duration |
|||
) |
|||
`, task) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
} |
|||
|
|||
err = tx.Commit() |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (r *logRepository) Delete(ctx context.Context, log models.Log) error { |
|||
tx, err := r.db.BeginTxx(ctx, nil) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
_, err = tx.ExecContext(ctx, "DELETE FROM log WHERE log_id=?", log.ID) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
_, err = tx.ExecContext(ctx, "DELETE FROM log_item WHERE log_id=?", log.ID) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
_, err = tx.ExecContext(ctx, "DELETE FROM log_task WHERE log_id=?", log.ID) |
|||
if err != nil { |
|||
_ = tx.Rollback() |
|||
return err |
|||
} |
|||
|
|||
return tx.Commit() |
|||
} |
|||
|
|||
func (r *logRepository) fill(ctx context.Context, logs []*models.Log) error { |
|||
logMap := make(map[string]int, len(logs)) |
|||
ids := make([]string, len(logs)) |
|||
for i, log := range logs { |
|||
ids[i] = log.ID |
|||
logMap[log.ID] = i |
|||
} |
|||
|
|||
itemsQuery, itemsArgs, err := sq.Select("*").From("log_item").Where(sq.Eq{"log_id": ids}).ToSql() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
tasksQuery, tasksArgs, err := sq.Select("*").From("log_task").Where(sq.Eq{"log_id": ids}).ToSql() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
items := make([]models.LogItem, 0, len(logs)*3) |
|||
err = r.db.SelectContext(ctx, &items, itemsQuery, itemsArgs...) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
for _, item := range items { |
|||
log := logs[logMap[item.LogID]] |
|||
log.Items = append(log.Items, item) |
|||
} |
|||
|
|||
tasks := make([]models.LogTask, 0, len(logs)*3) |
|||
err = r.db.SelectContext(ctx, &tasks, tasksQuery, tasksArgs...) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
for _, task := range tasks { |
|||
log := logs[logMap[task.LogID]] |
|||
log.Tasks = append(log.Tasks, task) |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,14 @@ |
|||
package repositories |
|||
|
|||
import ( |
|||
"context" |
|||
"git.aiterp.net/stufflog/server/models" |
|||
) |
|||
|
|||
type LogRepository interface { |
|||
Find(ctx context.Context, id string) (*models.Log, error) |
|||
List(ctx context.Context, filter models.LogFilter) ([]*models.Log, error) |
|||
Insert(ctx context.Context, log models.Log) (*models.Log, error) |
|||
Save(ctx context.Context, log models.Log) error |
|||
Delete(ctx context.Context, log models.Log) error |
|||
} |
@ -0,0 +1,44 @@ |
|||
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 *logResolver) User(ctx context.Context, obj *models.Log) (*models.User, error) { |
|||
return r.Database.Users().Find(ctx, obj.UserID) |
|||
} |
|||
|
|||
func (r *logItemResolver) Issue(ctx context.Context, obj *models.LogItem) (*models.Issue, error) { |
|||
return r.Database.Issues().Find(ctx, obj.IssueID) |
|||
} |
|||
|
|||
func (r *logItemResolver) Item(ctx context.Context, obj *models.LogItem) (*models.IssueItem, error) { |
|||
return r.Database.IssueItems().Find(ctx, obj.IssueItemID) |
|||
} |
|||
|
|||
func (r *logTaskResolver) Issue(ctx context.Context, obj *models.LogTask) (*models.Issue, error) { |
|||
return r.Database.Issues().Find(ctx, obj.IssueID) |
|||
} |
|||
|
|||
func (r *logTaskResolver) Task(ctx context.Context, obj *models.LogTask) (*models.IssueTask, error) { |
|||
return r.Database.IssueTasks().Find(ctx, obj.IssueTaskID) |
|||
} |
|||
|
|||
// Log returns graphcore.LogResolver implementation.
|
|||
func (r *Resolver) Log() graphcore.LogResolver { return &logResolver{r} } |
|||
|
|||
// LogItem returns graphcore.LogItemResolver implementation.
|
|||
func (r *Resolver) LogItem() graphcore.LogItemResolver { return &logItemResolver{r} } |
|||
|
|||
// LogTask returns graphcore.LogTaskResolver implementation.
|
|||
func (r *Resolver) LogTask() graphcore.LogTaskResolver { return &logTaskResolver{r} } |
|||
|
|||
type logResolver struct{ *Resolver } |
|||
type logItemResolver struct{ *Resolver } |
|||
type logTaskResolver struct{ *Resolver } |
@ -0,0 +1,110 @@ |
|||
""" |
|||
A log is a chunk of logged work, on one or more issues. |
|||
""" |
|||
type Log { |
|||
"The log's ID." |
|||
id: String! |
|||
"When the log is taking place." |
|||
date: Time! |
|||
"A description of the log." |
|||
description: String! |
|||
|
|||
"The user that logged the work." |
|||
user: User! |
|||
"The tasks logged." |
|||
tasks: [LogTask!]! |
|||
"The items changed." |
|||
items: [LogItem!]! |
|||
} |
|||
|
|||
type LogTask { |
|||
"Parent issue of the task." |
|||
issue: Issue! |
|||
"The issue task logged." |
|||
task: IssueTask! |
|||
"How many units of work is done, if applicable." |
|||
units: Int |
|||
"Time spent on the issue." |
|||
duration: Duration! |
|||
} |
|||
|
|||
""" |
|||
Log items are item changes related to a log. |
|||
""" |
|||
type LogItem { |
|||
"Parent issue of the item." |
|||
issue: Issue! |
|||
"The item that has been acquired." |
|||
item: IssueItem! |
|||
"The amount of items acquired." |
|||
amount: Int! |
|||
} |
|||
|
|||
"Filter for the logs query." |
|||
input LogFilter { |
|||
"Log IDs to select." |
|||
logIds: [String!] |
|||
"Limit to the user IDs." |
|||
userIds: [String!] |
|||
"The issue IDs to limit to." |
|||
issueIds: [String!] |
|||
"The issue task IDs to limit to." |
|||
issueTaskIds: [String!] |
|||
"The issue item IDs to limit to." |
|||
issueItemIds: [String!] |
|||
"Earliest date to get logs from (inclusive)." |
|||
fromDate: Time |
|||
"Latest date to get logs from (inclusive)." |
|||
toDate: Time |
|||
} |
|||
|
|||
"Input for the createLog mutation." |
|||
input LogCreateInput { |
|||
"When did it take place." |
|||
date: Time! |
|||
"Describe the logged work." |
|||
description: String! |
|||
|
|||
"Add issue items to the log." |
|||
items: [LogItemInput!] |
|||
"Add issue tasks to the log." |
|||
tasks: [LogTaskInput!] |
|||
} |
|||
|
|||
"Input for the editLog mutation." |
|||
input LogEditInput { |
|||
"The log to update." |
|||
logId: String! |
|||
|
|||
"Update the time of the log." |
|||
setDate: Time |
|||
"Update the description of the log." |
|||
setDescription: String |
|||
|
|||
"Add/update one or more items to the log." |
|||
updateItems: [LogItemInput!] |
|||
"Remove one or more items from the log." |
|||
removeItems: [String!] |
|||
"Add/update one or more items to the log." |
|||
updateTasks: [LogTaskInput!] |
|||
"Remove one or more items from the log." |
|||
removeTasks: [String!] |
|||
} |
|||
|
|||
"Sub-input for the createLog and editLog mutation." |
|||
input LogItemInput { |
|||
"The issue item to log ID." |
|||
issueItemId: String! |
|||
"The amount of items acquired." |
|||
amount: Int! |
|||
} |
|||
|
|||
"Sub-input for the createLog and editLog mutation." |
|||
input LogTaskInput { |
|||
"The issue item to log ID." |
|||
issueTaskId: String! |
|||
"The amount of units done, if applicable." |
|||
units: Int |
|||
"How long did it take?" |
|||
duration: Duration! |
|||
} |
@ -0,0 +1,17 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
CREATE TABLE log ( |
|||
log_id CHAR(24) NOT NULL PRIMARY KEY, |
|||
user_id CHAR(32) NOT NULL, |
|||
date TIMESTAMP NOT NULL, |
|||
description TEXT NOT NULL, |
|||
|
|||
INDEX (user_id), |
|||
INDEX (date) |
|||
); |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
DROP TABLE log; |
|||
-- +goose StatementEnd |
@ -0,0 +1,18 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
CREATE TABLE log_item ( |
|||
log_id CHAR(24) NOT NULL, |
|||
issue_id CHAR(32) NOT NULL, |
|||
issue_item_id CHAR(48) NOT NULL, |
|||
amount INTEGER NOT NULL, |
|||
|
|||
PRIMARY KEY(log_id, issue_item_id), |
|||
INDEX (issue_item_id), |
|||
INDEX (issue_id) |
|||
); |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
DROP TABLE log_item; |
|||
-- +goose StatementEnd |
@ -0,0 +1,19 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
CREATE TABLE log_task ( |
|||
log_id CHAR(24) NOT NULL, |
|||
issue_id CHAR(32) NOT NULL, |
|||
issue_task_id CHAR(48) NOT NULL, |
|||
units INTEGER, |
|||
duration BIGINT NOT NULL, |
|||
|
|||
PRIMARY KEY(log_id, issue_task_id), |
|||
INDEX (issue_task_id), |
|||
INDEX (issue_id) |
|||
); |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
DROP TABLE log_task; |
|||
-- +goose StatementEnd |
Write
Preview
Loading…
Cancel
Save
Reference in new issue