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