300 lines
8.4 KiB
Go
300 lines
8.4 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"ips-lacpass-backend/internal/ips/client"
|
|
customErrors "ips-lacpass-backend/pkg/errors"
|
|
authMiddleware "ips-lacpass-backend/pkg/middleware"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
type IpsService struct {
|
|
Repository *client.IpsClient
|
|
}
|
|
|
|
func NewService(r *client.IpsClient) IpsService {
|
|
return IpsService{
|
|
Repository: r,
|
|
}
|
|
}
|
|
|
|
func (is *IpsService) GetIps(ctx context.Context) (map[string]interface{}, error) {
|
|
userId, err := authMiddleware.GetUserDocIDFromContext(ctx)
|
|
if err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 401,
|
|
Body: []map[string]interface{}{{"error": "user_identifier_not_found", "message": "User identifier not found in request context"}},
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
bundle, err := is.Repository.GetDocumentReference(userId)
|
|
if err != nil {
|
|
fmt.Printf("Error fetching document reference: %v\n", err)
|
|
return nil, err
|
|
}
|
|
entries := bundle.Entry
|
|
if len(entries) == 0 {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 404,
|
|
Body: []map[string]interface{}{{"error": "not_found", "message": "No IPS found for the user"}},
|
|
Err: fmt.Errorf("no IPS found for the user"),
|
|
}
|
|
}
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
return entries[i].Resource.Meta.LastUpdated > entries[j].Resource.Meta.LastUpdated
|
|
})
|
|
|
|
ipsBundle, err := is.Repository.GetIpsBundle(entries[0].Resource.Content[0].Attachment.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ipsBundle, nil
|
|
|
|
}
|
|
|
|
func (is *IpsService) GetIpsICVP(ctx context.Context, idBundle string, immunizationId *string) (string, error) {
|
|
result, err := is.Repository.GetIpsICVP(idBundle, immunizationId)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Will return the IPS composition sections
|
|
func getIPSComposition(entries []Entry) (*Composition, error) {
|
|
i := slices.IndexFunc(entries, func(e Entry) bool {
|
|
if e.Resource["resourceType"] != "Composition" {
|
|
return false
|
|
}
|
|
var composition Composition
|
|
if err := mapstructure.Decode(e.Resource, &composition); err != nil {
|
|
return false
|
|
}
|
|
return composition.Type.Coding[0]["code"] == "60591-5"
|
|
})
|
|
if i == -1 {
|
|
return nil, fmt.Errorf(`no composition found`)
|
|
}
|
|
comp := entries[i].Resource
|
|
var composition Composition
|
|
if err := mapstructure.Decode(comp, &composition); err != nil {
|
|
return nil, fmt.Errorf(`error decoding composition: %v`, err)
|
|
}
|
|
composition.URL = entries[i].FullURL
|
|
// Remove empty sections
|
|
var result []Section
|
|
for _, s := range composition.Section {
|
|
if s.Code.Coding != nil {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
composition.Section = result
|
|
|
|
return &composition, nil
|
|
}
|
|
|
|
func getEntry(reference string, current []Entry, newIpsEntries []Entry) *Entry {
|
|
indInCurrent := slices.IndexFunc(current, func(e Entry) bool {
|
|
return e.FullURL == reference
|
|
})
|
|
if indInCurrent != -1 {
|
|
return ¤t[indInCurrent]
|
|
}
|
|
|
|
indInNew := slices.IndexFunc(newIpsEntries, func(e Entry) bool {
|
|
return e.FullURL == reference
|
|
})
|
|
if indInNew != -1 {
|
|
return &newIpsEntries[indInNew]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func findAllKeysContainingString(m map[string]interface{}, substring string) []string {
|
|
var matchingKeys []string
|
|
slower := strings.ToLower(substring)
|
|
for key := range m {
|
|
if strings.Contains(strings.ToLower(key), slower) {
|
|
matchingKeys = append(matchingKeys, key)
|
|
}
|
|
}
|
|
return matchingKeys
|
|
}
|
|
|
|
func removeDuplicates(entries []Entry) []Entry {
|
|
encountered := map[string]bool{}
|
|
var result []Entry
|
|
for _, e := range entries {
|
|
if !encountered[e.FullURL] {
|
|
encountered[e.FullURL] = true
|
|
result = append(result, e)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (is *IpsService) MergeIPS(ctx context.Context, currentIpsBundle map[string]interface{}, newIpsBundle map[string]interface{}) (map[string]interface{}, error) {
|
|
var currIPS, newIPS Bundle
|
|
if err := mapstructure.Decode(currentIpsBundle, &currIPS); err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 400,
|
|
Body: []map[string]interface{}{{"error": "bad_request", "message": "Malformed current IPS"}},
|
|
Err: fmt.Errorf("malformed current IPS"),
|
|
}
|
|
}
|
|
|
|
if err := mapstructure.Decode(newIpsBundle, &newIPS); err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 400,
|
|
Body: []map[string]interface{}{{"error": "bad_request", "message": "Malformed new IPS"}},
|
|
Err: fmt.Errorf("malformed new IPS"),
|
|
}
|
|
}
|
|
|
|
curComp, err := getIPSComposition(currIPS.Entry)
|
|
if err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 400,
|
|
Body: []map[string]interface{}{{"error": "bad_request", "message": "Current IPS does not have its composition"}},
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
newComp, err := getIPSComposition(newIPS.Entry)
|
|
if err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 400,
|
|
Body: []map[string]interface{}{{"error": "bad_request", "message": "Current IPS does not have its composition"}},
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
// Merge composition for IPSs
|
|
mergedComp := curComp
|
|
for _, section := range newComp.Section {
|
|
code := section.Code.Coding[0]["code"]
|
|
if code == nil {
|
|
continue
|
|
}
|
|
sectionIndex := slices.IndexFunc(mergedComp.Section, func(s Section) bool {
|
|
return len(s.Code.Coding) > 0 && s.Code.Coding[0] != nil && s.Code.Coding[0]["code"] == code
|
|
})
|
|
|
|
if sectionIndex == -1 {
|
|
// New IPS section is not present on current IPS
|
|
mergedComp.Section = append(mergedComp.Section, section)
|
|
} else {
|
|
// Sections exists, add entries that do not exist in the current IPS
|
|
for _, newEntry := range section.Entry {
|
|
exists := false
|
|
for _, oldEntry := range mergedComp.Section[sectionIndex].Entry {
|
|
if newEntry["reference"] == oldEntry["reference"] {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
if !exists {
|
|
mergedComp.Section[sectionIndex].Entry = append(mergedComp.Section[sectionIndex].Entry, newEntry)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
fullURL := mergedComp.URL
|
|
mergedComp.URL = ""
|
|
|
|
jsonData, err := json.Marshal(mergedComp)
|
|
if err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 500,
|
|
Body: []map[string]interface{}{{"error": "internal_error", "message": "Failed to convert composition to JSON"}},
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
var mergedResource map[string]interface{}
|
|
if err := json.Unmarshal(jsonData, &mergedResource); err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 500,
|
|
Body: []map[string]interface{}{{"error": "internal_error", "message": "Failed to convert composition JSON to a map"}},
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
// Build the merge ips with the merge Composition
|
|
mergedIPS := Bundle{
|
|
ID: uuid.NewString(),
|
|
Identifier: currIPS.Identifier,
|
|
Meta: currIPS.Meta,
|
|
ResourceType: currIPS.ResourceType,
|
|
Signature: nil,
|
|
Timestamp: time.Now().UTC().String(),
|
|
Type: currIPS.Type,
|
|
Entry: []Entry{{FullURL: fullURL, Resource: mergedResource}},
|
|
}
|
|
for _, section := range mergedComp.Section {
|
|
for _, secEntry := range section.Entry {
|
|
newEntry := getEntry(secEntry["reference"].(string), currIPS.Entry, newIPS.Entry)
|
|
|
|
if newEntry == nil {
|
|
break
|
|
}
|
|
mergedIPS.Entry = append(mergedIPS.Entry, *newEntry)
|
|
|
|
if newEntry.Resource == nil {
|
|
break
|
|
}
|
|
// Check for any resource that contains more reference in its representation
|
|
// If we find any reference we added it to the IPS
|
|
rk := findAllKeysContainingString(newEntry.Resource, "reference")
|
|
for _, k := range rk {
|
|
v, ok := newEntry.Resource[k]
|
|
if !ok {
|
|
break
|
|
}
|
|
var ref Reference
|
|
if err := mapstructure.Decode(v, &ref); err != nil {
|
|
return nil, fmt.Errorf(`error decoding codeable reference: %v`, err)
|
|
}
|
|
if ref.Reference != "" {
|
|
newEntry = getEntry(ref.Reference, currIPS.Entry, newIPS.Entry)
|
|
mergedIPS.Entry = append(mergedIPS.Entry, *newEntry)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
mergedIPS.Entry = removeDuplicates(mergedIPS.Entry)
|
|
|
|
jsonData, err = json.Marshal(mergedIPS)
|
|
if err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 500,
|
|
Body: []map[string]interface{}{{"error": "internal_error", "message": "Failed to convert composition to JSON"}},
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
var data map[string]interface{}
|
|
if err := json.Unmarshal(jsonData, &data); err != nil {
|
|
return nil, &customErrors.HttpError{
|
|
StatusCode: 500,
|
|
Body: []map[string]interface{}{{"error": "internal_error", "message": "Failed to convert composition JSON to a map"}},
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
return data, nil
|
|
}
|