Commit c21247a6 authored by Christopher Fullarton's avatar Christopher Fullarton

Ensures that the local db is loaded before initializing the app

parent 51d34251
Pipeline #34038 passed with stages
in 15 minutes and 11 seconds
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, switchMapTo, takeUntil } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, switchMap, takeUntil } from 'rxjs/operators';
import { QuizEntity } from '../../lib/entities/QuizEntity';
import { UserRole } from '../../lib/enums/UserRole';
import { QuizApiService } from '../../service/api/quiz/quiz-api.service';
......@@ -28,18 +28,18 @@ export class QuizDuplicateComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
this.route.paramMap.pipe(map(params => params.get('name')), filter(() => this.userService.isAuthorizedFor(UserRole.CreateQuiz)),
distinctUntilChanged(), switchMapTo(this.quizApiService.initQuizInstance(name)), takeUntil(this._destroy))
distinctUntilChanged(), switchMap(name => this.quizApiService.initQuizInstance(name)), takeUntil(this._destroy))
.subscribe(async data => {
await this.storageService.db.Quiz.put(data.payload.quiz);
this.quizService.quiz = new QuizEntity(data.payload.quiz);
this.quizService.isOwner = true;
console.log('router navigate duplicate');
this.router.navigate(['/quiz', 'flow']);
}, () => {
this.router.navigate(['/']);
});
}
public ngOnDestroy(): void {
console.log('ondestroy duplicate');
this._destroy.next();
this._destroy.complete();
}
......
......@@ -151,7 +151,7 @@ export class VotingComponent implements OnInit, OnDestroy, IHasTriggeredNavigati
if (data.status !== StatusProtocol.Success) {
console.log('VotingComponent: PutResponse failed', data);
}
});
}, () => {});
}
public initData(): void {
......
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, switchMapTo, takeUntil } from 'rxjs/operators';
import { DbState } from '../../lib/enums/enums';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { MessageProtocol, StatusProtocol } from '../../lib/enums/Message';
import { IMessage } from '../../lib/interfaces/communication/IMessage';
import { QuizApiService } from '../../service/api/quiz/quiz-api.service';
......@@ -28,8 +27,7 @@ export class QuizJoinComponent implements OnInit, OnDestroy {
private router: Router,
private casService: CasLoginService,
private themesService: ThemesService,
private quizApiService: QuizApiService,
private sharedService: SharedService, private storageService: StorageService,
private quizApiService: QuizApiService, private sharedService: SharedService, private storageService: StorageService,
) {
}
......@@ -46,18 +44,22 @@ export class QuizJoinComponent implements OnInit, OnDestroy {
this.sharedService.isLoadingEmitter.next(true);
const quizData$ = this.quizApiService.getFullQuizStatusData(quizname);
this.storageService.stateNotifier.pipe(filter(val => val === DbState.Initialized), distinctUntilChanged(), switchMapTo(quizData$),
takeUntil(this._destroy)).subscribe(quizStatusData => this.resolveQuizStatusData(quizStatusData));
this.quizApiService.getFullQuizStatusData(quizname).subscribe(data => {
this.resolveQuizStatusData(data);
}, () => {
this.router.navigate(['/']);
});
});
}
public ngOnDestroy(): void {
console.log('ondestroy');
this._destroy.next();
this._destroy.complete();
}
private resolveQuizStatusData(quizStatusData: IMessage): void {
console.log('resolevstatus', quizStatusData);
if (quizStatusData.status !== StatusProtocol.Success || quizStatusData.step !== MessageProtocol.Available) {
this.router.navigate(['/']);
return;
......
......@@ -6,7 +6,6 @@ import { environment } from '../../../environments/environment';
import { QuizEntity } from '../../lib/entities/QuizEntity';
import { MessageProtocol, StatusProtocol } from '../../lib/enums/Message';
import { UserRole } from '../../lib/enums/UserRole';
import { IMessage } from '../../lib/interfaces/communication/IMessage';
import { QuizSaveComponent } from '../../modals/quiz-save/quiz-save.component';
import { QuizApiService } from '../../service/api/quiz/quiz-api.service';
import { ConnectionService } from '../../service/connection/connection.service';
......@@ -157,18 +156,19 @@ export class QuizOverviewComponent implements OnInit {
return;
}
this.quizApiService.deleteQuiz(elem).subscribe((response: IMessage) => {
this.quizApiService.deleteQuiz(elem).subscribe(response => {
if (response.status !== StatusProtocol.Success) {
console.log('QuizOverviewComponent: DeleteQuiz failed', response);
} else {
const sessionName = elem.name;
this.storageService.db.Quiz.delete(sessionName).then(() => {
const index = this.sessions.findIndex(quiz => quiz.name === sessionName);
if (index > -1) {
this.sessions.splice(index, 1);
}
});
return;
}
const sessionName = elem.name;
this.storageService.db.Quiz.delete(sessionName).then(() => {
const index = this.sessions.findIndex(quiz => quiz.name === sessionName);
if (index > -1) {
this.sessions.splice(index, 1);
}
});
});
}
......
......@@ -27,6 +27,7 @@ import { LoginComponent } from './root/login/login.component';
import { RootComponent } from './root/root/root.component';
import { ThemeSwitcherComponent } from './root/theme-switcher/theme-switcher.component';
import rxStompConfig from './rx-stomp.config';
import { InitDbGuard } from './service/init-db-guard/init-db.guard';
import { StaticLoginService } from './service/login/static-login.service';
import { SentryErrorHandler } from './shared/sentry-error-handler';
import { SharedModule } from './shared/shared.module';
......@@ -36,50 +37,63 @@ const appRoutes: Routes = [
path: 'admin',
canLoad: [StaticLoginService],
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canActivate: [InitDbGuard],
}, {
path: 'info',
loadChildren: () => import('./root/info/info.module').then(m => m.InfoModule),
canActivate: [InitDbGuard],
}, {
path: 'i18n-manager',
canLoad: [StaticLoginService],
loadChildren: () => import('./i18n-manager/i18n-manager.module').then(m => m.I18nManagerModule),
canActivate: [InitDbGuard],
}, {
path: 'quiz/manager',
loadChildren: () => import('./quiz/quiz-manager/quiz-manager.module').then(m => m.QuizManagerModule),
canActivate: [InitDbGuard],
}, {
path: 'quiz/flow',
loadChildren: () => import('./quiz/quiz-flow/quiz-flow.module').then(m => m.QuizFlowModule),
data: {
preload: false,
},
canActivate: [InitDbGuard],
}, {
path: 'quiz',
loadChildren: () => import('./quiz/quiz.module').then(m => m.QuizModule),
canActivate: [InitDbGuard],
}, {
path: 'nicks',
loadChildren: () => import('./root/nickname-chooser/nickname-chooser.module').then(m => m.NicknameChooserModule),
data: {
preload: false,
},
canActivate: [InitDbGuard],
}, {
path: 'themes',
component: ThemeSwitcherComponent,
canActivate: [InitDbGuard],
}, {
path: 'preview/:themeId/:languageId',
component: HomeComponent,
canActivate: [InitDbGuard],
}, {
path: 'languages',
component: LanguageSwitcherComponent,
canActivate: [InitDbGuard],
}, {
path: 'login',
component: LoginComponent,
canActivate: [InitDbGuard],
}, {
path: '',
component: HomeComponent,
pathMatch: 'full',
canActivate: [InitDbGuard],
}, {
path: '**',
redirectTo: '/',
canActivate: [InitDbGuard],
},
];
......
......@@ -4,7 +4,7 @@ import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Subject } from 'rxjs';
import { distinctUntilChanged, filter, switchMapTo, takeUntil } from 'rxjs/operators';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import { checkABCDOrdering } from '../../lib/checkABCDOrdering';
import { DefaultSettings } from '../../lib/default.settings';
......@@ -12,7 +12,7 @@ import { AbstractAnswerEntity } from '../../lib/entities/answer/AbstractAnswerEn
import { DefaultAnswerEntity } from '../../lib/entities/answer/DefaultAnswerEntity';
import { ABCDSingleChoiceQuestionEntity } from '../../lib/entities/question/ABCDSingleChoiceQuestionEntity';
import { QuizEntity } from '../../lib/entities/QuizEntity';
import { DbState, Language, StorageKey } from '../../lib/enums/enums';
import { Language, StorageKey } from '../../lib/enums/enums';
import { MessageProtocol, StatusProtocol } from '../../lib/enums/Message';
import { QuestionType } from '../../lib/enums/QuestionType';
import { QuizState } from '../../lib/enums/QuizState';
......@@ -140,11 +140,9 @@ export class HomeComponent implements OnInit, OnDestroy {
this.canUsePublicQuizzes = !environment.requireLoginToCreateQuiz || (isLoggedIn && this.userService.isAuthorizedFor(UserRole.CreateQuiz));
});
const params$ = this.activatedRoute.paramMap.pipe(distinctUntilChanged(), takeUntil(this._destroy));
const state$ = this.storageService.stateNotifier.pipe(filter(val => val !== DbState.Destroy), distinctUntilChanged(), takeUntil(this._destroy));
state$.pipe(filter(state => state === DbState.Initialized)).subscribe(() => {
this.activatedRoute.paramMap.pipe(distinctUntilChanged(), takeUntil(this._destroy)).subscribe(async params => {
this.cleanUpSessionStorage();
this.storageService.db.getAllQuiznames().then(quizNames => {
this._ownQuizzes = quizNames;
......@@ -164,9 +162,7 @@ export class HomeComponent implements OnInit, OnDestroy {
this.ownPublicQuizAmount = val;
});
}
});
state$.pipe(switchMapTo(params$)).subscribe(async params => {
if (!Object.keys(params).length || !params.get('themeId') || !params.get('languageId')) {
const theme = this.storageService.db.Config.get(StorageKey.DefaultTheme);
if (!theme) {
......@@ -490,7 +486,7 @@ export class HomeComponent implements OnInit, OnDestroy {
console.error('Invalid quiz status response in home component', value);
}
}
});
}, () => {});
}
private cleanUpSessionStorage(): void {
......
......@@ -93,6 +93,9 @@ export class RootComponent implements OnInit, AfterViewInit {
}
});
this.themeService.initTheme();
this.i18nService.initLanguage();
this.storageService.stateNotifier.pipe(filter(val => val !== DbState.Destroy), takeUntil(this._destroy)).subscribe(() => {
this.themeService.updateCurrentlyUsedTheme();
});
......
......@@ -136,7 +136,7 @@ export class AttendeeService {
}
private restoreMembers(): Promise<void> {
return new Promise<void>(resolve => {
return new Promise<void>((resolve, reject) => {
this.memberApiService.getMembers(this.quizService.quiz.name).subscribe((data) => {
if (!data || !data.payload) {
return;
......@@ -147,7 +147,7 @@ export class AttendeeService {
});
this.footerBarService.footerElemStartQuiz.isActive = this._attendees.length > 0;
resolve();
});
}, () => reject());
});
}
......
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { filter } from 'rxjs/operators';
import { CurrencyType, DbState, Language, NumberType, StorageKey } from '../../lib/enums/enums';
import { CurrencyType, Language, NumberType, StorageKey } from '../../lib/enums/enums';
import { StorageService } from '../storage/storage.service';
@Injectable({
......@@ -29,11 +28,7 @@ export class I18nService {
this._currentLanguage = value;
}
constructor(@Inject(PLATFORM_ID) private platformId: Object, private translateService: TranslateService, private storageService: StorageService) {
this.storageService.stateNotifier.pipe(filter(state => state === DbState.Initialized)).subscribe(() => {
this.initLanguage();
});
}
constructor(@Inject(PLATFORM_ID) private platformId: Object, private translateService: TranslateService, private storageService: StorageService) {}
public formatNumber(number: number, type: NumberType = NumberType.Decimal, locale?: string): string {
if (isNaN(number)) {
......@@ -87,7 +82,7 @@ export class I18nService {
});
}
private initLanguage(): void {
public initLanguage(): void {
if (isPlatformServer(this.platformId)) {
this.setLanguage(Language.EN);
return;
......
import { inject, TestBed } from '@angular/core/testing';
import { InitDbGuard } from './init-db.guard';
describe('InitDbGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [InitDbGuard],
});
});
it('should ...', inject([InitDbGuard], (guard: InitDbGuard) => {
expect(guard).toBeTruthy();
}));
});
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { DbState } from '../../lib/enums/enums';
import { StorageService } from '../storage/storage.service';
@Injectable({
providedIn: 'root',
})
export class InitDbGuard implements CanActivate {
constructor(private storageService: StorageService) {}
public canActivate(): Observable<boolean> {
return this.storageService.stateNotifier.pipe(filter(val => val === DbState.Initialized), map(() => true));
}
}
......@@ -182,7 +182,7 @@ export class QuizService {
this.isOwner = !!quiz;
console.log('QuizService: isOwner', this.isOwner);
this.restoreSettings(quizName).then(() => resolve());
});
}).catch(() => reject());
});
}
......@@ -225,7 +225,7 @@ export class QuizService {
this.quiz = response.payload.quiz;
resolve();
});
}, () => reject());
});
}
}
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { EventEmitter, Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { filter } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import { themes } from '../../lib/available-themes';
import { DbState, StorageKey } from '../../lib/enums/enums';
import { StorageKey } from '../../lib/enums/enums';
import { MessageProtocol, StatusProtocol } from '../../lib/enums/Message';
import { QuizTheme } from '../../lib/enums/QuizTheme';
import { ITheme } from '../../lib/interfaces/ITheme';
......@@ -48,10 +47,6 @@ export class ThemesService {
this._defaultTheme = environment.darkModeCheckEnabled && //
window.matchMedia('(prefers-color-scheme: dark)').matches ? //
QuizTheme.Blackbeauty : environment.defaultTheme;
this.storageService.stateNotifier.pipe(filter(val => val === DbState.Initialized)).subscribe(() => {
this.initTheme();
});
}
public async updateCurrentlyUsedTheme(): Promise<void> {
......@@ -112,10 +107,10 @@ export class ThemesService {
this.addNewNode(elem);
}
});
});
}, () => {});
}
private initTheme(): void {
public initTheme(): void {
if (isPlatformBrowser(this.platformId)) {
this.storageService.db.Config.get(StorageKey.DefaultTheme).then(val => {
......
......@@ -87,36 +87,9 @@ export class UserService {
private jwtHelper: JwtHelperService,
private quizService: QuizService,
) {
this.storageService.stateNotifier.pipe(filter(type => this.username !== DbName.Default && type !== null && type !== DbState.Destroy))
.subscribe(() => {
if (this._staticLoginTokenContent && this._staticLoginTokenContent.privateKey) {
console.log('UserService: having static token content with private key');
this.storageService.db.Config.put({
type: StorageKey.PrivateKey,
value: this._staticLoginTokenContent.privateKey,
});
sessionStorage.setItem(StorageKey.PrivateKey, this._staticLoginTokenContent.privateKey);
if (this._tmpRemoteQuizData.length) {
console.log('UserService: having remote quiz data');
this.storageService.db.Quiz.toCollection().filter(localQuiz => !this._tmpRemoteQuizData.find(val => val.name === localQuiz.name))
.each(localQuiz => {
console.log('UserService: syncing local quiz data to server');
this.quizService.persistQuiz(new QuizEntity(localQuiz));
}).then(() => {
this._tmpRemoteQuizData.forEach(quiz => {
this.quizService.persistQuiz(new QuizEntity(quiz));
console.log('UserService: persisting remote quiz to local db', quiz.name);
});
});
} else {
console.log('UserService: not received remote quiz data');
}
} else {
console.log('UserService: not received any static login token content');
}
this.reloadState();
});
this.loadConfig();
......@@ -135,15 +108,18 @@ export class UserService {
}
public authenticateThroughCas(token: string): Promise<boolean> {
return new Promise(async resolve => {
const data = await this.authorizeApiService.getAuthorizationForToken(token).toPromise();
if (data.status === StatusProtocol.Success) {
this._casTicket = data.payload.casTicket;
this.isLoggedIn = true;
resolve(true);
} else {
this.isLoggedIn = false;
return new Promise(async (resolve, reject) => {
try {
const data = await this.authorizeApiService.getAuthorizationForToken(token).toPromise();
if (data.status === StatusProtocol.Success) {
this._casTicket = data.payload.casTicket;
this.isLoggedIn = true;
resolve(true);
} else {
this.isLoggedIn = false;
resolve(false);
}
} catch (e) {
resolve(false);
}
});
......@@ -232,6 +208,35 @@ export class UserService {
return this.staticLoginTokenContent.userAuthorizations.includes(authorization);
}
private reloadState(): void {
if (this._staticLoginTokenContent && this._staticLoginTokenContent.privateKey) {
console.log('UserService: having static token content with private key');
this.storageService.db.Config.put({
type: StorageKey.PrivateKey,
value: this._staticLoginTokenContent.privateKey,
});
sessionStorage.setItem(StorageKey.PrivateKey, this._staticLoginTokenContent.privateKey);
if (this._tmpRemoteQuizData.length) {
console.log('UserService: having remote quiz data');
this.storageService.db.Quiz.toCollection().filter(localQuiz => !this._tmpRemoteQuizData.find(val => val.name === localQuiz.name))
.each(localQuiz => {
console.log('UserService: syncing local quiz data to server');
this.quizService.persistQuiz(new QuizEntity(localQuiz));
}).then(() => {
this._tmpRemoteQuizData.forEach(quiz => {
this.quizService.persistQuiz(new QuizEntity(quiz));
console.log('UserService: persisting remote quiz to local db', quiz.name);
});
});
} else {
console.log('UserService: not received remote quiz data');
}
} else {
console.log('UserService: not received any static login token content');
}
}
private loadConfig(): boolean {
if (isPlatformServer(this.platformId)) {
this.isLoggedIn = false;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment