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