1package bug
  2
  3import (
  4	"fmt"
  5
  6	"github.com/git-bug/git-bug/entities/identity"
  7	"github.com/git-bug/git-bug/entity"
  8	"github.com/git-bug/git-bug/entity/dag"
  9	"github.com/git-bug/git-bug/repository"
 10	"github.com/git-bug/git-bug/util/text"
 11	"github.com/git-bug/git-bug/util/timestamp"
 12)
 13
 14var _ Operation = &CreateOperation{}
 15var _ dag.OperationWithFiles = &CreateOperation{}
 16
 17// CreateOperation define the initial creation of a bug
 18type CreateOperation struct {
 19	dag.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 dag.IdOperation(op, &op.OpBase)
 27}
 28
 29func (op *CreateOperation) Apply(snapshot *Snapshot) {
 30	// sanity check: will fail when adding a second Create
 31	if snapshot.id != "" && snapshot.id != entity.UnsetId && snapshot.id != op.Id() {
 32		return
 33	}
 34
 35	// the Id of the Bug/Snapshot is the Id of the first Operation: CreateOperation
 36	opId := op.Id()
 37	snapshot.id = opId
 38
 39	snapshot.addActor(op.Author())
 40	snapshot.addParticipant(op.Author())
 41
 42	snapshot.Title = op.Title
 43
 44	comment := Comment{
 45		combinedId: entity.CombineIds(snapshot.id, opId),
 46		targetId:   opId,
 47		Message:    op.Message,
 48		Author:     op.Author(),
 49		unixTime:   timestamp.Timestamp(op.UnixTime),
 50	}
 51
 52	snapshot.Comments = []Comment{comment}
 53	snapshot.Author = op.Author()
 54	snapshot.CreateTime = op.Time()
 55
 56	snapshot.Timeline = []TimelineItem{
 57		&CreateTimelineItem{
 58			CommentTimelineItem: NewCommentTimelineItem(comment),
 59		},
 60	}
 61}
 62
 63func (op *CreateOperation) GetFiles() []repository.Hash {
 64	return op.Files
 65}
 66
 67func (op *CreateOperation) Validate() error {
 68	if err := op.OpBase.Validate(op, CreateOp); err != nil {
 69		return err
 70	}
 71
 72	if text.Empty(op.Title) {
 73		return fmt.Errorf("title is empty")
 74	}
 75	if !text.SafeOneLine(op.Title) {
 76		return fmt.Errorf("title has unsafe characters")
 77	}
 78
 79	if !text.Safe(op.Message) {
 80		return fmt.Errorf("message is not fully printable")
 81	}
 82
 83	for _, file := range op.Files {
 84		if !file.IsValid() {
 85			return fmt.Errorf("invalid file hash")
 86		}
 87	}
 88
 89	return nil
 90}
 91
 92func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
 93	return &CreateOperation{
 94		OpBase:  dag.NewOpBase(CreateOp, author, unixTime),
 95		Title:   title,
 96		Message: message,
 97		Files:   files,
 98	}
 99}
100
101// CreateTimelineItem replace a Create operation in the Timeline and hold its edition history
102type CreateTimelineItem struct {
103	CommentTimelineItem
104}
105
106// IsAuthored is a sign post method for gqlgen
107func (c *CreateTimelineItem) IsAuthored() {}
108
109// Create is a convenience function to create a bug
110func Create(author identity.Interface, unixTime int64, title, message string, files []repository.Hash, metadata map[string]string) (*Bug, *CreateOperation, error) {
111	b := NewBug()
112	op := NewCreateOp(author, unixTime, title, message, files)
113	for key, val := range metadata {
114		op.SetMetadata(key, val)
115	}
116	if err := op.Validate(); err != nil {
117		return nil, op, err
118	}
119	b.Append(op)
120	return b, op, nil
121}