Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMure committed Aug 25, 2022
1 parent 1889d65 commit 386f3f8
Show file tree
Hide file tree
Showing 18 changed files with 462 additions and 33 deletions.
82 changes: 80 additions & 2 deletions entities/board/board.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package board

import (
"fmt"

"github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entities/identity"

Expand All @@ -9,20 +11,24 @@ import (
"github.com/MichaelMure/git-bug/repository"
)

var _ entity.Interface = &Board{}
var _ Interface = &Board{}

// 1: original format
const formatVersion = 1

var def = dag.Definition{
Typename: "board",
Namespace: "boards",
OperationUnmarshaler: operationUnmarshaller,
OperationUnmarshaler: operationUnmarshaler,
FormatVersion: formatVersion,
}

var ClockLoader = dag.ClockLoader(def)

type Interface interface {
dag.Interface[*Snapshot, Operation]
}

// Board holds the data of a project board.
type Board struct {
*dag.Entity
Expand Down Expand Up @@ -55,3 +61,75 @@ func ReadWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers, i
}
return &Board{Entity: e}, nil
}

// Validate check if the Board data is valid
func (board *Board) Validate() error {
if err := board.Entity.Validate(); err != nil {
return err
}

// The very first Op should be a CreateOp
firstOp := board.FirstOp()
if firstOp == nil || firstOp.Type() != CreateOp {
return fmt.Errorf("first operation should be a Create op")
}

// Check that there is no more CreateOp op
for i, op := range board.Entity.Operations() {
if i == 0 {
continue
}
if op.Type() == CreateOp {
return fmt.Errorf("only one Create op allowed")
}
}

return nil
}

// Append add a new Operation to the Board
func (board *Board) Append(op Operation) {
board.Entity.Append(op)
}

// Operations return the ordered operations
func (board *Board) Operations() []Operation {
source := board.Entity.Operations()
result := make([]Operation, len(source))
for i, op := range source {
result[i] = op.(Operation)
}
return result
}

// Compile a board in an easily usable snapshot
func (board *Board) Compile() *Snapshot {
snap := &Snapshot{
id: board.Id(),
}

for _, op := range board.Operations() {
op.Apply(snap)
snap.Operations = append(snap.Operations, op)
}

return snap
}

// FirstOp lookup for the very first operation of the board.
// For a valid Board, this operation should be a CreateOp
func (board *Board) FirstOp() Operation {
if fo := board.Entity.FirstOp(); fo != nil {
return fo.(Operation)
}
return nil
}

// LastOp lookup for the very last operation of the board.
// For a valid Board, should never be nil
func (board *Board) LastOp() Operation {
if lo := board.Entity.LastOp(); lo != nil {
return lo.(Operation)
}
return nil
}
58 changes: 58 additions & 0 deletions entities/board/board_actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package board

import (
"github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
)

// Fetch retrieve updates from a remote
// This does not change the local board state
func Fetch(repo repository.Repo, remote string) (string, error) {
return dag.Fetch(def, repo, remote)
}

// Push update a remote with the local changes
func Push(repo repository.Repo, remote string) (string, error) {
return dag.Push(def, repo, remote)
}

// Pull will do a Fetch + MergeAll
// This function will return an error if a merge fail
// Note: an author is necessary for the case where a merge commit is created, as this commit will
// have an author and may be signed if a signing key is available.
func Pull(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) error {
return dag.Pull(def, repo, resolvers, remote, mergeAuthor)
}

// MergeAll will merge all the available remote board
// Note: an author is necessary for the case where a merge commit is created, as this commit will
// have an author and may be signed if a signing key is available.
func MergeAll(repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, mergeAuthor identity.Interface) <-chan entity.MergeResult {
out := make(chan entity.MergeResult)

go func() {
defer close(out)

results := dag.MergeAll(def, repo, resolvers, remote, mergeAuthor)

// wrap the dag.Entity into a complete Bug
for result := range results {
result := result
if result.Entity != nil {
result.Entity = &Board{
Entity: result.Entity.(*dag.Entity),
}
}
out <- result
}
}()

return out
}

// Remove will remove a local bug from its entity.Id
func Remove(repo repository.ClockedRepo, id entity.Id) error {
return dag.Remove(def, repo, id)
}
19 changes: 16 additions & 3 deletions entities/board/draft.go → entities/board/item_draft.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
package board

import (
"github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/dustin/go-humanize"

"github.com/MichaelMure/git-bug/entities/common"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/timestamp"
)

var _ CardItem = &Draft{}
var _ Item = &Draft{}

type Draft struct {
// combinedId should be the result of entity.CombineIds with the Board id and the id
// of the Operation that created the Draft
combinedId entity.CombinedId

author identity.Interface
status common.Status
title string
message string
files []repository.Hash

// Creation time of the comment.
// Should be used only for human display, never for ordering as we can't rely on it in a distributed system.
unixTime timestamp.Timestamp
}

func (d *Draft) CombinedId() entity.CombinedId {
if d.combinedId == "" || d.combinedId == entity.UnsetCombinedId {
// simply panic as it would be a coding error (no id provided at construction)
panic("no combined id")
}
return d.combinedId
}

func (d *Draft) Status() common.Status {
// TODO implement me
panic("implement me")
Expand Down
21 changes: 21 additions & 0 deletions entities/board/item_entity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package board

import (
"github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/entity"
)

var _ Item = &BugItem{}

type BugItem struct {
combinedId entity.CombinedId
bug bug.Interface
}

func (e *BugItem) CombinedId() entity.CombinedId {
if e.combinedId == "" || e.combinedId == entity.UnsetCombinedId {
// simply panic as it would be a coding error (no id provided at construction)
panic("no combined id")
}
return e.combinedId
}
85 changes: 79 additions & 6 deletions entities/board/op_add_item_draft.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,101 @@
package board

import (
"fmt"

"github.com/MichaelMure/git-bug/entities/common"
"github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/text"
"github.com/MichaelMure/git-bug/util/timestamp"
)

var _ Operation = &AddItemDraftOperation{}

type AddItemDraftOperation struct {
dag.OpBase
Title string `json:"title"`
Message string `json:"message"`
ColumnId entity.Id `json:"column"`
Title string `json:"title"`
Message string `json:"message"`
Files []repository.Hash `json:"files"`
}

func (op *AddItemDraftOperation) Id() entity.Id {
return dag.IdOperation(op, &op.OpBase)
}

func (op *AddItemDraftOperation) GetFiles() []repository.Hash {
return op.Files
}

func (op *AddItemDraftOperation) Validate() error {
// TODO implement me
panic("implement me")
if err := op.OpBase.Validate(op, AddItemDraftOp); err != nil {
return err
}

if err := op.ColumnId.Validate(); err != nil {
return err
}

if text.Empty(op.Title) {
return fmt.Errorf("title is empty")
}
if !text.SafeOneLine(op.Title) {
return fmt.Errorf("title has unsafe characters")
}

if !text.Safe(op.Message) {
return fmt.Errorf("message is not fully printable")
}

for _, file := range op.Files {
if !file.IsValid() {
return fmt.Errorf("invalid file hash")
}
}

return nil
}

func (op *AddItemDraftOperation) Apply(snapshot *Snapshot) {
// TODO implement me
panic("implement me")
snapshot.addActor(op.Author())

for _, column := range snapshot.Columns {
if column.Id == op.ColumnId {
column.Items = append(column.Items, &Draft{
combinedId: entity.CombineIds(snapshot.id, op.Id()),
author: op.Author(),
status: common.OpenStatus,
title: op.Title,
message: op.Message,
unixTime: timestamp.Timestamp(op.UnixTime),
})
return
}
}
}

func NewAddItemDraftOp(author identity.Interface, unixTime int64, columnId entity.Id, title, message string, files []repository.Hash) *AddItemDraftOperation {
return &AddItemDraftOperation{
OpBase: dag.NewOpBase(AddItemDraftOp, author, unixTime),
ColumnId: columnId,
Title: title,
Message: message,
Files: files,
}
}

// AddItemDraft is a convenience function to add a draft item to a Board
func AddItemDraft(b *Board, author identity.Interface, unixTime int64, columnId entity.Id, title, message string, files []repository.Hash, metadata map[string]string) (*AddItemDraftOperation, error) {
op := NewAddItemDraftOp(author, unixTime, columnId, title, message, files)
for key, val := range metadata {
op.SetMetadata(key, val)
}
if err := op.Validate(); err != nil {
return nil, err
}
b.Append(op)
return op, nil
}
19 changes: 19 additions & 0 deletions entities/board/op_add_item_draft_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package board

import (
"testing"

"github.com/MichaelMure/git-bug/entities/identity"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
)

func TestAddItemDraftOpSerialize(t *testing.T) {
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddItemDraftOperation, entity.Resolvers) {
return NewAddItemDraftOp(author, unixTime, "foo", "title", "message", nil), nil
})
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddItemDraftOperation, entity.Resolvers) {
return NewAddItemDraftOp(author, unixTime, "foo", "title", "message", []repository.Hash{"hash1", "hash2"}), nil
})
}

0 comments on commit 386f3f8

Please sign in to comment.