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