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	commentId := entity.CombineIds(snapshot.Id(), op.Id())
 60	comment := Comment{
 61		id:       commentId,
 62		Message:  op.Message,
 63		Author:   op.Author_,
 64		UnixTime: timestamp.Timestamp(op.UnixTime),
 65	}
 66
 67	snapshot.Comments = []Comment{comment}
 68	snapshot.Author = op.Author_
 69	snapshot.CreateTime = op.Time()
 70
 71	snapshot.Timeline = []TimelineItem{
 72		&CreateTimelineItem{
 73			CommentTimelineItem: NewCommentTimelineItem(commentId, comment),
 74		},
 75	}
 76}
 77
 78func (op *CreateOperation) GetFiles() []repository.Hash {
 79	return op.Files
 80}
 81
 82func (op *CreateOperation) Validate() error {
 83	if err := op.OpBase.Validate(op, CreateOp); err != nil {
 84		return err
 85	}
 86
 87	if len(op.Nonce) > 64 {
 88		return fmt.Errorf("create nonce is too big")
 89	}
 90	if len(op.Nonce) < 20 {
 91		return fmt.Errorf("create nonce is too small")
 92	}
 93
 94	if text.Empty(op.Title) {
 95		return fmt.Errorf("title is empty")
 96	}
 97	if strings.Contains(op.Title, "\n") {
 98		return fmt.Errorf("title should be a single line")
 99	}
100	if !text.Safe(op.Title) {
101		return fmt.Errorf("title is not fully printable")
102	}
103
104	if !text.Safe(op.Message) {
105		return fmt.Errorf("message is not fully printable")
106	}
107
108	return nil
109}
110
111// UnmarshalJSON is a two step JSON unmarshalling
112// This workaround is necessary to avoid the inner OpBase.MarshalJSON
113// overriding the outer op's MarshalJSON
114func (op *CreateOperation) UnmarshalJSON(data []byte) error {
115	// Unmarshal OpBase and the op separately
116
117	base := OpBase{}
118	err := json.Unmarshal(data, &base)
119	if err != nil {
120		return err
121	}
122
123	aux := struct {
124		Nonce   []byte            `json:"nonce"`
125		Title   string            `json:"title"`
126		Message string            `json:"message"`
127		Files   []repository.Hash `json:"files"`
128	}{}
129
130	err = json.Unmarshal(data, &aux)
131	if err != nil {
132		return err
133	}
134
135	op.OpBase = base
136	op.Nonce = aux.Nonce
137	op.Title = aux.Title
138	op.Message = aux.Message
139	op.Files = aux.Files
140
141	return nil
142}
143
144// Sign post method for gqlgen
145func (op *CreateOperation) IsAuthored() {}
146
147func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
148	return &CreateOperation{
149		OpBase:  newOpBase(CreateOp, author, unixTime),
150		Title:   title,
151		Message: message,
152		Files:   files,
153	}
154}
155
156// CreateTimelineItem replace a Create operation in the Timeline and hold its edition history
157type CreateTimelineItem struct {
158	CommentTimelineItem
159}
160
161// Sign post method for gqlgen
162func (c *CreateTimelineItem) IsAuthored() {}
163
164// Convenience function to apply the operation
165func Create(author identity.Interface, unixTime int64, title, message string) (*Bug, *CreateOperation, error) {
166	return CreateWithFiles(author, unixTime, title, message, nil)
167}
168
169func CreateWithFiles(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) (*Bug, *CreateOperation, error) {
170	newBug := NewBug()
171	createOp := NewCreateOp(author, unixTime, title, message, files)
172
173	if err := createOp.Validate(); err != nil {
174		return nil, createOp, err
175	}
176
177	newBug.Append(createOp)
178
179	return newBug, createOp, nil
180}