Skip to content

Commit

Permalink
use DatabaseResolver instead of direct Database DI
Browse files Browse the repository at this point in the history
  • Loading branch information
sleidig committed Jan 7, 2025
1 parent 5d23f12 commit 25868e2
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 123 deletions.
2 changes: 0 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import { ConfigurableEnumModule } from "./core/basic-datatypes/configurable-enum
import { FaIconLibrary } from "@fortawesome/angular-fontawesome";
import { fas } from "@fortawesome/free-solid-svg-icons";
import { far } from "@fortawesome/free-regular-svg-icons";
import { DatabaseModule } from "./core/database/database.module";
import { Angulartics2Matomo, Angulartics2Module } from "angulartics2";
import {
DEFAULT_LANGUAGE,
Expand Down Expand Up @@ -111,7 +110,6 @@ import { SkillModule } from "./features/skill/skill.module";
// Core modules
CoreModule,
ConfigurableEnumModule,
DatabaseModule,
LanguageModule,
LatestChangesModule,
PermissionsModule,
Expand Down
7 changes: 4 additions & 3 deletions src/app/core/admin/admin-overview/admin-overview.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { MatSnackBar } from "@angular/material/snack-bar";
import { ConfigService } from "../../config/config.service";
import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
import { readFile } from "../../../utils/utils";
import { Database } from "../../database/database";
import { ExtendedAlertConfig } from "../../alerts/alert-config";
import { MatButtonModule } from "@angular/material/button";
import { RouterLink } from "@angular/router";
Expand All @@ -15,6 +14,7 @@ import { DownloadService } from "../../export/download-service/download.service"
import { MatListModule } from "@angular/material/list";
import { RouteTarget } from "../../../route-target";
import { AdminOverviewService } from "./admin-overview.service";
import { DatabaseResolverService } from "../../database/database-resolver.service";

/**
* Admin GUI giving administrative users different options/actions.
Expand All @@ -36,7 +36,7 @@ export class AdminOverviewComponent implements OnInit {
private alertService: AlertService,
private backupService: BackupService,
private downloadService: DownloadService,
private db: Database,
private dbResolver: DatabaseResolverService,
private confirmationDialog: ConfirmationDialogService,
private snackBar: MatSnackBar,
private configService: ConfigService,
Expand All @@ -51,7 +51,8 @@ export class AdminOverviewComponent implements OnInit {
* Send a reference of the PouchDB to the browser's developer console for real-time debugging.
*/
debugDatabase() {
console.log(this.db);
console.log(this.dbResolver);
console.log(this.dbResolver.getDatabase());
}

/**
Expand Down
8 changes: 7 additions & 1 deletion src/app/core/admin/backup/backup.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from "@angular/core";
import { Database } from "../../database/database";
import { Config } from "../../config/config";
import { DatabaseResolverService } from "../../database/database-resolver.service";

/**
* Create and load backups of the database.
Expand All @@ -9,7 +10,12 @@ import { Config } from "../../config/config";
providedIn: "root",
})
export class BackupService {
constructor(private db: Database) {}
private db: Database;

constructor(private dbResolver: DatabaseResolverService) {
this.db = this.dbResolver.getDatabase();
// TODO: backup other databases?
}

/**
* Creates an array holding all elements of the database.
Expand Down
30 changes: 30 additions & 0 deletions src/app/core/database/database-factory.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { inject, Injectable } from "@angular/core";
import { Database } from "./database";
import { PouchDatabase } from "./pouch-database";
import { KeycloakAuthService } from "../session/auth/keycloak/keycloak-auth.service";
import { environment } from "../../../environments/environment";
import { SessionType } from "../session/session-type";

/**
* Provides a method to generate Database instances.
*
* (can be re-implemented to cater to different Database implementations).
*/
@Injectable({
providedIn: "root",
})
export class DatabaseFactoryService {
private authService = inject(KeycloakAuthService);

createDatabase(dbName: string): Database {
const db = new PouchDatabase(this.authService);

if (environment.session_type === SessionType.mock) {
db.initInMemoryDB(dbName);
} else {
db.initIndexedDB(dbName);
}

return db;
}
}
16 changes: 16 additions & 0 deletions src/app/core/database/database-resolver.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { DatabaseResolverService } from './database-resolver.service';

describe('DatabaseResolverService', () => {
let service: DatabaseResolverService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DatabaseResolverService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
38 changes: 38 additions & 0 deletions src/app/core/database/database-resolver.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { inject, Injectable } from "@angular/core";
import { Database } from "./database";
import { SessionInfo } from "../session/auth/session-info";
import { environment } from "../../../environments/environment";
import { DatabaseFactoryService } from "./database-factory.service";

/**
* Manages access to individual databases,
* as data may be stored across multiple different instances.
*/
@Injectable({
providedIn: "root",
})
export class DatabaseResolverService {
static readonly DEFAULT_DB = "app";

private databases: Map<string, Database> = new Map();
private databaseFactory = inject(DatabaseFactoryService);

getDatabase(dbName: string = DatabaseResolverService.DEFAULT_DB): Database {
return this.databases.get(dbName);
}

resetDatabases() {}

async initDatabasesForSession(session: SessionInfo) {
this.initializeAppDatabaseForCurrentUser(session);
// TODO: init other DBs
}

private initializeAppDatabaseForCurrentUser(user: SessionInfo) {
const userDBName = `${user.name}-${environment.DB_NAME}`;
const userDb = this.databaseFactory.createDatabase(userDBName);
this.databases.set(environment.DB_NAME, userDb);

// TODO: have removed fallback to old "app" IndexedDB database here; check sentry if this may cause larger impact
}
}
15 changes: 0 additions & 15 deletions src/app/core/database/database.module.ts

This file was deleted.

4 changes: 4 additions & 0 deletions src/app/core/database/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { Observable } from "rxjs";
/**
* An implementation of this abstract class provides functions for direct database access.
* This interface is an extension of the [PouchDB API](https://pouchdb.com/api.html).
*
* PLEASE NOTE:
* Direct access to the Database layer is rarely necessary, and you should probably use EntityMapperService instead.
* Database is not an Angular Service and has to be accessed through the DatabaseResolverService.
*/
export abstract class Database {
/**
Expand Down
8 changes: 5 additions & 3 deletions src/app/core/database/pouch-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { Logging } from "../logging/logging.service";
import PouchDB from "pouchdb-browser";
import memory from "pouchdb-adapter-memory";
import { PerformanceAnalysisLogging } from "../../utils/performance-analysis-logging";
import { Injectable, Optional } from "@angular/core";
import { firstValueFrom, Observable, Subject } from "rxjs";
import { filter } from "rxjs/operators";
import { HttpStatusCode } from "@angular/common/http";
Expand All @@ -34,7 +33,6 @@ import { KeycloakAuthService } from "../session/auth/keycloak/keycloak-auth.serv
* Additional convenience functions on top of the PouchDB API
* should be implemented in the abstract {@link Database}.
*/
@Injectable()
export class PouchDatabase extends Database {
/**
* Small helper function which creates a database with in-memory PouchDB initialized
Expand Down Expand Up @@ -66,9 +64,13 @@ export class PouchDatabase extends Database {

/**
* Create a PouchDB database manager.
* @param authService (Optional) the authentication service that may require additional auth headers for remote DB access
*/
constructor(@Optional() private authService?: KeycloakAuthService) {
constructor(private authService?: KeycloakAuthService) {
super();

// TODO: refactor to run the init method directly from constructor and remove databaseInitialized
// now that DatabaseResolver handles creation and provider we don't have to separate creation and initialization anymore
}

/**
Expand Down
18 changes: 6 additions & 12 deletions src/app/core/demo-data/demo-data-initializer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import { DemoDataGeneratingProgressDialogComponent } from "./demo-data-generatin
import { SessionManagerService } from "../session/session-service/session-manager.service";
import { LocalAuthService } from "../session/auth/local/local-auth.service";
import { SessionInfo, SessionSubject } from "../session/auth/session-info";
import { Logging } from "../logging/logging.service";
import { Database } from "../database/database";
import { PouchDatabase } from "../database/pouch-database";
import { environment } from "../../../environments/environment";
import { LoginState } from "../session/session-states/login-state.enum";
import { LoginStateSubject, SessionType } from "../session/session-type";
import memory from "pouchdb-adapter-memory";
import PouchDB from "pouchdb-browser";
import { DatabaseResolverService } from "../database/database-resolver.service";

/**
* This service handles everything related to the demo-mode
Expand All @@ -25,7 +24,6 @@ import PouchDB from "pouchdb-browser";
@Injectable()
export class DemoDataInitializerService {
private liveSyncHandle: PouchDB.Replication.Sync<any>;
private pouchDatabase: PouchDatabase;
private readonly normalUser: SessionInfo = {
name: DemoUserGeneratorService.DEFAULT_USERNAME,
id: DemoUserGeneratorService.DEFAULT_USERNAME,
Expand All @@ -38,12 +36,13 @@ export class DemoDataInitializerService {
roles: ["user_app", "admin_app"],
entityId: `User:${DemoUserGeneratorService.ADMIN_USERNAME}`,
};

constructor(
private demoDataService: DemoDataService,
private localAuthService: LocalAuthService,
private sessionManager: SessionManagerService,
private dialog: MatDialog,
private database: Database,
private dbResolver: DatabaseResolverService,
private loginState: LoginStateSubject,
private sessionInfo: SessionSubject,
) {}
Expand All @@ -52,13 +51,6 @@ export class DemoDataInitializerService {
const dialogRef = this.dialog.open(
DemoDataGeneratingProgressDialogComponent,
);
if (this.database instanceof PouchDatabase) {
this.pouchDatabase = this.database;
} else {
Logging.warn(
"Cannot create demo data with session: " + environment.session_type,
);
}

this.localAuthService.saveUser(this.normalUser);
this.localAuthService.saveUser(this.adminUser);
Expand Down Expand Up @@ -95,7 +87,9 @@ export class DemoDataInitializerService {
} else {
demoUserDB = new PouchDB(dbName);
}
const currentUserDB = this.pouchDatabase.getPouchDB();
const currentUserDB = (
this.dbResolver.getDatabase() as PouchDatabase
).getPouchDB();
await currentUserDB.sync(demoUserDB, { batch_size: 500 });
this.liveSyncHandle = currentUserDB.sync(demoUserDB, {
live: true,
Expand Down
6 changes: 3 additions & 3 deletions src/app/core/demo-data/demo-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
} from "@angular/core";
import { DemoDataGenerator } from "./demo-data-generator";
import { EntityMapperService } from "../entity/entity-mapper/entity-mapper.service";
import { Database } from "../database/database";
import { DatabaseResolverService } from "../database/database-resolver.service";

/**
* General config object to pass all initially register DemoDataGenerators
Expand Down Expand Up @@ -58,7 +58,7 @@ export class DemoDataService {
private entityMapper: EntityMapperService,
private injector: Injector,
private config: DemoDataServiceConfig,
private database: Database,
private dbResolver: DatabaseResolverService,
) {}

private registerAllProvidedDemoDataGenerators() {
Expand All @@ -75,7 +75,7 @@ export class DemoDataService {
* and add all the generated entities to the Database.
*/
async publishDemoData() {
if (!(await this.database.isEmpty())) {
if (!(await this.dbResolver.getDatabase().isEmpty())) {
return;
}
this.registerAllProvidedDemoDataGenerators();
Expand Down
17 changes: 12 additions & 5 deletions src/app/core/entity/database-indexing/database-indexing.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
*/

import { Injectable } from "@angular/core";
import { Database, QueryOptions } from "../../database/database";
import { QueryOptions } from "../../database/database";
import { BehaviorSubject, firstValueFrom, Observable } from "rxjs";
import { BackgroundProcessState } from "../../ui/sync-status/background-process-state.interface";
import { Entity, EntityConstructor } from "../model/entity";
import { EntitySchemaService } from "../schema/entity-schema.service";
import { first } from "rxjs/operators";
import { DatabaseResolverService } from "../../database/database-resolver.service";

/**
* Manage database query index creation and use, working as a facade in front of the Database service.
Expand All @@ -41,7 +42,7 @@ export class DatabaseIndexingService {
}

constructor(
private db: Database,
private dbResolver: DatabaseResolverService,
private entitySchemaService: EntitySchemaService,
) {}

Expand All @@ -52,15 +53,20 @@ export class DatabaseIndexingService {
*
* @param designDoc The design document (see @link{Database}) describing the query/index.
*/
async createIndex(designDoc: any): Promise<void> {
async createIndex(
designDoc: any,
db: string = DatabaseResolverService.DEFAULT_DB,
): Promise<void> {
const indexDetails = designDoc._id.replace(/_design\//, "");
const indexState: BackgroundProcessState = {
title: $localize`Preparing data (Indexing)`,
details: indexDetails,
pending: true,
};

const indexCreationPromise = this.db.saveDatabaseIndex(designDoc);
const indexCreationPromise = this.dbResolver
.getDatabase(db)
.saveDatabaseIndex(designDoc);
this._indicesRegistered.next([
...this._indicesRegistered.value.filter(
(state) => state.details !== indexDetails,
Expand Down Expand Up @@ -213,12 +219,13 @@ export class DatabaseIndexingService {
indexName: string,
options: QueryOptions,
doNotWaitForIndexCreation?: boolean,
db: string = DatabaseResolverService.DEFAULT_DB,
): Promise<any> {
if (!doNotWaitForIndexCreation) {
await this.waitForIndexAvailable(indexName);
}

return this.db.query(indexName, options);
return this.dbResolver.getDatabase(db).query(indexName, options);
}

/**
Expand Down
Loading

0 comments on commit 25868e2

Please sign in to comment.