1package board
2
3import (
4 "fmt"
5
6 "github.com/MichaelMure/git-bug/entities/bug"
7 "github.com/MichaelMure/git-bug/entities/identity"
8
9 "github.com/MichaelMure/git-bug/entity"
10 "github.com/MichaelMure/git-bug/entity/dag"
11 "github.com/MichaelMure/git-bug/repository"
12)
13
14var _ Interface = &Board{}
15
16// 1: original format
17const formatVersion = 1
18
19const Typename = "board"
20const Namespace = "boards"
21
22var def = dag.Definition{
23 Typename: Typename,
24 Namespace: Namespace,
25 OperationUnmarshaler: operationUnmarshaler,
26 FormatVersion: formatVersion,
27}
28
29var ClockLoader = dag.ClockLoader(def)
30
31type Interface interface {
32 dag.Interface[*Snapshot, Operation]
33}
34
35// Board holds the data of a project board.
36type Board struct {
37 *dag.Entity
38}
39
40// NewBoard create a new Board
41func NewBoard() *Board {
42 return &Board{
43 Entity: dag.New(def),
44 }
45}
46
47func wrapper(e *dag.Entity) *Board {
48 return &Board{Entity: e}
49}
50
51func simpleResolvers(repo repository.ClockedRepo) entity.Resolvers {
52 return entity.Resolvers{
53 &identity.Identity{}: identity.NewSimpleResolver(repo),
54 &bug.Bug{}: bug.NewSimpleResolver(repo),
55 }
56}
57
58// Read will read a board from a repository
59func Read(repo repository.ClockedRepo, id entity.Id) (*Board, error) {
60 return ReadWithResolver(repo, simpleResolvers(repo), id)
61}
62
63// ReadWithResolver will read a board from its Id, with a custom identity.Resolver
64func ReadWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*Board, error) {
65 return dag.Read(def, wrapper, repo, resolvers, id)
66}
67
68// ReadAll read and parse all local boards
69func ReadAll(repo repository.ClockedRepo) <-chan entity.StreamedEntity[*Board] {
70 return dag.ReadAll(def, wrapper, repo, simpleResolvers(repo))
71}
72
73// ReadAllWithResolver read and parse all local boards
74func ReadAllWithResolver(repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan entity.StreamedEntity[*Board] {
75 return dag.ReadAll(def, wrapper, repo, resolvers)
76}
77
78// Validate check if the Board data is valid
79func (board *Board) Validate() error {
80 if err := board.Entity.Validate(); err != nil {
81 return err
82 }
83
84 // The very first Op should be a CreateOp
85 firstOp := board.FirstOp()
86 if firstOp == nil || firstOp.Type() != CreateOp {
87 return fmt.Errorf("first operation should be a Create op")
88 }
89
90 // Check that there is no more CreateOp op
91 for i, op := range board.Entity.Operations() {
92 if i == 0 {
93 continue
94 }
95 if op.Type() == CreateOp {
96 return fmt.Errorf("only one Create op allowed")
97 }
98 }
99
100 return nil
101}
102
103// Append add a new Operation to the Board
104func (board *Board) Append(op Operation) {
105 board.Entity.Append(op)
106}
107
108// Operations return the ordered operations
109func (board *Board) Operations() []Operation {
110 source := board.Entity.Operations()
111 result := make([]Operation, len(source))
112 for i, op := range source {
113 result[i] = op.(Operation)
114 }
115 return result
116}
117
118// Compile a board in an easily usable snapshot
119func (board *Board) Compile() *Snapshot {
120 snap := &Snapshot{
121 id: board.Id(),
122 }
123
124 for _, op := range board.Operations() {
125 op.Apply(snap)
126 snap.Operations = append(snap.Operations, op)
127 }
128
129 return snap
130}
131
132// FirstOp lookup for the very first operation of the board.
133// For a valid Board, this operation should be a CreateOp
134func (board *Board) FirstOp() Operation {
135 if fo := board.Entity.FirstOp(); fo != nil {
136 return fo.(Operation)
137 }
138 return nil
139}
140
141// LastOp lookup for the very last operation of the board.
142// For a valid Board, should never be nil
143func (board *Board) LastOp() Operation {
144 if lo := board.Entity.LastOp(); lo != nil {
145 return lo.(Operation)
146 }
147 return nil
148}