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