op_create.go

  1package bug
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6
  7	"github.com/MichaelMure/git-bug/entity"
  8	"github.com/MichaelMure/git-bug/entity/dag"
  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{}
 16var _ dag.OperationWithFiles = &CreateOperation{}
 17
 18// CreateOperation define the initial creation of a bug
 19type CreateOperation struct {
 20	OpBase
 21	Title   string            `json:"title"`
 22	Message string            `json:"message"`
 23	Files   []repository.Hash `json:"files"`
 24}
 25
 26func (op *CreateOperation) Id() entity.Id {
 27	return idOperation(op, &op.OpBase)
 28}
 29
 30// OVERRIDE
 31func (op *CreateOperation) SetMetadata(key string, value string) {
 32	// sanity check: we make sure we are not in the following scenario:
 33	// - the bug is created with a first operation
 34	// - Id() is used
 35	// - metadata are added, which will change the Id
 36	// - Id() is used again
 37
 38	if op.id != entity.UnsetId {
 39		panic("usage of Id() after changing the first operation")
 40	}
 41
 42	op.OpBase.SetMetadata(key, value)
 43}
 44
 45func (op *CreateOperation) Apply(snapshot *Snapshot) {
 46	// sanity check: will fail when adding a second Create
 47	if snapshot.id != "" && snapshot.id != entity.UnsetId && snapshot.id != op.Id() {
 48		panic("adding a second Create operation")
 49	}
 50
 51	snapshot.id = op.Id()
 52
 53	snapshot.addActor(op.Author_)
 54	snapshot.addParticipant(op.Author_)
 55
 56	snapshot.Title = op.Title
 57
 58	commentId := entity.CombineIds(snapshot.Id(), op.Id())
 59	comment := Comment{
 60		id:       commentId,
 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(commentId, 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 !text.SafeOneLine(op.Title) {
 97		return fmt.Errorf("title has unsafe characters")
 98	}
 99
100	if !text.Safe(op.Message) {
101		return fmt.Errorf("message is not fully printable")
102	}
103
104	return nil
105}
106
107// UnmarshalJSON is a two step JSON unmarshalling
108// This workaround is necessary to avoid the inner OpBase.MarshalJSON
109// overriding the outer op's MarshalJSON
110func (op *CreateOperation) UnmarshalJSON(data []byte) error {
111	// Unmarshal OpBase and the op separately
112
113	base := OpBase{}
114	err := json.Unmarshal(data, &base)
115	if err != nil {
116		return err
117	}
118
119	aux := struct {
120		Nonce   []byte            `json:"nonce"`
121		Title   string            `json:"title"`
122		Message string            `json:"message"`
123		Files   []repository.Hash `json:"files"`
124	}{}
125
126	err = json.Unmarshal(data, &aux)
127	if err != nil {
128		return err
129	}
130
131	op.OpBase = base
132	op.Nonce = aux.Nonce
133	op.Title = aux.Title
134	op.Message = aux.Message
135	op.Files = aux.Files
136
137	return nil
138}
139
140// Sign post method for gqlgen
141func (op *CreateOperation) IsAuthored() {}
142
143func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
144	return &CreateOperation{
145		OpBase:  newOpBase(CreateOp, author, unixTime),
146		Title:   title,
147		Message: message,
148		Files:   files,
149	}
150}
151
152// CreateTimelineItem replace a Create operation in the Timeline and hold its edition history
153type CreateTimelineItem struct {
154	CommentTimelineItem
155}
156
157// Sign post method for gqlgen
158func (c *CreateTimelineItem) IsAuthored() {}
159
160// Convenience function to apply the operation
161func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
162	return CreateWithFiles(author, unixTime, title, message, nil)
163}
164
165func CreateWithFiles(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) (*Bug, *CreateOperation, error) {
166	newBug := NewBug()
167	createOp := NewCreateOp(author, unixTime, title, message, files)
168
169	if err := createOp.Validate(); err != nil {
170		return nil, createOp, err
171	}
172
173	newBug.Append(createOp)
174
175	return newBug, createOp, nil
176}