1package bug
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7
8 "github.com/MichaelMure/git-bug/entity"
9 "github.com/MichaelMure/git-bug/entity/dag"
10 "github.com/MichaelMure/git-bug/identity"
11 "github.com/MichaelMure/git-bug/repository"
12 "github.com/MichaelMure/git-bug/util/text"
13 "github.com/MichaelMure/git-bug/util/timestamp"
14)
15
16var _ Operation = &CreateOperation{}
17var _ dag.OperationWithFiles = &CreateOperation{}
18
19// CreateOperation define the initial creation of a bug
20type CreateOperation struct {
21 OpBase
22 Title string `json:"title"`
23 Message string `json:"message"`
24 Files []repository.Hash `json:"files"`
25}
26
27func (op *CreateOperation) Id() entity.Id {
28 return idOperation(op, &op.OpBase)
29}
30
31// OVERRIDE
32func (op *CreateOperation) SetMetadata(key string, value string) {
33 // sanity check: we make sure we are not in the following scenario:
34 // - the bug is created with a first operation
35 // - Id() is used
36 // - metadata are added, which will change the Id
37 // - Id() is used again
38
39 if op.id != entity.UnsetId {
40 panic("usage of Id() after changing the first operation")
41 }
42
43 op.OpBase.SetMetadata(key, value)
44}
45
46func (op *CreateOperation) Apply(snapshot *Snapshot) {
47 // sanity check: will fail when adding a second Create
48 if snapshot.id != "" && snapshot.id != entity.UnsetId && snapshot.id != op.Id() {
49 panic("adding a second Create operation")
50 }
51
52 snapshot.id = op.Id()
53
54 snapshot.addActor(op.Author_)
55 snapshot.addParticipant(op.Author_)
56
57 snapshot.Title = op.Title
58
59 comment := Comment{
60 id: entity.CombineIds(snapshot.Id(), op.Id()),
61 Message: op.Message,
62 Author: op.Author_,
63 UnixTime: timestamp.Timestamp(op.UnixTime),
64 }
65
66 snapshot.Comments = []Comment{comment}
67 snapshot.Author = op.Author_
68 snapshot.CreateTime = op.Time()
69
70 snapshot.Timeline = []TimelineItem{
71 &CreateTimelineItem{
72 CommentTimelineItem: NewCommentTimelineItem(comment),
73 },
74 }
75}
76
77func (op *CreateOperation) GetFiles() []repository.Hash {
78 return op.Files
79}
80
81func (op *CreateOperation) Validate() error {
82 if err := op.OpBase.Validate(op, CreateOp); err != nil {
83 return err
84 }
85
86 if len(op.Nonce) > 64 {
87 return fmt.Errorf("create nonce is too big")
88 }
89 if len(op.Nonce) < 20 {
90 return fmt.Errorf("create nonce is too small")
91 }
92
93 if text.Empty(op.Title) {
94 return fmt.Errorf("title is empty")
95 }
96 if strings.Contains(op.Title, "\n") {
97 return fmt.Errorf("title should be a single line")
98 }
99 if !text.Safe(op.Title) {
100 return fmt.Errorf("title is not fully printable")
101 }
102
103 if !text.Safe(op.Message) {
104 return fmt.Errorf("message is not fully printable")
105 }
106
107 return nil
108}
109
110// UnmarshalJSON is a two step JSON unmarshalling
111// This workaround is necessary to avoid the inner OpBase.MarshalJSON
112// overriding the outer op's MarshalJSON
113func (op *CreateOperation) UnmarshalJSON(data []byte) error {
114 // Unmarshal OpBase and the op separately
115
116 base := OpBase{}
117 err := json.Unmarshal(data, &base)
118 if err != nil {
119 return err
120 }
121
122 aux := struct {
123 Nonce []byte `json:"nonce"`
124 Title string `json:"title"`
125 Message string `json:"message"`
126 Files []repository.Hash `json:"files"`
127 }{}
128
129 err = json.Unmarshal(data, &aux)
130 if err != nil {
131 return err
132 }
133
134 op.OpBase = base
135 op.Nonce = aux.Nonce
136 op.Title = aux.Title
137 op.Message = aux.Message
138 op.Files = aux.Files
139
140 return nil
141}
142
143// Sign post method for gqlgen
144func (op *CreateOperation) IsAuthored() {}
145
146func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
147 return &CreateOperation{
148 OpBase: newOpBase(CreateOp, author, unixTime),
149 Title: title,
150 Message: message,
151 Files: files,
152 }
153}
154
155// CreateTimelineItem replace a Create operation in the Timeline and hold its edition history
156type CreateTimelineItem struct {
157 CommentTimelineItem
158}
159
160// Sign post method for gqlgen
161func (c *CreateTimelineItem) IsAuthored() {}
162
163// Convenience function to apply the operation
164func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
165 return CreateWithFiles(author, unixTime, title, message, nil)
166}
167
168func CreateWithFiles(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) (*Bug, *CreateOperation, error) {
169 newBug := NewBug()
170 createOp := NewCreateOp(author, unixTime, title, message, files)
171
172 if err := createOp.Validate(); err != nil {
173 return nil, createOp, err
174 }
175
176 newBug.Append(createOp)
177
178 return newBug, createOp, nil
179}