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