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