add logging.
This commit is contained in:
@@ -18,6 +18,7 @@ type Database interface {
|
|||||||
Session() repositories.SessionRepository
|
Session() repositories.SessionRepository
|
||||||
Users() repositories.UserRepository
|
Users() repositories.UserRepository
|
||||||
ProjectStatuses() repositories.ProjectStatusRepository
|
ProjectStatuses() repositories.ProjectStatusRepository
|
||||||
|
Logs() repositories.LogRepository
|
||||||
// Migrate the database.
|
// Migrate the database.
|
||||||
Migrate() error
|
Migrate() error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type DB struct {
|
|||||||
sessions *sessionRepository
|
sessions *sessionRepository
|
||||||
users *userRepository
|
users *userRepository
|
||||||
projectStatuses *projectStatusRepository
|
projectStatuses *projectStatusRepository
|
||||||
|
logs *logRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Activities() repositories.ActivityRepository {
|
func (db *DB) Activities() repositories.ActivityRepository {
|
||||||
@@ -61,6 +62,10 @@ func (db *DB) ProjectStatuses() repositories.ProjectStatusRepository {
|
|||||||
return db.projectStatuses
|
return db.projectStatuses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) Logs() repositories.LogRepository {
|
||||||
|
return db.logs
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) Migrate() error {
|
func (db *DB) Migrate() error {
|
||||||
err := goose.SetDialect("mysql")
|
err := goose.SetDialect("mysql")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,6 +106,7 @@ func Open(connectionString string) (*DB, error) {
|
|||||||
projectStatuses := &projectStatusRepository{db: db}
|
projectStatuses := &projectStatusRepository{db: db}
|
||||||
issueTasks := &issueTaskRepository{db: db}
|
issueTasks := &issueTaskRepository{db: db}
|
||||||
issueItems := &issueItemRepository{db: db}
|
issueItems := &issueItemRepository{db: db}
|
||||||
|
logs := &logRepository{db: db}
|
||||||
|
|
||||||
return &DB{
|
return &DB{
|
||||||
db: db,
|
db: db,
|
||||||
@@ -113,6 +119,7 @@ func Open(connectionString string) (*DB, error) {
|
|||||||
users: users,
|
users: users,
|
||||||
sessions: sessions,
|
sessions: sessions,
|
||||||
projectStatuses: projectStatuses,
|
projectStatuses: projectStatuses,
|
||||||
|
logs: logs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -13,9 +13,10 @@ require (
|
|||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pressly/goose v2.6.0+incompatible
|
github.com/pressly/goose v2.6.0+incompatible
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.5.1
|
||||||
github.com/urfave/cli/v2 v2.2.0
|
github.com/urfave/cli/v2 v2.2.0
|
||||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e
|
|
||||||
github.com/vektah/gqlparser/v2 v2.0.1
|
github.com/vektah/gqlparser/v2 v2.0.1
|
||||||
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8
|
golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8
|
||||||
|
gopkg.in/ini.v1 v1.56.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
|||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
||||||
@@ -49,6 +51,8 @@ github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
|||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@@ -98,6 +102,10 @@ github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJ
|
|||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
@@ -137,6 +145,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
|
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
|
||||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
@@ -144,6 +153,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
|
||||||
|
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package resolvers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"git.aiterp.net/stufflog/server/graph/graphcore"
|
"git.aiterp.net/stufflog/server/graph/graphcore"
|
||||||
"git.aiterp.net/stufflog/server/graph/graphutil"
|
"git.aiterp.net/stufflog/server/graph/graphutil"
|
||||||
"git.aiterp.net/stufflog/server/graph/loaders"
|
"git.aiterp.net/stufflog/server/graph/loaders"
|
||||||
@@ -94,6 +93,16 @@ func (r *issueResolver) Items(ctx context.Context, obj *models.Issue, filter *mo
|
|||||||
return r.Database.IssueItems().List(ctx, *filter)
|
return r.Database.IssueItems().List(ctx, *filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *issueResolver) Logs(ctx context.Context, obj *models.Issue) ([]*models.Log, error) {
|
||||||
|
logs, err := r.Database.Logs().List(ctx, models.LogFilter{IssueIDs: []string{obj.ID}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.Auth.FilterLogList(ctx, &logs)
|
||||||
|
|
||||||
|
return logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Issue returns graphcore.IssueResolver implementation.
|
// Issue returns graphcore.IssueResolver implementation.
|
||||||
func (r *Resolver) Issue() graphcore.IssueResolver { return &issueResolver{r} }
|
func (r *Resolver) Issue() graphcore.IssueResolver { return &issueResolver{r} }
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
||||||
@@ -475,6 +475,162 @@ func (r *mutationResolver) EditIssueItem(ctx context.Context, input graphcore.Is
|
|||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) CreateLog(ctx context.Context, input graphcore.LogCreateInput) (*models.Log, error) {
|
||||||
|
user := r.Auth.UserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return nil, slerrors.PermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
log := &models.Log{
|
||||||
|
UserID: user.ID,
|
||||||
|
Date: input.Date,
|
||||||
|
Description: input.Description,
|
||||||
|
Items: make([]models.LogItem, 0, len(input.Items)),
|
||||||
|
Tasks: make([]models.LogTask, 0, len(input.Tasks)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, itemInput := range input.Items {
|
||||||
|
item, err := r.Database.IssueItems().Find(ctx, itemInput.IssueItemID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := r.Database.Issues().Find(ctx, item.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = r.Auth.IssuePermission(ctx, *issue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Items = append(log.Items, models.LogItem{
|
||||||
|
IssueID: item.IssueID,
|
||||||
|
IssueItemID: item.ID,
|
||||||
|
Amount: itemInput.Amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, taskInput := range input.Tasks {
|
||||||
|
task, err := r.Database.IssueTasks().Find(ctx, taskInput.IssueTaskID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := r.Database.Issues().Find(ctx, task.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = r.Auth.IssuePermission(ctx, *issue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tasks = append(log.Tasks, models.LogTask{
|
||||||
|
IssueID: task.IssueID,
|
||||||
|
IssueTaskID: task.ID,
|
||||||
|
Duration: taskInput.Duration,
|
||||||
|
Units: taskInput.Units,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Database.Logs().Insert(ctx, *log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) EditLog(ctx context.Context, input graphcore.LogEditInput) (*models.Log, error) {
|
||||||
|
user := r.Auth.UserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return nil, slerrors.PermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
log, err := r.Database.Logs().Find(ctx, input.LogID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItems := append([]string{}, input.RemoveItems...)
|
||||||
|
for _, itemInput := range input.UpdateItems {
|
||||||
|
removeItems = append(removeItems, itemInput.IssueItemID)
|
||||||
|
}
|
||||||
|
removeTasks := append([]string{}, input.RemoveTasks...)
|
||||||
|
for _, taskInput := range input.UpdateTasks {
|
||||||
|
removeTasks = append(removeTasks, taskInput.IssueTaskID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.SetDate != nil {
|
||||||
|
log.Date = *input.SetDate
|
||||||
|
}
|
||||||
|
if input.SetDescription != nil {
|
||||||
|
log.Description = *input.SetDescription
|
||||||
|
}
|
||||||
|
for _, remove := range removeItems {
|
||||||
|
for i, item := range log.Items {
|
||||||
|
if remove == item.IssueItemID {
|
||||||
|
log.Items = append(log.Items[:i], log.Items[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, remove := range removeTasks {
|
||||||
|
for i, task := range log.Tasks {
|
||||||
|
if remove == task.IssueTaskID {
|
||||||
|
log.Tasks = append(log.Tasks[:i], log.Tasks[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, itemInput := range input.UpdateItems {
|
||||||
|
item, err := r.Database.IssueItems().Find(ctx, itemInput.IssueItemID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := r.Database.Issues().Find(ctx, item.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = r.Auth.IssuePermission(ctx, *issue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Items = append(log.Items, models.LogItem{
|
||||||
|
LogID: log.ID,
|
||||||
|
IssueID: item.IssueID,
|
||||||
|
IssueItemID: item.ID,
|
||||||
|
Amount: itemInput.Amount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, taskInput := range input.UpdateTasks {
|
||||||
|
task, err := r.Database.IssueTasks().Find(ctx, taskInput.IssueTaskID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := r.Database.Issues().Find(ctx, task.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = r.Auth.IssuePermission(ctx, *issue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tasks = append(log.Tasks, models.LogTask{
|
||||||
|
LogID: log.ID,
|
||||||
|
IssueID: task.IssueID,
|
||||||
|
IssueTaskID: task.ID,
|
||||||
|
Duration: taskInput.Duration,
|
||||||
|
Units: taskInput.Units,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.Database.Logs().Save(ctx, *log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return log, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) LoginUser(ctx context.Context, input graphcore.UserLoginInput) (*models.User, error) {
|
func (r *mutationResolver) LoginUser(ctx context.Context, input graphcore.UserLoginInput) (*models.User, error) {
|
||||||
return r.Auth.Login(ctx, input.Username, input.Password)
|
return r.Auth.Login(ctx, input.Username, input.Password)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,46 @@ func (r *queryResolver) Projects(ctx context.Context, filter *models.ProjectFilt
|
|||||||
return projects, nil
|
return projects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) Log(ctx context.Context, id string) (*models.Log, error) {
|
||||||
|
user := r.Auth.UserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return nil, slerrors.PermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
log, err := r.Database.Logs().Find(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Auth.FilterLog(ctx, log)
|
||||||
|
if log.Empty() {
|
||||||
|
return nil, slerrors.NotFound("Log")
|
||||||
|
}
|
||||||
|
|
||||||
|
return log, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) Logs(ctx context.Context, filter *models.LogFilter) ([]*models.Log, error) {
|
||||||
|
user := r.Auth.UserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return nil, slerrors.PermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter == nil {
|
||||||
|
filter = &models.LogFilter{
|
||||||
|
UserIDs: []string{user.ID},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := r.Database.Logs().List(ctx, *filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.Auth.FilterLogList(ctx, &logs)
|
||||||
|
|
||||||
|
return logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Session(ctx context.Context) (*models.User, error) {
|
func (r *queryResolver) Session(ctx context.Context) (*models.User, error) {
|
||||||
user := r.Auth.UserFromContext(ctx)
|
user := r.Auth.UserFromContext(ctx)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ type Issue {
|
|||||||
tasks(filter: IssueTaskFilter): [IssueTask!]!
|
tasks(filter: IssueTaskFilter): [IssueTask!]!
|
||||||
"Issue items."
|
"Issue items."
|
||||||
items(filter: IssueIssueItemFilter): [IssueItem!]!
|
items(filter: IssueIssueItemFilter): [IssueItem!]!
|
||||||
#"Logs related to this issue."
|
"Logs related to this issue."
|
||||||
#logs: [Log!]!
|
logs: [Log!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
input IssueFilter {
|
input IssueFilter {
|
||||||
|
|||||||
@@ -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!
|
||||||
|
}
|
||||||
@@ -30,6 +30,12 @@ type Mutation {
|
|||||||
editIssueItem(input: IssueItemEditInput!): IssueItem!
|
editIssueItem(input: IssueItemEditInput!): IssueItem!
|
||||||
|
|
||||||
# LOG
|
# LOG
|
||||||
|
"Create a log."
|
||||||
|
createLog(input: LogCreateInput!): Log!
|
||||||
|
"Edit a log."
|
||||||
|
editLog(input: LogEditInput!): Log!
|
||||||
|
|
||||||
|
# GOAL
|
||||||
|
|
||||||
# USER
|
# USER
|
||||||
"Log in."
|
"Log in."
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ type Query {
|
|||||||
"List projects."
|
"List projects."
|
||||||
projects(filter: ProjectFilter): [Project!]!
|
projects(filter: ProjectFilter): [Project!]!
|
||||||
|
|
||||||
|
"Find log."
|
||||||
|
log(id: String!): Log!
|
||||||
|
"List logs."
|
||||||
|
logs(filter: LogFilter): [Log!]!
|
||||||
|
|
||||||
"Check the user session."
|
"Check the user session."
|
||||||
session: User
|
session: User
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,10 @@ func ItemID() string {
|
|||||||
return Generate(32, "I")
|
return Generate(32, "I")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LogID() string {
|
||||||
|
return Generate(24, "L")
|
||||||
|
}
|
||||||
|
|
||||||
func SessionID() string {
|
func SessionID() string {
|
||||||
return Generate(32, "S")
|
return Generate(32, "S")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "s3-host",
|
Name: "s3-host",
|
||||||
Value: "localhost",
|
Value: "",
|
||||||
Usage: "S3 Host (without https://)",
|
Usage: "S3 Host (without https://)",
|
||||||
EnvVars: []string{"S3_HOST"},
|
EnvVars: []string{"S3_HOST"},
|
||||||
Destination: &s3Host,
|
Destination: &s3Host,
|
||||||
@@ -201,12 +201,15 @@ func main() {
|
|||||||
return errors.Wrap(err, "Failed to connect to database")
|
return errors.Wrap(err, "Failed to connect to database")
|
||||||
}
|
}
|
||||||
|
|
||||||
s3, err := space.Connect(
|
var s3 *space.Space
|
||||||
s3Host, s3AccessKey, s3SecretKey, s3BucketName,
|
if s3Host != "" {
|
||||||
s3Secure, s3MaxFileSize, s3RootDirectory, s3UrlRoot,
|
s3, err = space.Connect(
|
||||||
)
|
s3Host, s3AccessKey, s3SecretKey, s3BucketName,
|
||||||
if err != nil {
|
s3Secure, s3MaxFileSize, s3RootDirectory, s3UrlRoot,
|
||||||
return err
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle := services.NewBundle(db, s3)
|
bundle := services.NewBundle(db, s3)
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
-- +goose Up
|
-- +goose Up
|
||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
CREATE TABLE issue_item (
|
CREATE TABLE issue_item (
|
||||||
issue_item_id CHAR(48) NOT NULL PRIMARY KEY,
|
issue_item_id CHAR(48) NOT NULL PRIMARY KEY,
|
||||||
issue_id CHAR(32) NOT NULL,
|
issue_id CHAR(32) NOT NULL,
|
||||||
item_id CHAR(32) NOT NULL,
|
item_id CHAR(32) NOT NULL,
|
||||||
quantity INTEGER NOT NULL,
|
quantity INTEGER NOT NULL,
|
||||||
acquired BOOLEAN NOT NULL,
|
acquired BOOLEAN NOT NULL,
|
||||||
|
|
||||||
INDEX (acquired),
|
INDEX (acquired),
|
||||||
INDEX (issue_id),
|
INDEX (issue_id),
|
||||||
INDEX (item_id)
|
INDEX (item_id)
|
||||||
);
|
);
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
+28
-11
@@ -3,23 +3,40 @@ package models
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
ID string `db:"log_id"`
|
ID string `db:"log_id"`
|
||||||
IssueID string `db:"issue_id"`
|
UserID string `db:"user_id"`
|
||||||
UserID string `db:"user_id"`
|
Date time.Time `db:"date"`
|
||||||
IssueTaskID *string `db:"issue_task_id"`
|
Description string `db:"description"`
|
||||||
UnitsDone *int `db:"units_done"`
|
|
||||||
Duration time.Duration `db:"duration"`
|
Items []LogItem
|
||||||
Description []string `db:"description"`
|
Tasks []LogTask
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogFilter struct {
|
func (log *Log) Empty() bool {
|
||||||
LogIDs []string
|
return len(log.Items) == 0 && len(log.Tasks) == 0
|
||||||
IssueIDs []string
|
}
|
||||||
IssueTaskIDs []string
|
|
||||||
|
type LogTask struct {
|
||||||
|
LogID string `db:"log_id"`
|
||||||
|
IssueID string `db:"issue_id"`
|
||||||
|
IssueTaskID string `db:"issue_task_id"`
|
||||||
|
Units *int `db:"units"`
|
||||||
|
Duration time.Duration `db:"duration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogItem struct {
|
type LogItem struct {
|
||||||
LogID string `db:"log_id"`
|
LogID string `db:"log_id"`
|
||||||
|
IssueID string `db:"issue_id"`
|
||||||
IssueItemID string `db:"issue_item_id"`
|
IssueItemID string `db:"issue_item_id"`
|
||||||
Amount int `db:"amount"`
|
Amount int `db:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogFilter struct {
|
||||||
|
LogIDs []string
|
||||||
|
UserIDs []string
|
||||||
|
IssueIDs []string
|
||||||
|
IssueTaskIDs []string
|
||||||
|
IssueItemIDs []string
|
||||||
|
FromDate *time.Time
|
||||||
|
ToDate *time.Time
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type Auth struct {
|
|||||||
users repositories.UserRepository
|
users repositories.UserRepository
|
||||||
session repositories.SessionRepository
|
session repositories.SessionRepository
|
||||||
projects repositories.ProjectRepository
|
projects repositories.ProjectRepository
|
||||||
|
issues repositories.IssueRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) Login(ctx context.Context, username, password string) (*models.User, error) {
|
func (auth *Auth) Login(ctx context.Context, username, password string) (*models.User, error) {
|
||||||
@@ -225,3 +226,92 @@ func (auth *Auth) EditUser(ctx context.Context, username string, setName *string
|
|||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) FilterLogList(ctx context.Context, logs *[]*models.Log) {
|
||||||
|
user := auth.UserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
panic("Auth.FilterLogList called without user")
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.FilterLog(ctx, *logs...)
|
||||||
|
deleteList := make([]int, 0, len(*logs)/2)
|
||||||
|
for i, log := range *logs {
|
||||||
|
if log.Empty() && log.UserID != user.ID {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list := *logs
|
||||||
|
for _, index := range deleteList {
|
||||||
|
list = append(list[:index], list[index+1:]...)
|
||||||
|
}
|
||||||
|
*logs = list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) FilterLog(ctx context.Context, logs ...*models.Log) {
|
||||||
|
userID := ""
|
||||||
|
if user := auth.UserFromContext(ctx); user != nil {
|
||||||
|
userID = user.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
accessMap := make(map[string]bool)
|
||||||
|
deleteList := make([]int, 0, 16)
|
||||||
|
for _, log := range logs {
|
||||||
|
if userID == log.UserID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteList = deleteList[:0]
|
||||||
|
for i, item := range log.Items {
|
||||||
|
if access, ok := accessMap[item.IssueID]; ok && access {
|
||||||
|
continue
|
||||||
|
} else if ok && !access {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := auth.issues.Find(ctx, item.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
accessMap[item.IssueID] = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = auth.IssuePermission(ctx, *issue)
|
||||||
|
if err != nil {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
}
|
||||||
|
|
||||||
|
accessMap[issue.ID] = err != nil
|
||||||
|
}
|
||||||
|
for _, index := range deleteList {
|
||||||
|
log.Items = append(log.Items[:index], log.Items[index+1:]...)
|
||||||
|
}
|
||||||
|
deleteList = deleteList[:0]
|
||||||
|
for i, task := range log.Tasks {
|
||||||
|
if access, ok := accessMap[task.IssueID]; ok && access {
|
||||||
|
continue
|
||||||
|
} else if ok && !access {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
issue, err := auth.issues.Find(ctx, task.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
accessMap[task.IssueID] = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = auth.IssuePermission(ctx, *issue)
|
||||||
|
if err != nil {
|
||||||
|
deleteList = append(deleteList, i-len(deleteList))
|
||||||
|
}
|
||||||
|
|
||||||
|
accessMap[issue.ID] = err != nil
|
||||||
|
}
|
||||||
|
for _, index := range deleteList {
|
||||||
|
log.Tasks = append(log.Tasks[:index], log.Tasks[index+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user