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/util/git"
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 []git.Hash `json:"files"`
23}
24
25func (op *CreateOperation) base() *OpBase {
26 return &op.OpBase
27}
28
29func (op *CreateOperation) Id() entity.Id {
30 return idOperation(op)
31}
32
33func (op *CreateOperation) Apply(snapshot *Snapshot) {
34 snapshot.addActor(op.Author)
35 snapshot.addParticipant(op.Author)
36
37 snapshot.Title = op.Title
38
39 comment := Comment{
40 id: op.Id(),
41 Message: op.Message,
42 Author: op.Author,
43 UnixTime: timestamp.Timestamp(op.UnixTime),
44 }
45
46 snapshot.Comments = []Comment{comment}
47 snapshot.Author = op.Author
48 snapshot.CreatedAt = op.Time()
49
50 snapshot.Timeline = []TimelineItem{
51 &CreateTimelineItem{
52 CommentTimelineItem: NewCommentTimelineItem(op.Id(), comment),
53 },
54 }
55}
56
57func (op *CreateOperation) GetFiles() []git.Hash {
58 return op.Files
59}
60
61func (op *CreateOperation) Validate() error {
62 if err := opBaseValidate(op, CreateOp); err != nil {
63 return err
64 }
65
66 if text.Empty(op.Title) {
67 return fmt.Errorf("title is empty")
68 }
69
70 if strings.Contains(op.Title, "\n") {
71 return fmt.Errorf("title should be a single line")
72 }
73
74 if !text.Safe(op.Title) {
75 return fmt.Errorf("title is not fully printable")
76 }
77
78 if !text.Safe(op.Message) {
79 return fmt.Errorf("message is not fully printable")
80 }
81
82 return nil
83}
84
85// UnmarshalJSON is a two step JSON unmarshaling
86// This workaround is necessary to avoid the inner OpBase.MarshalJSON
87// overriding the outer op's MarshalJSON
88func (op *CreateOperation) UnmarshalJSON(data []byte) error {
89 // Unmarshal OpBase and the op separately
90
91 base := OpBase{}
92 err := json.Unmarshal(data, &base)
93 if err != nil {
94 return err
95 }
96
97 aux := struct {
98 Title string `json:"title"`
99 Message string `json:"message"`
100 Files []git.Hash `json:"files"`
101 }{}
102
103 err = json.Unmarshal(data, &aux)
104 if err != nil {
105 return err
106 }
107
108 op.OpBase = base
109 op.Title = aux.Title
110 op.Message = aux.Message
111 op.Files = aux.Files
112
113 return nil
114}
115
116// Sign post method for gqlgen
117func (op *CreateOperation) IsAuthored() {}
118
119func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []git.Hash) *CreateOperation {
120 return &CreateOperation{
121 OpBase: newOpBase(CreateOp, author, unixTime),
122 Title: title,
123 Message: message,
124 Files: files,
125 }
126}
127
128// CreateTimelineItem replace a Create operation in the Timeline and hold its edition history
129type CreateTimelineItem struct {
130 CommentTimelineItem
131}
132
133// Sign post method for gqlgen
134func (c *CreateTimelineItem) IsAuthored() {}
135
136// Convenience function to apply the operation
137func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
138 return CreateWithFiles(author, unixTime, title, message, nil)
139}
140
141func CreateWithFiles(author identity.Interface, unixTime int64, title, message string, files []git.Hash) (*Bug, *CreateOperation, error) {
142 newBug := NewBug()
143 createOp := NewCreateOp(author, unixTime, title, message, files)
144
145 if err := createOp.Validate(); err != nil {
146 return nil, createOp, err
147 }
148
149 newBug.Append(createOp)
150
151 return newBug, createOp, nil
152}