op_create.go

  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/entity/dag"
 10	"github.com/MichaelMure/git-bug/identity"
 11	"github.com/MichaelMure/git-bug/repository"
 12	"github.com/MichaelMure/git-bug/util/text"
 13	"github.com/MichaelMure/git-bug/util/timestamp"
 14)
 15
 16var _ Operation = &CreateOperation{}
 17var _ dag.OperationWithFiles = &CreateOperation{}
 18
 19// CreateOperation define the initial creation of a bug
 20type CreateOperation struct {
 21	OpBase
 22	Title   string            `json:"title"`
 23	Message string            `json:"message"`
 24	Files   []repository.Hash `json:"files"`
 25}
 26
 27func (op *CreateOperation) Id() entity.Id {
 28	return idOperation(op, &op.OpBase)
 29}
 30
 31// OVERRIDE
 32func (op *CreateOperation) SetMetadata(key string, value string) {
 33	// sanity check: we make sure we are not in the following scenario:
 34	// - the bug is created with a first operation
 35	// - Id() is used
 36	// - metadata are added, which will change the Id
 37	// - Id() is used again
 38
 39	if op.id != entity.UnsetId {
 40		panic("usage of Id() after changing the first operation")
 41	}
 42
 43	op.OpBase.SetMetadata(key, value)
 44}
 45
 46func (op *CreateOperation) Apply(snapshot *Snapshot) {
 47	// sanity check: will fail when adding a second Create
 48	if snapshot.id != "" && snapshot.id != entity.UnsetId && snapshot.id != op.Id() {
 49		panic("adding a second Create operation")
 50	}
 51
 52	snapshot.id = op.Id()
 53
 54	snapshot.addActor(op.Author_)
 55	snapshot.addParticipant(op.Author_)
 56
 57	snapshot.Title = op.Title
 58
 59	comment := Comment{
 60		id:       entity.CombineIds(snapshot.Id(), op.Id()),
 61		Message:  op.Message,
 62		Author:   op.Author_,
 63		UnixTime: timestamp.Timestamp(op.UnixTime),
 64	}
 65
 66	snapshot.Comments = []Comment{comment}
 67	snapshot.Author = op.Author_
 68	snapshot.CreateTime = op.Time()
 69
 70	snapshot.Timeline = []TimelineItem{
 71		&CreateTimelineItem{
 72			CommentTimelineItem: NewCommentTimelineItem(comment),
 73		},
 74	}
 75}
 76
 77func (op *CreateOperation) GetFiles() []repository.Hash {
 78	return op.Files
 79}
 80
 81func (op *CreateOperation) Validate() error {
 82	if err := op.OpBase.Validate(op, CreateOp); err != nil {
 83		return err
 84	}
 85
 86	if len(op.Nonce) > 64 {
 87		return fmt.Errorf("create nonce is too big")
 88	}
 89	if len(op.Nonce) < 20 {
 90		return fmt.Errorf("create nonce is too small")
 91	}
 92
 93	if text.Empty(op.Title) {
 94		return fmt.Errorf("title is empty")
 95	}
 96	if strings.Contains(op.Title, "\n") {
 97		return fmt.Errorf("title should be a single line")
 98	}
 99	if !text.Safe(op.Title) {
100		return fmt.Errorf("title is not fully printable")
101	}
102
103	if !text.Safe(op.Message) {
104		return fmt.Errorf("message is not fully printable")
105	}
106
107	return nil
108}
109
110// UnmarshalJSON is a two step JSON unmarshalling
111// This workaround is necessary to avoid the inner OpBase.MarshalJSON
112// overriding the outer op's MarshalJSON
113func (op *CreateOperation) UnmarshalJSON(data []byte) error {
114	// Unmarshal OpBase and the op separately
115
116	base := OpBase{}
117	err := json.Unmarshal(data, &base)
118	if err != nil {
119		return err
120	}
121
122	aux := struct {
123		Nonce   []byte            `json:"nonce"`
124		Title   string            `json:"title"`
125		Message string            `json:"message"`
126		Files   []repository.Hash `json:"files"`
127	}{}
128
129	err = json.Unmarshal(data, &aux)
130	if err != nil {
131		return err
132	}
133
134	op.OpBase = base
135	op.Nonce = aux.Nonce
136	op.Title = aux.Title
137	op.Message = aux.Message
138	op.Files = aux.Files
139
140	return nil
141}
142
143// Sign post method for gqlgen
144func (op *CreateOperation) IsAuthored() {}
145
146func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
147	return &CreateOperation{
148		OpBase:  newOpBase(CreateOp, author, unixTime),
149		Title:   title,
150		Message: message,
151		Files:   files,
152	}
153}
154
155// CreateTimelineItem replace a Create operation in the Timeline and hold its edition history
156type CreateTimelineItem struct {
157	CommentTimelineItem
158}
159
160// Sign post method for gqlgen
161func (c *CreateTimelineItem) IsAuthored() {}
162
163// Convenience function to apply the operation
164func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
165	return CreateWithFiles(author, unixTime, title, message, nil)
166}
167
168func CreateWithFiles(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) (*Bug, *CreateOperation, error) {
169	newBug := NewBug()
170	createOp := NewCreateOp(author, unixTime, title, message, files)
171
172	if err := createOp.Validate(); err != nil {
173		return nil, createOp, err
174	}
175
176	newBug.Append(createOp)
177
178	return newBug, createOp, nil
179}