diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5a64a9e5..686fd446 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -65,5 +65,7 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + FASTEN_ENV=${{ github.ref_name == 'main' && 'prod' || 'sandbox' }} # cache-from: type=gha # cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 499ef709..72470362 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ # Frontend Build ######################################################################################################### FROM node:18.9.0 as frontend-build +ARG FASTEN_ENV=sandbox WORKDIR /usr/src/fastenhealth/frontend #COPY frontend/package.json frontend/yarn.lock ./ COPY frontend/package.json ./ @@ -9,7 +10,7 @@ COPY frontend/package.json ./ RUN yarn config set registry "http://registry.npmjs.org" \ && yarn install --frozen-lockfile --network-timeout 100000 COPY frontend/ ./ -RUN yarn run build -- --configuration sandbox --output-path=../dist +RUN yarn run build -- --configuration ${FASTEN_ENV} --output-path=../dist ######################################################################################################### # Backend Build @@ -29,26 +30,13 @@ RUN CGO_ENABLED=0 go build -o /go/bin/fasten ./backend/cmd/fasten/ # create folder structure RUN mkdir -p /opt/fasten/db \ && mkdir -p /opt/fasten/web \ - && mkdir -p /opt/fasten/config \ - && curl -o /opt/fasten/db/fasten.db -L https://github.com/fastenhealth/testdata/raw/main/fasten.db - + && mkdir -p /opt/fasten/config ######################################################################################################### # Distribution Build ######################################################################################################### -FROM couchdb:3.2 +FROM gcr.io/distroless/static-debian11 -ENV FASTEN_COUCHDB_ADMIN_USERNAME=admin -ENV FASTEN_COUCHDB_ADMIN_PASSWORD=mysecretpassword -ENV FASTEN_JWT_ISSUER_KEY=thisismysupersecuressessionsecretlength - -ARG S6_ARCH=amd64 -RUN curl https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-${S6_ARCH}.tar.gz -L -s --output /tmp/s6-overlay-${S6_ARCH}.tar.gz \ - && tar xzf /tmp/s6-overlay-${S6_ARCH}.tar.gz -C / \ - && rm -rf /tmp/s6-overlay-${S6_ARCH}.tar.gz - -COPY /docker/couchdb/fasten.ini /opt/couchdb/etc/local.ini -COPY /docker/rootfs / WORKDIR /opt/fasten/ COPY --from=backend-build /opt/fasten/ /opt/fasten/ @@ -57,7 +45,7 @@ COPY --from=backend-build /go/bin/fasten /opt/fasten/fasten COPY LICENSE.md /opt/fasten/LICENSE.md COPY config.yaml /opt/fasten/config/config.yaml -ENTRYPOINT ["/init"] +CMD ["/opt/fasten/fasten", "start", "--config", "/opt/fasten/config/config.yaml"] diff --git a/backend/pkg/database/interface.go b/backend/pkg/database/interface.go index 35835520..33c14fac 100644 --- a/backend/pkg/database/interface.go +++ b/backend/pkg/database/interface.go @@ -22,6 +22,8 @@ type DatabaseRepository interface { GetResourceBySourceId(context.Context, string, string) (*models.ResourceFhir, error) ListResources(context.Context, models.ListResourceQueryOptions) ([]models.ResourceFhir, error) GetPatientForSources(ctx context.Context) ([]models.ResourceFhir, error) + AddResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error + RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error //UpsertProfile(context.Context, *models.Profile) error //UpsertOrganziation(context.Context, *models.Organization) error diff --git a/backend/pkg/database/sqlite_repository.go b/backend/pkg/database/sqlite_repository.go index cce9db18..28bf06e1 100644 --- a/backend/pkg/database/sqlite_repository.go +++ b/backend/pkg/database/sqlite_repository.go @@ -194,7 +194,47 @@ func (sr *SqliteRepository) UpsertRawResource(ctx context.Context, sourceCredent SourceResourceID: rawResource.SourceResourceID, SourceResourceType: rawResource.SourceResourceType, }, - ResourceRaw: datatypes.JSON(rawResource.ResourceRaw), + ResourceRaw: datatypes.JSON(rawResource.ResourceRaw), + RelatedResourceFhir: nil, + } + + //create associations + //note: we create the association in the related_resources table **before** the model actually exists. + + if rawResource.ReferencedResources != nil { + //these are resources that are referenced by the current resource + relatedResources := []*models.ResourceFhir{} + + //reciprocalRelatedResources := []*models.ResourceFhir{} + for _, referencedResource := range rawResource.ReferencedResources { + parts := strings.Split(referencedResource, "/") + if len(parts) == 2 { + + relatedResource := &models.ResourceFhir{ + OriginBase: models.OriginBase{ + SourceID: source.ID, + SourceResourceType: parts[0], + SourceResourceID: parts[1], + }, + RelatedResourceFhir: nil, + } + relatedResources = append(relatedResources, relatedResource) + + //if the related resource is an Encounter or Condition, make sure we create a reciprocal association as well, just incase + if parts[0] == "Condition" || parts[0] == "Encounter" { + //manually create association (we've tried to create using Association.Append, and it doesnt work for some reason. + err := sr.AddResourceAssociation(ctx, &source, parts[0], parts[1], &source, wrappedResourceModel.SourceResourceType, wrappedResourceModel.SourceResourceID) + if err != nil { + sr.Logger.Errorf("Error when creating a reciprocal association for %s: %v", referencedResource, err) + } + } + + } + } + + //ignore errors when creating associations (we always get a 'WHERE conditions required' error, ) + sr.GormClient.WithContext(ctx).Model(wrappedResourceModel).Association("RelatedResourceFhir").Append(relatedResources) + } sr.Logger.Infof("insert/update (%v) %v", rawResource.SourceResourceType, rawResource.SourceResourceID) @@ -203,7 +243,7 @@ func (sr *SqliteRepository) UpsertRawResource(ctx context.Context, sourceCredent SourceID: wrappedResourceModel.GetSourceID(), SourceResourceID: wrappedResourceModel.GetSourceResourceID(), SourceResourceType: wrappedResourceModel.GetSourceResourceType(), //TODO: and UpdatedAt > old UpdatedAt - }).FirstOrCreate(wrappedResourceModel) + }).Omit("RelatedResourceFhir.*").FirstOrCreate(wrappedResourceModel) if createResult.Error != nil { return false, createResult.Error @@ -211,7 +251,7 @@ func (sr *SqliteRepository) UpsertRawResource(ctx context.Context, sourceCredent //at this point, wrappedResourceModel contains the data found in the database. // check if the database resource matches the new resource. if wrappedResourceModel.ResourceRaw.String() != string(rawResource.ResourceRaw) { - updateResult := createResult.Updates(wrappedResourceModel) + updateResult := createResult.Omit("RelatedResourceFhir.*").Updates(wrappedResourceModel) return updateResult.RowsAffected > 0, updateResult.Error } else { return false, nil @@ -271,13 +311,20 @@ func (sr *SqliteRepository) ListResources(ctx context.Context, queryOptions mode queryParam.OriginBase.SourceID = sourceUUID } + if len(queryOptions.SourceResourceID) > 0 { + queryParam.OriginBase.SourceResourceID = queryOptions.SourceResourceID + } manifestJson, _ := json.MarshalIndent(queryParam, "", " ") sr.Logger.Infof("THE QUERY OBJECT===========> %v", string(manifestJson)) var wrappedResourceModels []models.ResourceFhir - results := sr.GormClient.WithContext(ctx). - Where(queryParam). + queryBuilder := sr.GormClient.WithContext(ctx) + if queryOptions.PreloadRelated { + //enable preload functionality in query + queryBuilder = queryBuilder.Preload("RelatedResourceFhir").Preload("RelatedResourceFhir.RelatedResourceFhir") + } + results := queryBuilder.Where(queryParam). Find(&wrappedResourceModels) return wrappedResourceModels, results.Error @@ -345,6 +392,48 @@ func (sr *SqliteRepository) GetPatientForSources(ctx context.Context) ([]models. return wrappedResourceModels, results.Error } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Resource Associations +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +func (sr *SqliteRepository) AddResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error { + //ensure that the sources are "owned" by the same user + + if source.UserID != relatedSource.UserID { + return fmt.Errorf("user id's must match when adding associations") + } else if source.UserID != sr.GetCurrentUser(ctx).ID { + return fmt.Errorf("user id's must match current user") + } + + //manually create association (we've tried to create using Association.Append, and it doesnt work for some reason. + return sr.GormClient.WithContext(ctx).Table("related_resources").Create(map[string]interface{}{ + "resource_fhir_source_id": source.ID, + "resource_fhir_source_resource_type": resourceType, + "resource_fhir_source_resource_id": resourceId, + "related_resource_fhir_source_id": relatedSource.ID, + "related_resource_fhir_source_resource_type": relatedResourceType, + "related_resource_fhir_source_resource_id": relatedResourceId, + }).Error +} + +func (sr *SqliteRepository) RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error { + if source.UserID != relatedSource.UserID { + return fmt.Errorf("user id's must match when adding associations") + } else if source.UserID != sr.GetCurrentUser(ctx).ID { + return fmt.Errorf("user id's must match current user") + } + + //manually create association (we've tried to create using Association.Append, and it doesnt work for some reason. + return sr.GormClient.WithContext(ctx).Table("related_resources").Delete(map[string]interface{}{ + "resource_fhir_source_id": source.ID, + "resource_fhir_source_resource_type": resourceType, + "resource_fhir_source_resource_id": resourceId, + "related_resource_fhir_source_id": relatedSource.ID, + "related_resource_fhir_source_resource_type": relatedResourceType, + "related_resource_fhir_source_resource_id": relatedResourceId, + }).Error +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SourceCredential //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/backend/pkg/models/resource_association.go b/backend/pkg/models/resource_association.go new file mode 100644 index 00000000..f453df9b --- /dev/null +++ b/backend/pkg/models/resource_association.go @@ -0,0 +1,15 @@ +package models + +type ResourceAssociation struct { + SourceID string `json:"source_id"` + SourceResourceType string `json:"source_resource_type"` + SourceResourceID string `json:"source_resource_id"` + + OldRelatedSourceID string `json:"old_related_source_id"` + OldRelatedSourceResourceType string `json:"old_related_source_resource_type"` + OldRelatedSourceResourceID string `json:"old_related_source_resource_id"` + + NewRelatedSourceID string `json:"new_related_source_id"` + NewRelatedSourceResourceType string `json:"new_related_source_resource_type"` + NewRelatedSourceResourceID string `json:"new_related_source_resource_id"` +} diff --git a/backend/pkg/models/resource_fhir.go b/backend/pkg/models/resource_fhir.go index 02ba3936..cc037a30 100644 --- a/backend/pkg/models/resource_fhir.go +++ b/backend/pkg/models/resource_fhir.go @@ -9,9 +9,15 @@ type ResourceFhir struct { //embedded data ResourceRaw datatypes.JSON `json:"resource_raw" gorm:"resource_raw"` + + //relationships + RelatedResourceFhir []*ResourceFhir `json:"related_resources" gorm:"many2many:related_resources;ForeignKey:source_id,source_resource_type,source_resource_id;references:source_id,source_resource_type,source_resource_id;"` } type ListResourceQueryOptions struct { SourceID string SourceResourceType string + SourceResourceID string + + PreloadRelated bool } diff --git a/backend/pkg/web/handler/resource_fhir.go b/backend/pkg/web/handler/resource_fhir.go index 73d50b9e..b5d26ac4 100644 --- a/backend/pkg/web/handler/resource_fhir.go +++ b/backend/pkg/web/handler/resource_fhir.go @@ -20,6 +20,12 @@ func ListResourceFhir(c *gin.Context) { if len(c.Query("sourceID")) > 0 { listResourceQueryOptions.SourceID = c.Query("sourceID") } + if len(c.Query("sourceResourceID")) > 0 { + listResourceQueryOptions.SourceResourceID = c.Query("sourceResourceID") + } + if len(c.Query("preloadRelated")) > 0 { + listResourceQueryOptions.PreloadRelated = true + } wrappedResourceModels, err := databaseRepo.ListResources(c, listResourceQueryOptions) @@ -49,3 +55,55 @@ func GetResourceFhir(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"success": true, "data": wrappedResourceModel}) } + +func ReplaceResourceAssociation(c *gin.Context) { + + logger := c.MustGet("LOGGER").(*logrus.Entry) + databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository) + + resourceAssociation := models.ResourceAssociation{} + if err := c.ShouldBindJSON(&resourceAssociation); err != nil { + logger.Errorln("An error occurred while parsing posted resource association data", err) + c.JSON(http.StatusBadRequest, gin.H{"success": false}) + return + } + + sourceCred, err := databaseRepo.GetSource(c, resourceAssociation.SourceID) + if err != nil { + logger.Errorln("An error occurred while retrieving source", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + + if len(resourceAssociation.OldRelatedSourceID) > 0 { + oldRelatedSourceCred, err := databaseRepo.GetSource(c, resourceAssociation.OldRelatedSourceID) + if err != nil { + logger.Errorln("An error occurred while retrieving old related source", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + + err = databaseRepo.RemoveResourceAssociation(c, sourceCred, resourceAssociation.SourceResourceType, resourceAssociation.SourceResourceID, oldRelatedSourceCred, resourceAssociation.OldRelatedSourceResourceType, resourceAssociation.OldRelatedSourceResourceID) + if err != nil { + logger.Errorln("An error occurred while deleting resource association", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + } + + newRelatedSourceCred, err := databaseRepo.GetSource(c, resourceAssociation.NewRelatedSourceID) + if err != nil { + logger.Errorln("An error occurred while retrieving new related source", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + + err = databaseRepo.AddResourceAssociation(c, sourceCred, resourceAssociation.SourceResourceType, resourceAssociation.SourceResourceID, newRelatedSourceCred, resourceAssociation.NewRelatedSourceResourceType, resourceAssociation.NewRelatedSourceResourceID) + if err != nil { + logger.Errorln("An error occurred while associating resource", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + + c.JSON(http.StatusOK, gin.H{"success": true}) +} diff --git a/backend/pkg/web/server.go b/backend/pkg/web/server.go index c0c5df6c..bf8d5dd7 100644 --- a/backend/pkg/web/server.go +++ b/backend/pkg/web/server.go @@ -59,6 +59,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine { secure.GET("/source/:sourceId/summary", handler.GetSourceSummary) secure.GET("/resource/fhir", handler.ListResourceFhir) // secure.GET("/resource/fhir/:sourceId/:resourceId", handler.GetResourceFhir) + secure.POST("/resource/association", handler.ReplaceResourceAssociation) } if ae.Config.GetString("log.level") == "DEBUG" { diff --git a/config.yaml b/config.yaml index 46533d65..383516e5 100644 --- a/config.yaml +++ b/config.yaml @@ -22,4 +22,4 @@ web: log: file: '' #absolute or relative paths allowed, eg. web.log - level: DEBUG + level: INFO diff --git a/docker/couchdb/Dockerfile b/docker/couchdb/Dockerfile deleted file mode 100644 index 0e2aadab..00000000 --- a/docker/couchdb/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -######################################################################################################### -# CouchDB Build -# NOTE: the context for this build should be the root of the repository. -######################################################################################################### -FROM couchdb:3.2 - -ENV FASTEN_COUCHDB_ADMIN_USERNAME=admin -ENV FASTEN_COUCHDB_ADMIN_PASSWORD=mysecretpassword -ENV FASTEN_JWT_ISSUER_KEY=thisismysupersecuressessionsecretlength - -ARG S6_ARCH=amd64 -RUN curl https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-${S6_ARCH}.tar.gz -L -s --output /tmp/s6-overlay-${S6_ARCH}.tar.gz \ - && tar xzf /tmp/s6-overlay-${S6_ARCH}.tar.gz -C / \ - && rm -rf /tmp/s6-overlay-${S6_ARCH}.tar.gz - -COPY /docker/couchdb/fasten.ini /opt/couchdb/etc/local.ini -COPY /docker/rootfs / -RUN rm -rf /etc/services.d/fasten #delete the fasten app from the couchdb-only container. - -ENTRYPOINT ["/init"] diff --git a/docker/couchdb/fasten.ini b/docker/couchdb/fasten.ini deleted file mode 100644 index 7b82eeb7..00000000 --- a/docker/couchdb/fasten.ini +++ /dev/null @@ -1,54 +0,0 @@ -; CouchDB Configuration Settings -; Custom settings should be made in this file. They will override settings -; in default.ini, but unlike changes made to default.ini, this file won't be -; overwritten on server upgrade. - -[couch_peruser] - -; fasten requires that each user have a private database. These databases are writable only by the corresponding user. -; Databases are in the following form: userdb-{hex encoded username} -enable = true - -[chttpd_auth] - -; require_valid_user must be set to false because Fasten will check session endpoint to determine if user is authenticated. -; if this option is not disabled, user is prompted with basic auth. -require_valid_user = false - -[httpd] - -; enable CORS support, required because the database is hosted on a different node. -enable_cors = true - -; ------------------------------------------ DOCKER MODIFICATIONS -; ------------------------------------------ DOCKER MODIFICATIONS -; ------------------------------------------ DOCKER MODIFICATIONS -; ------------------------------------------ DOCKER MODIFICATIONS - -; always use single node in docker -[couchdb] -;max_document_size = 4294967296 ; bytes -;os_process_timeout = 5000 -single_node = true - -; when running in docker, allow cors for all domains -; TODO, we should find a more secure way to do this -[cors] -origins = * -headers = accept, authorization, content-type, origin, referer -credentials = true -methods = GET, PUT, POST, HEAD, DELETE -max_age = 3600 - -# make sure the databse is listening to all traffic, not just from localhost within the container. -[chttpd] -;port = 5984 -;bind_address = 127.0.0.1 -bind_address = 0.0.0.0 -enable_cors = true -x_forwarded_host = X-Forwarded-Host -; require_valid_user must be set to false because Fasten will check session endpoint to determine if user is authenticated. -; if this option is not disabled, user is prompted with basic auth. -require_valid_user = false -; fasten uses JWT tokens to authenticate against the database. we override the authentication_handlers to add jwt_authentication_handler -authentication_handlers = {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, default_authentication_handler} diff --git a/docker/rootfs/etc/cont-init.d/01-timezone b/docker/rootfs/etc/cont-init.d/01-timezone deleted file mode 100644 index 83fb30c2..00000000 --- a/docker/rootfs/etc/cont-init.d/01-timezone +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/with-contenv bash - -if [ -n "${TZ}" ] -then - ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime - echo "${TZ}" > /etc/timezone -fi diff --git a/docker/rootfs/etc/cont-init.d/05-couchdb-config b/docker/rootfs/etc/cont-init.d/05-couchdb-config deleted file mode 100644 index a9af87c7..00000000 --- a/docker/rootfs/etc/cont-init.d/05-couchdb-config +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/with-contenv bash - -if [ -f "/opt/couchdb/data/.config_complete" ]; then - echo "Couchdb config has already completed, skipping" -else - #echo -n means we dont pass newline to base64 (ugh). eg. hello = aGVsbG8K vs aGVsbG8= - FASTEN_JWT_ISSUER_KEY_BASE64=$(echo -n "${FASTEN_JWT_ISSUER_KEY}" | base64) - - -cat << EOF >> /opt/couchdb/etc/local.d/generated.ini - -; ------------------------------------------ GENERATED MODIFICATIONS -; ------------------------------------------ GENERATED MODIFICATIONS -; ------------------------------------------ GENERATED MODIFICATIONS -; -[jwt_auth] -required_claims = exp, {iss, "docker-fastenhealth"} - -[jwt_keys] -hmac:_default = ${FASTEN_JWT_ISSUER_KEY_BASE64} - - -; users should change this default password -[admins] -${FASTEN_COUCHDB_ADMIN_USERNAME} = ${FASTEN_COUCHDB_ADMIN_PASSWORD} - -EOF - - # create the config complete flag - echo "Couchdb config: complete" - touch /opt/couchdb/data/.config_complete - -fi diff --git a/docker/rootfs/etc/cont-init.d/50-couchdb-init b/docker/rootfs/etc/cont-init.d/50-couchdb-init deleted file mode 100755 index 6338b0ad..00000000 --- a/docker/rootfs/etc/cont-init.d/50-couchdb-init +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/with-contenv bash - -if [ -f "/opt/couchdb/data/.init_complete" ]; then - echo "Couchdb initialization has already completed, skipping" -else - # start couchdb as a background process (store PID) - echo "Couchdb initialization: start couchdb in background mode (non-standard port)" - # https://linux.die.net/man/1/couchdb - sed -i -e 's/;port = 5984/port = 5432/g' /opt/couchdb/etc/local.ini - sed -i -e 's/bind_address = 0.0.0.0/bind_address = 127.0.0.1/g' /opt/couchdb/etc/local.ini - /opt/couchdb/bin/couchdb -b & - COUCHDB_PID=$! - - # wait for couchdb to be ready - until $(curl --output /dev/null --silent --head --fail http://127.0.0.1:5432/_up); do echo "couchdb not ready" && sleep 5; done - - # create couch_peruser required system databases manually on startup - echo "couchdb ready, start creating system databases" - curl --fail -X PUT -u ${FASTEN_COUCHDB_ADMIN_USERNAME}:${FASTEN_COUCHDB_ADMIN_PASSWORD} http://127.0.0.1:5432/_users - curl --fail -X PUT -u ${FASTEN_COUCHDB_ADMIN_USERNAME}:${FASTEN_COUCHDB_ADMIN_PASSWORD} http://127.0.0.1:5432/_replicator - curl --fail -X PUT -u ${FASTEN_COUCHDB_ADMIN_USERNAME}:${FASTEN_COUCHDB_ADMIN_PASSWORD} http://127.0.0.1:5432/_global_changes - echo "system databases created successfully" - - # gracefully stop couchdb process - echo "killing couchdb process" - /opt/couchdb/bin/couchdb -k - - sed -i -e 's/port = 5432/;port = 5984/g' /opt/couchdb/etc/local.ini - sed -i -e 's/bind_address = 127.0.0.1/bind_address = 0.0.0.0/g' /opt/couchdb/etc/local.ini - - # create the init complete flag - echo "Couchdb initialization: complete" - touch /opt/couchdb/data/.init_complete - -fi diff --git a/docker/rootfs/etc/services.d/couchdb/run b/docker/rootfs/etc/services.d/couchdb/run deleted file mode 100644 index 677da6c7..00000000 --- a/docker/rootfs/etc/services.d/couchdb/run +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/with-contenv bash - -echo "starting couchdb" -/docker-entrypoint.sh /opt/couchdb/bin/couchdb diff --git a/docker/rootfs/etc/services.d/fasten/run b/docker/rootfs/etc/services.d/fasten/run deleted file mode 100644 index e7874cb9..00000000 --- a/docker/rootfs/etc/services.d/fasten/run +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/with-contenv bash - -# wait for couchdb to be ready -until $(curl --output /dev/null --silent --head --fail http://127.0.0.1:5984/_up); do echo "couchdb not ready" && sleep 5; done - -echo "starting fasten" -/opt/fasten/fasten start --config /opt/fasten/config/config.yaml diff --git a/frontend/angular.json b/frontend/angular.json index 6f741371..b51f5332 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -73,7 +73,7 @@ ], "optimization": true, "outputHashing": "all", - "sourceMap": false, + "sourceMap": true, "namedChunks": false, "extractLicenses": true, "vendorChunk": false, @@ -82,7 +82,7 @@ { "type": "initial", "maximumWarning": "2mb", - "maximumError": "5mb" + "maximumError": "15mb" }, { "type": "anyComponentStyle", diff --git a/frontend/package.json b/frontend/package.json index c297d9f5..eabef9ae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "@angular/platform-browser": "^14.1.3", "@angular/platform-browser-dynamic": "^14.1.3", "@angular/router": "^14.1.3", + "@circlon/angular-tree-component": "^11.0.4", "@fortawesome/angular-fontawesome": "^0.11.1", "@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/free-regular-svg-icons": "^6.2.0", @@ -34,6 +35,8 @@ "chart.js": "2.9.4", "crypto-pouch": "^4.0.1", "fhirclient": "^2.5.1", + "fhirpath": "^3.3.0", + "fuse.js": "^6.6.2", "garbados-crypt": "^3.0.0-beta", "humanize-duration": "^3.27.3", "idb": "^7.1.0", diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 6f9431f1..52fb9c66 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -29,6 +29,7 @@ import {AuthService} from './services/auth.service'; import { PatientProfileComponent } from './pages/patient-profile/patient-profile.component'; import { MedicalHistoryComponent } from './pages/medical-history/medical-history.component'; + @NgModule({ declarations: [ AppComponent, diff --git a/frontend/src/app/components/header/header.component.html b/frontend/src/app/components/header/header.component.html index a0a588dd..fb57e02e 100644 --- a/frontend/src/app/components/header/header.component.html +++ b/frontend/src/app/components/header/header.component.html @@ -10,9 +10,13 @@ ×