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