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.(type) {
63 case *CreateTimelineItem:
64 item := target.(*CreateTimelineItem)
65 item.Append(comment)
66
67 case *AddCommentTimelineItem:
68 item := target.(*AddCommentTimelineItem)
69 item.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}