1package bug
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7
8 "github.com/MichaelMure/git-bug/identity"
9 "github.com/MichaelMure/git-bug/util/timestamp"
10
11 "github.com/MichaelMure/git-bug/util/text"
12)
13
14var _ Operation = &SetTitleOperation{}
15
16// SetTitleOperation will change the title of a bug
17type SetTitleOperation struct {
18 OpBase
19 Title string
20 Was string
21}
22
23func (op *SetTitleOperation) base() *OpBase {
24 return &op.OpBase
25}
26
27func (op *SetTitleOperation) ID() string {
28 return idOperation(op)
29}
30
31func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
32 snapshot.Title = op.Title
33 snapshot.addActor(op.Author)
34
35 item := &SetTitleTimelineItem{
36 id: op.ID(),
37 Author: op.Author,
38 UnixTime: timestamp.Timestamp(op.UnixTime),
39 Title: op.Title,
40 Was: op.Was,
41 }
42
43 snapshot.Timeline = append(snapshot.Timeline, item)
44}
45
46func (op *SetTitleOperation) Validate() error {
47 if err := opBaseValidate(op, SetTitleOp); err != nil {
48 return err
49 }
50
51 if text.Empty(op.Title) {
52 return fmt.Errorf("title is empty")
53 }
54
55 if strings.Contains(op.Title, "\n") {
56 return fmt.Errorf("title should be a single line")
57 }
58
59 if !text.Safe(op.Title) {
60 return fmt.Errorf("title should be fully printable")
61 }
62
63 if strings.Contains(op.Was, "\n") {
64 return fmt.Errorf("previous title should be a single line")
65 }
66
67 if !text.Safe(op.Was) {
68 return fmt.Errorf("previous title should be fully printable")
69 }
70
71 return nil
72}
73
74// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op
75// MarshalJSON
76func (op *SetTitleOperation) MarshalJSON() ([]byte, error) {
77 base, err := json.Marshal(op.OpBase)
78 if err != nil {
79 return nil, err
80 }
81
82 // revert back to a flat map to be able to add our own fields
83 var data map[string]interface{}
84 if err := json.Unmarshal(base, &data); err != nil {
85 return nil, err
86 }
87
88 data["title"] = op.Title
89 data["was"] = op.Was
90
91 return json.Marshal(data)
92}
93
94// Workaround to avoid the inner OpBase.MarshalJSON overriding the outer op
95// MarshalJSON
96func (op *SetTitleOperation) UnmarshalJSON(data []byte) error {
97 // Unmarshal OpBase and the op separately
98
99 base := OpBase{}
100 err := json.Unmarshal(data, &base)
101 if err != nil {
102 return err
103 }
104
105 aux := struct {
106 Title string `json:"title"`
107 Was string `json:"was"`
108 }{}
109
110 err = json.Unmarshal(data, &aux)
111 if err != nil {
112 return err
113 }
114
115 op.OpBase = base
116 op.Title = aux.Title
117 op.Was = aux.Was
118
119 return nil
120}
121
122// Sign post method for gqlgen
123func (op *SetTitleOperation) IsAuthored() {}
124
125func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
126 return &SetTitleOperation{
127 OpBase: newOpBase(SetTitleOp, author, unixTime),
128 Title: title,
129 Was: was,
130 }
131}
132
133type SetTitleTimelineItem struct {
134 id string
135 Author identity.Interface
136 UnixTime timestamp.Timestamp
137 Title string
138 Was string
139}
140
141func (s SetTitleTimelineItem) ID() string {
142 return s.id
143}
144
145// Sign post method for gqlgen
146func (s *SetTitleTimelineItem) IsAuthored() {}
147
148// Convenience function to apply the operation
149func SetTitle(b Interface, author identity.Interface, unixTime int64, title string) (*SetTitleOperation, error) {
150 it := NewOperationIterator(b)
151
152 var lastTitleOp Operation
153 for it.Next() {
154 op := it.Value()
155 if op.base().OperationType == SetTitleOp {
156 lastTitleOp = op
157 }
158 }
159
160 var was string
161 if lastTitleOp != nil {
162 was = lastTitleOp.(*SetTitleOperation).Title
163 } else {
164 was = b.FirstOp().(*CreateOperation).Title
165 }
166
167 setTitleOp := NewSetTitleOp(author, unixTime, title, was)
168
169 if err := setTitleOp.Validate(); err != nil {
170 return nil, err
171 }
172
173 b.Append(setTitleOp)
174 return setTitleOp, nil
175}