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/timestamp"
11
12 "github.com/MichaelMure/git-bug/util/text"
13)
14
15var _ Operation = &SetTitleOperation{}
16
17// SetTitleOperation will change the title of a bug
18type SetTitleOperation struct {
19 OpBase
20 Title string `json:"title"`
21 Was string `json:"was"`
22}
23
24func (op *SetTitleOperation) Id() entity.Id {
25 return idOperation(op, &op.OpBase)
26}
27
28func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
29 snapshot.Title = op.Title
30 snapshot.addActor(op.Author_)
31
32 item := &SetTitleTimelineItem{
33 id: op.Id(),
34 Author: op.Author_,
35 UnixTime: timestamp.Timestamp(op.UnixTime),
36 Title: op.Title,
37 Was: op.Was,
38 }
39
40 snapshot.Timeline = append(snapshot.Timeline, item)
41}
42
43func (op *SetTitleOperation) Validate() error {
44 if err := op.OpBase.Validate(op, SetTitleOp); err != nil {
45 return err
46 }
47
48 if text.Empty(op.Title) {
49 return fmt.Errorf("title is empty")
50 }
51
52 if strings.Contains(op.Title, "\n") {
53 return fmt.Errorf("title should be a single line")
54 }
55
56 if !text.Safe(op.Title) {
57 return fmt.Errorf("title should be fully printable")
58 }
59
60 if strings.Contains(op.Was, "\n") {
61 return fmt.Errorf("previous title should be a single line")
62 }
63
64 if !text.Safe(op.Was) {
65 return fmt.Errorf("previous title should be fully printable")
66 }
67
68 return nil
69}
70
71// UnmarshalJSON is a two step JSON unmarshalling
72// This workaround is necessary to avoid the inner OpBase.MarshalJSON
73// overriding the outer op's MarshalJSON
74func (op *SetTitleOperation) UnmarshalJSON(data []byte) error {
75 // Unmarshal OpBase and the op separately
76
77 base := OpBase{}
78 err := json.Unmarshal(data, &base)
79 if err != nil {
80 return err
81 }
82
83 aux := struct {
84 Title string `json:"title"`
85 Was string `json:"was"`
86 }{}
87
88 err = json.Unmarshal(data, &aux)
89 if err != nil {
90 return err
91 }
92
93 op.OpBase = base
94 op.Title = aux.Title
95 op.Was = aux.Was
96
97 return nil
98}
99
100// Sign post method for gqlgen
101func (op *SetTitleOperation) IsAuthored() {}
102
103func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
104 return &SetTitleOperation{
105 OpBase: newOpBase(SetTitleOp, author, unixTime),
106 Title: title,
107 Was: was,
108 }
109}
110
111type SetTitleTimelineItem struct {
112 id entity.Id
113 Author identity.Interface
114 UnixTime timestamp.Timestamp
115 Title string
116 Was string
117}
118
119func (s SetTitleTimelineItem) Id() entity.Id {
120 return s.id
121}
122
123// Sign post method for gqlgen
124func (s *SetTitleTimelineItem) IsAuthored() {}
125
126// Convenience function to apply the operation
127func SetTitle(b Interface, author identity.Interface, unixTime int64, title string) (*SetTitleOperation, error) {
128 var lastTitleOp *SetTitleOperation
129 for _, op := range b.Operations() {
130 switch op := op.(type) {
131 case *SetTitleOperation:
132 lastTitleOp = op
133 }
134 }
135
136 var was string
137 if lastTitleOp != nil {
138 was = lastTitleOp.Title
139 } else {
140 was = b.FirstOp().(*CreateOperation).Title
141 }
142
143 setTitleOp := NewSetTitleOp(author, unixTime, title, was)
144
145 if err := setTitleOp.Validate(); err != nil {
146 return nil, err
147 }
148
149 b.Append(setTitleOp)
150 return setTitleOp, nil
151}