1package bug
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6
  7	"github.com/pkg/errors"
  8
  9	"github.com/MichaelMure/git-bug/entity"
 10	"github.com/MichaelMure/git-bug/entity/dag"
 11	"github.com/MichaelMure/git-bug/identity"
 12	"github.com/MichaelMure/git-bug/repository"
 13	"github.com/MichaelMure/git-bug/util/timestamp"
 14
 15	"github.com/MichaelMure/git-bug/util/text"
 16)
 17
 18var _ Operation = &EditCommentOperation{}
 19var _ dag.OperationWithFiles = &EditCommentOperation{}
 20
 21// EditCommentOperation will change a comment in the bug
 22type EditCommentOperation struct {
 23	OpBase
 24	Target  entity.Id         `json:"target"`
 25	Message string            `json:"message"`
 26	Files   []repository.Hash `json:"files"`
 27}
 28
 29func (op *EditCommentOperation) Id() entity.Id {
 30	return idOperation(op, &op.OpBase)
 31}
 32
 33func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
 34	// Todo: currently any message can be edited, even by a different author
 35	// crypto signature are needed.
 36
 37	// Recreate the Comment Id to match on
 38	commentId := entity.CombineIds(snapshot.Id(), op.Target)
 39
 40	var target TimelineItem
 41	for i, item := range snapshot.Timeline {
 42		if item.Id() == commentId {
 43			target = snapshot.Timeline[i]
 44			break
 45		}
 46	}
 47
 48	if target == nil {
 49		// Target not found, edit is a no-op
 50		return
 51	}
 52
 53	comment := Comment{
 54		id:       commentId,
 55		Message:  op.Message,
 56		Files:    op.Files,
 57		UnixTime: timestamp.Timestamp(op.UnixTime),
 58	}
 59
 60	switch target := target.(type) {
 61	case *CreateTimelineItem:
 62		target.Append(comment)
 63	case *AddCommentTimelineItem:
 64		target.Append(comment)
 65	default:
 66		// somehow, the target matched on something that is not a comment
 67		// we make the op a no-op
 68		return
 69	}
 70
 71	snapshot.addActor(op.Author_)
 72
 73	// Updating the corresponding comment
 74
 75	for i := range snapshot.Comments {
 76		if snapshot.Comments[i].Id() == commentId {
 77			snapshot.Comments[i].Message = op.Message
 78			snapshot.Comments[i].Files = op.Files
 79			break
 80		}
 81	}
 82}
 83
 84func (op *EditCommentOperation) GetFiles() []repository.Hash {
 85	return op.Files
 86}
 87
 88func (op *EditCommentOperation) Validate() error {
 89	if err := op.OpBase.Validate(op, EditCommentOp); err != nil {
 90		return err
 91	}
 92
 93	if err := op.Target.Validate(); err != nil {
 94		return errors.Wrap(err, "target hash is invalid")
 95	}
 96
 97	if !text.Safe(op.Message) {
 98		return fmt.Errorf("message is not fully printable")
 99	}
100
101	return nil
102}
103
104// UnmarshalJSON is two steps JSON unmarshalling
105// This workaround is necessary to avoid the inner OpBase.MarshalJSON
106// overriding the outer op's MarshalJSON
107func (op *EditCommentOperation) UnmarshalJSON(data []byte) error {
108	// Unmarshal OpBase and the op separately
109
110	base := OpBase{}
111	err := json.Unmarshal(data, &base)
112	if err != nil {
113		return err
114	}
115
116	aux := struct {
117		Target  entity.Id         `json:"target"`
118		Message string            `json:"message"`
119		Files   []repository.Hash `json:"files"`
120	}{}
121
122	err = json.Unmarshal(data, &aux)
123	if err != nil {
124		return err
125	}
126
127	op.OpBase = base
128	op.Target = aux.Target
129	op.Message = aux.Message
130	op.Files = aux.Files
131
132	return nil
133}
134
135// Sign post method for gqlgen
136func (op *EditCommentOperation) IsAuthored() {}
137
138func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
139	return &EditCommentOperation{
140		OpBase:  newOpBase(EditCommentOp, author, unixTime),
141		Target:  target,
142		Message: message,
143		Files:   files,
144	}
145}
146
147// EditComment is a convenience function to apply the operation
148func EditComment(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string) (*EditCommentOperation, error) {
149	return EditCommentWithFiles(b, author, unixTime, target, message, nil)
150}
151
152func EditCommentWithFiles(b Interface, author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) (*EditCommentOperation, error) {
153	editCommentOp := NewEditCommentOp(author, unixTime, target, message, files)
154	if err := editCommentOp.Validate(); err != nil {
155		return nil, err
156	}
157	b.Append(editCommentOp)
158	return editCommentOp, nil
159}
160
161// EditCreateComment is a convenience function to edit the body of a bug (the first comment)
162func EditCreateComment(b Interface, author identity.Interface, unixTime int64, message string) (*EditCommentOperation, error) {
163	createOp := b.FirstOp().(*CreateOperation)
164	return EditComment(b, author, unixTime, createOp.Id(), message)
165}
166
167// EditCreateCommentWithFiles is a convenience function to edit the body of a bug (the first comment)
168func EditCreateCommentWithFiles(b Interface, author identity.Interface, unixTime int64, message string, files []repository.Hash) (*EditCommentOperation, error) {
169	createOp := b.FirstOp().(*CreateOperation)
170	return EditCommentWithFiles(b, author, unixTime, createOp.Id(), message, files)
171}