Skip to content

Commit

Permalink
BE for All Initiative Data Btn
Browse files Browse the repository at this point in the history
  • Loading branch information
gbdubs committed Jan 8, 2024
1 parent e4dbd9f commit b3305ef
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 0 deletions.
75 changes: 75 additions & 0 deletions cmd/server/pactasrv/initiative.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pactasrv

import (
"context"
"fmt"

"github.com/RMI/pacta/cmd/server/pactasrv/conv"
"github.com/RMI/pacta/db"
Expand Down Expand Up @@ -118,3 +119,77 @@ func (s *Server) ListInitiatives(ctx context.Context, request api.ListInitiative
}
return api.ListInitiatives200JSONResponse(result), nil
}

// Returns all of the portfolios that are participating in the initiative
// (GET /initiative/{id}/all-data)
func (s *Server) AllInitiativeData(ctx context.Context, request api.AllInitiativeDataRequestObject) (api.AllInitiativeDataResponseObject, error) {
actorInfo, err := s.getActorInfoOrFail(ctx)
if err != nil {
return nil, err
}
// TODO(#12) Implement Authorization, along the lines of #121
i, err := s.DB.Initiative(s.DB.NoTxn(ctx), pacta.InitiativeID(request.Id))
if err != nil {
if db.IsNotFound(err) {
return nil, oapierr.NotFound("initiative not found", zap.String("initiative_id", request.Id))
}
return nil, oapierr.Internal("failed to load initiative", zap.String("initiative_id", request.Id), zap.Error(err))
}
portfolioMembers, err := s.DB.PortfolioInitiativeMembershipsByInitiative(s.DB.NoTxn(ctx), i.ID)
if err != nil {
return nil, oapierr.Internal("failed to load portfolio memberships for initiative", zap.String("initiative_id", string(i.ID)), zap.Error(err))
}
portfolioIDs := []pacta.PortfolioID{}
for _, pm := range portfolioMembers {
portfolioIDs = append(portfolioIDs, pm.Portfolio.ID)
}
portfolios, err := s.DB.Portfolios(s.DB.NoTxn(ctx), portfolioIDs)
if err != nil {
return nil, oapierr.Internal("failed to load portfolios for initiative", zap.String("initiative_id", string(i.ID)), zap.Error(err))
}
if err := s.populateBlobsInPortfolios(ctx, values(portfolios)...); err != nil {
return nil, err
}
err = s.DB.Transactional(ctx, func(tx db.Tx) error {
for _, p := range portfolios {
_, err := s.DB.CreateAuditLog(tx, &pacta.AuditLog{
Action: pacta.AuditLogAction_Download,
ActorType: pacta.AuditLogActorType_Admin, // TODO(#12) When merging with #121, use the actor type from authorization
ActorID: string(actorInfo.UserID),
ActorOwner: &pacta.Owner{ID: actorInfo.OwnerID},
PrimaryTargetType: pacta.AuditLogTargetType_Portfolio,
PrimaryTargetID: string(p.ID),
PrimaryTargetOwner: p.Owner,
SecondaryTargetType: pacta.AuditLogTargetType_Initiative,
SecondaryTargetID: string(i.ID),
SecondaryTargetOwner: &pacta.Owner{ID: "SYSTEM"}, // TODO(#12) When merging with #121, use the const type.
})
if err != nil {
return fmt.Errorf("creating audit log: %w", err)
}
}
return nil
})
if err != nil {
return nil, oapierr.Internal("failed to create audit logs nescessary to return download urls", zap.Error(err))
}

// Note, it is likely this code will need to be parallelized in the future - initiatives may eventually become large.
// However, since this action is unlikely to be taken frequently, and will only be taken by admins, getting the experience
// perfect here is not a priority.
response := api.InitiativeAllData{}
for _, portfolio := range portfolios {
url, expiryTime, err := s.Blob.SignedDownloadURL(ctx, string(portfolio.Blob.BlobURI))
if err != nil {
return nil, oapierr.Internal("error getting signed download url", zap.Error(err), zap.String("blob_uri", string(portfolio.Blob.BlobURI)))
}
response.Items = append(response.Items, api.InitiativeAllDataPortfolioItem{
Name: portfolio.Name,
BlobId: string(portfolio.Blob.BlobURI),
DownloadUrl: url,
ExpirationTime: expiryTime,
})
}

return api.AllInitiativeData200JSONResponse(response), nil
}
10 changes: 10 additions & 0 deletions cmd/server/pactasrv/pactasrv.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ func dereference[T any](ts []*T, e error) ([]T, error) {
return result, nil
}

func values[K comparable, V any](m map[K]*V) []*V {
result := make([]*V, len(m))
i := 0
for _, v := range m {
result[i] = v
i++
}
return result
}

func getUserID(ctx context.Context) (pacta.UserID, error) {
userID, err := session.UserIDFromContext(ctx)
if err != nil {
Expand Down
23 changes: 23 additions & 0 deletions cmd/server/pactasrv/populate.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ func (s *Server) populateArtifactsInAnalyses(
return nil
}

func (s *Server) populateBlobsInPortfolios(
ctx context.Context,
ps ...*pacta.Portfolio,
) error {
getFn := func(p *pacta.Portfolio) ([]*pacta.Blob, error) {
result := []*pacta.Blob{}
if p.Blob != nil {
result = append(result, p.Blob)
}
return result, nil
}
lookupFn := func(ids []pacta.BlobID) (map[pacta.BlobID]*pacta.Blob, error) {
return s.DB.Blobs(s.DB.NoTxn(ctx), ids)
}
getIDFn := func(a *pacta.Blob) pacta.BlobID {
return a.ID
}
if err := populateAll(ps, getFn, getIDFn, lookupFn); err != nil {
return oapierr.Internal("populating blobs in portfolios failed", zap.Error(err))
}
return nil
}

func (s *Server) populateBlobsInAnalysisArtifacts(
ctx context.Context,
ts ...*pacta.AnalysisArtifact,
Expand Down
2 changes: 2 additions & 0 deletions frontend/openapi/generated/pacta/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export type { HoldingsDate } from './models/HoldingsDate';
export type { IncompleteUpload } from './models/IncompleteUpload';
export type { IncompleteUploadChanges } from './models/IncompleteUploadChanges';
export type { Initiative } from './models/Initiative';
export type { InitiativeAllData } from './models/InitiativeAllData';
export type { InitiativeAllDataPortfolioItem } from './models/InitiativeAllDataPortfolioItem';
export type { InitiativeChanges } from './models/InitiativeChanges';
export type { InitiativeCreate } from './models/InitiativeCreate';
export type { InitiativeInvitation } from './models/InitiativeInvitation';
Expand Down
14 changes: 14 additions & 0 deletions frontend/openapi/generated/pacta/models/InitiativeAllData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

import type { InitiativeAllDataPortfolioItem } from './InitiativeAllDataPortfolioItem';

export type InitiativeAllData = {
/**
* the list of portfolios that are members of this initiative
*/
items: Array<InitiativeAllDataPortfolioItem>;
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */

export type InitiativeAllDataPortfolioItem = {
/**
* the name of the portfolio
*/
name: string;
/**
* the id of the blob of the portfolio, which can be used to start a new partial download if the first download times out
*/
blobId: string;
/**
* the url to download the portfolio
*/
downloadUrl: string;
/**
* the time at which the download url will expire
*/
expirationTime: string;
};

19 changes: 19 additions & 0 deletions frontend/openapi/generated/pacta/services/DefaultService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { CompletePortfolioUploadResp } from '../models/CompletePortfolioUpl
import type { IncompleteUpload } from '../models/IncompleteUpload';
import type { IncompleteUploadChanges } from '../models/IncompleteUploadChanges';
import type { Initiative } from '../models/Initiative';
import type { InitiativeAllData } from '../models/InitiativeAllData';
import type { InitiativeChanges } from '../models/InitiativeChanges';
import type { InitiativeCreate } from '../models/InitiativeCreate';
import type { InitiativeInvitation } from '../models/InitiativeInvitation';
Expand Down Expand Up @@ -233,6 +234,24 @@ export class DefaultService {
});
}

/**
* Returns all of the portfolios that are participating in the initiative
* @param id ID of the initiative to fetch data for
* @returns InitiativeAllData the initiative data
* @throws ApiError
*/
public allInitiativeData(
id: string,
): CancelablePromise<InitiativeAllData> {
return this.httpRequest.request({
method: 'GET',
url: '/initiative/{id}/all-data',
path: {
'id': id,
},
});
}

/**
* Returns all initiatives
* @returns Initiative gets all initiatives
Expand Down
49 changes: 49 additions & 0 deletions openapi/pacta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ paths:
responses:
'204':
description: initiative deleted successfully
/initiative/{id}/all-data:
get:
summary: Returns all of the portfolios that are participating in the initiative
operationId: allInitiativeData
parameters:
- name: id
in: path
description: ID of the initiative to fetch data for
required: true
schema:
type: string
responses:
'200':
description: the initiative data
content:
application/json:
schema:
$ref: '#/components/schemas/InitiativeAllData'
/initiatives:
get:
summary: Returns all initiatives
Expand Down Expand Up @@ -1280,6 +1298,37 @@ components:
pactaVersion:
type: string
description: The pacta model that this initiative should use, if not specified, the default pacta model will be used.
InitiativeAllData:
type: object
required:
- items
properties:
items:
type: array
description: the list of portfolios that are members of this initiative
items:
$ref: '#/components/schemas/InitiativeAllDataPortfolioItem'
InitiativeAllDataPortfolioItem:
type: object
required:
- name
- blobId
- downloadUrl
- expirationTime
properties:
name:
type: string
description: the name of the portfolio
blobId:
type: string
description: the id of the blob of the portfolio, which can be used to start a new partial download if the first download times out
downloadUrl:
type: string
description: the url to download the portfolio
expirationTime:
type: string
format: date-time
description: the time at which the download url will expire
InitiativeInvitationCreate:
type: object
required:
Expand Down

0 comments on commit b3305ef

Please sign in to comment.