Skip to content

Commit

Permalink
feat: api to create a tenant
Browse files Browse the repository at this point in the history
  • Loading branch information
Muchogoc committed Mar 15, 2023
1 parent 53d26aa commit 7445818
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 33 deletions.
9 changes: 9 additions & 0 deletions pkg/clinical/application/dto/enums.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dto

type OrganizationIdentifierType string

const (
SladeCode OrganizationIdentifierType = "SladeCode"
MFLCode OrganizationIdentifierType = "MFLCode"
Other OrganizationIdentifierType = "Other"
)
18 changes: 18 additions & 0 deletions pkg/clinical/application/dto/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dto

// ErrorDetails contains more details about the error that occurred while making a REST API call to FHIR servers
type ErrorDetails struct {
Text string `json:"text"`
}

// ErrorIssue models the error issue returned from FHIR
type ErrorIssue struct {
Details ErrorDetails `json:"details"`
Diagnostics string `json:"diagnostics"`
}

// ErrorResponse models the json object returned when an error occurs while calling the
// FHIR server REST APIs
type ErrorResponse struct {
Issue []ErrorIssue `json:"issue"`
}
11 changes: 11 additions & 0 deletions pkg/clinical/application/dto/input.go
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
package dto

type OrganizationIdentifier struct {
Type OrganizationIdentifierType `json:"type"`
Value string `json:"value"`
}

type OrganizationInput struct {
Name string `json:"name"`
PhoneNumber string `json:"phoneNumber"`
Identifiers []OrganizationIdentifier `json:"identifiers"`
}
38 changes: 8 additions & 30 deletions pkg/clinical/application/dto/output.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,14 @@
package dto

// UserInfo is a collection of standard profile information for a user.
type UserInfo struct {
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
PhotoURL string `json:"photoUrl,omitempty"`
// In the ProviderUserInfo[] ProviderID can be a short domain name (e.g. google.com),
// or the identity of an OpenID identity provider.
// In UserRecord.UserInfo it will return the constant string "firebase".
ProviderID string `json:"providerId,omitempty"`
UID string `json:"rawId,omitempty"`
}

// ErrorDetails contains more details about the error that occurred while making a REST API call to FHIR servers
type ErrorDetails struct {
Text string `json:"text"`
}

// ErrorIssue models the error issue returned from FHIR
type ErrorIssue struct {
Details ErrorDetails `json:"details"`
Diagnostics string `json:"diagnostics"`
}

// ErrorResponse models the json object returned when an error occurs while calling the
// FHIR server REST APIs
type ErrorResponse struct {
Issue []ErrorIssue `json:"issue"`
}

// TenantIdentifiers models the json object used to store some of the tenant identifiers
type TenantIdentifiers struct {
OrganizationID string `json:"organizationID,omitempty"`
}

type Organization struct {
ID string `json:"id"`
Active bool `json:"active"`
Name string `json:"name"`
Identifiers []OrganizationIdentifier `json:"identifiers"`
PhoneNumbers []string `json:"phoneNumbers"`
}
20 changes: 20 additions & 0 deletions pkg/clinical/application/utils/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,23 @@ func AddPubSubNamespace(topicName, serviceName string) string {
TopicVersion,
)
}

// CustomError represents a custom error struct
// Reference https://blog.golang.org/error-handling-and-go
type CustomError struct {
Err error `json:"error,omitempty"`
Message string `json:"message,omitempty"`
}

// Error implements the error interface
func (e CustomError) Error() string {
return fmt.Sprintf("%d: %s", e.Err, e.Message)
}

// NewCustomError is a helper function to create a new custom error
func NewCustomError(err error, message string) CustomError {
return CustomError{
Err: err,
Message: message,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,41 @@ func NewFHIRMock() *FHIRMock {
}, nil
},
MockCreateFHIROrganizationFn: func(ctx context.Context, input domain.FHIROrganizationInput) (*domain.FHIROrganizationRelayPayload, error) {
active := true
name := gofakeit.Name()
uri := ""

use := domain.ContactPointUseEnumWork
rank := int64(1)
phoneSystem := domain.ContactPointSystemEnumPhone
phoneNumber := gofakeit.Phone()

return &domain.FHIROrganizationRelayPayload{
Resource: &domain.FHIROrganization{
ID: &UUID,
ID: &UUID,
Name: &name,
Active: &active,
Identifier: []*domain.FHIRIdentifier{
{
Use: "official",
Type: domain.FHIRCodeableConcept{
Text: "type",
},
System: (*scalarutils.URI)(&uri),
Value: "",
Period: &domain.FHIRPeriod{},
Assigner: &domain.FHIRReference{},
},
},
Telecom: []*domain.FHIRContactPoint{
{
System: &phoneSystem,
Value: &phoneNumber,
Use: &use,
Rank: &rank,
Period: &domain.FHIRPeriod{},
},
},
},
}, nil
},
Expand Down
11 changes: 9 additions & 2 deletions pkg/clinical/presentation/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,15 @@ func SetupRoutes(r *gin.Engine, authclient *authutils.Client, usecases usecases.
ide := r.Group("/ide")
ide.Any("", playgroundHandler())

pubsubPath := r.Group("/pubsub")
pubsubPath.POST("", handlers.ReceivePubSubPushMessage)
r.POST("/pubsub", handlers.ReceivePubSubPushMessage)

apis := r.Group("/api")
apis.Use(authutils.SladeAuthenticationGinMiddleware(*authclient))

v1 := apis.Group("/v1")

tenants := v1.Group("/tenants")
tenants.POST("", handlers.RegisterTenant)
}

// GQLHandler sets up a GraphQL resolver
Expand Down
18 changes: 18 additions & 0 deletions pkg/clinical/presentation/rest/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,21 @@ func (p PresentationHandlersImpl) ReceivePubSubPushMessage(c *gin.Context) {
resp := map[string]string{"Status": "Success"}
c.JSON(http.StatusOK, resp)
}

func (p PresentationHandlersImpl) RegisterTenant(c *gin.Context) {
input := dto.OrganizationInput{}

err := c.BindJSON(&input)
if err != nil {
resp := map[string]string{"error": err.Error()}
c.JSON(http.StatusBadRequest, resp)
}

organization, err := p.usecases.RegisterTenant(c.Request.Context(), input)
if err != nil {
resp := map[string]string{"error": err.Error()}
c.JSON(http.StatusBadRequest, resp)
}

c.JSON(http.StatusOK, organization)
}
129 changes: 129 additions & 0 deletions pkg/clinical/usecases/clinical/organization.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package clinical

import (
"context"
"fmt"

"github.com/savannahghi/clinical/pkg/clinical/application/common"
"github.com/savannahghi/clinical/pkg/clinical/application/common/helpers"
"github.com/savannahghi/clinical/pkg/clinical/application/dto"
"github.com/savannahghi/clinical/pkg/clinical/application/utils"
"github.com/savannahghi/clinical/pkg/clinical/domain"
"github.com/savannahghi/scalarutils"
)

// This file holds all the business logic for creating a FHIR organization. We have a notion of tenants and facilities
// The tenant ID will be used as a logical partitioning key since we want to show that this data resource belongs to this patient who is part of a certain organization(tenant).

// RegisterTenant is used to create an organisation/tenant in the FHIR stores. The tenant ID will be used for logical
// partitioning of data
func (c *UseCasesClinicalImpl) RegisterTenant(ctx context.Context, input dto.OrganizationInput) (*dto.Organization, error) {
if len(input.Identifiers) == 0 {
err := fmt.Errorf("expected at least one tenant identifier")
message := "please provide at least one identifier"

return nil, utils.NewCustomError(err, message)
}

if input.Name == "" {
err := fmt.Errorf("expected name to be defined")
message := "please provide the tenant name"

return nil, utils.NewCustomError(err, message)
}

payload := mapOrganizationInputToFHIROrganizationInput(input)

organisationPayload, err := c.infrastructure.FHIR.CreateFHIROrganization(ctx, *payload)
if err != nil {
return nil, err
}

// fhir organization to dto organization
return mapFHIROrganizationToDTOOrganization(organisationPayload.Resource), nil
}

func mapIdentifierToFHIRIdentifierInput(idType, value string) *domain.FHIRIdentifierInput {
identificationDocumentIdentifierSystem := scalarutils.URI(idType)
userSelected := true
idSystem := scalarutils.URI(identificationDocumentIdentifierSystem)
version := helpers.DefaultVersion

identifier := domain.FHIRIdentifierInput{
Use: domain.IdentifierUseEnumOfficial,
Type: domain.FHIRCodeableConceptInput{
Coding: []*domain.FHIRCodingInput{
{
System: &identificationDocumentIdentifierSystem,
Version: &version,
Code: scalarutils.Code(value),
Display: value,
UserSelected: &userSelected,
},
},
Text: value,
},
System: &idSystem,
Value: value,
Period: common.DefaultPeriodInput(),
}

return &identifier
}

func mapPhoneNumberToFHIRContactPointInput(phoneNumber string) *domain.FHIRContactPointInput {
use := domain.ContactPointUseEnumWork
rank := int64(1)
phoneSystem := domain.ContactPointSystemEnumPhone

return &domain.FHIRContactPointInput{
System: &phoneSystem,
Value: &phoneNumber,
Use: &use,
Rank: &rank,
Period: common.DefaultPeriodInput(),
}
}

func mapOrganizationInputToFHIROrganizationInput(organization dto.OrganizationInput) *domain.FHIROrganizationInput {
active := true
org := domain.FHIROrganizationInput{
Name: &organization.Name,
Active: &active,
Telecom: []*domain.FHIRContactPointInput{},
Identifier: []*domain.FHIRIdentifierInput{},
}

contact := mapPhoneNumberToFHIRContactPointInput(organization.PhoneNumber)
org.Telecom = append(org.Telecom, contact)

for _, id := range organization.Identifiers {
identifier := mapIdentifierToFHIRIdentifierInput(string(id.Type), id.Value)
org.Identifier = append(org.Identifier, identifier)
}

return &org
}

func mapFHIROrganizationToDTOOrganization(organisation *domain.FHIROrganization) *dto.Organization {
org := &dto.Organization{
ID: *organisation.ID,
Active: *organisation.Active,
Name: *organisation.Name,
Identifiers: make([]dto.OrganizationIdentifier, 0),
PhoneNumbers: make([]string, 0),
}

for _, identifier := range organisation.Identifier {
org.Identifiers = append(org.Identifiers, dto.OrganizationIdentifier{
Type: dto.OrganizationIdentifierType(*identifier.System),
Value: identifier.Value,
})
}

for _, telecom := range organisation.Telecom {
org.PhoneNumbers = append(org.PhoneNumbers, *telecom.Value)
}

return org
}
Loading

0 comments on commit 7445818

Please sign in to comment.