Skip to content

Commit 3d62a17

Browse files
authored
Event listeners (#43)
* implement CRUD for event listeners * add integration field to event listeners * start implementing frontend for event listeners * implement placeholders for events * minor * minor * add event listener description * support member event listeners * show number of event listeners in dashboard overview
1 parent d0a2947 commit 3d62a17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2241
-149
lines changed

kite-service/cmd/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ func (ml databaseMigrationLogger) Printf(format string, v ...interface{}) {
229229
ml.logger.Info(fmt.Sprintf(format, v...))
230230
}
231231

232-
// Printf is like fmt.Printf
232+
// Verbose returns the verbose flag
233233
func (ml databaseMigrationLogger) Verbose() bool {
234234
return ml.verbose
235235
}

kite-service/cmd/server.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func serverStartCMD(c *cli.Context) error {
7575
pg,
7676
pg,
7777
pg,
78+
pg,
7879
&http.Client{}, // TODO: think about proxying http requests
7980
openaiClient,
8081
)
@@ -93,13 +94,14 @@ func serverStartCMD(c *cli.Context) error {
9394
DiscordClientID: cfg.Discord.ClientID,
9495
DiscordClientSecret: cfg.Discord.ClientSecret,
9596
UserLimits: api.APIUserLimitsConfig{
96-
MaxAppsPerUser: cfg.API.UserLimits.MaxAppsPerUser,
97-
MaxCommandsPerApp: cfg.API.UserLimits.MaxCommandsPerApp,
98-
MaxVariablesPerApp: cfg.API.UserLimits.MaxVariablesPerApp,
99-
MaxMessagesPerApp: cfg.API.UserLimits.MaxMessagesPerApp,
100-
MaxAssetSize: cfg.API.UserLimits.MaxAssetSize,
97+
MaxAppsPerUser: cfg.API.UserLimits.MaxAppsPerUser,
98+
MaxCommandsPerApp: cfg.API.UserLimits.MaxCommandsPerApp,
99+
MaxVariablesPerApp: cfg.API.UserLimits.MaxVariablesPerApp,
100+
MaxMessagesPerApp: cfg.API.UserLimits.MaxMessagesPerApp,
101+
MaxEventListenersPerApp: cfg.API.UserLimits.MaxEventListenersPerApp,
102+
MaxAssetSize: cfg.API.UserLimits.MaxAssetSize,
101103
},
102-
}, pg, pg, pg, pg, pg, pg, pg, pg, pg, assetStore, gateway)
104+
}, pg, pg, pg, pg, pg, pg, pg, pg, pg, pg, assetStore, gateway)
103105
address := fmt.Sprintf("%s:%d", cfg.API.Host, cfg.API.Port)
104106
if err := apiServer.Serve(context.Background(), address); err != nil {
105107
slog.With("error", err).Error("Failed to start API server")

kite-service/internal/api/access/manager.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,25 @@ package access
33
import "github.com/kitecloud/kite/kite-service/internal/store"
44

55
type AccessManager struct {
6-
appStore store.AppStore
7-
commandStore store.CommandStore
8-
variableStore store.VariableStore
9-
messageStore store.MessageStore
6+
appStore store.AppStore
7+
commandStore store.CommandStore
8+
variableStore store.VariableStore
9+
messageStore store.MessageStore
10+
eventListenerStore store.EventListenerStore
1011
}
1112

1213
func NewAccessManager(
1314
appStore store.AppStore,
1415
commandStore store.CommandStore,
1516
variableStore store.VariableStore,
1617
messageStore store.MessageStore,
18+
eventListenerStore store.EventListenerStore,
1719
) *AccessManager {
1820
return &AccessManager{
19-
appStore: appStore,
20-
commandStore: commandStore,
21-
variableStore: variableStore,
22-
messageStore: messageStore,
21+
appStore: appStore,
22+
commandStore: commandStore,
23+
variableStore: variableStore,
24+
messageStore: messageStore,
25+
eventListenerStore: eventListenerStore,
2326
}
2427
}

kite-service/internal/api/access/middleware.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,26 @@ func (m *AccessManager) MessageAccess(next handler.HandlerFunc) handler.HandlerF
9696
return next(c)
9797
}
9898
}
99+
100+
func (m *AccessManager) EventListenerAccess(next handler.HandlerFunc) handler.HandlerFunc {
101+
return func(c *handler.Context) error {
102+
listenerID := c.Param("listenerID")
103+
appID := c.Param("appID")
104+
105+
eventListener, err := m.eventListenerStore.EventListener(c.Context(), listenerID)
106+
if err != nil {
107+
if errors.Is(err, store.ErrNotFound) {
108+
return handler.ErrNotFound("unknown_event_listener", "Event listener not found")
109+
}
110+
return err
111+
}
112+
113+
// We assume that app access has already been checked
114+
if eventListener.AppID != appID {
115+
return handler.ErrForbidden("missing_access", "Access to event listener missing")
116+
}
117+
118+
c.EventListener = eventListener
119+
return next(c)
120+
}
121+
}

kite-service/internal/api/handler/context.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ type Context struct {
1414
r *http.Request
1515
w http.ResponseWriter
1616

17-
Session *model.Session
18-
App *model.App
19-
Command *model.Command
20-
Variable *model.Variable
21-
Message *model.Message
17+
Session *model.Session
18+
App *model.App
19+
Command *model.Command
20+
Variable *model.Variable
21+
Message *model.Message
22+
EventListener *model.EventListener
2223
}
2324

2425
func (c *Context) Context() context.Context {
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package eventlistener
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"time"
7+
8+
"github.com/kitecloud/kite/kite-service/internal/api/handler"
9+
"github.com/kitecloud/kite/kite-service/internal/api/wire"
10+
"github.com/kitecloud/kite/kite-service/internal/model"
11+
"github.com/kitecloud/kite/kite-service/internal/store"
12+
"github.com/kitecloud/kite/kite-service/internal/util"
13+
"github.com/kitecloud/kite/kite-service/pkg/flow"
14+
)
15+
16+
type EventListenerHandler struct {
17+
eventListenerStore store.EventListenerStore
18+
maxEventListenersPerApp int
19+
}
20+
21+
func NewEventListenerHandler(eventListenerStore store.EventListenerStore, maxEventListenersPerApp int) *EventListenerHandler {
22+
return &EventListenerHandler{
23+
eventListenerStore: eventListenerStore,
24+
maxEventListenersPerApp: maxEventListenersPerApp,
25+
}
26+
}
27+
28+
func (h *EventListenerHandler) HandleEventListenerList(c *handler.Context) (*wire.EventListenerListResponse, error) {
29+
eventListeners, err := h.eventListenerStore.EventListenersByApp(c.Context(), c.App.ID)
30+
if err != nil {
31+
return nil, fmt.Errorf("failed to get event listeners: %w", err)
32+
}
33+
34+
res := make([]*wire.EventListener, len(eventListeners))
35+
for i, eventListener := range eventListeners {
36+
res[i] = wire.EventListenerToWire(eventListener)
37+
}
38+
39+
return &res, nil
40+
}
41+
42+
func (h *EventListenerHandler) HandleEventListenerGet(c *handler.Context) (*wire.EventListenerGetResponse, error) {
43+
return wire.EventListenerToWire(c.EventListener), nil
44+
}
45+
46+
func (h *EventListenerHandler) HandleEventListenerCreate(c *handler.Context, req wire.EventListenerCreateRequest) (*wire.EventListenerCreateResponse, error) {
47+
if h.maxEventListenersPerApp != 0 {
48+
eventListenerCount, err := h.eventListenerStore.CountEventListenersByApp(c.Context(), c.App.ID)
49+
if err != nil {
50+
return nil, fmt.Errorf("failed to count event listeners: %w", err)
51+
}
52+
53+
if eventListenerCount >= h.maxEventListenersPerApp {
54+
return nil, handler.ErrBadRequest("resource_limit", fmt.Sprintf("maximum number of event listeners (%d) reached", h.maxEventListenersPerApp))
55+
}
56+
}
57+
58+
eventFlow, err := flow.CompileEventListener(req.FlowSource)
59+
if err != nil {
60+
return nil, fmt.Errorf("failed to compile event listener: %w", err)
61+
}
62+
63+
eventListener, err := h.eventListenerStore.CreateEventListener(c.Context(), &model.EventListener{
64+
ID: util.UniqueID(),
65+
AppID: c.App.ID,
66+
CreatorUserID: c.Session.UserID,
67+
Source: model.EventSource(req.Source),
68+
Type: model.EventListenerType(eventFlow.EventListenerType()),
69+
Description: eventFlow.EventDescription(),
70+
// TODO: Filter: eventFlow.EventListenerFilter(),
71+
FlowSource: req.FlowSource,
72+
Enabled: req.Enabled,
73+
CreatedAt: time.Now().UTC(),
74+
UpdatedAt: time.Now().UTC(),
75+
})
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to create event listener: %w", err)
78+
}
79+
80+
return wire.EventListenerToWire(eventListener), nil
81+
}
82+
83+
func (h *EventListenerHandler) HandleEventListenerUpdate(c *handler.Context, req wire.EventListenerUpdateRequest) (*wire.EventListenerUpdateResponse, error) {
84+
eventFlow, err := flow.CompileEventListener(req.FlowSource)
85+
if err != nil {
86+
return nil, fmt.Errorf("failed to compile event listener: %w", err)
87+
}
88+
89+
eventListener, err := h.eventListenerStore.UpdateEventListener(c.Context(), &model.EventListener{
90+
ID: c.EventListener.ID,
91+
Type: model.EventListenerType(eventFlow.EventListenerType()),
92+
Description: eventFlow.EventDescription(),
93+
// TODO: Filter: eventFlow.EventListenerFilter(),
94+
FlowSource: req.FlowSource,
95+
Enabled: req.Enabled,
96+
UpdatedAt: time.Now().UTC(),
97+
})
98+
if err != nil {
99+
if errors.Is(err, store.ErrNotFound) {
100+
return nil, handler.ErrNotFound("unknown_event_listener", "Event listener not found")
101+
}
102+
return nil, fmt.Errorf("failed to update event listener: %w", err)
103+
}
104+
105+
return wire.EventListenerToWire(eventListener), nil
106+
}
107+
108+
func (h *EventListenerHandler) HandleEventListenerDelete(c *handler.Context) (*wire.EventListenerDeleteResponse, error) {
109+
err := h.eventListenerStore.DeleteEventListener(c.Context(), c.EventListener.ID)
110+
if err != nil {
111+
if errors.Is(err, store.ErrNotFound) {
112+
return nil, handler.ErrNotFound("unknown_event_listener", "Event listener not found")
113+
}
114+
return nil, fmt.Errorf("failed to delete event listener: %w", err)
115+
}
116+
117+
return &wire.EventListenerDeleteResponse{}, nil
118+
}

kite-service/internal/api/routes.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/kitecloud/kite/kite-service/internal/api/handler/asset"
1313
"github.com/kitecloud/kite/kite-service/internal/api/handler/auth"
1414
"github.com/kitecloud/kite/kite-service/internal/api/handler/command"
15+
eventlistener "github.com/kitecloud/kite/kite-service/internal/api/handler/event_listener"
1516
"github.com/kitecloud/kite/kite-service/internal/api/handler/logs"
1617
"github.com/kitecloud/kite/kite-service/internal/api/handler/message"
1718
"github.com/kitecloud/kite/kite-service/internal/api/handler/user"
@@ -31,14 +32,15 @@ func (s *APIServer) RegisterRoutes(
3132
variableValueStore store.VariableValueStore,
3233
messageStore store.MessageStore,
3334
messageInstanceStore store.MessageInstanceStore,
35+
eventListenerStore store.EventListenerStore,
3436
assetStore store.AssetStore,
3537
appStateManager store.AppStateManager,
3638
) {
3739
sessionManager := session.NewSessionManager(session.SessionManagerConfig{
3840
StrictCookies: s.config.StrictCookies,
3941
SecureCookies: s.config.SecureCookies,
4042
}, sessionStore)
41-
accessManager := access.NewAccessManager(appStore, commandStore, variableStore, messageStore)
43+
accessManager := access.NewAccessManager(appStore, commandStore, variableStore, messageStore, eventListenerStore)
4244

4345
webHandler, err := kiteweb.NewHandler()
4446
if err == nil {
@@ -118,6 +120,18 @@ func (s *APIServer) RegisterRoutes(
118120
commandGroup.Patch("/", handler.TypedWithBody(commandsHandler.HandleCommandUpdate))
119121
commandGroup.Delete("/", handler.Typed(commandsHandler.HandleCommandDelete))
120122

123+
// Event listener routes
124+
eventListenerHandler := eventlistener.NewEventListenerHandler(eventListenerStore, s.config.UserLimits.MaxEventListenersPerApp)
125+
126+
eventListenersGroup := appGroup.Group("/event-listeners")
127+
eventListenersGroup.Get("/", handler.Typed(eventListenerHandler.HandleEventListenerList))
128+
eventListenersGroup.Post("/", handler.TypedWithBody(eventListenerHandler.HandleEventListenerCreate))
129+
130+
eventListenerGroup := eventListenersGroup.Group("/{listenerID}", accessManager.EventListenerAccess)
131+
eventListenerGroup.Get("/", handler.Typed(eventListenerHandler.HandleEventListenerGet))
132+
eventListenerGroup.Patch("/", handler.TypedWithBody(eventListenerHandler.HandleEventListenerUpdate))
133+
eventListenerGroup.Delete("/", handler.Typed(eventListenerHandler.HandleEventListenerDelete))
134+
121135
// Variable routes
122136
variablesHandler := variable.NewVariableHandler(variableStore, variableValueStore, s.config.UserLimits.MaxVariablesPerApp)
123137

kite-service/internal/api/server.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ type APIServerConfig struct {
2020
}
2121

2222
type APIUserLimitsConfig struct {
23-
MaxAppsPerUser int
24-
MaxCommandsPerApp int
25-
MaxVariablesPerApp int
26-
MaxMessagesPerApp int
27-
MaxAssetSize int
23+
MaxAppsPerUser int
24+
MaxCommandsPerApp int
25+
MaxVariablesPerApp int
26+
MaxMessagesPerApp int
27+
MaxEventListenersPerApp int
28+
MaxAssetSize int
2829
}
2930

3031
type APIServer struct {
@@ -44,6 +45,7 @@ func NewAPIServer(
4445
variableValueStore store.VariableValueStore,
4546
messageStore store.MessageStore,
4647
messageInstanceStore store.MessageInstanceStore,
48+
eventListenerStore store.EventListenerStore,
4749
assetStore store.AssetStore,
4850
appStateManager store.AppStateManager,
4951
) *APIServer {
@@ -61,6 +63,7 @@ func NewAPIServer(
6163
variableValueStore,
6264
messageStore,
6365
messageInstanceStore,
66+
eventListenerStore,
6467
assetStore,
6568
appStateManager,
6669
)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package wire
2+
3+
import (
4+
"time"
5+
6+
validation "github.com/go-ozzo/ozzo-validation/v4"
7+
"github.com/kitecloud/kite/kite-service/internal/model"
8+
"github.com/kitecloud/kite/kite-service/pkg/flow"
9+
"gopkg.in/guregu/null.v4"
10+
)
11+
12+
type EventListener struct {
13+
ID string `json:"id"`
14+
Source string `json:"source"`
15+
Type string `json:"type"`
16+
Description string `json:"description"`
17+
Enabled bool `json:"enabled"`
18+
AppID string `json:"app_id"`
19+
ModuleID null.String `json:"module_id"`
20+
CreatorUserID string `json:"creator_user_id"`
21+
Filter *EventListenerFilter `json:"filter"`
22+
FlowSource flow.FlowData `json:"flow_source"`
23+
CreatedAt time.Time `json:"created_at"`
24+
UpdatedAt time.Time `json:"updated_at"`
25+
}
26+
27+
type EventListenerFilter struct{}
28+
29+
type EventListenerGetResponse = EventListener
30+
31+
type EventListenerListResponse = []*EventListener
32+
33+
type EventListenerCreateRequest struct {
34+
Source string `json:"source"`
35+
FlowSource flow.FlowData `json:"flow_source"`
36+
Enabled bool `json:"enabled"`
37+
}
38+
39+
func (req EventListenerCreateRequest) Validate() error {
40+
return validation.ValidateStruct(&req,
41+
validation.Field(&req.Source, validation.Required, validation.In(string(model.EventSourceDiscord))),
42+
validation.Field(&req.FlowSource, validation.Required),
43+
)
44+
}
45+
46+
type EventListenerCreateResponse = EventListener
47+
48+
type EventListenerUpdateRequest struct {
49+
FlowSource flow.FlowData `json:"flow_source"`
50+
Enabled bool `json:"enabled"`
51+
}
52+
53+
func (req EventListenerUpdateRequest) Validate() error {
54+
return validation.ValidateStruct(&req,
55+
validation.Field(&req.FlowSource, validation.Required),
56+
)
57+
}
58+
59+
type EventListenerUpdateResponse = EventListener
60+
61+
type EventListenerDeleteResponse = Empty
62+
63+
func EventListenerToWire(eventListener *model.EventListener) *EventListener {
64+
if eventListener == nil {
65+
return nil
66+
}
67+
68+
return &EventListener{
69+
ID: eventListener.ID,
70+
Source: string(eventListener.Source),
71+
Type: string(eventListener.Type),
72+
Description: eventListener.Description,
73+
Enabled: eventListener.Enabled,
74+
AppID: eventListener.AppID,
75+
ModuleID: eventListener.ModuleID,
76+
CreatorUserID: eventListener.CreatorUserID,
77+
Filter: (*EventListenerFilter)(eventListener.Filter),
78+
FlowSource: eventListener.FlowSource,
79+
CreatedAt: eventListener.CreatedAt,
80+
UpdatedAt: eventListener.UpdatedAt,
81+
}
82+
}

0 commit comments

Comments
 (0)