@vira-ui/cli 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +454 -1029
- package/dist/go/appYaml.js +30 -30
- package/dist/go/backendEnvExample.js +17 -17
- package/dist/go/backendReadme.js +14 -14
- package/dist/go/channelHelpers.js +25 -25
- package/dist/go/configGo.js +258 -258
- package/dist/go/dbGo.js +43 -43
- package/dist/go/dbYaml.js +7 -7
- package/dist/go/dockerCompose.js +48 -48
- package/dist/go/dockerComposeProd.js +78 -78
- package/dist/go/dockerfile.js +15 -15
- package/dist/go/eventHandlerTemplate.js +22 -22
- package/dist/go/eventsAPI.js +411 -411
- package/dist/go/goMod.js +16 -16
- package/dist/go/kafkaGo.js +67 -67
- package/dist/go/kafkaYaml.js +6 -6
- package/dist/go/kanbanHandlers.js +216 -216
- package/dist/go/mainGo.js +558 -558
- package/dist/go/readme.js +27 -27
- package/dist/go/redisGo.js +31 -31
- package/dist/go/redisYaml.js +4 -4
- package/dist/go/registryGo.js +38 -38
- package/dist/go/sqlcYaml.js +13 -13
- package/dist/go/stateStore.js +115 -115
- package/dist/go/typesGo.js +11 -11
- package/dist/index.js +472 -24
- package/dist/react/envExample.js +3 -3
- package/dist/react/envLocal.js +1 -1
- package/dist/react/indexCss.js +17 -17
- package/dist/react/indexHtml.js +12 -12
- package/dist/react/kanbanAppTsx.js +29 -29
- package/dist/react/kanbanBoard.js +58 -58
- package/dist/react/kanbanCard.js +60 -60
- package/dist/react/kanbanColumn.js +62 -62
- package/dist/react/kanbanModels.js +32 -32
- package/dist/react/mainTsx.js +12 -12
- package/dist/react/viteConfig.js +27 -27
- package/package.json +47 -45
- package/dist/go/useViraState.js +0 -160
- package/dist/go/useViraStream.js +0 -167
package/dist/go/goMod.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.goMod = void 0;
|
|
4
|
-
exports.goMod = `module vira-engine-backend
|
|
5
|
-
|
|
6
|
-
go 1.21
|
|
7
|
-
|
|
8
|
-
require (
|
|
9
|
-
github.com/go-chi/chi/v5 v5.0.10
|
|
10
|
-
github.com/go-chi/cors v1.2.1
|
|
11
|
-
github.com/gorilla/websocket v1.5.1
|
|
12
|
-
github.com/google/uuid v1.5.0
|
|
13
|
-
github.com/rs/zerolog v1.32.0
|
|
14
|
-
github.com/jackc/pgx/v5 v5.5.4
|
|
15
|
-
github.com/redis/go-redis/v9 v9.5.1
|
|
16
|
-
github.com/segmentio/kafka-go v0.4.45
|
|
17
|
-
github.com/evanphx/json-patch/v5 v5.9.0
|
|
18
|
-
gopkg.in/yaml.v3 v3.0.1
|
|
19
|
-
)
|
|
4
|
+
exports.goMod = `module vira-engine-backend
|
|
5
|
+
|
|
6
|
+
go 1.21
|
|
7
|
+
|
|
8
|
+
require (
|
|
9
|
+
github.com/go-chi/chi/v5 v5.0.10
|
|
10
|
+
github.com/go-chi/cors v1.2.1
|
|
11
|
+
github.com/gorilla/websocket v1.5.1
|
|
12
|
+
github.com/google/uuid v1.5.0
|
|
13
|
+
github.com/rs/zerolog v1.32.0
|
|
14
|
+
github.com/jackc/pgx/v5 v5.5.4
|
|
15
|
+
github.com/redis/go-redis/v9 v9.5.1
|
|
16
|
+
github.com/segmentio/kafka-go v0.4.45
|
|
17
|
+
github.com/evanphx/json-patch/v5 v5.9.0
|
|
18
|
+
gopkg.in/yaml.v3 v3.0.1
|
|
19
|
+
)
|
|
20
20
|
`;
|
package/dist/go/kafkaGo.js
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.kafkaGo = void 0;
|
|
4
|
-
exports.kafkaGo = `package events
|
|
5
|
-
|
|
6
|
-
import (
|
|
7
|
-
"context"
|
|
8
|
-
"fmt"
|
|
9
|
-
"time"
|
|
10
|
-
|
|
11
|
-
"github.com/segmentio/kafka-go"
|
|
12
|
-
"github.com/rs/zerolog"
|
|
13
|
-
|
|
14
|
-
"vira-engine-backend/internal/config"
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
type Client struct {
|
|
18
|
-
dialer *kafka.Dialer
|
|
19
|
-
cfg config.Config
|
|
20
|
-
logger zerolog.Logger
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
func NewKafka(cfg config.Config, logger zerolog.Logger) (*Client, error) {
|
|
24
|
-
d := &kafka.Dialer{
|
|
25
|
-
Timeout: 5 * time.Second,
|
|
26
|
-
DualStack: true,
|
|
27
|
-
}
|
|
28
|
-
c := &Client{dialer: d, cfg: cfg, logger: logger}
|
|
29
|
-
if err := c.Ping(context.Background()); err != nil {
|
|
30
|
-
return nil, err
|
|
31
|
-
}
|
|
32
|
-
return c, nil
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func (c *Client) Ping(ctx context.Context) error {
|
|
36
|
-
if len(c.cfg.Kafka.Brokers) == 0 {
|
|
37
|
-
return fmt.Errorf("no kafka brokers configured")
|
|
38
|
-
}
|
|
39
|
-
conn, err := c.dialer.DialContext(ctx, "tcp", c.cfg.Kafka.Brokers[0])
|
|
40
|
-
if err != nil {
|
|
41
|
-
return fmt.Errorf("dial kafka: %w", err)
|
|
42
|
-
}
|
|
43
|
-
_ = conn.Close()
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func (c *Client) NewWriter(topic string) *kafka.Writer {
|
|
48
|
-
return &kafka.Writer{
|
|
49
|
-
Addr: kafka.TCP(c.cfg.Kafka.Brokers...),
|
|
50
|
-
Topic: topic,
|
|
51
|
-
RequiredAcks: kafka.RequireAll,
|
|
52
|
-
BatchTimeout: 10 * time.Millisecond,
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
func (c *Client) NewReader(topic string) *kafka.Reader {
|
|
57
|
-
return kafka.NewReader(kafka.ReaderConfig{
|
|
58
|
-
Brokers: c.cfg.Kafka.Brokers,
|
|
59
|
-
GroupID: c.cfg.Kafka.GroupID,
|
|
60
|
-
Topic: topic,
|
|
61
|
-
StartOffset: kafka.FirstOffset,
|
|
62
|
-
SessionTimeout: 10 * time.Second,
|
|
63
|
-
HeartbeatInterval: 3 * time.Second,
|
|
64
|
-
CommitInterval: 5 * time.Second,
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func (c *Client) Close() error {
|
|
69
|
-
return nil
|
|
70
|
-
}
|
|
4
|
+
exports.kafkaGo = `package events
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"context"
|
|
8
|
+
"fmt"
|
|
9
|
+
"time"
|
|
10
|
+
|
|
11
|
+
"github.com/segmentio/kafka-go"
|
|
12
|
+
"github.com/rs/zerolog"
|
|
13
|
+
|
|
14
|
+
"vira-engine-backend/internal/config"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
type Client struct {
|
|
18
|
+
dialer *kafka.Dialer
|
|
19
|
+
cfg config.Config
|
|
20
|
+
logger zerolog.Logger
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func NewKafka(cfg config.Config, logger zerolog.Logger) (*Client, error) {
|
|
24
|
+
d := &kafka.Dialer{
|
|
25
|
+
Timeout: 5 * time.Second,
|
|
26
|
+
DualStack: true,
|
|
27
|
+
}
|
|
28
|
+
c := &Client{dialer: d, cfg: cfg, logger: logger}
|
|
29
|
+
if err := c.Ping(context.Background()); err != nil {
|
|
30
|
+
return nil, err
|
|
31
|
+
}
|
|
32
|
+
return c, nil
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func (c *Client) Ping(ctx context.Context) error {
|
|
36
|
+
if len(c.cfg.Kafka.Brokers) == 0 {
|
|
37
|
+
return fmt.Errorf("no kafka brokers configured")
|
|
38
|
+
}
|
|
39
|
+
conn, err := c.dialer.DialContext(ctx, "tcp", c.cfg.Kafka.Brokers[0])
|
|
40
|
+
if err != nil {
|
|
41
|
+
return fmt.Errorf("dial kafka: %w", err)
|
|
42
|
+
}
|
|
43
|
+
_ = conn.Close()
|
|
44
|
+
return nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func (c *Client) NewWriter(topic string) *kafka.Writer {
|
|
48
|
+
return &kafka.Writer{
|
|
49
|
+
Addr: kafka.TCP(c.cfg.Kafka.Brokers...),
|
|
50
|
+
Topic: topic,
|
|
51
|
+
RequiredAcks: kafka.RequireAll,
|
|
52
|
+
BatchTimeout: 10 * time.Millisecond,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func (c *Client) NewReader(topic string) *kafka.Reader {
|
|
57
|
+
return kafka.NewReader(kafka.ReaderConfig{
|
|
58
|
+
Brokers: c.cfg.Kafka.Brokers,
|
|
59
|
+
GroupID: c.cfg.Kafka.GroupID,
|
|
60
|
+
Topic: topic,
|
|
61
|
+
StartOffset: kafka.FirstOffset,
|
|
62
|
+
SessionTimeout: 10 * time.Second,
|
|
63
|
+
HeartbeatInterval: 3 * time.Second,
|
|
64
|
+
CommitInterval: 5 * time.Second,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func (c *Client) Close() error {
|
|
69
|
+
return nil
|
|
70
|
+
}
|
|
71
71
|
`;
|
package/dist/go/kafkaYaml.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.kafkaYaml = void 0;
|
|
4
|
-
exports.kafkaYaml = `brokers:
|
|
5
|
-
- localhost:9092
|
|
6
|
-
groupId: vira-engine
|
|
7
|
-
topics:
|
|
8
|
-
events: vira.events
|
|
9
|
-
dlq: vira.events.dlq
|
|
4
|
+
exports.kafkaYaml = `brokers:
|
|
5
|
+
- localhost:9092
|
|
6
|
+
groupId: vira-engine
|
|
7
|
+
topics:
|
|
8
|
+
events: vira.events
|
|
9
|
+
dlq: vira.events.dlq
|
|
10
10
|
`;
|
|
@@ -2,220 +2,220 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.kanbanHandlers = void 0;
|
|
4
4
|
// Internal handlers for Kanban board (generated by CLI make event)
|
|
5
|
-
exports.kanbanHandlers = `package main
|
|
6
|
-
|
|
7
|
-
import (
|
|
8
|
-
"context"
|
|
9
|
-
"encoding/json"
|
|
10
|
-
"time"
|
|
11
|
-
|
|
12
|
-
"github.com/google/uuid"
|
|
13
|
-
"github.com/gorilla/websocket"
|
|
14
|
-
"vira-engine-backend/internal/events"
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
// handleKanbanCardCreate handles kanban.card.create events.
|
|
18
|
-
func handleKanbanCardCreate(ctx context.Context, hub events.EventEmitter, conn *websocket.Conn, msg events.WSMessage) {
|
|
19
|
-
var payload struct {
|
|
20
|
-
BoardID string \`json:"boardId"\`
|
|
21
|
-
ColumnID string \`json:"columnId"\`
|
|
22
|
-
Title string \`json:"title"\`
|
|
23
|
-
Description string \`json:"description,omitempty"\`
|
|
24
|
-
At int64 \`json:"at"\`
|
|
25
|
-
}
|
|
26
|
-
if err := json.Unmarshal(msg.Data, &payload); err != nil {
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
channel := events.ChannelKanban(payload.BoardID)
|
|
31
|
-
snap, version, ok := hub.Snapshot(channel)
|
|
32
|
-
if !ok {
|
|
33
|
-
// Initialize board
|
|
34
|
-
initialBoard := map[string]any{
|
|
35
|
-
"id": payload.BoardID,
|
|
36
|
-
"title": "Kanban Board",
|
|
37
|
-
"columns": []any{},
|
|
38
|
-
"cards": map[string]any{},
|
|
39
|
-
"createdAt": time.Now().UnixMilli(),
|
|
40
|
-
"updatedAt": time.Now().UnixMilli(),
|
|
41
|
-
}
|
|
42
|
-
hub.Update(channel, initialBoard)
|
|
43
|
-
snap, _, _ = hub.Snapshot(channel)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
var board map[string]any
|
|
47
|
-
if err := json.Unmarshal(snap, &board); err != nil {
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Create new card
|
|
52
|
-
cardID := uuid.NewString()
|
|
53
|
-
newCard := map[string]any{
|
|
54
|
-
"id": cardID,
|
|
55
|
-
"title": payload.Title,
|
|
56
|
-
"description": payload.Description,
|
|
57
|
-
"columnId": payload.ColumnID,
|
|
58
|
-
"order": 0,
|
|
59
|
-
"createdAt": payload.At,
|
|
60
|
-
"updatedAt": payload.At,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
cards, _ := board["cards"].(map[string]any)
|
|
64
|
-
if cards == nil {
|
|
65
|
-
cards = make(map[string]any)
|
|
66
|
-
}
|
|
67
|
-
cards[cardID] = newCard
|
|
68
|
-
board["cards"] = cards
|
|
69
|
-
|
|
70
|
-
// Add to column
|
|
71
|
-
columns, _ := board["columns"].([]any)
|
|
72
|
-
for i, colRaw := range columns {
|
|
73
|
-
col, _ := colRaw.(map[string]any)
|
|
74
|
-
if col["id"] == payload.ColumnID {
|
|
75
|
-
cardIds, _ := col["cardIds"].([]any)
|
|
76
|
-
if cardIds == nil {
|
|
77
|
-
cardIds = []any{}
|
|
78
|
-
}
|
|
79
|
-
col["cardIds"] = append(cardIds, cardID)
|
|
80
|
-
columns[i] = col
|
|
81
|
-
break
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
board["columns"] = columns
|
|
85
|
-
board["updatedAt"] = time.Now().UnixMilli()
|
|
86
|
-
|
|
87
|
-
hub.Update(channel, board)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// handleKanbanCardMove handles kanban.card.move events.
|
|
91
|
-
func handleKanbanCardMove(ctx context.Context, hub events.EventEmitter, conn *websocket.Conn, msg events.WSMessage) {
|
|
92
|
-
var payload struct {
|
|
93
|
-
BoardID string \`json:"boardId"\`
|
|
94
|
-
CardID string \`json:"cardId"\`
|
|
95
|
-
FromColumnID string \`json:"fromColumnId"\`
|
|
96
|
-
ToColumnID string \`json:"toColumnId"\`
|
|
97
|
-
NewOrder int \`json:"newOrder"\`
|
|
98
|
-
At int64 \`json:"at"\`
|
|
99
|
-
}
|
|
100
|
-
if err := json.Unmarshal(msg.Data, &payload); err != nil {
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
channel := events.ChannelKanban(payload.BoardID)
|
|
105
|
-
snap, _, ok := hub.Snapshot(channel)
|
|
106
|
-
if !ok {
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
var board map[string]any
|
|
111
|
-
if err := json.Unmarshal(snap, &board); err != nil {
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Update card columnId
|
|
116
|
-
cards, _ := board["cards"].(map[string]any)
|
|
117
|
-
if card, ok := cards[payload.CardID].(map[string]any); ok {
|
|
118
|
-
card["columnId"] = payload.ToColumnID
|
|
119
|
-
card["order"] = payload.NewOrder
|
|
120
|
-
card["updatedAt"] = payload.At
|
|
121
|
-
cards[payload.CardID] = card
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Remove from old column
|
|
125
|
-
columns, _ := board["columns"].([]any)
|
|
126
|
-
for i, colRaw := range columns {
|
|
127
|
-
col, _ := colRaw.(map[string]any)
|
|
128
|
-
if col["id"] == payload.FromColumnID {
|
|
129
|
-
cardIds, _ := col["cardIds"].([]any)
|
|
130
|
-
newCardIds := []any{}
|
|
131
|
-
for _, id := range cardIds {
|
|
132
|
-
if idStr, _ := id.(string); idStr != payload.CardID {
|
|
133
|
-
newCardIds = append(newCardIds, id)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
col["cardIds"] = newCardIds
|
|
137
|
-
columns[i] = col
|
|
138
|
-
}
|
|
139
|
-
// Add to new column
|
|
140
|
-
if col["id"] == payload.ToColumnID {
|
|
141
|
-
cardIds, _ := col["cardIds"].([]any)
|
|
142
|
-
if cardIds == nil {
|
|
143
|
-
cardIds = []any{}
|
|
144
|
-
}
|
|
145
|
-
// Insert at position
|
|
146
|
-
newCardIds := make([]any, len(cardIds)+1)
|
|
147
|
-
copy(newCardIds, cardIds[:payload.NewOrder])
|
|
148
|
-
newCardIds[payload.NewOrder] = payload.CardID
|
|
149
|
-
copy(newCardIds[payload.NewOrder+1:], cardIds[payload.NewOrder:])
|
|
150
|
-
col["cardIds"] = newCardIds
|
|
151
|
-
columns[i] = col
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
board["columns"] = columns
|
|
155
|
-
board["updatedAt"] = time.Now().UnixMilli()
|
|
156
|
-
|
|
157
|
-
hub.Update(channel, board)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// handleKanbanCardDelete handles kanban.card.delete events.
|
|
161
|
-
func handleKanbanCardDelete(ctx context.Context, hub events.EventEmitter, conn *websocket.Conn, msg events.WSMessage) {
|
|
162
|
-
var payload struct {
|
|
163
|
-
BoardID string \`json:"boardId"\`
|
|
164
|
-
CardID string \`json:"cardId"\`
|
|
165
|
-
At int64 \`json:"at"\`
|
|
166
|
-
}
|
|
167
|
-
if err := json.Unmarshal(msg.Data, &payload); err != nil {
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
channel := events.ChannelKanban(payload.BoardID)
|
|
172
|
-
snap, _, ok := hub.Snapshot(channel)
|
|
173
|
-
if !ok {
|
|
174
|
-
return
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
var board map[string]any
|
|
178
|
-
if err := json.Unmarshal(snap, &board); err != nil {
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Remove card
|
|
183
|
-
cards, _ := board["cards"].(map[string]any)
|
|
184
|
-
var cardColumnID string
|
|
185
|
-
if card, ok := cards[payload.CardID].(map[string]any); ok {
|
|
186
|
-
cardColumnID, _ = card["columnId"].(string)
|
|
187
|
-
delete(cards, payload.CardID)
|
|
188
|
-
}
|
|
189
|
-
board["cards"] = cards
|
|
190
|
-
|
|
191
|
-
// Remove from column
|
|
192
|
-
if cardColumnID != "" {
|
|
193
|
-
columns, _ := board["columns"].([]any)
|
|
194
|
-
for i, colRaw := range columns {
|
|
195
|
-
col, _ := colRaw.(map[string]any)
|
|
196
|
-
if col["id"] == cardColumnID {
|
|
197
|
-
cardIds, _ := col["cardIds"].([]any)
|
|
198
|
-
newCardIds := []any{}
|
|
199
|
-
for _, id := range cardIds {
|
|
200
|
-
if idStr, _ := id.(string); idStr != payload.CardID {
|
|
201
|
-
newCardIds = append(newCardIds, id)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
col["cardIds"] = newCardIds
|
|
205
|
-
columns[i] = col
|
|
206
|
-
break
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
board["columns"] = columns
|
|
210
|
-
}
|
|
211
|
-
board["updatedAt"] = time.Now().UnixMilli()
|
|
212
|
-
|
|
213
|
-
hub.Update(channel, board)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
func init() {
|
|
217
|
-
events.Register("kanban.card.create", handleKanbanCardCreate)
|
|
218
|
-
events.Register("kanban.card.move", handleKanbanCardMove)
|
|
219
|
-
events.Register("kanban.card.delete", handleKanbanCardDelete)
|
|
220
|
-
}
|
|
5
|
+
exports.kanbanHandlers = `package main
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"context"
|
|
9
|
+
"encoding/json"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
"github.com/google/uuid"
|
|
13
|
+
"github.com/gorilla/websocket"
|
|
14
|
+
"vira-engine-backend/internal/events"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
// handleKanbanCardCreate handles kanban.card.create events.
|
|
18
|
+
func handleKanbanCardCreate(ctx context.Context, hub events.EventEmitter, conn *websocket.Conn, msg events.WSMessage) {
|
|
19
|
+
var payload struct {
|
|
20
|
+
BoardID string \`json:"boardId"\`
|
|
21
|
+
ColumnID string \`json:"columnId"\`
|
|
22
|
+
Title string \`json:"title"\`
|
|
23
|
+
Description string \`json:"description,omitempty"\`
|
|
24
|
+
At int64 \`json:"at"\`
|
|
25
|
+
}
|
|
26
|
+
if err := json.Unmarshal(msg.Data, &payload); err != nil {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
channel := events.ChannelKanban(payload.BoardID)
|
|
31
|
+
snap, version, ok := hub.Snapshot(channel)
|
|
32
|
+
if !ok {
|
|
33
|
+
// Initialize board
|
|
34
|
+
initialBoard := map[string]any{
|
|
35
|
+
"id": payload.BoardID,
|
|
36
|
+
"title": "Kanban Board",
|
|
37
|
+
"columns": []any{},
|
|
38
|
+
"cards": map[string]any{},
|
|
39
|
+
"createdAt": time.Now().UnixMilli(),
|
|
40
|
+
"updatedAt": time.Now().UnixMilli(),
|
|
41
|
+
}
|
|
42
|
+
hub.Update(channel, initialBoard)
|
|
43
|
+
snap, _, _ = hub.Snapshot(channel)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var board map[string]any
|
|
47
|
+
if err := json.Unmarshal(snap, &board); err != nil {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create new card
|
|
52
|
+
cardID := uuid.NewString()
|
|
53
|
+
newCard := map[string]any{
|
|
54
|
+
"id": cardID,
|
|
55
|
+
"title": payload.Title,
|
|
56
|
+
"description": payload.Description,
|
|
57
|
+
"columnId": payload.ColumnID,
|
|
58
|
+
"order": 0,
|
|
59
|
+
"createdAt": payload.At,
|
|
60
|
+
"updatedAt": payload.At,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
cards, _ := board["cards"].(map[string]any)
|
|
64
|
+
if cards == nil {
|
|
65
|
+
cards = make(map[string]any)
|
|
66
|
+
}
|
|
67
|
+
cards[cardID] = newCard
|
|
68
|
+
board["cards"] = cards
|
|
69
|
+
|
|
70
|
+
// Add to column
|
|
71
|
+
columns, _ := board["columns"].([]any)
|
|
72
|
+
for i, colRaw := range columns {
|
|
73
|
+
col, _ := colRaw.(map[string]any)
|
|
74
|
+
if col["id"] == payload.ColumnID {
|
|
75
|
+
cardIds, _ := col["cardIds"].([]any)
|
|
76
|
+
if cardIds == nil {
|
|
77
|
+
cardIds = []any{}
|
|
78
|
+
}
|
|
79
|
+
col["cardIds"] = append(cardIds, cardID)
|
|
80
|
+
columns[i] = col
|
|
81
|
+
break
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
board["columns"] = columns
|
|
85
|
+
board["updatedAt"] = time.Now().UnixMilli()
|
|
86
|
+
|
|
87
|
+
hub.Update(channel, board)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// handleKanbanCardMove handles kanban.card.move events.
|
|
91
|
+
func handleKanbanCardMove(ctx context.Context, hub events.EventEmitter, conn *websocket.Conn, msg events.WSMessage) {
|
|
92
|
+
var payload struct {
|
|
93
|
+
BoardID string \`json:"boardId"\`
|
|
94
|
+
CardID string \`json:"cardId"\`
|
|
95
|
+
FromColumnID string \`json:"fromColumnId"\`
|
|
96
|
+
ToColumnID string \`json:"toColumnId"\`
|
|
97
|
+
NewOrder int \`json:"newOrder"\`
|
|
98
|
+
At int64 \`json:"at"\`
|
|
99
|
+
}
|
|
100
|
+
if err := json.Unmarshal(msg.Data, &payload); err != nil {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
channel := events.ChannelKanban(payload.BoardID)
|
|
105
|
+
snap, _, ok := hub.Snapshot(channel)
|
|
106
|
+
if !ok {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var board map[string]any
|
|
111
|
+
if err := json.Unmarshal(snap, &board); err != nil {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Update card columnId
|
|
116
|
+
cards, _ := board["cards"].(map[string]any)
|
|
117
|
+
if card, ok := cards[payload.CardID].(map[string]any); ok {
|
|
118
|
+
card["columnId"] = payload.ToColumnID
|
|
119
|
+
card["order"] = payload.NewOrder
|
|
120
|
+
card["updatedAt"] = payload.At
|
|
121
|
+
cards[payload.CardID] = card
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Remove from old column
|
|
125
|
+
columns, _ := board["columns"].([]any)
|
|
126
|
+
for i, colRaw := range columns {
|
|
127
|
+
col, _ := colRaw.(map[string]any)
|
|
128
|
+
if col["id"] == payload.FromColumnID {
|
|
129
|
+
cardIds, _ := col["cardIds"].([]any)
|
|
130
|
+
newCardIds := []any{}
|
|
131
|
+
for _, id := range cardIds {
|
|
132
|
+
if idStr, _ := id.(string); idStr != payload.CardID {
|
|
133
|
+
newCardIds = append(newCardIds, id)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
col["cardIds"] = newCardIds
|
|
137
|
+
columns[i] = col
|
|
138
|
+
}
|
|
139
|
+
// Add to new column
|
|
140
|
+
if col["id"] == payload.ToColumnID {
|
|
141
|
+
cardIds, _ := col["cardIds"].([]any)
|
|
142
|
+
if cardIds == nil {
|
|
143
|
+
cardIds = []any{}
|
|
144
|
+
}
|
|
145
|
+
// Insert at position
|
|
146
|
+
newCardIds := make([]any, len(cardIds)+1)
|
|
147
|
+
copy(newCardIds, cardIds[:payload.NewOrder])
|
|
148
|
+
newCardIds[payload.NewOrder] = payload.CardID
|
|
149
|
+
copy(newCardIds[payload.NewOrder+1:], cardIds[payload.NewOrder:])
|
|
150
|
+
col["cardIds"] = newCardIds
|
|
151
|
+
columns[i] = col
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
board["columns"] = columns
|
|
155
|
+
board["updatedAt"] = time.Now().UnixMilli()
|
|
156
|
+
|
|
157
|
+
hub.Update(channel, board)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// handleKanbanCardDelete handles kanban.card.delete events.
|
|
161
|
+
func handleKanbanCardDelete(ctx context.Context, hub events.EventEmitter, conn *websocket.Conn, msg events.WSMessage) {
|
|
162
|
+
var payload struct {
|
|
163
|
+
BoardID string \`json:"boardId"\`
|
|
164
|
+
CardID string \`json:"cardId"\`
|
|
165
|
+
At int64 \`json:"at"\`
|
|
166
|
+
}
|
|
167
|
+
if err := json.Unmarshal(msg.Data, &payload); err != nil {
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
channel := events.ChannelKanban(payload.BoardID)
|
|
172
|
+
snap, _, ok := hub.Snapshot(channel)
|
|
173
|
+
if !ok {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
var board map[string]any
|
|
178
|
+
if err := json.Unmarshal(snap, &board); err != nil {
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Remove card
|
|
183
|
+
cards, _ := board["cards"].(map[string]any)
|
|
184
|
+
var cardColumnID string
|
|
185
|
+
if card, ok := cards[payload.CardID].(map[string]any); ok {
|
|
186
|
+
cardColumnID, _ = card["columnId"].(string)
|
|
187
|
+
delete(cards, payload.CardID)
|
|
188
|
+
}
|
|
189
|
+
board["cards"] = cards
|
|
190
|
+
|
|
191
|
+
// Remove from column
|
|
192
|
+
if cardColumnID != "" {
|
|
193
|
+
columns, _ := board["columns"].([]any)
|
|
194
|
+
for i, colRaw := range columns {
|
|
195
|
+
col, _ := colRaw.(map[string]any)
|
|
196
|
+
if col["id"] == cardColumnID {
|
|
197
|
+
cardIds, _ := col["cardIds"].([]any)
|
|
198
|
+
newCardIds := []any{}
|
|
199
|
+
for _, id := range cardIds {
|
|
200
|
+
if idStr, _ := id.(string); idStr != payload.CardID {
|
|
201
|
+
newCardIds = append(newCardIds, id)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
col["cardIds"] = newCardIds
|
|
205
|
+
columns[i] = col
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
board["columns"] = columns
|
|
210
|
+
}
|
|
211
|
+
board["updatedAt"] = time.Now().UnixMilli()
|
|
212
|
+
|
|
213
|
+
hub.Update(channel, board)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func init() {
|
|
217
|
+
events.Register("kanban.card.create", handleKanbanCardCreate)
|
|
218
|
+
events.Register("kanban.card.move", handleKanbanCardMove)
|
|
219
|
+
events.Register("kanban.card.delete", handleKanbanCardDelete)
|
|
220
|
+
}
|
|
221
221
|
`;
|