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