Skip to content

Commit

Permalink
feat: get patient media resources
Browse files Browse the repository at this point in the history
Signed-off-by: Kathurima Kimathi <kathurimakimathi415@gmail.com>
  • Loading branch information
KathurimaKimathi committed May 19, 2023
1 parent 7c4c76b commit b944ffe
Show file tree
Hide file tree
Showing 18 changed files with 1,301 additions and 105 deletions.
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ require (
github.com/savannahghi/scalarutils v0.0.4
github.com/savannahghi/serverutils v0.0.7
github.com/segmentio/ksuid v1.0.4
github.com/sirupsen/logrus v1.9.0
github.com/sirupsen/logrus v1.9.1
github.com/stretchr/testify v1.8.2
github.com/vektah/gqlparser/v2 v2.5.1
golang.org/x/oauth2 v0.8.0
Expand All @@ -41,7 +41,7 @@ require (

require (
cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute v1.19.2 // indirect
cloud.google.com/go/compute v1.19.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/errorreporting v0.3.0 // indirect
cloud.google.com/go/iam v1.0.1 // indirect
Expand All @@ -53,7 +53,7 @@ require (
contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect
firebase.google.com/go v3.13.0+incompatible // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/aws/aws-sdk-go v1.44.261 // indirect
github.com/aws/aws-sdk-go v1.44.264 // indirect
github.com/bytedance/sonic v1.8.8 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
Expand Down Expand Up @@ -90,7 +90,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/prometheus v0.43.1 // indirect
github.com/prometheus/prometheus v0.44.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
Expand Down
18 changes: 9 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.19.2 h1:GbJtPo8OKVHbVep8jvM57KidbYHxeE68LOVqouNLrDY=
cloud.google.com/go/compute v1.19.2/go.mod h1:5f5a+iC1IriXYauaQ0EyQmEAEq9CGRnV5xJSQSlTV08=
cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
Expand Down Expand Up @@ -102,8 +102,8 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.261 h1:PcTMX/QVk+P3yh2n34UzuXDF5FS2z5Lse2bt+r3IpU4=
github.com/aws/aws-sdk-go v1.44.261/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.264 h1:5klL62ebn6uv3oJ0ixF7K12hKItj8lV3QqWeQPlkFSs=
github.com/aws/aws-sdk-go v1.44.264/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8=
github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc=
Expand Down Expand Up @@ -439,13 +439,13 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/prometheus v0.43.1 h1:Z/Z0S0CoPUVtUnHGokFksWMssSw2Y1Ir9NnWS1pPWU0=
github.com/prometheus/prometheus v0.43.1/go.mod h1:2BA14LgBeqlPuzObSEbh+Y+JwLH2GcqDlJKbF2sA6FM=
github.com/prometheus/prometheus v0.44.0 h1:sgn8Fdx+uE5tHQn0/622swlk2XnIj6udoZCnbVjHIgc=
github.com/prometheus/prometheus v0.44.0/go.mod h1:aPsmIK3py5XammeTguyqTmuqzX/jeCdyOWWobLHNKQg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
Expand Down Expand Up @@ -506,8 +506,8 @@ github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJ
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ=
github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
Expand Down
48 changes: 41 additions & 7 deletions pkg/clinical/application/dto/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,45 @@ type TerminologyConnection struct {
PageInfo PageInfo `json:"pageInfo,omitempty"`
}

// MediaOutPut is the output to show the results of the created media resource item
type MediaOutPut struct {
PatientID string `json:"patientID"`
PatientName string `json:"patientName"`
URL string `json:"url"`
Name string `json:"name"`
ContentType string `json:"contentType"`
// Media is the output to show the results of the created media resource item
type Media struct {
ID string `json:"id,omitempty"`
PatientID string `json:"patientID,omitempty"`
PatientName string `json:"patientName,omitempty"`
URL string `json:"url,omitempty"`
Name string `json:"name,omitempty"`
ContentType string `json:"contentType,omitempty"`
}

// MediaEdge is an media connection edge
type MediaEdge struct {
Node Media `json:"node,omitempty"`
Cursor string `json:"cursor,omitempty"`
}

// MediaConnection is a media connection
type MediaConnection struct {
TotalCount int `json:"totalCount,omitempty"`
Edges []MediaEdge `json:"edges,omitempty"`
PageInfo PageInfo `json:"pageInfo,omitempty"`
}

// CreateMediaConnection creates a connection that follows the GraphQl Cursor Connection Specification
func CreateMediaConnection(mediaList []*Media, pageInfo PageInfo, total int) MediaConnection {
connection := MediaConnection{
TotalCount: total,
Edges: []MediaEdge{},
PageInfo: pageInfo,
}

for _, media := range mediaList {
edge := MediaEdge{
Node: *media,
Cursor: media.ID,
}

connection.Edges = append(connection.Edges, edge)
}

return connection
}
10 changes: 10 additions & 0 deletions pkg/clinical/domain/media.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,13 @@ type FHIRMedia struct {
Content *FHIRAttachmentInput `json:"content,omitempty"`
Meta *FHIRMetaInput `json:"meta,omitempty"`
}

// PagedFHIRMedia is a media's paginaton dataclass
type PagedFHIRMedia struct {
Media []FHIRMedia
HasNextPage bool
NextCursor string
HasPreviousPage bool
PreviousCursor string
TotalCount int
}
39 changes: 39 additions & 0 deletions pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,45 @@ func (fh StoreImpl) SearchPatientEncounters(
return &encounterOutput, nil
}

// SearchPatentMedia searches all the patients media resources
func (fh StoreImpl) SearchPatientMedia(_ context.Context, patientReference string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRMedia, error) {
params := map[string]interface{}{
"patient": patientReference,
}

resources, err := fh.Dataset.SearchFHIRResource(mediaResourceType, params, tenant, pagination)
if err != nil {
return nil, err
}

mediaOutput := domain.PagedFHIRMedia{
Media: []domain.FHIRMedia{},
HasNextPage: resources.HasNextPage,
NextCursor: resources.NextCursor,
HasPreviousPage: resources.HasPreviousPage,
PreviousCursor: resources.PreviousCursor,
TotalCount: resources.TotalCount,
}

for _, resource := range resources.Resources {
var media domain.FHIRMedia

resourceBs, err := json.Marshal(resource)
if err != nil {
return nil, fmt.Errorf("unable to marshal resource to JSON: %w", err)
}

err = json.Unmarshal(resourceBs, &media)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal resource: %w", err)
}

mediaOutput.Media = append(mediaOutput.Media, media)
}

return &mediaOutput, nil
}

// SearchFHIREpisodeOfCare provides a search API for FHIREpisodeOfCare
func (fh StoreImpl) SearchFHIREpisodeOfCare(_ context.Context, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.FHIREpisodeOfCareRelayConnection, error) {
output := domain.FHIREpisodeOfCareRelayConnection{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4135,3 +4135,67 @@ func TestStoreImpl_CreateFHIRMedia(t *testing.T) {
})
}
}

func TestStoreImpl_SearchPatientMedia(t *testing.T) {
first := 1
type args struct {
ctx context.Context
patientReference string
tenant dto.TenantIdentifiers
pagination dto.Pagination
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Happy case: search patient media",
args: args{
ctx: context.Background(),
patientReference: gofakeit.BeerIbu(),
tenant: dto.TenantIdentifiers{
OrganizationID: gofakeit.UUID(),
FacilityID: gofakeit.UUID(),
},
pagination: dto.Pagination{
First: &first,
},
},
wantErr: false,
},
{
name: "Sad case: unable to search patient media",
args: args{
ctx: context.Background(),
patientReference: gofakeit.BeerIbu(),
tenant: dto.TenantIdentifiers{
OrganizationID: gofakeit.UUID(),
FacilityID: gofakeit.UUID(),
},
pagination: dto.Pagination{
First: &first,
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeDataset := fakeDataset.NewFakeFHIRRepositoryMock()
fh := FHIR.NewFHIRStoreImpl(fakeDataset)

if tt.name == "Sad case: unable to search patient media" {
fakeDataset.MockSearchFHIRResourceFn = func(resourceType string, params map[string]interface{}, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRResource, error) {
return nil, errors.New("unable to search patient media")
}
}

_, err := fh.SearchPatientMedia(tt.args.ctx, tt.args.patientReference, tt.args.tenant, tt.args.pagination)
if (err != nil) != tt.wantErr {
t.Errorf("StoreImpl.SearchPatentMedia() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type FHIRMock struct {
MockGetFHIRAllergyIntoleranceFn func(ctx context.Context, id string) (*domain.FHIRAllergyIntoleranceRelayPayload, error)
MockSearchPatientAllergyIntoleranceFn func(ctx context.Context, patientReference string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRAllergy, error)
MockCreateFHIRMediaFn func(ctx context.Context, input domain.FHIRMedia) (*domain.FHIRMedia, error)
MockSearchPatientMediaFn func(ctx context.Context, patientReference string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRMedia, error)
}

// NewFHIRMock initializes a new instance of FHIR mock
Expand Down Expand Up @@ -206,6 +207,40 @@ func NewFHIRMock() *FHIRMock {
MockHasOpenEpisodeFn: func(ctx context.Context, patient domain.FHIRPatient, tenant dto.TenantIdentifiers, pagination dto.Pagination) (bool, error) {
return true, nil
},
MockSearchPatientMediaFn: func(ctx context.Context, patientReference string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRMedia, error) {
UUID := uuid.New().String()
PatientRef := "Patient/1"
OrgRef := "Organization/1"
contentType := "video/mp4"
url := gofakeit.URL()
title := "test"
return &domain.PagedFHIRMedia{
Media: []domain.FHIRMedia{
{
ID: &UUID,
Subject: &domain.FHIRReferenceInput{
ID: &UUID,
Reference: &PatientRef,
},
Operator: &domain.FHIRReferenceInput{
ID: &UUID,
Reference: &OrgRef,
},
Content: &domain.FHIRAttachmentInput{
ID: &UUID,
ContentType: (*scalarutils.Code)(&contentType),
URL: (*scalarutils.URL)(&url),
Title: &title,
},
},
},
HasNextPage: true,
NextCursor: "",
HasPreviousPage: true,
PreviousCursor: "",
TotalCount: 10,
}, nil
},
MockOpenEpisodesFn: func(ctx context.Context, patientReference string, tenant dto.TenantIdentifiers, pagination dto.Pagination) ([]*domain.FHIREpisodeOfCare, error) {
UUID := uuid.New().String()
PatientRef := "Patient/1"
Expand Down Expand Up @@ -1243,3 +1278,8 @@ func (fh *FHIRMock) SearchPatientAllergyIntolerance(ctx context.Context, patient
func (fh *FHIRMock) CreateFHIRMedia(ctx context.Context, input domain.FHIRMedia) (*domain.FHIRMedia, error) {
return fh.MockCreateFHIRMediaFn(ctx, input)
}

// SearchPatentMedia mocks the searching of patient media
func (fh *FHIRMock) SearchPatientMedia(ctx context.Context, patientReference string, tenant dto.TenantIdentifiers, pagination dto.Pagination) (*domain.PagedFHIRMedia, error) {
return fh.MockSearchPatientMediaFn(ctx, patientReference, tenant, pagination)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ import (

// FakeUpload is a mock of the fake upload
type FakeUpload struct {
MockUploadMediaFn func(ctx context.Context, name string, file io.Reader, contentType string) (*dto.MediaOutPut, error)
MockUploadMediaFn func(ctx context.Context, name string, file io.Reader, contentType string) (*dto.Media, error)
}

// NewFakeUploadMock initializes a new instance of upload mock
func NewFakeUploadMock() *FakeUpload {
return &FakeUpload{
MockUploadMediaFn: func(ctx context.Context, name string, file io.Reader, contentType string) (*dto.MediaOutPut, error) {
return &dto.MediaOutPut{
MockUploadMediaFn: func(ctx context.Context, name string, file io.Reader, contentType string) (*dto.Media, error) {
return &dto.Media{
URL: "https://google.com",
}, nil
},
}
}

// UploadMedia is a mock implementation of uploading media to GCS
func (u *FakeUpload) UploadMedia(ctx context.Context, name string, file io.Reader, contentType string) (*dto.MediaOutPut, error) {
func (u *FakeUpload) UploadMedia(ctx context.Context, name string, file io.Reader, contentType string) (*dto.Media, error) {
return u.MockUploadMediaFn(ctx, name, file, contentType)
}
6 changes: 3 additions & 3 deletions pkg/clinical/infrastructure/services/upload/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

// ServiceUpload holds the upload service methods
type ServiceUpload interface {
UploadMedia(ctx context.Context, name string, file io.Reader, contentType string) (*dto.MediaOutPut, error)
UploadMedia(ctx context.Context, name string, file io.Reader, contentType string) (*dto.Media, error)
}

// ServiceUploadImpl represents upload service implementations
Expand All @@ -38,7 +38,7 @@ func NewServiceUpload(ctx context.Context) *ServiceUploadImpl {
}

// UploadMedia uploads media to GCS
func (u *ServiceUploadImpl) UploadMedia(ctx context.Context, name string, file io.Reader, contentType string) (*dto.MediaOutPut, error) {
func (u *ServiceUploadImpl) UploadMedia(ctx context.Context, name string, file io.Reader, contentType string) (*dto.Media, error) {
bucketName := serverutils.MustGetEnvVar("CLINICAL_BUCKET_NAME")

// Set the content type based on the request header
Expand Down Expand Up @@ -69,7 +69,7 @@ func (u *ServiceUploadImpl) UploadMedia(ctx context.Context, name string, file i
return nil, err
}

output := &dto.MediaOutPut{
output := &dto.Media{
URL: url.MediaLink,
Name: url.Name,
ContentType: url.ContentType,
Expand Down
3 changes: 3 additions & 0 deletions pkg/clinical/presentation/graph/clinical.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ extend type Query {
searchAllergy(name: String!, pagination: Pagination!): TerminologyConnection
getAllergy(id: ID!): Allergy!
listPatientAllergies(patientID: ID!, pagination:Pagination!): AllergyConnection

# Media
listPatientMedia(patientID: ID!, pagination: Pagination!): MediaConnection
}

extend type Mutation {
Expand Down

0 comments on commit b944ffe

Please sign in to comment.