Skip to content

Commit

Permalink
feat: list activities api (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
FemiNoviaLina authored Apr 23, 2024
1 parent 535a1b3 commit fc0e9e6
Show file tree
Hide file tree
Showing 23 changed files with 2,262 additions and 615 deletions.
3 changes: 3 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ packages:
ActionService:
config:
filename: "action_service.go"
ActivityService:
config:
filename: "activity_service.go"
GroupService:
config:
filename: "group_service.go"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ GOVERSION := $(shell go version | cut -d ' ' -f 3 | cut -d '.' -f 2)

.PHONY: build check fmt lint test test-race vet test-cover-html help install proto
.DEFAULT_GOAL := build
PROTON_COMMIT := "253aa80d6a65c575cdf4a6a1433867fd4d8092ca"
PROTON_COMMIT := "8a9467fbc5539da13276eb858b4d91245fb43edd"

install:
@echo "Clean up imports..."
Expand Down
7 changes: 5 additions & 2 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func StartServer(logger *log.Zap, cfg *config.Shield) error {

schemaMigrationConfig := schema.NewSchemaMigrationConfig(cfg.App.DefaultSystemEmail)

appConfig := activity.AppConfig{Version: config.Version}
var activityRepository activity.Repository
switch cfg.Log.Activity.Sink {
case activity.SinkTypeDB:
Expand All @@ -107,7 +108,7 @@ func StartServer(logger *log.Zap, cfg *config.Shield) error {
default:
activityRepository = activity.NewStdoutRepository(io.Discard)
}
activityService := activity.NewService(activityRepository)
activityService := activity.NewService(appConfig, activityRepository)

userRepository := postgres.NewUserRepository(dbClient)
userService := user.NewService(logger, userRepository, activityService)
Expand Down Expand Up @@ -184,7 +185,8 @@ func BuildAPIDependencies(
dbc *db.Client,
sdb *spicedb.SpiceDB,
) (api.Deps, error) {
activityService := activity.NewService(activityRepository)
appConfig := activity.AppConfig{Version: config.Version}
activityService := activity.NewService(appConfig, activityRepository)

userRepository := postgres.NewUserRepository(dbc)
userService := user.NewService(logger, userRepository, activityService)
Expand Down Expand Up @@ -232,6 +234,7 @@ func BuildAPIDependencies(
ActionService: actionService,
NamespaceService: namespaceService,
RelationAdapter: relationAdapter,
ActivityService: activityService,
}
return dependencies, nil
}
Expand Down
22 changes: 22 additions & 0 deletions core/activity/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package activity

import (
"context"
"time"

"github.com/goto/salt/audit"
)
Expand All @@ -14,4 +15,25 @@ const (

type Repository interface {
Insert(ctx context.Context, log *audit.Log) error
List(ctx context.Context, filter Filter) ([]audit.Log, error)
}

type AppConfig struct {
Version string
}

type Filter struct {
Actor string
Action string
Data map[string]string
Metadata map[string]string
StartTime time.Time
EndTime time.Time
Limit int32
Page int32
}

type PagedActivity struct {
Count int32
Activities []audit.Log
}
6 changes: 4 additions & 2 deletions core/activity/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package activity
import "errors"

var (
ErrInvalidUUID = errors.New("invalid syntax of uuid")
ErrInvalidData = errors.New("invalid log data")
ErrInvalidUUID = errors.New("invalid syntax of uuid")
ErrInvalidData = errors.New("invalid log data")
ErrInvalidFilter = errors.New("invalid activity filter")
ErrNotFound = errors.New("activities not found")
)
23 changes: 20 additions & 3 deletions core/activity/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import (
"time"

"github.com/goto/salt/audit"
"github.com/goto/shield/config"
"github.com/mitchellh/mapstructure"
)

type Service struct {
appConfig AppConfig
repository Repository
}

func NewService(repository Repository) *Service {
func NewService(appConfig AppConfig, repository Repository) *Service {
return &Service{
appConfig: appConfig,
repository: repository,
}
}
Expand All @@ -31,7 +32,7 @@ func (s Service) Log(ctx context.Context, action string, actor string, data any)

metadata := map[string]string{
"app_name": "shield",
"app_version": config.Version,
"app_version": s.appConfig.Version,
}

log := &audit.Log{
Expand All @@ -44,3 +45,19 @@ func (s Service) Log(ctx context.Context, action string, actor string, data any)

return s.repository.Insert(ctx, log)
}

func (s Service) List(ctx context.Context, filter Filter) (PagedActivity, error) {
if !filter.EndTime.IsZero() && !filter.StartTime.IsZero() && filter.EndTime.Before(filter.StartTime) {
return PagedActivity{}, ErrInvalidFilter
}

activities, err := s.repository.List(ctx, filter)
if err != nil {
return PagedActivity{}, err
}

return PagedActivity{
Count: int32(len(activities)),
Activities: activities,
}, nil
}
4 changes: 4 additions & 0 deletions core/activity/stdout_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ func NewStdoutRepository(writer io.Writer) *StdoutRepository {
func (r StdoutRepository) Insert(ctx context.Context, log *audit.Log) error {
return json.NewEncoder(r.writer).Encode(log)
}

func (r StdoutRepository) List(ctx context.Context, filter Filter) ([]audit.Log, error) {
return []audit.Log{}, ErrNotFound
}
1 change: 0 additions & 1 deletion core/user/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"strings"

"github.com/goto/salt/log"

pkgctx "github.com/goto/shield/pkg/context"
"github.com/goto/shield/pkg/uuid"
)
Expand Down
2 changes: 2 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"github.com/goto/shield/core/action"
"github.com/goto/shield/core/activity"
"github.com/goto/shield/core/group"
"github.com/goto/shield/core/namespace"
"github.com/goto/shield/core/organization"
Expand All @@ -28,4 +29,5 @@ type Deps struct {
RelationAdapter *adapter.Relation
ResourceService *resource.Service
RuleService *rule.Service
ActivityService *activity.Service
}
146 changes: 146 additions & 0 deletions internal/api/v1beta1/activity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package v1beta1

import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"time"

"github.com/goto/salt/audit"
"github.com/goto/shield/core/activity"
"github.com/goto/shield/core/user"
"github.com/goto/shield/pkg/uuid"
shieldv1beta1 "github.com/goto/shield/proto/v1beta1"
grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"google.golang.org/protobuf/types/known/timestamppb"
)

type ActivityService interface {
List(ctx context.Context, filter activity.Filter) (activity.PagedActivity, error)
}

func (h Handler) ListActivities(ctx context.Context, request *shieldv1beta1.ListActivitiesRequest) (*shieldv1beta1.ListActivitiesResponse, error) {
logger := grpczap.Extract(ctx)
var activities []*shieldv1beta1.Activity

startTime := time.Time{}
endTime := time.Time{}

if request.StartTime != "" {
parseStartTime, err := strconv.ParseInt(request.StartTime, 10, 64)
if err != nil {
logger.Error(err.Error())
return nil, grpcBadBodyError
}
startTime = time.Unix(parseStartTime, 0)
}

if request.EndTime != "" {
parseEndTime, err := strconv.ParseInt(request.EndTime, 10, 64)
if err != nil {
logger.Error(err.Error())
return nil, grpcBadBodyError
}
endTime = time.Unix(parseEndTime, 0)
}

if request.Actor != "" && !uuid.IsValid(request.Actor) {
actor, err := h.userService.GetByEmail(ctx, request.Actor)
if err != nil {
logger.Error(err.Error())
switch {
case errors.Is(err, user.ErrInvalidEmail), errors.Is(err, user.ErrNotExist):
return nil, grpcBadBodyError
default:
return nil, grpcInternalServerError
}
}
request.Actor = actor.ID
}

filter := activity.Filter{
Actor: request.Actor,
Action: request.Action,
Data: request.Data,
Metadata: request.Metadata,
StartTime: startTime,
EndTime: endTime,
Limit: request.PageSize,
Page: request.PageNum,
}

activityResp, err := h.activityService.List(ctx, filter)
if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

for _, activity := range activityResp.Activities {
activityPB, err := transformActivityToPB(activity)
if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}
activities = append(activities, &activityPB)
}

return &shieldv1beta1.ListActivitiesResponse{
Count: int32(len(activities)),
Activities: activities,
}, nil
}

func transformActivityToPB(from audit.Log) (shieldv1beta1.Activity, error) {
var dataMapString map[string]string
if from.Data != nil {
var dataMap map[string]interface{}
if err := json.Unmarshal(from.Data.([]uint8), &dataMap); err != nil {
return shieldv1beta1.Activity{}, err
}
var err error
dataMapString, err = mapInterfaceToMapString(dataMap)
if err != nil {
return shieldv1beta1.Activity{}, err
}
}

var metadataMapString map[string]string
if from.Metadata != nil {
var metadataMap map[string]interface{}
if err := json.Unmarshal(from.Metadata.([]uint8), &metadataMap); err != nil {
return shieldv1beta1.Activity{}, err
}
var err error
metadataMapString, err = mapInterfaceToMapString(metadataMap)
if err != nil {
return shieldv1beta1.Activity{}, err
}
}

return shieldv1beta1.Activity{
Actor: from.Actor,
Action: from.Action,
Data: dataMapString,
Metadata: metadataMapString,
Timestamp: timestamppb.New(from.Timestamp),
}, nil
}

func mapInterfaceToMapString(from map[string]interface{}) (map[string]string, error) {
to := make(map[string]string)

for k, v := range from {
switch v.(type) {
case string:
to[k] = v.(string)
case []interface{}:
to[k] = fmt.Sprintf("%v", v)
default:
return map[string]string{}, ErrInternalServer
}
}

return to, nil
}
Loading

0 comments on commit fc0e9e6

Please sign in to comment.