op_create.go

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