From ddd537d49e1546319298b7aee7420b463927d2f2 Mon Sep 17 00:00:00 2001 From: LordNoteworthy Date: Mon, 4 Sep 2023 17:33:58 +1000 Subject: [PATCH] feat: add a new endpoint for getting system events --- internal/behavior/api.go | 23 +++++++++ internal/behavior/repostitory.go | 83 ++++++++++++++++++++++++++++++++ internal/behavior/service.go | 20 ++++++++ 3 files changed, 126 insertions(+) diff --git a/internal/behavior/api.go b/internal/behavior/api.go index 6d4d754..17db104 100644 --- a/internal/behavior/api.go +++ b/internal/behavior/api.go @@ -30,6 +30,7 @@ func RegisterHandlers(g *echo.Group, service Service, g.GET("/behaviors/:id/", res.get, verifyID) g.GET("/behaviors/:id/api-trace/", res.apis, verifyID) + g.GET("/behaviors/:id/sys-events/", res.events, verifyID) } @@ -81,6 +82,28 @@ func (r resource) apis(c echo.Context) error { return c.JSON(http.StatusOK, pages) } +func (r resource) events(c echo.Context) error { + ctx := c.Request().Context() + + if len(c.QueryParams()) > 0 { + ctx = WithFilters(ctx, c.QueryParams()) + } + + count, err := r.service.CountEvents(ctx, c.Param("id")) + if err != nil { + return err + } + + pages := pagination.NewFromRequest(c.Request(), count) + events, err := r.service.Events( + ctx, c.Param("id"), pages.Offset(), pages.Limit()) + if err != nil { + return err + } + pages.Items = events + return c.JSON(http.StatusOK, pages) +} + // WithFilters returns a context that contains the API filters. func WithFilters(ctx context.Context, value map[string][]string) context.Context { return context.WithValue(ctx, filtersKey, value) diff --git a/internal/behavior/repostitory.go b/internal/behavior/repostitory.go index 07e675e..65fd1cc 100644 --- a/internal/behavior/repostitory.go +++ b/internal/behavior/repostitory.go @@ -26,8 +26,11 @@ type Repository interface { Query(ctx context.Context, offset, limit int) ([]entity.Behavior, error) CountAPIs(ctx context.Context, id string) (int, error) + CountEvents(ctx context.Context, id string) (int, error) APIs(ctx context.Context, id string, offset, limit int) ( interface{}, error) + Events(ctx context.Context, id string, offset, limit int) ( + interface{}, error) } // repository persists file scan behaviors in database. @@ -152,6 +155,39 @@ func (r repository) CountAPIs(ctx context.Context, id string) (int, error) { return count, err } +// CountEvents returns the number of strings in a file doc in the database. +func (r repository) CountEvents(ctx context.Context, id string) (int, error) { + var count int + var statement string + params := make(map[string]interface{}, 1) + params["id"] = id + "::events" + + filters, ok := ctx.Value(filtersKey).(map[string][]string) + if ok { + statement = + "SELECT RAW COUNT(event) AS count FROM `" + r.db.Bucket.Name() + "` d" + + " USE KEYS $id UNNEST d.sys_events as event" + i := 0 + for k, v := range filters { + if i == 0 { + statement += " WHERE" + } else { + statement += " AND" + } + i++ + statement += fmt.Sprintf(" event.%s IN $%s", k, k) + params[k] = v + } + } else { + statement = + "SELECT RAW ARRAY_LENGTH(d.sys_events) AS count FROM `" + r.db.Bucket.Name() + "` d" + + " USE KEYS $id" + } + + err := r.db.Count(ctx, statement, params, &count) + return count, err +} + func (r repository) APIs(ctx context.Context, id string, offset, limit int) (interface{}, error) { @@ -198,3 +234,50 @@ func (r repository) APIs(ctx context.Context, id string, offset, } return results.([]interface{}), nil } + +func (r repository) Events(ctx context.Context, id string, offset, + limit int) (interface{}, error) { + + var results interface{} + var statement string + + params := make(map[string]interface{}, 1) + params["offset"] = offset + params["limit"] = limit + params["id"] = id + "::events" + + filters, ok := ctx.Value(filtersKey).(map[string][]string) + if ok { + statement = + "SELECT RAW event FROM `" + r.db.Bucket.Name() + "` d" + + " USE KEYS $id UNNEST d.sys_events as event" + i := 0 + for k, v := range filters { + if i == 0 { + statement += " WHERE" + } else { + statement += " AND" + } + i++ + statement += fmt.Sprintf(" event.%s IN $%s", k, k) + params[k] = v + } + statement += " OFFSET $offset LIMIT $limit" + } else { + statement = + "SELECT RAW sys_events[$offset:$limit] FROM `" + r.db.Bucket.Name() + + "` USE KEYS $id" + + } + + err := r.db.Query(ctx, statement, params, &results) + if err != nil { + return nil, err + } + if len(results.([]interface{})) == 0 { + return results, nil + } else if len(results.([]interface{})) == 1 { + return results.([]interface{})[0], nil + } + return results.([]interface{}), nil +} diff --git a/internal/behavior/service.go b/internal/behavior/service.go index 5161430..fec72c1 100644 --- a/internal/behavior/service.go +++ b/internal/behavior/service.go @@ -17,7 +17,9 @@ type Service interface { Exists(ctx context.Context, id string) (bool, error) Count(ctx context.Context) (int, error) CountAPIs(ctx context.Context, id string) (int, error) + CountEvents(ctx context.Context, id string) (int, error) APIs(ctx context.Context, id string, offset, limit int) (interface{}, error) + Events(ctx context.Context, id string, offset, limit int) (interface{}, error) } // Behavior represents the data about a behavior scan. @@ -62,6 +64,14 @@ func (s service) CountAPIs(ctx context.Context, id string) (int, error) { return count, err } +func (s service) CountEvents(ctx context.Context, id string) (int, error) { + count, err := s.repo.CountEvents(ctx, id) + if err != nil { + return 0, err + } + return count, err +} + func (s service) APIs(ctx context.Context, id string, offset, limit int) ( interface{}, error) { @@ -71,3 +81,13 @@ func (s service) APIs(ctx context.Context, id string, offset, limit int) ( } return result, nil } + +func (s service) Events(ctx context.Context, id string, offset, limit int) ( + interface{}, error) { + + result, err := s.repo.Events(ctx, id, offset, limit) + if err != nil { + return nil, err + } + return result, nil +}