Skip to content

Commit

Permalink
Backend for Portfolio Groups (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
gbdubs authored Dec 18, 2023
1 parent acac0f8 commit 3aab8bf
Show file tree
Hide file tree
Showing 15 changed files with 735 additions and 82 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ bazel run //scripts:run_server -- --use_azure_auth
bazel run //scripts:run_db

# In another terminal, run the PACTA server
bazel run //scripts:run_server
bazel run //scripts:run_server -- --with_public_endpoint=$USER

# In one last terminal, run the frontend
cd frontend
Expand Down
2 changes: 1 addition & 1 deletion cmd/runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ If you do want to actually run the full `runner` image on Azure, you can use:

```bash
# Run the backend, tell it to create tasks as real Azure Container Apps Jobs.
bazel run //scripts:run_apiserver -- --use_azure_runner
bazel run //scripts:run_server -- --use_azure_runner
```

### Creating a new docker image to run locally
Expand Down
2 changes: 2 additions & 0 deletions cmd/server/pactasrv/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ go_library(
"pacta_version.go",
"pactasrv.go",
"parallel.go",
"populate.go",
"portfolio.go",
"portfolio_group.go",
"upload.go",
"user.go",
],
Expand Down
17 changes: 17 additions & 0 deletions cmd/server/pactasrv/conv/oapi_to_pacta.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,20 @@ func HoldingsDateFromOAPI(hd *api.HoldingsDate) (*pacta.HoldingsDate, error) {
Time: hd.Time,
}, nil
}

func PortfolioGroupCreateFromOAPI(pg *api.PortfolioGroupCreate, ownerID pacta.OwnerID) (*pacta.PortfolioGroup, error) {
if pg == nil {
return nil, oapierr.Internal("portfolioGroupCreateFromOAPI: can't convert nil pointer")
}
if pg.Name == "" {
return nil, oapierr.BadRequest("name must not be empty")
}
if ownerID == "" {
return nil, oapierr.Internal("portfolioGroupCreateFromOAPI: ownerID must not be empty")
}
return &pacta.PortfolioGroup{
Name: pg.Name,
Description: pg.Description,
Owner: &pacta.Owner{ID: ownerID},
}, nil
}
40 changes: 40 additions & 0 deletions cmd/server/pactasrv/conv/pacta_to_oapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ func PortfolioToOAPI(p *pacta.Portfolio) (*api.Portfolio, error) {
if err != nil {
return nil, oapierr.Internal("portfolioToOAPI: holdingsDateToOAPI failed", zap.Error(err))
}
memberOfs := []api.PortfolioGroupMembershipPortfolioGroup{}
for _, m := range p.MemberOf {
pg, err := PortfolioGroupToOAPI(m.PortfolioGroup)
if err != nil {
return nil, oapierr.Internal("portfolioToOAPI: portfolioGroupToOAPI failed", zap.Error(err))
}
memberOfs = append(memberOfs, api.PortfolioGroupMembershipPortfolioGroup{
CreatedAt: m.CreatedAt,
PortfolioGroup: *pg,
})
}
return &api.Portfolio{
Id: string(p.ID),
Name: p.Name,
Expand All @@ -150,5 +161,34 @@ func PortfolioToOAPI(p *pacta.Portfolio) (*api.Portfolio, error) {
CreatedAt: p.CreatedAt,
NumberOfRows: p.NumberOfRows,
AdminDebugEnabled: p.AdminDebugEnabled,
Groups: &memberOfs,
}, nil
}

func PortfolioGroupToOAPI(pg *pacta.PortfolioGroup) (*api.PortfolioGroup, error) {
if pg == nil {
return nil, oapierr.Internal("portfolioGroupToOAPI: can't convert nil pointer")
}
members := []api.PortfolioGroupMembershipPortfolio{}
for _, m := range pg.Members {
portfolio, err := PortfolioToOAPI(m.Portfolio)
if err != nil {
return nil, oapierr.Internal("portfolioGroupToOAPI: portfolioToOAPI failed", zap.Error(err))
}
members = append(members, api.PortfolioGroupMembershipPortfolio{
CreatedAt: m.CreatedAt,
Portfolio: *portfolio,
})
}
return &api.PortfolioGroup{
Id: string(pg.ID),
Name: pg.Name,
Description: pg.Description,
CreatedAt: pg.CreatedAt,
Members: &members,
}, nil
}

func PortfolioGroupsToOAPI(pgs []*pacta.PortfolioGroup) ([]*api.PortfolioGroup, error) {
return convAll(pgs, PortfolioGroupToOAPI)
}
10 changes: 10 additions & 0 deletions cmd/server/pactasrv/pactasrv.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type DB interface {

Portfolio(tx db.Tx, id pacta.PortfolioID) (*pacta.Portfolio, error)
PortfoliosByOwner(tx db.Tx, owner pacta.OwnerID) ([]*pacta.Portfolio, error)
Portfolios(tx db.Tx, ids []pacta.PortfolioID) (map[pacta.PortfolioID]*pacta.Portfolio, error)
CreatePortfolio(tx db.Tx, i *pacta.Portfolio) (pacta.PortfolioID, error)
UpdatePortfolio(tx db.Tx, id pacta.PortfolioID, mutations ...db.UpdatePortfolioFn) error
DeletePortfolio(tx db.Tx, id pacta.PortfolioID) ([]pacta.BlobURI, error)
Expand All @@ -83,6 +84,15 @@ type DB interface {

GetOrCreateOwnerForUser(tx db.Tx, uID pacta.UserID) (pacta.OwnerID, error)

PortfolioGroup(tx db.Tx, id pacta.PortfolioGroupID) (*pacta.PortfolioGroup, error)
PortfolioGroupsByOwner(tx db.Tx, owner pacta.OwnerID) ([]*pacta.PortfolioGroup, error)
PortfolioGroups(tx db.Tx, ids []pacta.PortfolioGroupID) (map[pacta.PortfolioGroupID]*pacta.PortfolioGroup, error)
CreatePortfolioGroup(tx db.Tx, p *pacta.PortfolioGroup) (pacta.PortfolioGroupID, error)
UpdatePortfolioGroup(tx db.Tx, id pacta.PortfolioGroupID, mutations ...db.UpdatePortfolioGroupFn) error
DeletePortfolioGroup(tx db.Tx, id pacta.PortfolioGroupID) error
CreatePortfolioGroupMembership(tx db.Tx, pgID pacta.PortfolioGroupID, pID pacta.PortfolioID) error
DeletePortfolioGroupMembership(tx db.Tx, pgID pacta.PortfolioGroupID, pID pacta.PortfolioID) error

GetOrCreateUserByAuthn(tx db.Tx, mech pacta.AuthnMechanism, authnID, email, canonicalEmail string) (*pacta.User, error)
User(tx db.Tx, id pacta.UserID) (*pacta.User, error)
Users(tx db.Tx, ids []pacta.UserID) (map[pacta.UserID]*pacta.User, error)
Expand Down
109 changes: 109 additions & 0 deletions cmd/server/pactasrv/populate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package pactasrv

import (
"context"
"fmt"

"github.com/RMI/pacta/oapierr"
"github.com/RMI/pacta/pacta"
"go.uber.org/zap"
)

func (s *Server) populatePortfoliosInPortfolioGroups(
ctx context.Context,
ts []*pacta.PortfolioGroup,
) error {
getFn := func(pg *pacta.PortfolioGroup) ([]*pacta.Portfolio, error) {
result := []*pacta.Portfolio{}
for _, member := range pg.Members {
result = append(result, member.Portfolio)
}
return result, nil
}
lookupFn := func(ids []pacta.PortfolioID) (map[pacta.PortfolioID]*pacta.Portfolio, error) {
return s.DB.Portfolios(s.DB.NoTxn(ctx), ids)
}
getIDFn := func(p *pacta.Portfolio) pacta.PortfolioID {
return p.ID
}
if err := populateAll(ts, getFn, getIDFn, lookupFn); err != nil {
return oapierr.Internal("populating portfolios in portfolio groups failed", zap.Error(err))
}
return nil
}

func (s *Server) populatePortfolioGroupsInPortfolios(
ctx context.Context,
ts []*pacta.Portfolio,
) error {
getFn := func(pg *pacta.Portfolio) ([]*pacta.PortfolioGroup, error) {
result := []*pacta.PortfolioGroup{}
for _, member := range pg.MemberOf {
result = append(result, member.PortfolioGroup)
}
return result, nil
}
lookupFn := func(ids []pacta.PortfolioGroupID) (map[pacta.PortfolioGroupID]*pacta.PortfolioGroup, error) {
return s.DB.PortfolioGroups(s.DB.NoTxn(ctx), ids)
}
getIDFn := func(p *pacta.PortfolioGroup) pacta.PortfolioGroupID {
return p.ID
}
if err := populateAll(ts, getFn, getIDFn, lookupFn); err != nil {
return oapierr.Internal("populating portfolio groups in portfolios failed", zap.Error(err))
}
return nil
}

// This helper function populates the given targets in the given sources,
// to allow for generic population of nested data structures.
// sources = entities that you want to populate sub-entity references in.
// the sub-entities should be pointers to structs with an ID populated.
// getTargetsFn = function that takes a source and returns zero or more sub-entities to populate.
// getTargetIDFn = function that takes a sub-entity and returns its ID.
// lookupTargetsFn = function that takes a list of sub-entity IDs and returns a map of ID -> sub-entity.
func populateAll[Source any, TargetID ~string, Target any](
sources []*Source,
getTargetsFn func(*Source) ([]*Target, error),
getTargetIDFn func(*Target) TargetID,
lookupTargetsFn func([]TargetID) (map[TargetID]*Target, error),
) error {
allTargets := []*Target{}
for i, source := range sources {
targets, err := getTargetsFn(source)
if err != nil {
return fmt.Errorf("getting %d-th targets: %w", i, err)
}
allTargets = append(allTargets, targets...)
}

seen := map[TargetID]bool{}
uniqueIds := []TargetID{}
for _, target := range allTargets {
id := getTargetIDFn(target)
if _, ok := seen[id]; !ok {
uniqueIds = append(uniqueIds, id)
seen[id] = true
}
}

populatedTargets, err := lookupTargetsFn(uniqueIds)
if err != nil {
return fmt.Errorf("looking up populated: %w", err)
}
for i, source := range sources {
targets, err := getTargetsFn(source)
if err != nil {
return fmt.Errorf("re-getting %d-th targets: %w", i, err)
}
for _, target := range targets {
id := getTargetIDFn(target)
if populated, ok := populatedTargets[id]; ok {
*target = *populated
} else {
return fmt.Errorf("can't find populated target %s", id)
}
}
}
return nil
}
14 changes: 10 additions & 4 deletions cmd/server/pactasrv/portfolio.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ func (s *Server) ListPortfolios(ctx context.Context, request api.ListPortfoliosR
if err != nil {
return nil, err
}
ius, err := s.DB.PortfoliosByOwner(s.DB.NoTxn(ctx), ownerID)
ps, err := s.DB.PortfoliosByOwner(s.DB.NoTxn(ctx), ownerID)
if err != nil {
return nil, oapierr.Internal("failed to query portfolios", zap.Error(err))
}
items, err := dereference(conv.PortfoliosToOAPI(ius))
if err := s.populatePortfolioGroupsInPortfolios(ctx, ps); err != nil {
return nil, err
}
items, err := dereference(conv.PortfoliosToOAPI(ps))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -52,11 +55,14 @@ func (s *Server) DeletePortfolio(ctx context.Context, request api.DeletePortfoli
// Returns an portfolio by ID
// (GET /portfolio/{id})
func (s *Server) FindPortfolioById(ctx context.Context, request api.FindPortfolioByIdRequestObject) (api.FindPortfolioByIdResponseObject, error) {
iu, err := s.checkPortfolioAuthorization(ctx, pacta.PortfolioID(request.Id))
p, err := s.checkPortfolioAuthorization(ctx, pacta.PortfolioID(request.Id))
if err != nil {
return nil, err
}
converted, err := conv.PortfolioToOAPI(iu)
if err := s.populatePortfolioGroupsInPortfolios(ctx, []*pacta.Portfolio{p}); err != nil {
return nil, err
}
converted, err := conv.PortfolioToOAPI(p)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 3aab8bf

Please sign in to comment.