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