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