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
24// Sign-post method for gqlgen
25func (op *SetTitleOperation) IsOperation() {}
26
27func (op *SetTitleOperation) Id() entity.Id {
28 return idOperation(op, &op.OpBase)
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 := op.OpBase.Validate(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// UnmarshalJSON is a two step JSON unmarshalling
75// This workaround is necessary to avoid the inner OpBase.MarshalJSON
76// overriding the outer op's MarshalJSON
77func (op *SetTitleOperation) UnmarshalJSON(data []byte) error {
78 // Unmarshal OpBase and the op separately
79
80 base := OpBase{}
81 err := json.Unmarshal(data, &base)
82 if err != nil {
83 return err
84 }
85
86 aux := struct {
87 Title string `json:"title"`
88 Was string `json:"was"`
89 }{}
90
91 err = json.Unmarshal(data, &aux)
92 if err != nil {
93 return err
94 }
95
96 op.OpBase = base
97 op.Title = aux.Title
98 op.Was = aux.Was
99
100 return nil
101}
102
103// Sign post method for gqlgen
104func (op *SetTitleOperation) IsAuthored() {}
105
106func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
107 return &SetTitleOperation{
108 OpBase: newOpBase(SetTitleOp, author, unixTime),
109 Title: title,
110 Was: was,
111 }
112}
113
114type SetTitleTimelineItem struct {
115 id entity.Id
116 Author identity.Interface
117 UnixTime timestamp.Timestamp
118 Title string
119 Was string
120}
121
122func (s SetTitleTimelineItem) Id() entity.Id {
123 return s.id
124}
125
126// Sign post method for gqlgen
127func (s *SetTitleTimelineItem) IsAuthored() {}
128
129// Convenience function to apply the operation
130func SetTitle(b Interface, author identity.Interface, unixTime int64, title string) (*SetTitleOperation, error) {
131 var lastTitleOp *SetTitleOperation
132 for _, op := range b.Operations() {
133 switch op := op.(type) {
134 case *SetTitleOperation:
135 lastTitleOp = op
136 }
137 }
138
139 var was string
140 if lastTitleOp != nil {
141 was = lastTitleOp.Title
142 } else {
143 was = b.FirstOp().(*CreateOperation).Title
144 }
145
146 setTitleOp := NewSetTitleOp(author, unixTime, title, was)
147
148 if err := setTitleOp.Validate(); err != nil {
149 return nil, err
150 }
151
152 b.Append(setTitleOp)
153 return setTitleOp, nil
154}