Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notifications/separate db #2852

Open
wants to merge 3 commits into
base: feat/1055_notifications-final
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions src/app/core/database/database-resolver.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Injectable } from "@angular/core";
import { Database, DatabaseDocChange } from "./database";
import { SessionInfo } from "../session/auth/session-info";
import { environment } from "../../../environments/environment";
import { DatabaseFactoryService } from "./database-factory.service";
import { Entity } from "../entity/model/entity";
import { Observable, Subject } from "rxjs";
import { NotificationEvent } from "app/features/notification/model/notification-event";
import { SyncedPouchDatabase } from "./pouchdb/synced-pouch-database";

/**
* Manages access to individual databases,
Expand All @@ -28,16 +29,6 @@ export class DatabaseResolverService {

constructor(private databaseFactory: DatabaseFactoryService) {
this._changesFeed = new Subject();
this.initDatabaseStubs();
}

/**
* Generate Database objects so that change subscriptions and other operations
* can already be performed during bootstrap.
* @private
*/
private initDatabaseStubs() {
this.registerDatabase(Entity.DATABASE);
}

private registerDatabase(dbName: string) {
Expand All @@ -47,6 +38,10 @@ export class DatabaseResolverService {
}

getDatabase(dbName: string = Entity.DATABASE): Database {
if (!this.databases.has(dbName)) {
this.registerDatabase(dbName);
}

let db = this.databases.get(dbName);
return db;
}
Expand All @@ -65,14 +60,25 @@ export class DatabaseResolverService {

async initDatabasesForSession(session: SessionInfo) {
this.initializeAppDatabaseForCurrentUser(session);
// ... in future initialize additional DBs here
this.initializeNotificationsDatabaseForCurrentUser(session);
}

private initializeAppDatabaseForCurrentUser(user: SessionInfo) {
const userDBName = `${user.name}-${environment.DB_NAME}`;
const userDBName = `${user.name}-${Entity.DATABASE}`;
this.getDatabase(Entity.DATABASE).init(userDBName);
}

private initializeNotificationsDatabaseForCurrentUser(user: SessionInfo) {
const db = this.getDatabase(NotificationEvent.DATABASE);
const serverDbName = `${NotificationEvent.DATABASE}_${user.id}`;
const browserDbName = serverDbName;
if (db instanceof SyncedPouchDatabase) {
db.init(browserDbName, serverDbName);
} else {
db.init(browserDbName);
}
}

initDatabasesForAnonymous() {
if (!this.getDatabase(Entity.DATABASE).isInitialized()) {
this.getDatabase(Entity.DATABASE).init(null);
Expand Down
5 changes: 4 additions & 1 deletion src/app/core/database/pouchdb/pouch-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export class PouchDatabase extends Database {
* @param dbName the name for the database under which the IndexedDB entries will be created
* @param options PouchDB options which are directly passed to the constructor
*/
init(dbName?: string, options?: PouchDB.Configuration.DatabaseConfiguration) {
init(
dbName?: string,
options?: PouchDB.Configuration.DatabaseConfiguration | any,
) {
this.pouchDB = new PouchDB(dbName ?? this.dbName, options);
this.databaseInitialized.complete();
}
Expand Down
27 changes: 18 additions & 9 deletions src/app/core/database/pouchdb/synced-pouch-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ export class SyncedPouchDatabase extends PouchDatabase {
SYNC_INTERVAL = 30000;

private remoteDatabase: RemotePouchDatabase;
private syncState: SyncStateSubject = new SyncStateSubject();

/** trigger to unsubscribe any internal subscriptions */
private destroy$ = new Subject<void>();

constructor(
dbName: string,
authService: KeycloakAuthService,
private syncStateSubject: SyncStateSubject,
private globalSyncState: SyncStateSubject,
private navigator: Navigator,
private loginStateSubject: LoginStateSubject,
) {
Expand All @@ -52,7 +53,7 @@ export class SyncedPouchDatabase extends PouchDatabase {
this.remoteDatabase = new RemotePouchDatabase(dbName, authService);

this.logSyncContext();
this.syncStateSubject
this.syncState
.pipe(
takeUntil(this.destroy$),
filter((state) => state === SyncState.COMPLETED),
Expand All @@ -63,6 +64,11 @@ export class SyncedPouchDatabase extends PouchDatabase {
this.logSyncContext();
});

// forward sync state to global sync state (combining state from all synced databases)
this.syncState
.pipe(takeUntil(this.destroy$))
.subscribe((state: SyncState) => this.globalSyncState.next(state));

this.loginStateSubject
.pipe(
takeUntil(this.destroy$),
Expand All @@ -75,8 +81,9 @@ export class SyncedPouchDatabase extends PouchDatabase {
* Initializes the PouchDB with local indexeddb as well as a remote server connection for syncing.
* @param dbName local database name (for the current user);
* if explicitly passed as `null`, a remote-only, anonymous session is initialized
* @param remoteDbName (optional) remote database name (if different from local browser database name)
*/
override init(dbName?: string | null) {
override init(dbName?: string | null, remoteDbName?: string) {
if (dbName === null) {
this.remoteDatabase.init();
// use the remote database as internal database driver
Expand All @@ -86,7 +93,7 @@ export class SyncedPouchDatabase extends PouchDatabase {
super.init(dbName ?? this.dbName);

// keep remote database on default name (e.g. "app" instead of "user_uuid-app")
this.remoteDatabase.init();
this.remoteDatabase.init(remoteDbName);
}
}

Expand All @@ -103,24 +110,26 @@ export class SyncedPouchDatabase extends PouchDatabase {
sync(): Promise<SyncResult> {
if (!this.navigator.onLine) {
Logging.debug("Not syncing because offline");
this.syncStateSubject.next(SyncState.UNSYNCED);
this.syncState.next(SyncState.UNSYNCED);
return Promise.resolve({});
}

this.syncStateSubject.next(SyncState.STARTED);
this.syncState.next(SyncState.STARTED);

return this.getPouchDB()
.sync(this.remoteDatabase.getPouchDB(), {
batch_size: this.POUCHDB_SYNC_BATCH_SIZE,
})
.then((res) => {
if (res) res["dbName"] = this.dbName;
Logging.debug("sync completed", res);
this.syncStateSubject.next(SyncState.COMPLETED);

this.syncState.next(SyncState.COMPLETED);
return res as SyncResult;
})
.catch((err) => {
Logging.debug("sync error", err);
this.syncStateSubject.next(SyncState.FAILED);
this.syncState.next(SyncState.FAILED);
throw err;
});
}
Expand All @@ -144,7 +153,7 @@ export class SyncedPouchDatabase extends PouchDatabase {
.pipe(
debounceTime(500),
mergeMap(() => {
if (this.syncStateSubject.value == SyncState.STARTED) {
if (this.syncState.value == SyncState.STARTED) {
return of();
} else {
return from(this.sync());
Expand Down
5 changes: 3 additions & 2 deletions src/app/core/demo-data/demo-data-initializer.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PouchDatabase } from "../database/pouchdb/pouch-database";
import { LoginState } from "../session/session-states/login-state.enum";
import { DatabaseResolverService } from "../database/database-resolver.service";
import { MemoryPouchDatabase } from "../database/pouchdb/memory-pouch-database";
import { Entity } from "../entity/model/entity";

describe("DemoDataInitializerService", () => {
const normalUser: SessionInfo = {
Expand All @@ -40,8 +41,8 @@ describe("DemoDataInitializerService", () => {

beforeEach(() => {
environment.session_type = SessionType.mock;
demoUserDBName = `${DemoUserGeneratorService.DEFAULT_USERNAME}-${environment.DB_NAME}`;
adminDBName = `${DemoUserGeneratorService.ADMIN_USERNAME}-${environment.DB_NAME}`;
demoUserDBName = `${DemoUserGeneratorService.DEFAULT_USERNAME}-${Entity.DATABASE}`;
adminDBName = `${DemoUserGeneratorService.ADMIN_USERNAME}-${Entity.DATABASE}`;
mockDemoDataService = jasmine.createSpyObj(["publishDemoData"]);
mockDemoDataService.publishDemoData.and.resolveTo();
mockDialog = jasmine.createSpyObj(["open"]);
Expand Down
3 changes: 2 additions & 1 deletion src/app/core/demo-data/demo-data-initializer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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";
import { Entity } from "../entity/model/entity";

/**
* This service handles everything related to the demo-mode
Expand Down Expand Up @@ -79,7 +80,7 @@ export class DemoDataInitializerService {
}

private async syncWithDemoUserDB() {
const dbName = `${DemoUserGeneratorService.DEFAULT_USERNAME}-${environment.DB_NAME}`;
const dbName = `${DemoUserGeneratorService.DEFAULT_USERNAME}-${Entity.DATABASE}`;
let demoUserDB: PouchDB.Database;
if (environment.session_type === SessionType.mock) {
PouchDB.plugin(memory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe("SessionManagerService", () => {
let mockKeycloak: jasmine.SpyObj<KeycloakAuthService>;
let mockNavigator: { onLine: boolean };
let dbUser: SessionInfo;
const userDBName = `${TEST_USER}-${environment.DB_NAME}`;
const userDBName = `${TEST_USER}-app`;
let mockDatabaseResolver: jasmine.SpyObj<DatabaseResolverService>;

beforeEach(waitForAsync(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ describe("UserSecurityComponent", () => {
tick();

expect(mockHttp.post).toHaveBeenCalledWith(
`${environment.DB_PROXY_PREFIX}/${environment.DB_NAME}/clear_local`,
`${environment.DB_PROXY_PREFIX}/${Entity.DATABASE}/clear_local`,
undefined,
);
flush();
Expand Down
4 changes: 3 additions & 1 deletion src/app/core/user/user-security/user-security.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,11 @@ export class UserSecurityComponent implements OnInit {
}

private triggerSyncReset() {
// TODO: does this need to be triggered for other CouchDBs as well?

this.http
.post(
`${environment.DB_PROXY_PREFIX}/${environment.DB_NAME}/clear_local`,
`${environment.DB_PROXY_PREFIX}/${Entity.DATABASE}/clear_local`,
undefined,
)
.subscribe({
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/file/couchdb-file.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("CouchdbFileService", () => {
let mockSnackbar: jasmine.SpyObj<MatSnackBar>;
let dismiss: jasmine.Spy;
let updates: Subject<UpdatedEntity<Entity>>;
const attachmentUrlPrefix = `${environment.DB_PROXY_PREFIX}/${environment.DB_NAME}-attachments`;
const attachmentUrlPrefix = `${environment.DB_PROXY_PREFIX}/${Entity.DATABASE}-attachments`;
let mockNavigator;

beforeEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/file/couchdb-file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { SyncedPouchDatabase } from "app/core/database/pouchdb/synced-pouch-data
*/
@Injectable()
export class CouchdbFileService extends FileService {
private attachmentsUrl = `${environment.DB_PROXY_PREFIX}/${environment.DB_NAME}-attachments`;
private attachmentsUrl = `${environment.DB_PROXY_PREFIX}/${Entity.DATABASE}-attachments`;
// TODO it seems like failed requests are executed again when a new one is done
private requestQueue = new ObservableQueue();
private cache: { [key: string]: Observable<string> } = {};
Expand Down
3 changes: 3 additions & 0 deletions src/app/features/notification/model/notification-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { EntityNotificationContext } from "./entity-notification-context";
*/
@DatabaseEntity("NotificationEvent")
export class NotificationEvent extends Entity {
// notification events are stored in a separate, user-specific database
static override DATABASE = "notifications";

/*
* The title of the notification.
*/
Expand Down
1 change: 0 additions & 1 deletion src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export const environment = {
account_url: "https://keycloak.aam-digital.net",
email: undefined,
DB_PROXY_PREFIX: "/db",
DB_NAME: "app",

enableNotificationModule: true,
};
1 change: 0 additions & 1 deletion src/environments/environment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const environment = {
account_url: "https://accounts.aam-digital.net",
email: undefined,
DB_PROXY_PREFIX: "/db",
DB_NAME: "app",

enableNotificationModule: false,
};
3 changes: 0 additions & 3 deletions src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,5 @@ export const environment = {
/** Path for the reverse proxy that forwards to the database - configured in `proxy.conf.json` and `default.conf` */
DB_PROXY_PREFIX: "/db",

/** Name of the database that is used */
DB_NAME: "app",

enableNotificationModule: true,
};
Loading