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