From f0f37cd738a39b93fa391b5fe1b7d7de1324fdb2 Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Fri, 28 Oct 2022 19:01:20 -0700 Subject: [PATCH] adding a new configuration for "cloud_sandbox" - used for AWS bucket. consistent couchdb creds. removed metadata endpoint and config. ensure that the couchdb endpoint can be relative or absolute url. added tests for relative vs abs urls. --- .github/workflows/cloud-deploy.yaml | 2 +- backend/pkg/config/config.go | 10 ++-- backend/pkg/database/couchdb_repository.go | 6 +-- backend/pkg/web/handler/couchdb_proxy.go | 2 +- backend/pkg/web/handler/metadata.go | 48 ------------------- backend/pkg/web/server.go | 1 - frontend/angular.json | 33 +++++++++++++ .../app/models/queue/source-sync-message.ts | 1 + .../src/app/services/fasten-db.service.ts | 14 +++++- frontend/src/app/workers/queue.service.ts | 2 + .../src/app/workers/source-sync.worker.ts | 2 +- .../environments/environment.cloud_sandbox.ts | 10 ++++ frontend/src/environments/environment.prod.ts | 8 +++- .../src/environments/environment.sandbox.ts | 17 +++---- frontend/src/environments/environment.ts | 23 +++++---- frontend/src/index.html | 14 +++++- .../src/lib/conduit/fhir/base/base_client.ts | 1 + .../fhir/base/fhir401_r4_client.spec.ts | 2 +- frontend/src/lib/database/interface.ts | 2 + .../lib/database/pouchdb_repository.spec.ts | 40 +++++++++++++++- .../src/lib/database/pouchdb_repository.ts | 34 ++++++++++--- 21 files changed, 180 insertions(+), 92 deletions(-) delete mode 100644 backend/pkg/web/handler/metadata.go create mode 100644 frontend/src/environments/environment.cloud_sandbox.ts diff --git a/.github/workflows/cloud-deploy.yaml b/.github/workflows/cloud-deploy.yaml index dd5e1c18..501d4a3b 100644 --- a/.github/workflows/cloud-deploy.yaml +++ b/.github/workflows/cloud-deploy.yaml @@ -26,7 +26,7 @@ jobs: - run: | make frontend-dep cd frontend - yarn run build -- --configuration sandbox --output-path=../dist + yarn run build -- --configuration cloud_sandbox --output-path=../dist - name: Configure AWS credentials using delegated access uses: aws-actions/configure-aws-credentials@v1 diff --git a/backend/pkg/config/config.go b/backend/pkg/config/config.go index 44b30d70..9e6dbf0e 100644 --- a/backend/pkg/config/config.go +++ b/backend/pkg/config/config.go @@ -26,11 +26,11 @@ func (c *configuration) Init() error { c.SetDefault("web.listen.basepath", "") c.SetDefault("web.src.frontend.path", "/opt/fasten/web") - c.SetDefault("web.couchdb.scheme", "http") - c.SetDefault("web.couchdb.host", "localhost") - c.SetDefault("web.couchdb.port", "5984") - c.SetDefault("web.couchdb.admin_username", "admin") - c.SetDefault("web.couchdb.admin_password", "mysecretpassword") + c.SetDefault("couchdb.scheme", "http") + c.SetDefault("couchdb.host", "localhost") + c.SetDefault("couchdb.port", "5984") + c.SetDefault("couchdb.admin.username", "admin") + c.SetDefault("couchdb.admin.password", "mysecretpassword") c.SetDefault("log.level", "INFO") c.SetDefault("log.file", "") diff --git a/backend/pkg/database/couchdb_repository.go b/backend/pkg/database/couchdb_repository.go index 266c6126..3cf83c02 100644 --- a/backend/pkg/database/couchdb_repository.go +++ b/backend/pkg/database/couchdb_repository.go @@ -15,7 +15,7 @@ func NewRepository(appConfig config.Interface, globalLogger logrus.FieldLogger) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Couchdb setup //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - couchdbUrl := fmt.Sprintf("%s://%s:%s", appConfig.GetString("web.couchdb.scheme"), appConfig.GetString("web.couchdb.host"), appConfig.GetString("web.couchdb.port")) + couchdbUrl := fmt.Sprintf("%s://%s:%s", appConfig.GetString("couchdb.scheme"), appConfig.GetString("couchdb.host"), appConfig.GetString("couchdb.port")) globalLogger.Infof("Trying to connect to couchdb: %s\n", couchdbUrl) @@ -26,8 +26,8 @@ func NewRepository(appConfig config.Interface, globalLogger logrus.FieldLogger) err = database.Authenticate(context.Background(), couchdb.BasicAuth( - appConfig.GetString("web.couchdb.admin_username"), - appConfig.GetString("web.couchdb.admin_password")), + appConfig.GetString("couchdb.admin.username"), + appConfig.GetString("couchdb.admin.password")), ) if err != nil { diff --git a/backend/pkg/web/handler/couchdb_proxy.go b/backend/pkg/web/handler/couchdb_proxy.go index 27dac632..d61b13dd 100644 --- a/backend/pkg/web/handler/couchdb_proxy.go +++ b/backend/pkg/web/handler/couchdb_proxy.go @@ -14,7 +14,7 @@ import ( func CouchDBProxy(c *gin.Context) { appConfig := c.MustGet("CONFIG").(config.Interface) - couchdbUrl := fmt.Sprintf("%s://%s:%s", appConfig.GetString("web.couchdb.scheme"), appConfig.GetString("web.couchdb.host"), appConfig.GetString("web.couchdb.port")) + couchdbUrl := fmt.Sprintf("%s://%s:%s", appConfig.GetString("couchdb.scheme"), appConfig.GetString("couchdb.host"), appConfig.GetString("couchdb.port")) remote, err := url.Parse(couchdbUrl) if err != nil { panic(err) diff --git a/backend/pkg/web/handler/metadata.go b/backend/pkg/web/handler/metadata.go deleted file mode 100644 index 939ea489..00000000 --- a/backend/pkg/web/handler/metadata.go +++ /dev/null @@ -1,48 +0,0 @@ -package handler - -import ( - "github.com/fastenhealth/fastenhealth-onprem/backend/pkg" - "github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models" - "github.com/gin-gonic/gin" - "net/http" -) - -func GetMetadataSource(c *gin.Context) { - metadataSource := map[string]models.MetadataSource{ - - string(pkg.SourceTypeAthena): {Display: "Athena (Sandbox)", SourceType: pkg.SourceTypeAthena, Category: []string{"Sandbox"}, Supported: true}, - string(pkg.SourceTypeEpic): {Display: "Epic (Sandbox)", SourceType: pkg.SourceTypeEpic, Category: []string{"Sandbox"}, Supported: true}, - string(pkg.SourceTypeLogica): {Display: "Logica (Sandbox)", SourceType: pkg.SourceTypeLogica, Category: []string{"Sandbox"}, Supported: true}, - string(pkg.SourceTypeHealthIT): {Display: "HealthIT (Sandbox)", SourceType: pkg.SourceTypeHealthIT, Category: []string{"Sandbox"}, Supported: true}, - //fails with CORS error when swapping token. Must be a confidential client. - string(pkg.SourceTypeCareEvolution): {Display: "CareEvolution (Sandbox)", SourceType: pkg.SourceTypeCareEvolution, Category: []string{"Sandbox"}, Supported: false}, - - // enabled - string(pkg.SourceTypeAetna): {Display: "Aetna", SourceType: pkg.SourceTypeAetna, Category: []string{"Insurance"}, Supported: true}, - string(pkg.SourceTypeCigna): {Display: "Cigna", SourceType: pkg.SourceTypeCigna, Category: []string{"Insurance", "Hospital"}, Supported: true}, - string(pkg.SourceTypeBlueButtonMedicare): {Display: "Medicare/VA Health (BlueButton)", SourceType: pkg.SourceTypeBlueButtonMedicare, Category: []string{"Hospital"}, Supported: true}, - - //TODO: infinite pagination for Encounters?? - string(pkg.SourceTypeCerner): {Display: "Cerner (Sandbox)", SourceType: pkg.SourceTypeCerner, Category: []string{"Sandbox"}, Supported: true}, - - // pending - string(pkg.SourceTypeAnthem): {Display: "Anthem", SourceType: pkg.SourceTypeAnthem, Category: []string{"Insurance"}}, - string(pkg.SourceTypeCedarSinai): {Display: "Cedar Sinai", SourceType: pkg.SourceTypeCedarSinai, Category: []string{"Hospital"}}, - string(pkg.SourceTypeCommonSpirit): {Display: "Common Spirit", SourceType: pkg.SourceTypeCommonSpirit, Category: []string{"Hospital"}}, - string(pkg.SourceTypeDeltaDental): {Display: "Delta Dental", SourceType: pkg.SourceTypeDeltaDental, Category: []string{"Insurance"}}, - string(pkg.SourceTypeDignityHealth): {Display: "Dignity Health", SourceType: pkg.SourceTypeDignityHealth, Category: []string{"Hospital"}}, - string(pkg.SourceTypeHCAHealthcare): {Display: "HCA Healthcare", SourceType: pkg.SourceTypeHCAHealthcare, Category: []string{"Insurance"}}, - string(pkg.SourceTypeHumana): {Display: "Humana", SourceType: pkg.SourceTypeHumana, Category: []string{"Insurance"}}, - string(pkg.SourceTypeKaiser): {Display: "Kaiser", SourceType: pkg.SourceTypeKaiser, Category: []string{"Hospital", "Insurance"}}, - string(pkg.SourceTypeMetlife): {Display: "Metlife", SourceType: pkg.SourceTypeMetlife, Category: []string{"Insurance"}}, - string(pkg.SourceTypeProvidence): {Display: "Providence", SourceType: pkg.SourceTypeProvidence, Category: []string{"Hospital"}}, - string(pkg.SourceTypeStanford): {Display: "Stanford Healthcare", SourceType: pkg.SourceTypeStanford, Category: []string{"Hospital"}}, - string(pkg.SourceTypeSutter): {Display: "Sutter", SourceType: pkg.SourceTypeSutter, Category: []string{"Hospital"}}, - string(pkg.SourceTypeTrinity): {Display: "Trinity", SourceType: pkg.SourceTypeTrinity, Category: []string{"Hospital"}}, - string(pkg.SourceTypeUCSF): {Display: "UCSF", SourceType: pkg.SourceTypeUCSF, Category: []string{"Hospital"}}, - string(pkg.SourceTypeUnitedHealthcare): {Display: "United Healthcare", SourceType: pkg.SourceTypeUnitedHealthcare, Category: []string{"Insurance"}}, - string(pkg.SourceTypeVerity): {Display: "Verity", SourceType: pkg.SourceTypeVerity, Category: []string{"Hospital"}}, - } - - c.JSON(http.StatusOK, gin.H{"success": true, "data": metadataSource}) -} diff --git a/backend/pkg/web/server.go b/backend/pkg/web/server.go index 3a20f628..4ffdfaf2 100644 --- a/backend/pkg/web/server.go +++ b/backend/pkg/web/server.go @@ -41,7 +41,6 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine { }) api.POST("/auth/signup", handler.AuthSignup) - api.GET("/metadata/source", handler.GetMetadataSource) r.Any("/database/*proxyPath", handler.CouchDBProxy) r.GET("/cors/*proxyPath", handler.CORSProxy) diff --git a/frontend/angular.json b/frontend/angular.json index 39e10cc5..86aed5a6 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -91,6 +91,33 @@ "maximumError": "10kb" } ] + }, + "cloud_sandbox": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.cloud_sandbox.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" + } + ] } } @@ -103,6 +130,12 @@ "configurations": { "production": { "browserTarget": "fastenhealth:build:production" + }, + "sandbox": { + "browserTarget": "fastenhealth:build:sandbox" + }, + "cloud_sandbox": { + "browserTarget": "fastenhealth:build:cloud_sandbox" } } }, diff --git a/frontend/src/app/models/queue/source-sync-message.ts b/frontend/src/app/models/queue/source-sync-message.ts index fdfa8882..d459855f 100644 --- a/frontend/src/app/models/queue/source-sync-message.ts +++ b/frontend/src/app/models/queue/source-sync-message.ts @@ -3,6 +3,7 @@ import {Source} from '../../../lib/models/database/source'; export class SourceSyncMessage { source: Source current_user: string + couchdb_endpoint_base: string response?: any } diff --git a/frontend/src/app/services/fasten-db.service.ts b/frontend/src/app/services/fasten-db.service.ts index bb2cc738..76f99ceb 100644 --- a/frontend/src/app/services/fasten-db.service.ts +++ b/frontend/src/app/services/fasten-db.service.ts @@ -19,6 +19,7 @@ import * as PouchCrypto from 'crypto-pouch'; PouchDB.plugin(PouchCrypto); import PouchAuth from 'pouchdb-authentication' import {PouchdbCrypto} from '../../lib/database/plugins/crypto'; +import {environment} from '../../environments/environment'; PouchDB.plugin(PouchAuth); @Injectable({ @@ -27,7 +28,7 @@ PouchDB.plugin(PouchAuth); export class FastenDbService extends PouchdbRepository { constructor(private _httpClient: HttpClient) { - super(); + super(environment.couchdb_endpoint_base); } @@ -60,7 +61,16 @@ export class FastenDbService extends PouchdbRepository { */ public async Signup(newUser?: User): Promise { console.log("STARTING SIGNUP") - let resp = await this._httpClient.post(`${this.getBasePath()}/api/auth/signup`, newUser).toPromise() + + let fastenApiEndpointBase = environment.fasten_api_endpoint_base + if (!(fastenApiEndpointBase.indexOf('http://') === 0 || fastenApiEndpointBase.indexOf('https://') === 0)){ + + //relative, we need to retrieve the absolutePath from base + fastenApiEndpointBase = this.GetEndpointAbsolutePath(globalThis.location,fastenApiEndpointBase) + } + + + let resp = await this._httpClient.post(`${fastenApiEndpointBase}/auth/signup`, newUser).toPromise() console.log(resp) return this.Signin(newUser.username, newUser.password); } diff --git a/frontend/src/app/workers/queue.service.ts b/frontend/src/app/workers/queue.service.ts index 500272bc..fffbcd2d 100644 --- a/frontend/src/app/workers/queue.service.ts +++ b/frontend/src/app/workers/queue.service.ts @@ -6,6 +6,7 @@ import {SourceSyncMessage} from '../models/queue/source-sync-message'; import {ToastService} from '../services/toast.service'; import {ToastNotification, ToastType} from '../models/fasten/toast'; import {FastenDbService} from '../services/fasten-db.service'; +import {environment} from '../../environments/environment'; @Injectable({ providedIn: 'root' @@ -19,6 +20,7 @@ export class QueueService { const sourceSync = new SourceSyncMessage() sourceSync.source = source sourceSync.current_user = this.fastenDbService.current_user + sourceSync.couchdb_endpoint_base = environment.couchdb_endpoint_base const input$: Observable = of(JSON.stringify(sourceSync)); return fromWorker(() => new Worker(new URL('./source-sync.worker', import.meta.url), {type: 'module'}), input$) // .subscribe(message => { diff --git a/frontend/src/app/workers/source-sync.worker.ts b/frontend/src/app/workers/source-sync.worker.ts index d8c585f5..48480a0f 100644 --- a/frontend/src/app/workers/source-sync.worker.ts +++ b/frontend/src/app/workers/source-sync.worker.ts @@ -18,7 +18,7 @@ export class SourceSyncWorker implements DoWork { console.log(msg); // outputs 'Hello from main thread' const sourceSyncMessage = JSON.parse(msg) as SourceSyncMessage - const db = NewPouchdbRepositoryWebWorker(sourceSyncMessage.current_user) + const db = NewPouchdbRepositoryWebWorker(sourceSyncMessage.current_user, sourceSyncMessage.couchdb_endpoint_base) const client = NewClient(sourceSyncMessage.source.source_type, new Source(sourceSyncMessage.source)) //TODO: validate the FHIR version from the datasource matches the client // if the source token has been refreshed, we need to store it in the DB. diff --git a/frontend/src/environments/environment.cloud_sandbox.ts b/frontend/src/environments/environment.cloud_sandbox.ts new file mode 100644 index 00000000..7a9b2d9a --- /dev/null +++ b/frontend/src/environments/environment.cloud_sandbox.ts @@ -0,0 +1,10 @@ +export const environment = { + production: true, + lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/sandbox', + + //used to specify the couchdb server that we're going to use (can be relative or absolute). Must not have trailing slash + couchdb_endpoint_base: 'https://couchdb.sandbox.fastenhealth.com', + + //used to specify the api server that we're going to use (can be relative or absolute). Must not have trailing slash + fasten_api_endpoint_base: 'https://api.sandbox.fastenhealth.com/v1', +}; diff --git a/frontend/src/environments/environment.prod.ts b/frontend/src/environments/environment.prod.ts index dedcc086..22eb4442 100644 --- a/frontend/src/environments/environment.prod.ts +++ b/frontend/src/environments/environment.prod.ts @@ -1,4 +1,10 @@ export const environment = { production: true, - lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/v1' + lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/v1', + + //used to specify the couchdb server that we're going to use (can be relative or absolute). Must not have trailing slash + couchdb_endpoint_base: '/database', + + //used to specify the api server that we're going to use (can be relative or absolute). Must not have trailing slash + fasten_api_endpoint_base: '/api', }; diff --git a/frontend/src/environments/environment.sandbox.ts b/frontend/src/environments/environment.sandbox.ts index 33140eab..ecbb5586 100644 --- a/frontend/src/environments/environment.sandbox.ts +++ b/frontend/src/environments/environment.sandbox.ts @@ -4,14 +4,11 @@ export const environment = { production: true, - lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/sandbox' -}; + lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/sandbox', -/* - * For easier debugging in development mode, you can import the following file - * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. - * - * This import should be commented out in production mode because it will have a negative impact - * on performance if an error is thrown. - */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. + //used to specify the couchdb server that we're going to use (can be relative or absolute). Must not have trailing slash + couchdb_endpoint_base: '/database', + + //used to specify the api server that we're going to use (can be relative or absolute). Must not have trailing slash + fasten_api_endpoint_base: '/api', +}; diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index c9f04030..1ec648ef 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -4,14 +4,17 @@ export const environment = { production: false, - lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/sandbox' -}; -/* - * For easier debugging in development mode, you can import the following file - * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. - * - * This import should be commented out in production mode because it will have a negative impact - * on performance if an error is thrown. - */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. + //specify the lighthouse server that we're going to use to authenticate against all our source/providers. Must not have trailing slash + lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/sandbox', + + //used to specify the couchdb server that we're going to use (can be relative or absolute). Must not have trailing slash + couchdb_endpoint_base: 'https://couchdb.sandbox.fastenhealth.com/database', + // if relative, must start with / + // couchdb_endpoint_base: '/database' + + //used to specify the api server that we're going to use (can be relative or absolute). Must not have trailing slash + fasten_api_endpoint_base: 'https://api.sandbox.fastenhealth.com/v1', + // if relative, must start with / + // fasten_api_endpoint_base: '/api' +}; diff --git a/frontend/src/index.html b/frontend/src/index.html index b541cb93..4a33a891 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -23,7 +23,19 @@ rel="stylesheet" /> diff --git a/frontend/src/lib/conduit/fhir/base/base_client.ts b/frontend/src/lib/conduit/fhir/base/base_client.ts index e0c4d099..221c7be7 100644 --- a/frontend/src/lib/conduit/fhir/base/base_client.ts +++ b/frontend/src/lib/conduit/fhir/base/base_client.ts @@ -146,6 +146,7 @@ export abstract class BaseClient { } private getCORSProxyPath(): string { + //TODO: this path should be passed in as a variable const basePath = globalThis.location.pathname.split('/web').slice(0, 1)[0]; return `${globalThis.location.origin}${basePath || '/'}cors/` diff --git a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts index 390a09e7..e17ad4b1 100644 --- a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts +++ b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts @@ -70,7 +70,7 @@ describe('FHIR401Client', () => { let current_user = uuidv4() let cryptoConfig = await PouchdbCrypto.CryptConfig(current_user, current_user) await PouchdbCrypto.StoreCryptConfig(cryptoConfig) - repository = NewPouchdbRepositoryWebWorker(current_user, new PouchDB("FHIR401Client-"+ current_user)); + repository = NewPouchdbRepositoryWebWorker(current_user, '/database', new PouchDB("FHIR401Client-"+ current_user)); }); afterEach(async () => { diff --git a/frontend/src/lib/database/interface.ts b/frontend/src/lib/database/interface.ts index 6c40c22f..8f21716d 100644 --- a/frontend/src/lib/database/interface.ts +++ b/frontend/src/lib/database/interface.ts @@ -46,4 +46,6 @@ export interface IDatabaseRepository { GetResource(resource_id: string): Promise GetResources(): Promise GetResourcesForSource(source_id: string, source_resource_type?: string): Promise + + GetEndpointAbsolutePath(currentUrl: {pathname: string, protocol: string, host: string}, relativePath: string): string } diff --git a/frontend/src/lib/database/pouchdb_repository.spec.ts b/frontend/src/lib/database/pouchdb_repository.spec.ts index d7d45139..5a262b7c 100644 --- a/frontend/src/lib/database/pouchdb_repository.spec.ts +++ b/frontend/src/lib/database/pouchdb_repository.spec.ts @@ -14,7 +14,7 @@ describe('PouchdbRepository', () => { let current_user = uuidv4() let cryptoConfig = await PouchdbCrypto.CryptConfig(current_user, current_user) await PouchdbCrypto.StoreCryptConfig(cryptoConfig) - repository = NewPouchdbRepositoryWebWorker(current_user, new PouchDB("PouchdbRepository" + current_user)); + repository = NewPouchdbRepositoryWebWorker(current_user, '/database', new PouchDB("PouchdbRepository" + current_user)); }); afterEach(async () => { @@ -98,4 +98,42 @@ describe('PouchdbRepository', () => { expect((sourcesWrapped.rows[0] as Source).patient).toEqual('patient1'); }); }) + + + describe('GetEndpointAbsolutePath', () => { + + describe('with no subpath and no /web/', () => { + it('should return absolute path', async () => { + let currentUrl = new URL("http://www.example.com/") + const absoluteUrl = repository.GetEndpointAbsolutePath(currentUrl, '/database') + expect(absoluteUrl).toEqual('http://www.example.com/database'); + }); + }) + + describe('with subpath and no /web/', () => { + it('should return absolute path', async () => { + let currentUrl = new URL("http://www.example.com/hello/world") + const absoluteUrl = repository.GetEndpointAbsolutePath(currentUrl, '/database') + expect(absoluteUrl).toEqual('http://www.example.com/database'); + }); + }) + + describe('with no subpath and /web/', () => { + it('should return absolute path', async () => { + let currentUrl = new URL("http://www.example.com/web/world") + const absoluteUrl = repository.GetEndpointAbsolutePath(currentUrl, '/database') + expect(absoluteUrl).toEqual('http://www.example.com/database'); + }); + }) + + describe('with subpath and /web/', () => { + it('should return absolute path', async () => { + let currentUrl = new URL("http://www.example.com/fasten/web/hello/world") + const absoluteUrl = repository.GetEndpointAbsolutePath(currentUrl, '/database') + expect(absoluteUrl).toEqual('http://www.example.com/fasten/database'); + }); + }) + + }) + }); diff --git a/frontend/src/lib/database/pouchdb_repository.ts b/frontend/src/lib/database/pouchdb_repository.ts index 8a0705b5..f47e825b 100644 --- a/frontend/src/lib/database/pouchdb_repository.ts +++ b/frontend/src/lib/database/pouchdb_repository.ts @@ -49,8 +49,8 @@ import {PouchdbCryptConfig, PouchdbCrypto, PouchdbCryptoOptions} from './plugins * @constructor */ -export function NewPouchdbRepositoryWebWorker(current_user: string, localPouchDb?: PouchDB.Database): PouchdbRepository { - let pouchdbRepository = new PouchdbRepository(localPouchDb) +export function NewPouchdbRepositoryWebWorker(current_user: string, couchDbEndpointBase: string, localPouchDb?: PouchDB.Database): PouchdbRepository { + let pouchdbRepository = new PouchdbRepository(couchDbEndpointBase, localPouchDb) pouchdbRepository.current_user = current_user return pouchdbRepository } @@ -72,8 +72,16 @@ export class PouchdbRepository implements IDatabaseRepository { * @param userIdentifier * @param encryptionKey */ - constructor(localPouchDb?: PouchDB.Database) { - this.remotePouchEndpoint = `${globalThis.location.protocol}//${globalThis.location.host}${this.getBasePath()}/database` + constructor(couchDbEndpointBase: string, localPouchDb?: PouchDB.Database) { + // couchDbEndpointBase could be a relative or absolute path. + //if its absolute, we should pass it in, as-is + if (couchDbEndpointBase.indexOf('http://') === 0 || couchDbEndpointBase.indexOf('https://') === 0){ + //absolute + this.remotePouchEndpoint = couchDbEndpointBase + } else { + //relative, we need to retrive the absolutePath from base + this.remotePouchEndpoint = this.GetEndpointAbsolutePath(globalThis.location, couchDbEndpointBase) + } //setup PouchDB Plugins //https://pouchdb.com/guides/mango-queries.html @@ -442,7 +450,21 @@ export class PouchdbRepository implements IDatabaseRepository { /////////////////////////////////////////////////////////////////////////////////////// // Helper methods /////////////////////////////////////////////////////////////////////////////////////// - protected getBasePath(): string { - return globalThis.location.pathname.split('/web').slice(0, 1)[0]; + + //Fasten may be served behind a reverse proxy with a subpath, so lets try to find that component if it exists. + // if no subpath is found, just use the current url information to generate a path + public GetEndpointAbsolutePath(currentUrl: {pathname: string, protocol: string, host: string}, relativePath: string): string { + //no `/web` path to strip out, lets just use the relative path + let absolutePath = relativePath + + if(currentUrl.pathname.includes('/web')){ + // probably running locally, and *may* include a subpath + let subPath = currentUrl.pathname.split('/web').slice(0, 1)[0] + if(subPath != "/"){ + //subpath, so we need to update the absolutePath with the subpath before adding the relative path to the end + absolutePath = subPath + relativePath + } + } + return `${currentUrl.protocol}//${currentUrl.host}${absolutePath}` } }