Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Portfolio:Initiative Membership Backend #89

Merged
merged 3 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/server/pactasrv/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
"incomplete_upload.go",
"initiative.go",
"initiative_invitation.go",
"initiative_portfolio_relationship.go",
"initiative_user_relationship.go",
"pacta_version.go",
"pactasrv.go",
Expand Down
76 changes: 60 additions & 16 deletions cmd/server/pactasrv/conv/pacta_to_oapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,60 @@ func InitiativeToOAPI(i *pacta.Initiative) (*api.Initiative, error) {
if i == nil {
return nil, oapierr.Internal("initiativeToOAPI: can't convert nil pointer")
}
pims, err := convAll(i.PortfolioInitiativeMemberships, portfolioInitiativeMembershipToOAPIPortfolio)
if err != nil {
return nil, oapierr.Internal("initiativeToOAPI: portfolioInitiativeMembershipToOAPIInitiative failed", zap.Error(err))
}
return &api.Initiative{
Affiliation: i.Affiliation,
CreatedAt: i.CreatedAt,
Id: string(i.ID),
InternalDescription: i.InternalDescription,
IsAcceptingNewMembers: i.IsAcceptingNewMembers,
IsAcceptingNewPortfolios: i.IsAcceptingNewPortfolios,
Language: api.InitiativeLanguage(i.Language),
Name: i.Name,
PactaVersion: ptr(string(i.PACTAVersion.ID)),
PublicDescription: i.PublicDescription,
RequiresInvitationToJoin: i.RequiresInvitationToJoin,
Affiliation: i.Affiliation,
CreatedAt: i.CreatedAt,
Id: string(i.ID),
InternalDescription: i.InternalDescription,
IsAcceptingNewMembers: i.IsAcceptingNewMembers,
IsAcceptingNewPortfolios: i.IsAcceptingNewPortfolios,
Language: api.InitiativeLanguage(i.Language),
Name: i.Name,
PactaVersion: ptr(string(i.PACTAVersion.ID)),
PublicDescription: i.PublicDescription,
RequiresInvitationToJoin: i.RequiresInvitationToJoin,
PortfolioInitiativeMemberships: pims,
}, nil
}

func portfolioInitiativeMembershipToOAPIPortfolio(in *pacta.PortfolioInitiativeMembership) (api.PortfolioInitiativeMembershipPortfolio, error) {
var zero api.PortfolioInitiativeMembershipPortfolio
out := &api.PortfolioInitiativeMembershipPortfolio{
CreatedAt: in.CreatedAt,
}
if in.AddedBy != nil && in.AddedBy.ID == "" {
out.AddedByUserId = ptr(string(in.AddedBy.ID))
gbdubs marked this conversation as resolved.
Show resolved Hide resolved
}
p, err := PortfolioToOAPI(in.Portfolio)
if err != nil {
return zero, oapierr.Internal("portfolioInitiativeMembershipToOAPI: portfolioToOAPI failed", zap.Error(err))
}
out.Portfolio = *p
return zero, nil
}

func portfolioInitiativeMembershipToOAPIInitiative(in *pacta.PortfolioInitiativeMembership) (api.PortfolioInitiativeMembershipInitiative, error) {
var zero api.PortfolioInitiativeMembershipInitiative
out := api.PortfolioInitiativeMembershipInitiative{
CreatedAt: in.CreatedAt,
}
if in.AddedBy != nil && in.AddedBy.ID == "" {
out.AddedByUserId = ptr(string(in.AddedBy.ID))
}
if in.Initiative != nil {
i, err := InitiativeToOAPI(in.Initiative)
if err != nil {
return zero, oapierr.Internal("portfolioInitiativeMembershipToOAPI: initiativeToOAPI failed", zap.Error(err))
}
out.Initiative = *i
}
return out, nil
}

func UserToOAPI(user *pacta.User) (*api.User, error) {
if user == nil {
return nil, oapierr.Internal("userToOAPI: can't convert nil pointer")
Expand Down Expand Up @@ -142,17 +181,21 @@ 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 {
portfolioGroupMemberships := []api.PortfolioGroupMembershipPortfolioGroup{}
for _, m := range p.PortfolioGroupMemberships {
pg, err := PortfolioGroupToOAPI(m.PortfolioGroup)
if err != nil {
return nil, oapierr.Internal("portfolioToOAPI: portfolioGroupToOAPI failed", zap.Error(err))
}
memberOfs = append(memberOfs, api.PortfolioGroupMembershipPortfolioGroup{
portfolioGroupMemberships = append(portfolioGroupMemberships, api.PortfolioGroupMembershipPortfolioGroup{
CreatedAt: m.CreatedAt,
PortfolioGroup: *pg,
})
}
pims, err := convAll(p.PortfolioInitiativeMemberships, portfolioInitiativeMembershipToOAPIInitiative)
if err != nil {
return nil, oapierr.Internal("initiativeToOAPI: portfolioInitiativeMembershipToOAPIInitiative failed", zap.Error(err))
}
return &api.Portfolio{
Id: string(p.ID),
Name: p.Name,
Expand All @@ -161,7 +204,8 @@ func PortfolioToOAPI(p *pacta.Portfolio) (*api.Portfolio, error) {
CreatedAt: p.CreatedAt,
NumberOfRows: p.NumberOfRows,
AdminDebugEnabled: p.AdminDebugEnabled,
Groups: &memberOfs,
Groups: &portfolioGroupMemberships,
Initiatives: &pims,
}, nil
}

Expand All @@ -170,7 +214,7 @@ func PortfolioGroupToOAPI(pg *pacta.PortfolioGroup) (*api.PortfolioGroup, error)
return nil, oapierr.Internal("portfolioGroupToOAPI: can't convert nil pointer")
}
members := []api.PortfolioGroupMembershipPortfolio{}
for _, m := range pg.Members {
for _, m := range pg.PortfolioGroupMemberships {
portfolio, err := PortfolioToOAPI(m.Portfolio)
if err != nil {
return nil, oapierr.Internal("portfolioGroupToOAPI: portfolioToOAPI failed", zap.Error(err))
Expand Down
5 changes: 5 additions & 0 deletions cmd/server/pactasrv/initiative.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ func (s *Server) FindInitiativeById(ctx context.Context, request api.FindInitiat
}
return nil, oapierr.Internal("failed to load initiative", zap.String("initiative_id", request.Id), zap.Error(err))
}
portfolios, err := s.DB.PortfolioInitiativeMembershipsByInitiative(s.DB.NoTxn(ctx), i.ID)
if err != nil {
return nil, oapierr.Internal("failed to load portfolios for initiative", zap.String("initiative_id", string(i.ID)), zap.Error(err))
}
i.PortfolioInitiativeMemberships = portfolios
resp, err := conv.InitiativeToOAPI(i)
if err != nil {
return nil, err
Expand Down
43 changes: 43 additions & 0 deletions cmd/server/pactasrv/initiative_portfolio_relationship.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package pactasrv

import (
"context"

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

// creates an initiative portfolio relationship
// (POST /initiative/{initiativeId}/portfolio-relationship/{portfolioId})
func (s *Server) CreateInitiativePortfolioRelationship(ctx context.Context, request api.CreateInitiativePortfolioRelationshipRequestObject) (api.CreateInitiativePortfolioRelationshipResponseObject, error) {
// TODO(#12) Implement Authorization
userID, err := getUserID(ctx)
if err != nil {
return nil, err
}
err = s.DB.CreatePortfolioInitiativeMembership(s.DB.NoTxn(ctx), &pacta.PortfolioInitiativeMembership{
Portfolio: &pacta.Portfolio{ID: pacta.PortfolioID(request.PortfolioId)},
Initiative: &pacta.Initiative{ID: pacta.InitiativeID(request.InitiativeId)},
AddedBy: &pacta.User{ID: userID},
})
if err != nil {
return nil, oapierr.Internal("failed to create initiative", zap.Error(err))
}
return api.CreateInitiativePortfolioRelationship204Response{}, nil
}

// Deletes an initiative:portfolio relationship
// (DELETE /initiative/{initiativeId}/portfolio-relationship/{portfolioId})
func (s *Server) DeleteInitiativePortfolioRelationship(ctx context.Context, request api.DeleteInitiativePortfolioRelationshipRequestObject) (api.DeleteInitiativePortfolioRelationshipResponseObject, error) {
// TODO(#12) Implement Authorization
err := s.DB.DeletePortfolioInitiativeMembership(s.DB.NoTxn(ctx), pacta.PortfolioID(request.PortfolioId), pacta.InitiativeID(request.InitiativeId))
if err != nil {
return nil, oapierr.Internal("failed to create initiative-portfolio relationship",
zap.String("initiative_id", request.InitiativeId),
zap.String("portfolio_id", request.PortfolioId),
zap.Error(err))
}
return api.DeleteInitiativePortfolioRelationship204Response{}, nil
}
27 changes: 25 additions & 2 deletions cmd/server/pactasrv/populate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func (s *Server) populatePortfoliosInPortfolioGroups(
) error {
getFn := func(pg *pacta.PortfolioGroup) ([]*pacta.Portfolio, error) {
result := []*pacta.Portfolio{}
for _, member := range pg.Members {
for _, member := range pg.PortfolioGroupMemberships {
result = append(result, member.Portfolio)
}
return result, nil
Expand All @@ -32,13 +32,36 @@ func (s *Server) populatePortfoliosInPortfolioGroups(
return nil
}

func (s *Server) populateInitiativesInPortfolios(
ctx context.Context,
is []*pacta.Portfolio,
) error {
getFn := func(pg *pacta.Portfolio) ([]*pacta.Initiative, error) {
result := []*pacta.Initiative{}
for _, member := range pg.PortfolioInitiativeMemberships {
result = append(result, member.Initiative)
}
return result, nil
}
lookupFn := func(ids []pacta.InitiativeID) (map[pacta.InitiativeID]*pacta.Initiative, error) {
return s.DB.Initiatives(s.DB.NoTxn(ctx), ids)
}
getIDFn := func(p *pacta.Initiative) pacta.InitiativeID {
return p.ID
}
if err := populateAll(is, getFn, getIDFn, lookupFn); err != nil {
return oapierr.Internal("populating initiatives in portfolios 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 {
for _, member := range pg.PortfolioGroupMemberships {
result = append(result, member.PortfolioGroup)
}
return result, nil
Expand Down
6 changes: 6 additions & 0 deletions cmd/server/pactasrv/portfolio.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ func (s *Server) ListPortfolios(ctx context.Context, request api.ListPortfoliosR
if err := s.populatePortfolioGroupsInPortfolios(ctx, ps); err != nil {
return nil, err
}
if err := s.populateInitiativesInPortfolios(ctx, ps); err != nil {
return nil, err
}
items, err := dereference(conv.PortfoliosToOAPI(ps))
if err != nil {
return nil, err
Expand Down Expand Up @@ -62,6 +65,9 @@ func (s *Server) FindPortfolioById(ctx context.Context, request api.FindPortfoli
if err := s.populatePortfolioGroupsInPortfolios(ctx, []*pacta.Portfolio{p}); err != nil {
return nil, err
}
if err := s.populateInitiativesInPortfolios(ctx, []*pacta.Portfolio{p}); err != nil {
return nil, err
}
converted, err := conv.PortfolioToOAPI(p)
if err != nil {
return nil, err
Expand Down
64 changes: 50 additions & 14 deletions db/sqldb/portfolio.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ func portfolioQueryStanza(where string) string {
portfolio.admin_debug_enabled,
portfolio.number_of_rows,
ARRAY_AGG(portfolio_group_membership.portfolio_group_id),
ARRAY_AGG(portfolio_group_membership.created_at)
ARRAY_AGG(portfolio_group_membership.created_at),
ARRAY_AGG(portfolio_initiative_membership.initiative_id),
ARRAY_AGG(portfolio_initiative_membership.added_by_user_id),
ARRAY_AGG(portfolio_initiative_membership.created_at)
FROM portfolio
LEFT JOIN portfolio_group_membership
ON portfolio_group_membership.portfolio_id = portfolio.id
LEFT JOIN portfolio_initiative_membership
ON portfolio_initiative_membership.portfolio_id = portfolio.id
%s
GROUP BY portfolio.id;`, where)
}
Expand Down Expand Up @@ -159,8 +164,11 @@ func (d *DB) DeletePortfolio(tx db.Tx, id pacta.PortfolioID) ([]pacta.BlobURI, e
func rowToPortfolio(row rowScanner) (*pacta.Portfolio, error) {
p := &pacta.Portfolio{Owner: &pacta.Owner{}, Blob: &pacta.Blob{}}
hd := pgtype.Timestamptz{}
mid := []pgtype.Text{}
mca := []pgtype.Timestamptz{}
groupsIDs := []pgtype.Text{}
groupsCreatedAts := []pgtype.Timestamptz{}
initiativesIDs := []pgtype.Text{}
initiativesAddedByIDs := []pgtype.Text{}
initiativesCreatedAts := []pgtype.Timestamptz{}
err := row.Scan(
&p.ID,
&p.Owner.ID,
Expand All @@ -171,8 +179,11 @@ func rowToPortfolio(row rowScanner) (*pacta.Portfolio, error) {
&p.Blob.ID,
&p.AdminDebugEnabled,
&p.NumberOfRows,
&mid,
&mca,
&groupsIDs,
&groupsCreatedAts,
&initiativesIDs,
&initiativesAddedByIDs,
&initiativesCreatedAts,
)
if err != nil {
return nil, fmt.Errorf("scanning into portfolio row: %w", err)
Expand All @@ -181,24 +192,49 @@ func rowToPortfolio(row rowScanner) (*pacta.Portfolio, error) {
if err != nil {
return nil, fmt.Errorf("decoding holdings date: %w", err)
}
if len(mid) != len(mca) {
return nil, fmt.Errorf("portfolio group membership ids and created ats must be the same length")
if err := checkSizesEquivalent("groups", len(groupsIDs), len(groupsCreatedAts)); err != nil {
return nil, err
}
for i := range mid {
if !mid[i].Valid && !mca[i].Valid {
for i := range groupsIDs {
if !groupsIDs[i].Valid && !groupsCreatedAts[i].Valid {
continue // skip nulls
}
if !mid[i].Valid {
if !groupsIDs[i].Valid {
return nil, fmt.Errorf("portfolio group membership ids must be non-null")
}
if !mca[i].Valid {
if !groupsCreatedAts[i].Valid {
return nil, fmt.Errorf("portfolio group membership createdAt must be non-null")
}
p.MemberOf = append(p.MemberOf, &pacta.PortfolioGroupMembership{
p.PortfolioGroupMemberships = append(p.PortfolioGroupMemberships, &pacta.PortfolioGroupMembership{
PortfolioGroup: &pacta.PortfolioGroup{
ID: pacta.PortfolioGroupID(mid[i].String),
ID: pacta.PortfolioGroupID(groupsIDs[i].String),
},
CreatedAt: mca[i].Time,
CreatedAt: groupsCreatedAts[i].Time,
})
}
if err := checkSizesEquivalent("initiatives", len(initiativesIDs), len(initiativesAddedByIDs), len(initiativesCreatedAts)); err != nil {
return nil, err
}
for i := range initiativesIDs {
if !initiativesIDs[i].Valid && !initiativesCreatedAts[i].Valid {
continue // skip nulls
}
if !groupsIDs[i].Valid {
return nil, fmt.Errorf("portfolio group membership ids must be non-null")
}
if !groupsCreatedAts[i].Valid {
return nil, fmt.Errorf("portfolio group membership createdAt must be non-null")
}
var addedBy *pacta.User
if initiativesAddedByIDs[i].Valid {
addedBy = &pacta.User{ID: pacta.UserID(initiativesAddedByIDs[i].String)}
}
p.PortfolioInitiativeMemberships = append(p.PortfolioInitiativeMemberships, &pacta.PortfolioInitiativeMembership{
Initiative: &pacta.Initiative{
ID: pacta.InitiativeID(initiativesIDs[i].String),
},
CreatedAt: groupsCreatedAts[i].Time,
AddedBy: addedBy,
})
}
return p, nil
Expand Down
2 changes: 1 addition & 1 deletion db/sqldb/portfolio_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func rowToPortfolioGroup(row rowScanner) (*pacta.PortfolioGroup, error) {
if !mca[i].Valid {
return nil, fmt.Errorf("portfolio group membership createdAt must be non-null")
}
p.Members = append(p.Members, &pacta.PortfolioGroupMembership{
p.PortfolioGroupMemberships = append(p.PortfolioGroupMemberships, &pacta.PortfolioGroupMembership{
Portfolio: &pacta.Portfolio{
ID: pacta.PortfolioID(mid[i].String),
},
Expand Down
Loading