diff --git a/.changeset/silver-bobcats-cry.md b/.changeset/silver-bobcats-cry.md
new file mode 100644
index 00000000..4578aaf5
--- /dev/null
+++ b/.changeset/silver-bobcats-cry.md
@@ -0,0 +1,5 @@
+---
+'@openstapps/app': minor
+---
+
+Queue config update for next launch to not block app launches
diff --git a/.changeset/twelve-planes-knock.md b/.changeset/twelve-planes-knock.md
new file mode 100644
index 00000000..0eba8dbd
--- /dev/null
+++ b/.changeset/twelve-planes-knock.md
@@ -0,0 +1,5 @@
+---
+'@openstapps/proxy': minor
+---
+
+Send 426 to outdated clients instead of 404
diff --git a/backend/proxy/config/default.cjs b/backend/proxy/config/default.cjs
index 0a57e657..79b38365 100644
--- a/backend/proxy/config/default.cjs
+++ b/backend/proxy/config/default.cjs
@@ -14,9 +14,8 @@
* along with this program. If not, see .
*/
-// ESM is not supported, and cts is not detected, so we use type-checked cjs instead.
/** @type {import('../src/common').ConfigFile} */
-const configFile = {
+module.exports = {
activeVersions: ['1\\.0\\.\\d+', '2\\.0\\.\\d+'],
hiddenRoutes: ['/bulk'],
logFormat: 'default',
@@ -31,5 +30,3 @@ const configFile = {
dhparam: '/etc/nginx/certs/dhparam.pem',
},
};
-
-export default configFile;
diff --git a/backend/proxy/package.json b/backend/proxy/package.json
index f9e41738..f87b3a07 100644
--- a/backend/proxy/package.json
+++ b/backend/proxy/package.json
@@ -34,10 +34,12 @@
"build": "tsup-node --dts",
"build:docker": "docker build -t openstapps:proxy ../../.deploy/proxy",
"deploy": "pnpm --prod --filter=@openstapps/proxy deploy ../../.deploy/proxy",
+ "dev": "tsup --watch --onSuccess \"pnpm run start\"",
"format": "prettier . -c --ignore-path ../../.gitignore",
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
+ "start": "node app.js",
"test": "c8 mocha"
},
"dependencies": {
diff --git a/backend/proxy/src/app.ts b/backend/proxy/src/app.ts
index c5a44a73..3da1d7c0 100644
--- a/backend/proxy/src/app.ts
+++ b/backend/proxy/src/app.ts
@@ -37,7 +37,7 @@ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
* Reads the container information from the docker socket and updates the nginx config if necessary
*/
async function updateNginxConfig() {
- const containers = await getContainers();
+ const containers = await getContainers(process.env.DOCKER_SOCKET);
const containerHash = containers
.map((container: ContainerInfo) => {
@@ -78,4 +78,4 @@ async function updateNginxConfig() {
// start the process that checks the docker socket periodically
// eslint-disable-next-line unicorn/prefer-top-level-await
-updateNginxConfig();
+await updateNginxConfig();
diff --git a/examples/minimal-deployment/docker-compose.yml b/examples/minimal-deployment/docker-compose.yml
index 960e9437..a189d430 100644
--- a/examples/minimal-deployment/docker-compose.yml
+++ b/examples/minimal-deployment/docker-compose.yml
@@ -1,7 +1,7 @@
version: '3.7'
services:
database:
- image: registry.gitlab.com/openstapps/openstapps/database:2.0.0
+ image: registry.gitlab.com/openstapps/openstapps/database:3.0.0-next.4
volumes:
- ./database:/usr/share/elasticsearch/data
expose:
@@ -9,7 +9,7 @@ services:
restart: unless-stopped
backend:
- image: registry.gitlab.com/openstapps/openstapps/backend:3.0.0-next.0
+ image: registry.gitlab.com/openstapps/openstapps/backend:3.0.0-next.4
environment:
ES_ADDR: "http://database:9200"
NODE_CONFIG_ENV: "elasticsearch"
@@ -27,17 +27,17 @@ services:
- database
api:
- image: registry.gitlab.com/openstapps/openstapps/api:3.0.0-next.0
+ image: registry.gitlab.com/openstapps/openstapps/api:3.0.0-next.4
links:
- "backend"
minimal-connector:
- image: registry.gitlab.com/openstapps/minimal-connector:core-0.23
+ image: registry.gitlab.com/openstapps/openstapps/minimal-connector:3.0.0-next.4
container_name: minimal-connector-0.23
command: ["http://backend:3000", "minimal-connector", "f-u"]
app:
- image: registry.gitlab.com/openstapps/app/executable:core-0.23
+ image: registry.gitlab.com/openstapps/openstapps/app:3.0.0-next.4
expose:
- 8100
ports:
diff --git a/frontend/app/src/app/_helpers/ts-logger.ts b/frontend/app/src/app/_helpers/ts-logger.ts
deleted file mode 100644
index 766ab40d..00000000
--- a/frontend/app/src/app/_helpers/ts-logger.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2020 StApps
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-import {NGXLogger} from 'ngx-logger';
-
-export let logger: NGXLogger;
-
-export const initLogger = (newLogger: NGXLogger) => (logger = newLogger);
diff --git a/frontend/app/src/app/app.module.ts b/frontend/app/src/app/app.module.ts
index 7f4859b7..3a3c308e 100644
--- a/frontend/app/src/app/app.module.ts
+++ b/frontend/app/src/app/app.module.ts
@@ -21,17 +21,13 @@ import {RouteReuseStrategy} from '@angular/router';
import {IonicModule, IonicRouteStrategy, Platform} from '@ionic/angular';
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
-import moment from 'moment';
import 'moment/min/locales';
-import {LoggerModule, NGXLogger, NgxLoggerLevel} from 'ngx-logger';
+import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
import SwiperCore, {FreeMode, Navigation} from 'swiper';
-
import {environment} from '../environments/environment';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {CatalogModule} from './modules/catalog/catalog.module';
-import {ConfigModule} from './modules/config/config.module';
-import {ConfigProvider} from './modules/config/config.provider';
import {DashboardModule} from './modules/dashboard/dashboard.module';
import {DataModule} from './modules/data/data.module';
import {HebisModule} from './modules/hebis/hebis.module';
@@ -40,11 +36,9 @@ import {MenuModule} from './modules/menu/menu.module';
import {NewsModule} from './modules/news/news.module';
import {ScheduleModule} from './modules/schedule/schedule.module';
import {SettingsModule} from './modules/settings/settings.module';
-import {SettingsProvider} from './modules/settings/settings.provider';
import {StorageModule} from './modules/storage/storage.module';
import {ThingTranslateModule} from './translation/thing-translate.module';
import {UtilModule} from './util/util.module';
-import {initLogger} from './_helpers/ts-logger';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AboutModule} from './modules/about/about.module';
import {FavoritesModule} from './modules/favorites/favorites.module';
@@ -54,80 +48,22 @@ import {DebugDataCollectorService} from './modules/data/debug-data-collector.ser
import {AuthModule} from './modules/auth/auth.module';
import {BackgroundModule} from './modules/background/background.module';
import {LibraryModule} from './modules/library/library.module';
-import {StorageProvider} from './modules/storage/storage.provider';
import {AssessmentsModule} from './modules/assessments/assessments.module';
import {ServiceHandlerInterceptor} from './_helpers/service-handler.interceptor';
-import {RoutingStackService} from './util/routing-stack.service';
-import {SCLanguageCode, SCSettingValue} from '@openstapps/core';
-import {DefaultAuthService} from './modules/auth/default-auth.service';
-import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
import {IonIconModule} from './util/ion-icon/ion-icon.module';
import {NavigationModule} from './modules/menu/navigation/navigation.module';
import {browserFactory, SimpleBrowser} from './util/browser.factory';
-import {getDateFnsLocale} from './translation/dfns-locale';
-import {setDefaultOptions} from 'date-fns';
-import {DateFnsConfigurationService} from 'ngx-date-fns';
+import {ConfigProvider} from './modules/config/config.provider';
+import {SettingsProvider} from './modules/settings/settings.provider';
+import {TranslateServiceWrapper} from './translation/translate-service-wrapper';
+import {DefaultAuthService} from './modules/auth/default-auth.service';
+import {PAIAAuthService} from './modules/auth/paia/paia-auth.service';
+import {StorageProvider} from './modules/storage/storage.provider';
registerLocaleData(localeDe);
SwiperCore.use([FreeMode, Navigation]);
-/**
- * Initializes data needed on startup
- */
-export function initializerFactory(
- storageProvider: StorageProvider,
- logger: NGXLogger,
- settingsProvider: SettingsProvider,
- configProvider: ConfigProvider,
- translateService: TranslateService,
- _routingStackService: RoutingStackService,
- defaultAuthService: DefaultAuthService,
- paiaAuthService: PAIAAuthService,
- dateFnsConfigurationService: DateFnsConfigurationService,
-) {
- return async () => {
- initLogger(logger);
- await storageProvider.init();
- await configProvider.init();
- await settingsProvider.init();
- try {
- if (configProvider.firstSession) {
- // set language from browser
- await settingsProvider.setSettingValue(
- 'profile',
- 'language',
- translateService.getBrowserLang() as SCSettingValue,
- );
- }
- const languageCode = (await settingsProvider.getValue('profile', 'language')) as string;
- // this language will be used as a fallback when a translation isn't found in the current language
- translateService.setDefaultLang('en');
- translateService.use(languageCode);
- moment.locale(languageCode);
- const dateFnsLocale = await getDateFnsLocale(languageCode as SCLanguageCode);
- setDefaultOptions({locale: dateFnsLocale});
- dateFnsConfigurationService.setLocale(dateFnsLocale);
-
- await defaultAuthService.init();
- await paiaAuthService.init();
- } catch (error) {
- logger.warn(error);
- }
- };
-}
-
-/**
- * TODO
- * @param http TODO
- */
-export function createTranslateLoader(http: HttpClient) {
- return new TranslateHttpLoader(http, './assets/i18n/', '.json');
-}
-
-/**
- * TODO
- */
@NgModule({
bootstrap: [AppComponent],
declarations: [AppComponent],
@@ -141,7 +77,6 @@ export function createTranslateLoader(http: HttpClient) {
BrowserAnimationsModule,
CatalogModule,
CommonModule,
- ConfigModule,
DashboardModule,
DataModule,
HebisModule,
@@ -165,7 +100,9 @@ export function createTranslateLoader(http: HttpClient) {
loader: {
deps: [HttpClient],
provide: TranslateLoader,
- useFactory: createTranslateLoader,
+ useFactory(http: HttpClient) {
+ return new TranslateHttpLoader(http, './assets/i18n/', '.json');
+ },
},
}),
UtilModule,
@@ -175,6 +112,30 @@ export function createTranslateLoader(http: HttpClient) {
}),
],
providers: [
+ {
+ provide: APP_INITIALIZER,
+ useFactory:
+ (...providers: Array<{beforeAppInit(): Promise}>) =>
+ async () => {
+ for (const provider of providers) {
+ await provider.beforeAppInit();
+ }
+ },
+ // Declare initialization (order matters)
+ deps: [
+ StorageProvider,
+ ConfigProvider,
+ SettingsProvider,
+ TranslateService,
+ DefaultAuthService,
+ PAIAAuthService,
+ ],
+ multi: true,
+ },
+ {
+ provide: TranslateService,
+ useClass: TranslateServiceWrapper,
+ },
{
provide: RouteReuseStrategy,
useClass: IonicRouteStrategy,
@@ -188,22 +149,6 @@ export function createTranslateLoader(http: HttpClient) {
useFactory: browserFactory,
deps: [Platform],
},
- {
- provide: APP_INITIALIZER,
- multi: true,
- deps: [
- StorageProvider,
- NGXLogger,
- SettingsProvider,
- ConfigProvider,
- TranslateService,
- RoutingStackService,
- DefaultAuthService,
- PAIAAuthService,
- DateFnsConfigurationService,
- ],
- useFactory: initializerFactory,
- },
{
provide: HTTP_INTERCEPTORS,
useClass: ServiceHandlerInterceptor,
diff --git a/frontend/app/src/app/before-app-init.ts b/frontend/app/src/app/before-app-init.ts
new file mode 100644
index 00000000..b3968ce1
--- /dev/null
+++ b/frontend/app/src/app/before-app-init.ts
@@ -0,0 +1,10 @@
+/**
+ * Services or providers implementing this interface
+ * must be added to the `APP_INITIALIZER` deps
+ */
+export interface BeforeAppInit {
+ /**
+ * Any logic that has to run before the app is initialized
+ */
+ beforeAppInit(): Promise;
+}
diff --git a/frontend/app/src/app/modules/about/about-page/about-page.component.ts b/frontend/app/src/app/modules/about/about-page/about-page.component.ts
index abe311a0..6257a8dc 100644
--- a/frontend/app/src/app/modules/about/about-page/about-page.component.ts
+++ b/frontend/app/src/app/modules/about/about-page/about-page.component.ts
@@ -14,10 +14,9 @@
*/
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
-import {SCAboutPage, SCAppConfiguration} from '@openstapps/core';
-import {ConfigProvider} from '../../config/config.provider';
+import {SCAboutPage} from '@openstapps/core';
import packageJson from '../../../../../package.json';
-import config from 'capacitor.config';
+import {ConfigProvider} from '../../config/config.provider';
@Component({
selector: 'about-page',
@@ -27,15 +26,12 @@ import config from 'capacitor.config';
export class AboutPageComponent implements OnInit {
content: SCAboutPage;
- appName = config.appName;
-
version = packageJson.version;
- constructor(private readonly route: ActivatedRoute, private readonly configProvider: ConfigProvider) {}
+ constructor(readonly route: ActivatedRoute, readonly config: ConfigProvider) {}
- async ngOnInit() {
+ ngOnInit() {
const route = this.route.snapshot.url.map(it => it.path).join('/');
- this.content =
- (this.configProvider.getValue('aboutPages') as SCAppConfiguration['aboutPages'])[route] ?? {};
+ this.content = this.config.app.aboutPages[route] ?? {};
}
}
diff --git a/frontend/app/src/app/modules/about/about-page/about-page.html b/frontend/app/src/app/modules/about/about-page/about-page.html
index 166c0655..fd220631 100644
--- a/frontend/app/src/app/modules/about/about-page/about-page.html
+++ b/frontend/app/src/app/modules/about/about-page/about-page.html
@@ -25,7 +25,7 @@
- {{ appName }} v{{ version }}
+ {{ config.app.name }} v{{ version }}
diff --git a/frontend/app/src/app/modules/about/about.module.ts b/frontend/app/src/app/modules/about/about.module.ts
index 5c975fa3..0dc3876d 100644
--- a/frontend/app/src/app/modules/about/about.module.ts
+++ b/frontend/app/src/app/modules/about/about.module.ts
@@ -19,7 +19,6 @@ import {FormsModule} from '@angular/forms';
import {IonicModule} from '@ionic/angular';
import {TranslateModule} from '@ngx-translate/core';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
-import {ConfigProvider} from '../config/config.provider';
import {AboutPageComponent} from './about-page/about-page.component';
import {MarkdownModule} from 'ngx-markdown';
import {AboutPageContentComponent} from './about-page/about-page-content.component';
@@ -64,6 +63,5 @@ const settingsRoutes: Routes = [
ScrollingModule,
UtilModule,
],
- providers: [ConfigProvider],
})
export class AboutModule {}
diff --git a/frontend/app/src/app/modules/assessments/assessments.provider.ts b/frontend/app/src/app/modules/assessments/assessments.provider.ts
index b0f94abf..caa559a7 100644
--- a/frontend/app/src/app/modules/assessments/assessments.provider.ts
+++ b/frontend/app/src/app/modules/assessments/assessments.provider.ts
@@ -13,11 +13,12 @@
* this program. If not, see .
*/
import {Injectable} from '@angular/core';
-import {ConfigProvider} from '../config/config.provider';
import {SCAssessment, SCUuid} from '@openstapps/core';
import {DefaultAuthService} from '../auth/default-auth.service';
import {HttpClient} from '@angular/common/http';
import {uniqBy, keyBy} from '@openstapps/collection-utils';
+import {firstValueFrom} from 'rxjs';
+import {ConfigProvider} from '../config/config.provider';
/**
*
@@ -65,7 +66,7 @@ export class AssessmentsProvider {
cacheMaxAge = 15 * 60 * 1000;
constructor(
- readonly configProvider: ConfigProvider,
+ readonly config: ConfigProvider,
readonly defaultAuth: DefaultAuthService,
readonly http: HttpClient,
) {}
@@ -91,21 +92,20 @@ export class AssessmentsProvider {
return this.cache;
}
- const url = this.configProvider.config.app.features.extern?.hisometry.url;
+ const url = this.config.app.features.extern?.hisometry.url;
if (!url) throw new Error('Config lacks url for hisometry');
- this.cache = this.http
- .get<{data: SCAssessment[]}>(`${url}/${this.assessmentPath}`, {
+ this.cache = firstValueFrom(
+ this.http.get<{data: SCAssessment[]}>(`${url}/${this.assessmentPath}`, {
headers: {
Authorization: `Bearer ${accessToken ?? (await this.defaultAuth.getValidToken()).accessToken}`,
},
- })
- .toPromise()
- .then(it => {
- this.cacheTimestamp = Date.now();
+ }),
+ ).then(it => {
+ this.cacheTimestamp = Date.now();
- return it?.data ?? [];
- });
+ return it?.data ?? [];
+ });
this.assessments = this.cache.then(toAssessmentMap);
return this.cache;
diff --git a/frontend/app/src/app/modules/auth/auth-helper.service.ts b/frontend/app/src/app/modules/auth/auth-helper.service.ts
index 7dfdd726..f806e373 100644
--- a/frontend/app/src/app/modules/auth/auth-helper.service.ts
+++ b/frontend/app/src/app/modules/auth/auth-helper.service.ts
@@ -12,24 +12,18 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {Injectable} from '@angular/core';
import {IPAIAAuthAction} from './paia/paia-auth-action';
import {AuthActions, IAuthAction} from 'ionic-appauth';
import {TranslateService} from '@ngx-translate/core';
import {JSONPath} from 'jsonpath-plus';
-import {
- SCAuthorizationProvider,
- SCAuthorizationProviderType,
- SCUserConfiguration,
- SCUserConfigurationMap,
-} from '@openstapps/core';
-import {ConfigProvider} from '../config/config.provider';
+import {SCAuthorizationProviderType, SCUserConfiguration, SCUserConfigurationMap} from '@openstapps/core';
import {StorageProvider} from '../storage/storage.provider';
import {DefaultAuthService} from './default-auth.service';
import {PAIAAuthService} from './paia/paia-auth.service';
import {SimpleBrowser} from '../../util/browser.factory';
import {AlertController} from '@ionic/angular';
+import {ConfigProvider} from '../config/config.provider';
const AUTH_ORIGIN_PATH = 'stapps.auth.origin_path';
@@ -37,23 +31,19 @@ const AUTH_ORIGIN_PATH = 'stapps.auth.origin_path';
providedIn: 'root',
})
export class AuthHelperService {
- userConfigurationMap: SCUserConfigurationMap;
+ get userConfigurationMap(): SCUserConfigurationMap {
+ return this.config.auth.default!.endpoints.mapping;
+ }
constructor(
private translateService: TranslateService,
- private configProvider: ConfigProvider,
private storageProvider: StorageProvider,
private defaultAuth: DefaultAuthService,
private paiaAuth: PAIAAuthService,
private browser: SimpleBrowser,
private alertController: AlertController,
- ) {
- this.userConfigurationMap = (
- this.configProvider.getAnyValue('auth') as {
- default: SCAuthorizationProvider;
- }
- ).default.endpoints.mapping;
- }
+ private config: ConfigProvider,
+ ) {}
public getAuthMessage(provider: SCAuthorizationProviderType, action: IAuthAction | IPAIAAuthAction) {
let message: string | undefined;
diff --git a/frontend/app/src/app/modules/auth/auth.service.ts b/frontend/app/src/app/modules/auth/auth.service.ts
index cca6a5d1..3f418130 100644
--- a/frontend/app/src/app/modules/auth/auth.service.ts
+++ b/frontend/app/src/app/modules/auth/auth.service.ts
@@ -175,7 +175,7 @@ export abstract class AuthService implements IAuthService {
public async init() {
this.setupAuthorizationNotifier();
- this.loadTokenFromStorage();
+ await this.loadTokenFromStorage();
this.addActionObserver(this._actionHistory);
this.addActionObserver(this._session);
}
diff --git a/frontend/app/src/app/modules/auth/default-auth.service.ts b/frontend/app/src/app/modules/auth/default-auth.service.ts
index 701365a2..e2284551 100644
--- a/frontend/app/src/app/modules/auth/default-auth.service.ts
+++ b/frontend/app/src/app/modules/auth/default-auth.service.ts
@@ -12,29 +12,26 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {
AuthorizationRequestHandler,
AuthorizationServiceConfiguration,
JQueryRequestor,
LocalStorageBackend,
- Requestor,
- StorageBackend,
TokenRequestHandler,
} from '@openid/appauth';
-import {AuthActionBuilder, Browser, DefaultBrowser, EndSessionHandler, UserInfoHandler} from 'ionic-appauth';
-import {ConfigProvider} from '../config/config.provider';
-import {SCAuthorizationProvider} from '@openstapps/core';
+import {AuthActionBuilder, DefaultBrowser, EndSessionHandler, UserInfoHandler} from 'ionic-appauth';
import {getClientConfig, getEndpointsConfig} from './auth.provider.methods';
import {Injectable} from '@angular/core';
import {AuthService} from './auth.service';
+import {ConfigProvider} from '../config/config.provider';
+import {BeforeAppInit} from '../../before-app-init';
const TOKEN_RESPONSE_KEY = 'token_response';
@Injectable({
providedIn: 'root',
})
-export class DefaultAuthService extends AuthService {
+export class DefaultAuthService extends AuthService implements BeforeAppInit {
public localConfiguration: AuthorizationServiceConfiguration;
protected tokenHandler: TokenRequestHandler;
@@ -45,13 +42,17 @@ export class DefaultAuthService extends AuthService {
protected endSessionHandler: EndSessionHandler;
- constructor(
- protected browser: Browser = new DefaultBrowser(),
- protected storage: StorageBackend = new LocalStorageBackend(),
- protected requestor: Requestor = new JQueryRequestor(),
- private readonly configProvider: ConfigProvider,
- ) {
- super(browser, storage, requestor);
+ constructor(private config: ConfigProvider) {
+ super(new DefaultBrowser(), new LocalStorageBackend(), new JQueryRequestor());
+ }
+
+ async beforeAppInit() {
+ this.authConfig = getClientConfig('default', this.config.auth);
+ this.localConfiguration = new AuthorizationServiceConfiguration(
+ getEndpointsConfig('default', this.config.auth),
+ );
+
+ await super.init();
}
get configuration(): Promise {
@@ -60,22 +61,6 @@ export class DefaultAuthService extends AuthService {
return Promise.resolve(this.localConfiguration);
}
- public async init() {
- this.setupConfiguration();
- this.setupAuthorizationNotifier();
- await this.loadTokenFromStorage();
- }
-
- setupConfiguration() {
- const authConfig = this.configProvider.getAnyValue('auth') as {
- default: SCAuthorizationProvider;
- };
- this.authConfig = getClientConfig('default', authConfig);
- this.localConfiguration = new AuthorizationServiceConfiguration(
- getEndpointsConfig('default', authConfig),
- );
- }
-
public async signOut() {
await this.revokeTokens().catch(error => {
this.notifyActionListers(AuthActionBuilder.SignOutFailed(error));
diff --git a/frontend/app/src/app/modules/auth/paia/paia-auth.service.ts b/frontend/app/src/app/modules/auth/paia/paia-auth.service.ts
index 252ab1eb..da5bf1ab 100644
--- a/frontend/app/src/app/modules/auth/paia/paia-auth.service.ts
+++ b/frontend/app/src/app/modules/auth/paia/paia-auth.service.ts
@@ -12,7 +12,6 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {
AuthorizationError,
AuthorizationRequest,
@@ -47,10 +46,10 @@ import {PAIAAuthorizationResponse} from './paia-authorization-response';
import {PAIAAuthorizationNotifier} from './paia-authorization-notifier';
import {PAIATokenResponse} from './paia-token-response';
import {IPAIAAuthAction, PAIAAuthActionBuilder} from './paia-auth-action';
-import {SCAuthorizationProvider} from '@openstapps/core';
-import {ConfigProvider} from '../../config/config.provider';
import {getClientConfig, getEndpointsConfig} from '../auth.provider.methods';
import {Injectable} from '@angular/core';
+import {ConfigProvider} from '../../config/config.provider';
+import {BeforeAppInit} from '../../../before-app-init';
const TOKEN_RESPONSE_KEY = 'paia_token_response';
const AUTH_EXPIRY_BUFFER = 10 * 60 * -1; // 10 minutes in seconds
@@ -64,10 +63,8 @@ export interface IAuthService {
getValidToken(buffer?: number): Promise;
}
-@Injectable({
- providedIn: 'root',
-})
-export class PAIAAuthService {
+@Injectable({providedIn: 'root'})
+export class PAIAAuthService implements BeforeAppInit {
private _authConfig?: IAuthConfig;
private _authSubject: AuthSubject = new AuthSubject();
@@ -97,7 +94,7 @@ export class PAIAAuthService {
protected browser: Browser = new DefaultBrowser(),
protected storage: StorageBackend = new LocalStorageBackend(),
protected requestor: Requestor = new JQueryRequestor(),
- private readonly configProvider: ConfigProvider,
+ private config: ConfigProvider,
) {
this.tokenHandler = new PAIATokenRequestHandler(requestor);
this.userInfoHandler = new IonicUserInfoHandler(requestor);
@@ -110,6 +107,16 @@ export class PAIAAuthService {
this.endSessionHandler = new IonicEndSessionHandler(browser);
}
+ async beforeAppInit() {
+ this.authConfig = getClientConfig('paia', this.config.auth);
+ this.localConfiguration = new AuthorizationServiceConfiguration(
+ getEndpointsConfig('paia', this.config.auth),
+ );
+
+ this.setupAuthorizationNotifier();
+ await this.loadTokenFromStorage();
+ }
+
get token$(): Observable {
return this._tokenSubject.asObservable();
}
@@ -147,20 +154,6 @@ export class PAIAAuthService {
return Promise.resolve(this.localConfiguration);
}
- public async init() {
- this.setupConfiguration();
- this.setupAuthorizationNotifier();
- await this.loadTokenFromStorage();
- }
-
- setupConfiguration() {
- const authConfig = this.configProvider.getAnyValue('auth') as {
- paia: SCAuthorizationProvider;
- };
- this.authConfig = getClientConfig('paia', authConfig);
- this.localConfiguration = new AuthorizationServiceConfiguration(getEndpointsConfig('paia', authConfig));
- }
-
protected notifyActionListers(action: IPAIAAuthAction) {
/* eslint-disable unicorn/no-useless-undefined */
switch (action.action) {
diff --git a/frontend/app/src/app/modules/calendar/calendar.service.ts b/frontend/app/src/app/modules/calendar/calendar.service.ts
index 7627d5bb..5438ca3a 100644
--- a/frontend/app/src/app/modules/calendar/calendar.service.ts
+++ b/frontend/app/src/app/modules/calendar/calendar.service.ts
@@ -12,7 +12,6 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {Calendar} from '@awesome-cordova-plugins/calendar/ngx';
import {Injectable} from '@angular/core';
import {ICalEvent} from './ical/ical';
@@ -29,18 +28,18 @@ const RECURRENCE_PATTERNS: Partial>
day: 'daily',
};
-@Injectable()
+@Injectable({providedIn: 'root'})
export class CalendarService {
goToDate = new Subject();
goToDateClicked = this.goToDate.asObservable();
- calendarName = 'StApps';
+ get calendarName(): string {
+ return this.config.app.name ?? 'StApps';
+ }
// eslint-disable-next-line @typescript-eslint/no-empty-function
- constructor(readonly calendar: Calendar, private readonly configProvider: ConfigProvider) {
- this.calendarName = (this.configProvider.getValue('name') as string) ?? 'StApps';
- }
+ constructor(readonly calendar: Calendar, readonly config: ConfigProvider) {}
async createCalendar(): Promise {
await this.calendar.createCalendar({
diff --git a/frontend/app/src/app/modules/config/config.module.ts b/frontend/app/src/app/modules/config/config.module.ts
deleted file mode 100644
index cd3ee9cb..00000000
--- a/frontend/app/src/app/modules/config/config.module.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 StApps
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-import {NgModule} from '@angular/core';
-import {DataModule} from '../data/data.module';
-import {StorageModule} from '../storage/storage.module';
-import {ConfigProvider} from './config.provider';
-
-/**
- * TODO
- */
-@NgModule({
- imports: [StorageModule, DataModule],
- providers: [ConfigProvider],
-})
-export class ConfigModule {}
diff --git a/frontend/app/src/app/modules/config/config.provider.ts b/frontend/app/src/app/modules/config/config.provider.ts
index 259f1790..096f9675 100644
--- a/frontend/app/src/app/modules/config/config.provider.ts
+++ b/frontend/app/src/app/modules/config/config.provider.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/ban-types */
/*
* Copyright (C) 2022 StApps
* This program is free software: you can redistribute it and/or modify it
@@ -14,19 +15,17 @@
*/
import {Injectable} from '@angular/core';
import {Client} from '@openstapps/api';
-import {SCAppConfiguration, SCIndexResponse} from '@openstapps/core';
-import packageInfo from '@openstapps/core/package.json';
-import {NGXLogger} from 'ngx-logger';
-import {environment} from '../../../environments/environment';
-import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
-import {StorageProvider} from '../storage/storage.provider';
import {
- ConfigFetchError,
- ConfigInitError,
- ConfigValueNotAvailable,
- SavedConfigNotAvailable,
- WrongConfigVersionInStorage,
-} from './errors';
+ SCAppConfiguration,
+ SCAuthorizationProvider,
+ SCBackendConfiguration,
+ SCIndexResponse,
+} from '@openstapps/core';
+import coreInfo from '@openstapps/core/package.json';
+import {environment} from '../../../environments/environment';
+import {StorageProvider} from '../storage/storage.provider';
+import {BeforeAppInit} from '../../before-app-init';
+import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
/**
* Key to store config in storage module
@@ -35,145 +34,55 @@ import {
*/
export const STORAGE_KEY_CONFIG = 'stapps.config';
-/**
- * Provides configuration
- */
@Injectable({
providedIn: 'root',
})
-export class ConfigProvider {
- /**
- * Api client
- */
- client: Client;
+export class ConfigProvider implements SCIndexResponse, BeforeAppInit {
+ private client: Client;
- /**
- * App configuration as IndexResponse
- */
- config: SCIndexResponse;
+ constructor(private storageProvider: StorageProvider, httpClient: StAppsWebHttpClient) {
+ this.client = new Client(httpClient, environment.backend_url, environment.backend_version);
+ }
- /**
- * Version of the @openstapps/core package that app is using
- */
- scVersion = packageInfo.version;
+ async beforeAppInit() {
+ this.isFirstSession = !(await this.storageProvider.has(STORAGE_KEY_CONFIG));
+ // Queue config update for next launch; don't block current launch
+ const configUpdate = this.updateConfig();
+ console.log('Config update queued');
- /**
- * First session indicator (config not found in storage)
- */
- firstSession = true;
+ const config = await this.storageProvider
+ .get(STORAGE_KEY_CONFIG)
+ .then(it => it ?? configUpdate);
- /**
- * Constructor, initialise api client
- * @param storageProvider StorageProvider to load persistent configuration
- * @param swHttpClient Api client
- * @param logger An angular logger
- */
- constructor(
- private readonly storageProvider: StorageProvider,
- swHttpClient: StAppsWebHttpClient,
- private readonly logger: NGXLogger,
- ) {
- this.client = new Client(swHttpClient, environment.backend_url, environment.backend_version);
+ Object.assign(this, config);
+
+ console.assert(
+ this.backend.SCVersion === coreInfo.version,
+ 'Wrong config version in storage.',
+ 'Expected:',
+ coreInfo.version,
+ 'Actual:',
+ this.backend.SCVersion,
+ );
}
/**
- * Fetches configuration from backend
+ * Updates the config from remote
*/
- async fetch(): Promise {
- try {
- return await this.client.handshake(this.scVersion);
- } catch {
- throw new ConfigFetchError();
- }
- }
-
- /**
- * Returns the value of an app configuration
- * @param attribute requested attribute from app configuration
- */
- public getValue(attribute: keyof SCAppConfiguration) {
- if (this.config.app[attribute] !== undefined) {
- return this.config.app[attribute];
- }
- throw new ConfigValueNotAvailable(attribute);
- }
-
- /**
- * Returns a value of the configuration (not only app configuration)
- * @param attribute requested attribute from the configuration
- */
- public getAnyValue(attribute: keyof SCIndexResponse) {
- if (this.config[attribute] !== undefined) {
- return this.config[attribute];
- }
- throw new ConfigValueNotAvailable(attribute);
- }
-
- /**
- * Initialises the ConfigProvider
- * @throws ConfigInitError if no configuration could be loaded.
- * @throws WrongConfigVersionInStorage if fetch failed and saved config has wrong SCVersion
- */
- async init(): Promise {
- let loadError;
- let fetchError;
- // load saved configuration
- try {
- this.config = await this.loadLocal();
- this.firstSession = false;
- this.logger.log(`initialised configuration from storage`);
- if (this.config.backend.SCVersion !== this.scVersion) {
- loadError = new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion);
- }
- } catch (error) {
- loadError = error;
- }
- // fetch remote configuration from backend
- try {
- const fetchedConfig: SCIndexResponse = await this.fetch();
- await this.set(fetchedConfig);
- this.logger.log(`initialised configuration from remote`);
- } catch (error) {
- fetchError = error;
- }
- // check for occurred errors and throw them
- if (loadError !== undefined && fetchError !== undefined) {
- throw new ConfigInitError();
- }
- if (loadError !== undefined) {
- this.logger.warn(loadError);
- }
- if (fetchError !== undefined) {
- this.logger.warn(fetchError);
- }
- }
-
- /**
- * Returns saved configuration from StorageModule
- * @throws SavedConfigNotAvailable if no configuration could be loaded
- */
- async loadLocal(): Promise {
- // get local configuration
- if (await this.storageProvider.has(STORAGE_KEY_CONFIG)) {
- return this.storageProvider.get(STORAGE_KEY_CONFIG);
- }
- throw new SavedConfigNotAvailable();
- }
-
- /**
- * Saves the configuration from the provider
- * @param config configuration to save
- */
- async save(config: SCIndexResponse): Promise {
+ async updateConfig(): Promise {
+ const config = await this.client.handshake(coreInfo.version);
await this.storageProvider.put(STORAGE_KEY_CONFIG, config);
+
+ console.log(`Config updated`);
+
+ return config;
}
- /**
- * Sets the configuration in the module and writes it into app storage
- * @param config SCIndexResponse to set
- */
- async set(config: SCIndexResponse): Promise {
- this.config = config;
- await this.save(this.config);
- }
+ app: SCAppConfiguration;
+
+ auth: {default?: SCAuthorizationProvider | undefined; paia?: SCAuthorizationProvider | undefined};
+
+ backend: SCBackendConfiguration;
+
+ isFirstSession: boolean;
}
diff --git a/frontend/app/src/app/modules/config/errors.ts b/frontend/app/src/app/modules/config/errors.ts
deleted file mode 100644
index f9fcd84d..00000000
--- a/frontend/app/src/app/modules/config/errors.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2022 StApps
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-
-import {AppError} from '../../_helpers/errors';
-
-/**
- * Error that is thrown when fetching from backend fails
- */
-export class ConfigFetchError extends AppError {
- constructor() {
- super('ConfigFetchError', 'App configuration could not be fetched!');
- }
-}
-
-/**
- * Error that is thrown when the ConfigProvider could be initialised
- */
-export class ConfigInitError extends AppError {
- constructor() {
- super('ConfigInitError', 'App configuration could not be initialised!');
- }
-}
-
-/**
- * Error that is thrown when the requested config value is not available
- */
-export class ConfigValueNotAvailable extends AppError {
- constructor(valueKey: string) {
- super('ConfigValueNotAvailable', `No attribute "${valueKey}" in config available!`);
- }
-}
-
-/**
- * Error that is thrown when no saved config is available
- */
-export class SavedConfigNotAvailable extends AppError {
- constructor() {
- super('SavedConfigNotAvailable', 'No saved app configuration available.');
- }
-}
-
-/**
- * Error that is thrown when the SCVersion of the saved config is not compatible with the app
- */
-export class WrongConfigVersionInStorage extends AppError {
- constructor(correctVersion: string, savedVersion: string) {
- super(
- 'WrongConfigVersionInStorage',
- `The saved configs backend version ${savedVersion} ` +
- `does not equal the configured backend version ${correctVersion} of the app.`,
- );
- }
-}
diff --git a/frontend/app/src/app/modules/data/elements/offers-in-list.component.ts b/frontend/app/src/app/modules/data/elements/offers-in-list.component.ts
index 57281fe8..06224519 100644
--- a/frontend/app/src/app/modules/data/elements/offers-in-list.component.ts
+++ b/frontend/app/src/app/modules/data/elements/offers-in-list.component.ts
@@ -30,9 +30,8 @@ export class OffersInListComponent {
@Input() set offers(it: Array>) {
this._offers = it;
this.price = it[0].prices?.default;
- this.settingsProvider.getSetting('profile', 'group').then(group => {
- this.price = it[0].prices?.[(group.value as string).replace(/s$/, '') as never];
- });
+ const group = this.settingsProvider.getSetting('profile', 'group');
+ this.price = it[0].prices?.[(group.value as string).replace(/s$/, '') as never];
const availabilities = new Set(it.map(offer => offer.availability));
this.soldOut = availabilities.has('out of stock') && availabilities.size === 1;
diff --git a/frontend/app/src/app/modules/data/list/search-page.component.ts b/frontend/app/src/app/modules/data/list/search-page.component.ts
index adfa8bab..91553363 100644
--- a/frontend/app/src/app/modules/data/list/search-page.component.ts
+++ b/frontend/app/src/app/modules/data/list/search-page.component.ts
@@ -15,16 +15,9 @@
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Keyboard} from '@capacitor/keyboard';
-import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular';
+import {AlertController, AnimationController} from '@ionic/angular';
import {Capacitor} from '@capacitor/core';
-import {
- SCFacet,
- SCFeatureConfiguration,
- SCSearchFilter,
- SCSearchQuery,
- SCSearchSort,
- SCThings,
-} from '@openstapps/core';
+import {SCFacet, SCSearchFilter, SCSearchQuery, SCSearchSort, SCThings} from '@openstapps/core';
import {NGXLogger} from 'ngx-logger';
import {combineLatest, Subject} from 'rxjs';
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
@@ -33,9 +26,9 @@ import {SettingsProvider} from '../../settings/settings.provider';
import {DataRoutingService} from '../data-routing.service';
import {DataProvider} from '../data.provider';
import {PositionService} from '../../map/position.service';
-import {ConfigProvider} from '../../config/config.provider';
import {searchPageSwitchAnimation} from './search-page-switch-animation';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
+import {ConfigProvider} from '../../config/config.provider';
/**
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
@@ -144,21 +137,8 @@ export class SearchPageComponent implements OnInit {
destroy$ = inject(DestroyRef);
- routeAnimation: AnimationBuilder;
+ routeAnimation = searchPageSwitchAnimation(inject(AnimationController));
- /**
- * Injects the providers and creates subscriptions
- * @param alertController AlertController
- * @param dataProvider DataProvider
- * @param contextMenuService ContextMenuService
- * @param settingsProvider SettingsProvider
- * @param logger An angular logger
- * @param dataRoutingService DataRoutingService
- * @param router Router
- * @param route ActivatedRoute
- * @param positionService PositionService
- * @param configProvider ConfigProvider
- */
constructor(
protected readonly alertController: AlertController,
protected dataProvider: DataProvider,
@@ -169,11 +149,8 @@ export class SearchPageComponent implements OnInit {
protected router: Router,
private readonly route: ActivatedRoute,
protected positionService: PositionService,
- private readonly configProvider: ConfigProvider,
- animationController: AnimationController,
- ) {
- this.routeAnimation = searchPageSwitchAnimation(animationController);
- }
+ private readonly config: ConfigProvider,
+ ) {}
/**
* Fetches items with set query configuration
@@ -347,8 +324,7 @@ export class SearchPageComponent implements OnInit {
});
}
try {
- const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
- this.isHebisAvailable = !!features.plugins?.['hebis-plugin']?.urlPath;
+ this.isHebisAvailable = !!this.config.app.features.plugins?.['hebis-plugin']?.urlPath;
} catch (error) {
this.logger.error(error);
}
diff --git a/frontend/app/src/app/modules/data/rating.provider.ts b/frontend/app/src/app/modules/data/rating.provider.ts
index 3841acc6..8e8814c6 100644
--- a/frontend/app/src/app/modules/data/rating.provider.ts
+++ b/frontend/app/src/app/modules/data/rating.provider.ts
@@ -62,10 +62,8 @@ export class RatingProvider {
return new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString();
}
- private get userGroup(): Promise {
- return this.settingsProvider
- .getSetting('profile', 'group')
- .then(it => (it as SCUserGroupSetting).value as SCUserGroup);
+ private get userGroup(): SCUserGroup {
+ return (this.settingsProvider.getSetting('profile', 'group') as SCUserGroupSetting).value as SCUserGroup;
}
private async getStoredRatings(): Promise {
diff --git a/frontend/app/src/app/modules/favorites/favorites-page.component.ts b/frontend/app/src/app/modules/favorites/favorites-page.component.ts
index 167f863a..18221919 100644
--- a/frontend/app/src/app/modules/favorites/favorites-page.component.ts
+++ b/frontend/app/src/app/modules/favorites/favorites-page.component.ts
@@ -12,21 +12,13 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, OnInit} from '@angular/core';
-import {AlertController, AnimationController} from '@ionic/angular';
-import {ActivatedRoute, Router} from '@angular/router';
-import {NGXLogger} from 'ngx-logger';
+import {Component, inject, OnInit} from '@angular/core';
import {debounceTime, distinctUntilChanged, startWith, take} from 'rxjs/operators';
import {combineLatest} from 'rxjs';
import {SCThingType} from '@openstapps/core';
import {FavoritesService} from './favorites.service';
-import {DataRoutingService} from '../data/data-routing.service';
import {ContextMenuService} from '../menu/context/context-menu.service';
import {SearchPageComponent} from '../data/list/search-page.component';
-import {DataProvider} from '../data/data.provider';
-import {SettingsProvider} from '../settings/settings.provider';
-import {PositionService} from '../map/position.service';
-import {ConfigProvider} from '../config/config.provider';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
/**
@@ -42,34 +34,7 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
showNavigation = false;
- constructor(
- alertController: AlertController,
- dataProvider: DataProvider,
- contextMenuService: ContextMenuService,
- settingsProvider: SettingsProvider,
- logger: NGXLogger,
- dataRoutingService: DataRoutingService,
- router: Router,
- route: ActivatedRoute,
- positionService: PositionService,
- private favoritesService: FavoritesService,
- configProvider: ConfigProvider,
- animationController: AnimationController,
- ) {
- super(
- alertController,
- dataProvider,
- contextMenuService,
- settingsProvider,
- logger,
- dataRoutingService,
- router,
- route,
- positionService,
- configProvider,
- animationController,
- );
- }
+ favoritesService = inject(FavoritesService);
ngOnInit() {
super.ngOnInit(false);
diff --git a/frontend/app/src/app/modules/hebis/daia-data.provider.ts b/frontend/app/src/app/modules/hebis/daia-data.provider.ts
index 860ed3a6..0cb7fc07 100644
--- a/frontend/app/src/app/modules/hebis/daia-data.provider.ts
+++ b/frontend/app/src/app/modules/hebis/daia-data.provider.ts
@@ -16,10 +16,9 @@ import {Injectable} from '@angular/core';
import {DaiaAvailabilityResponse, DaiaHolding, DaiaService} from './protocol/response';
import {StorageProvider} from '../storage/storage.provider';
import {HttpClient, HttpHeaders} from '@angular/common/http';
-import {ConfigProvider} from '../config/config.provider';
-import {SCFeatureConfiguration} from '@openstapps/core';
import {NGXLogger} from 'ngx-logger';
import {TranslateService} from '@ngx-translate/core';
+import {ConfigProvider} from '../config/config.provider';
/**
* Generated class for the DataProvider provider.
@@ -44,18 +43,10 @@ export class DaiaDataProvider {
clientHeaders = new HttpHeaders();
- /**
- * TODO
- * @param storageProvider TODO
- * @param httpClient TODO
- * @param configProvider TODO
- * @param logger TODO
- * @param translateService TODO
- */
constructor(
storageProvider: StorageProvider,
httpClient: HttpClient,
- private configProvider: ConfigProvider,
+ private config: ConfigProvider,
private readonly logger: NGXLogger,
private translateService: TranslateService,
) {
@@ -67,15 +58,14 @@ export class DaiaDataProvider {
async getAvailability(id: string): Promise {
if (this.daiaServiceUrl === undefined) {
try {
- const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
- if (features.extern?.daia?.url) {
- this.daiaServiceUrl = features.extern?.daia?.url;
+ if (this.config.app.features.extern?.daia?.url) {
+ this.daiaServiceUrl = this.config.app.features.extern?.daia?.url;
} else {
this.logger.error('Daia service url undefined');
return undefined;
}
- if (features.extern?.hebisProxy?.url) {
- this.hebisProxyUrl = features.extern?.hebisProxy?.url;
+ if (this.config.app.features.extern?.hebisProxy?.url) {
+ this.hebisProxyUrl = this.config.app.features.extern?.hebisProxy?.url;
} else {
this.logger.error('HeBIS proxy url undefined');
return undefined;
diff --git a/frontend/app/src/app/modules/library/account/library-account.service.ts b/frontend/app/src/app/modules/library/account/library-account.service.ts
index f3d81d14..e3e7d148 100644
--- a/frontend/app/src/app/modules/library/account/library-account.service.ts
+++ b/frontend/app/src/app/modules/library/account/library-account.service.ts
@@ -12,22 +12,17 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
import {Injectable} from '@angular/core';
import {JQueryRequestor, Requestor} from '@openid/appauth';
-import {
- SCAuthorizationProviderType,
- SCFeatureConfiguration,
- SCFeatureConfigurationExtern,
-} from '@openstapps/core';
+import {SCAuthorizationProviderType} from '@openstapps/core';
import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIAFees, PAIAItems, PAIAPatron} from '../types';
import {HebisDataProvider} from '../../hebis/hebis-data.provider';
import {PAIATokenResponse} from '../../auth/paia/paia-token-response';
import {AuthHelperService} from '../../auth/auth-helper.service';
-import {ConfigProvider} from '../../config/config.provider';
import {TranslateService} from '@ngx-translate/core';
import {AlertController, ToastController} from '@ionic/angular';
import {HebisSearchResponse} from '../../hebis/protocol/response';
+import {ConfigProvider} from '../../config/config.provider';
@Injectable({
providedIn: 'root',
@@ -36,29 +31,26 @@ export class LibraryAccountService {
/**
* Base url of the external service
*/
- baseUrl: string;
+ get baseUrl(): string {
+ return this.config.app.features.extern!['paia'].url;
+ }
/**
* Authorization provider type
*/
- authType: SCAuthorizationProviderType;
+ get authType(): SCAuthorizationProviderType {
+ return this.config.app.features.extern!['paia'].authProvider!;
+ }
constructor(
protected requestor: Requestor = new JQueryRequestor(),
private readonly hebisDataProvider: HebisDataProvider,
private readonly authHelper: AuthHelperService,
- readonly configProvider: ConfigProvider,
private readonly translateService: TranslateService,
private readonly alertController: AlertController,
private readonly toastController: ToastController,
- ) {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const config: SCFeatureConfigurationExtern = (
- configProvider.getValue('features') as SCFeatureConfiguration
- ).extern!.paia;
- this.baseUrl = config.url;
- this.authType = config.authProvider as SCAuthorizationProviderType;
- }
+ private readonly config: ConfigProvider,
+ ) {}
async getProfile() {
const patron = ((await this.getValidToken()) as PAIATokenResponse).patron;
diff --git a/frontend/app/src/app/modules/map/map.module.ts b/frontend/app/src/app/modules/map/map.module.ts
index 6d631630..25b2f1e7 100644
--- a/frontend/app/src/app/modules/map/map.module.ts
+++ b/frontend/app/src/app/modules/map/map.module.ts
@@ -19,9 +19,7 @@ import {LeafletModule} from '@asymmetrik/ngx-leaflet';
import {LeafletMarkerClusterModule} from '@asymmetrik/ngx-leaflet-markercluster';
import {IonicModule} from '@ionic/angular';
import {TranslateModule} from '@ngx-translate/core';
-import {Polygon} from 'geojson';
import {ThingTranslateModule} from '../../translation/thing-translate.module';
-import {ConfigProvider} from '../config/config.provider';
import {DataFacetsProvider} from '../data/data-facets.provider';
import {DataModule} from '../data/data.module';
import {DataProvider} from '../data/data.provider';
@@ -35,17 +33,6 @@ import {UtilModule} from '../../util/util.module';
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
import {GeoNavigationDirective} from './geo-navigation.directive';
-/**
- * Initializes the default area to show in advance (before components are initialized)
- * @param configProvider An instance of the ConfigProvider to read the campus polygon from
- * @param mapProvider An instance of the MapProvider to set the default polygon (area to show on the map)
- */
-export function initMapConfigFactory(configProvider: ConfigProvider, mapProvider: MapProvider) {
- return async () => {
- mapProvider.defaultPolygon = (await configProvider.getValue('campusPolygon')) as Polygon;
- };
-}
-
const mapRoutes: Routes = [
{path: 'map', component: MapPageComponent},
{path: 'map/:uid', component: MapPageComponent},
diff --git a/frontend/app/src/app/modules/map/map.provider.ts b/frontend/app/src/app/modules/map/map.provider.ts
index ee488dce..851c0121 100644
--- a/frontend/app/src/app/modules/map/map.provider.ts
+++ b/frontend/app/src/app/modules/map/map.provider.ts
@@ -21,12 +21,11 @@ import {
SCThingType,
SCUuid,
} from '@openstapps/core';
-import {Point, Polygon} from 'geojson';
+import {Point} from 'geojson';
import {divIcon, geoJSON, LatLng, Map, marker, Marker} from 'leaflet';
import {DataProvider} from '../data/data.provider';
import {MapPosition, PositionService} from './position.service';
import {hasValidLocation} from '../data/types/place/place-types';
-import {ConfigProvider} from '../config/config.provider';
import {SCIcon} from '../../util/ion-icon/icon';
/**
@@ -36,11 +35,6 @@ import {SCIcon} from '../../util/ion-icon/icon';
providedIn: 'root',
})
export class MapProvider {
- /**
- * Area to show when the map is initialized (shown for the first time)
- */
- defaultPolygon: Polygon;
-
/**
* Provide a point marker for a leaflet map
* @param point Point to get marker for
@@ -111,13 +105,7 @@ export class MapProvider {
clearInterval(interval);
};
- constructor(
- private dataProvider: DataProvider,
- private positionService: PositionService,
- private configProvider: ConfigProvider,
- ) {
- this.defaultPolygon = this.configProvider.getValue('campusPolygon') as Polygon;
- }
+ constructor(private dataProvider: DataProvider, private positionService: PositionService) {}
/**
* Provide the specific place by its UID
diff --git a/frontend/app/src/app/modules/map/page/map-page.component.ts b/frontend/app/src/app/modules/map/page/map-page.component.ts
index 5b2bd14f..6529adca 100644
--- a/frontend/app/src/app/modules/map/page/map-page.component.ts
+++ b/frontend/app/src/app/modules/map/page/map-page.component.ts
@@ -30,6 +30,7 @@ import {Capacitor} from '@capacitor/core';
import {pauseWhen} from '../../../util/rxjs/pause-when';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {startViewTransition} from '../../../util/view-transition';
+import {ConfigProvider} from '../../config/config.provider';
/**
* The main page of the map
@@ -102,7 +103,7 @@ export class MapPageComponent implements OnInit {
* Options of the leaflet map
*/
options: MapOptions = {
- center: geoJSON(this.mapProvider.defaultPolygon).getBounds().getCenter(),
+ center: geoJSON(inject(ConfigProvider).app.campusPolygon).getBounds().getCenter(),
layers: [
tileLayer('https://osm.server.uni-frankfurt.de/tiles/roads/x={x}&y={y}&z={z}', {
attribution: '© OpenStreetMap contributors',
diff --git a/frontend/app/src/app/modules/menu/navigation/navigation.component.ts b/frontend/app/src/app/modules/menu/navigation/navigation.component.ts
index e10a7c83..18f23343 100644
--- a/frontend/app/src/app/modules/menu/navigation/navigation.component.ts
+++ b/frontend/app/src/app/modules/menu/navigation/navigation.component.ts
@@ -12,75 +12,23 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-import {Component, OnInit} from '@angular/core';
-import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
-import {
- SCAppConfigurationMenuCategory,
- SCLanguage,
- SCThingTranslator,
- SCTranslations,
-} from '@openstapps/core';
-import {NavigationService} from './navigation.service';
-import config from 'capacitor.config';
-import {SettingsProvider} from '../../settings/settings.provider';
+import {Component, inject} from '@angular/core';
import {BreakpointObserver} from '@angular/cdk/layout';
+import {map} from 'rxjs/operators';
+import {ConfigProvider} from '../../config/config.provider';
-/**
- * Generated class for the MenuPage page.
- *
- * See https://ionicframework.com/docs/components/#navigation for more info on
- * Ionic pages and navigation.
- */
@Component({
selector: 'stapps-navigation',
styleUrls: ['navigation.scss'],
templateUrl: 'navigation.html',
})
-export class NavigationComponent implements OnInit {
- showTabbar = true;
-
+export class NavigationComponent {
/**
- * Name of the app
+ * TODO: What was this for???
*/
- appName = config.appName;
+ showTabBar$ = inject(BreakpointObserver)
+ .observe(['(min-width: 768px)'])
+ .pipe(map(({matches}) => !matches));
- /**
- * Possible languages to be used for translation
- */
- language: keyof SCTranslations;
-
- /**
- * Menu entries from config module
- */
- menu: SCAppConfigurationMenuCategory[];
-
- /**
- * Core translator
- */
- translator: SCThingTranslator;
-
- constructor(
- public translateService: TranslateService,
- private navigationService: NavigationService,
- private settingsProvider: SettingsProvider,
- private responsive: BreakpointObserver,
- ) {
- translateService.onLangChange.subscribe((event: LangChangeEvent) => {
- this.language = event.lang as keyof SCTranslations;
- this.translator = new SCThingTranslator(this.language);
- });
-
- this.responsive.observe(['(min-width: 768px)']).subscribe(result => {
- this.showTabbar = !result.matches;
- });
- }
-
- async ngOnInit() {
- this.language = (await this.settingsProvider.getValue(
- 'profile',
- 'language',
- )) as keyof SCTranslations;
- this.translator = new SCThingTranslator(this.language);
- this.menu = await this.navigationService.getMenu();
- }
+ constructor(readonly config: ConfigProvider) {}
}
diff --git a/frontend/app/src/app/modules/menu/navigation/navigation.html b/frontend/app/src/app/modules/menu/navigation/navigation.html
index 965f5a00..906f431b 100644
--- a/frontend/app/src/app/modules/menu/navigation/navigation.html
+++ b/frontend/app/src/app/modules/menu/navigation/navigation.html
@@ -24,8 +24,8 @@
-
-
+
+
- {{ item.translations[language].title | titlecase }}
+ {{ 'title' | translateSimple: item | titlecase }}
diff --git a/frontend/app/src/app/modules/menu/navigation/navigation.module.ts b/frontend/app/src/app/modules/menu/navigation/navigation.module.ts
index 53879654..9bab2bf9 100644
--- a/frontend/app/src/app/modules/menu/navigation/navigation.module.ts
+++ b/frontend/app/src/app/modules/menu/navigation/navigation.module.ts
@@ -22,10 +22,11 @@ import {IonIconModule} from '../../../util/ion-icon/ion-icon.module';
import {TranslateModule} from '@ngx-translate/core';
import {RouterModule} from '@angular/router';
import {OfflineNoticeComponent} from './offline-notice.component';
+import {ThingTranslateModule} from '../../../translation/thing-translate.module';
@NgModule({
declarations: [RootLinkDirective, NavigationComponent, TabsComponent, OfflineNoticeComponent],
- imports: [CommonModule, IonicModule, IonIconModule, TranslateModule, RouterModule],
+ imports: [CommonModule, IonicModule, IonIconModule, TranslateModule, RouterModule, ThingTranslateModule],
exports: [TabsComponent, RootLinkDirective, NavigationComponent],
})
export class NavigationModule {}
diff --git a/frontend/app/src/app/modules/menu/navigation/navigation.service.ts b/frontend/app/src/app/modules/menu/navigation/navigation.service.ts
deleted file mode 100644
index 4f9033d7..00000000
--- a/frontend/app/src/app/modules/menu/navigation/navigation.service.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2022 StApps
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
-
-import {Injectable} from '@angular/core';
-import {SCAppConfigurationMenuCategory} from '@openstapps/core';
-import {ConfigProvider} from '../../config/config.provider';
-import {NGXLogger} from 'ngx-logger';
-
-@Injectable({
- providedIn: 'root',
-})
-export class NavigationService {
- constructor(private configProvider: ConfigProvider, private logger: NGXLogger) {}
-
- async getMenu() {
- let menu: SCAppConfigurationMenuCategory[] = [];
- try {
- menu = this.configProvider.getValue('menus') as SCAppConfigurationMenuCategory[];
- } catch (error) {
- this.logger.error(`error from loading menu entries: ${error}`);
- }
-
- return menu;
- }
-}
diff --git a/frontend/app/src/app/modules/menu/navigation/tabs.component.ts b/frontend/app/src/app/modules/menu/navigation/tabs.component.ts
index cea59eb5..b4b615b9 100644
--- a/frontend/app/src/app/modules/menu/navigation/tabs.component.ts
+++ b/frontend/app/src/app/modules/menu/navigation/tabs.component.ts
@@ -12,93 +12,44 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
-
-import {Component} from '@angular/core';
-import {NavigationEnd, Router} from '@angular/router';
-import {
- SCAppConfigurationMenuCategory,
- SCLanguage,
- SCThingTranslator,
- SCTranslations,
-} from '@openstapps/core';
+import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
+import {Event, NavigationEnd, Router} from '@angular/router';
+import {SCAppConfigurationMenuCategory} from '@openstapps/core';
import {ConfigProvider} from '../../config/config.provider';
-import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
-import {NGXLogger} from 'ngx-logger';
+import {filter} from 'rxjs';
+import {map, startWith} from 'rxjs/operators';
+
+/**
+ * Finds a tab name based on urls
+ */
+function findTabFromUrl(url: string, menus: SCAppConfigurationMenuCategory[]): string {
+ if (url === '/') {
+ return menus[0]?.title ?? '';
+ }
+ return menus.find(category => url.includes(category.route))?.title ?? '';
+}
+
+/**
+ * Type guard for event
+ */
+function isNavigationEnd(event: Event): event is NavigationEnd {
+ return event instanceof NavigationEnd;
+}
@Component({
selector: 'stapps-navigation-tabs',
- templateUrl: 'tabs.template.html',
- styleUrls: ['./tabs.component.scss'],
+ templateUrl: 'tabs.html',
+ styleUrls: ['./tabs.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TabsComponent {
- /**
- * Possible languages to be used for translation
- */
- language: keyof SCTranslations;
+ menus = inject(ConfigProvider).app.menus.slice(0, 5);
- /**
- * Menu entries from config module
- */
- menu: SCAppConfigurationMenuCategory[];
+ selectedTab$ = this.router.events.pipe(
+ filter(isNavigationEnd),
+ map(event => findTabFromUrl(event.url, this.menus)),
+ startWith(findTabFromUrl(this.router.url, this.menus)),
+ );
- /**
- * Core translator
- */
- translator: SCThingTranslator;
-
- /**
- * Name of selected tab
- */
- selectedTab: string;
-
- constructor(
- private readonly configProvider: ConfigProvider,
- public translateService: TranslateService,
- private readonly logger: NGXLogger,
- private readonly router: Router,
- ) {
- this.language = this.translateService.currentLang as keyof SCTranslations;
- this.translator = new SCThingTranslator(this.language);
- void this.loadMenuEntries();
- this.router.events.subscribe((event: unknown) => {
- if (event instanceof NavigationEnd) {
- this.selectTab(event.url);
- }
- });
- this.selectTab(router.url);
-
- translateService.onLangChange?.subscribe((event: LangChangeEvent) => {
- this.language = event.lang as keyof SCTranslations;
- this.translator = new SCThingTranslator(this.language);
- });
- }
-
- /**
- * Loads menu entries from configProvider
- */
- async loadMenuEntries() {
- try {
- const menus = (await this.configProvider.getValue('menus')) as SCAppConfigurationMenuCategory[];
-
- const menu = menus.slice(0, 5);
- if (menu) {
- this.menu = menu;
- }
- } catch (error) {
- this.logger.error(`error from loading menu entries: ${error}`);
- }
- }
-
- /* eslint-disable @typescript-eslint/no-explicit-any */
- selectTab(url: string) {
- if (!this.menu) {
- return;
- }
- if (url === '/') {
- this.selectedTab = (this.menu[0] as any)?.title ?? '';
- return;
- }
- const candidate = this.menu.slice(0, 5).find(category => url.includes(category.route));
- this.selectedTab = candidate?.title ?? '';
- }
+ constructor(private readonly router: Router) {}
}
diff --git a/frontend/app/src/app/modules/menu/navigation/tabs.template.html b/frontend/app/src/app/modules/menu/navigation/tabs.html
similarity index 89%
rename from frontend/app/src/app/modules/menu/navigation/tabs.template.html
rename to frontend/app/src/app/modules/menu/navigation/tabs.html
index 93fe858b..06655b28 100644
--- a/frontend/app/src/app/modules/menu/navigation/tabs.template.html
+++ b/frontend/app/src/app/modules/menu/navigation/tabs.html
@@ -33,19 +33,19 @@
-->
-
+
- {{ category.translations[language].title | titlecase }}
+ {{ 'title' | translateSimple: category | titlecase }}
diff --git a/frontend/app/src/app/modules/menu/navigation/tabs.component.scss b/frontend/app/src/app/modules/menu/navigation/tabs.scss
similarity index 100%
rename from frontend/app/src/app/modules/menu/navigation/tabs.component.scss
rename to frontend/app/src/app/modules/menu/navigation/tabs.scss
diff --git a/frontend/app/src/app/modules/settings/settings.module.ts b/frontend/app/src/app/modules/settings/settings.module.ts
index 911f4978..ef30b215 100644
--- a/frontend/app/src/app/modules/settings/settings.module.ts
+++ b/frontend/app/src/app/modules/settings/settings.module.ts
@@ -18,9 +18,7 @@ import {FormsModule} from '@angular/forms';
import {RouterModule, Routes} from '@angular/router';
import {IonicModule} from '@ionic/angular';
import {TranslateModule} from '@ngx-translate/core';
-
import {ThingTranslateModule} from '../../translation/thing-translate.module';
-import {ConfigProvider} from '../config/config.provider';
import {SettingsItemComponent} from './item/settings-item.component';
import {SettingsPageComponent} from './page/settings-page.component';
import {SettingTranslatePipe} from './setting-translate.pipe';
@@ -60,13 +58,6 @@ const settingsRoutes: Routes = [{path: 'settings', component: SettingsPageCompon
RouterModule.forChild(settingsRoutes),
UtilModule,
],
- providers: [
- ScheduleSyncService,
- ConfigProvider,
- SettingsProvider,
- CalendarService,
- ScheduleProvider,
- ThingTranslatePipe,
- ],
+ providers: [ScheduleSyncService, SettingsProvider, CalendarService, ScheduleProvider, ThingTranslatePipe],
})
export class SettingsModule {}
diff --git a/frontend/app/src/app/modules/settings/settings.provider.ts b/frontend/app/src/app/modules/settings/settings.provider.ts
index a3f52249..6f4c8d02 100644
--- a/frontend/app/src/app/modules/settings/settings.provider.ts
+++ b/frontend/app/src/app/modules/settings/settings.provider.ts
@@ -16,8 +16,9 @@ import {Injectable} from '@angular/core';
import {SCSetting, SCSettingValue, SCSettingValues} from '@openstapps/core';
import deepMerge from 'deepmerge';
import {Subject} from 'rxjs';
-import {ConfigProvider} from '../config/config.provider';
import {StorageProvider} from '../storage/storage.provider';
+import {ConfigProvider} from '../config/config.provider';
+import {BeforeAppInit} from '../../before-app-init';
export const STORAGE_KEY_SETTINGS = 'settings';
export const STORAGE_KEY_SETTINGS_SEPARATOR = '.';
@@ -89,19 +90,19 @@ export interface SettingsAction {
/**
* Provider for app settings
*/
-@Injectable()
-export class SettingsProvider {
+@Injectable({
+ providedIn: 'root',
+})
+export class SettingsProvider implements BeforeAppInit {
/**
* Source of settings actions
*/
private settingsActionSource = new Subject();
- private needsInit = true;
-
/**
* Order of the setting categories
*/
- categoriesOrder: string[];
+ categoriesOrder: string[] = [];
/**
* Settings actions observable
@@ -111,7 +112,45 @@ export class SettingsProvider {
/**
* Cache for the imported settings
*/
- settingsCache: SettingsCache;
+ settingsCache: SettingsCache = {};
+
+ constructor(private storageProvider: StorageProvider, private config: ConfigProvider) {}
+
+ async beforeAppInit() {
+ try {
+ for (const setting of this.config.app.settings) {
+ this.addSetting(setting);
+ }
+
+ for (const category of Object.keys(this.settingsCache)) {
+ if (!this.categoriesOrder.includes(category)) {
+ this.categoriesOrder.push(category);
+ }
+ }
+ } catch {
+ this.settingsCache = {};
+ }
+
+ if (await this.storageProvider.has(STORAGE_KEY_SETTING_VALUES)) {
+ // get setting values from StorageProvider into settingsCache
+ const valuesContainer: SettingValuesContainer = await this.storageProvider.get(
+ STORAGE_KEY_SETTING_VALUES,
+ );
+ // iterate through keys of categories
+ for (const categoryKey of Object.keys(this.settingsCache)) {
+ // iterate through setting keys of category
+ for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
+ // if saved setting value exists set it, otherwise set to default value
+ this.settingsCache[categoryKey].settings[settingKey].value =
+ valuesContainer[categoryKey] !== undefined &&
+ valuesContainer[categoryKey][settingKey] !== undefined
+ ? valuesContainer[categoryKey][settingKey]
+ : this.settingsCache[categoryKey].settings[settingKey].defaultValue;
+ }
+ }
+ await this.saveSettingValues();
+ }
+ }
/**
* Return true if all given values are valid to possible values in given settingInput
@@ -192,21 +231,11 @@ export class SettingsProvider {
return isValueValid;
}
- /**
- *
- * @param storage TODO
- * @param configProvider TODO
- */
- constructor(private readonly storage: StorageProvider, private readonly configProvider: ConfigProvider) {
- this.categoriesOrder = [];
- this.settingsCache = {};
- }
-
/**
* Add an Setting to the Cache if not exist and set undefined value to defaultValue
* @param setting Setting with categories, defaultValue, name, input type and valid values
*/
- private addSetting(setting: SCSetting): void {
+ addSetting(setting: SCSetting): void {
if (!this.categoryExists(setting.categories[0])) {
this.provideCategory(setting.categories[0]);
}
@@ -265,9 +294,7 @@ export class SettingsProvider {
/**
* Returns copy of cached settings
*/
- public async getCache(): Promise {
- await this.init();
-
+ public getCache(): SettingsCache {
return JSON.parse(JSON.stringify(this.settingsCache));
}
@@ -284,8 +311,7 @@ export class SettingsProvider {
* @param name the name of requested setting
* @throws Exception if setting is not provided
*/
- public async getSetting(category: string, name: string): Promise {
- await this.init();
+ public getSetting(category: string, name: string): SCSetting {
if (this.settingExists(category, name)) {
// return a copy of the settings
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name]));
@@ -299,8 +325,7 @@ export class SettingsProvider {
* @param name the name of requested setting
* @throws Exception if setting is not provided
*/
- public async getValue(category: string, name: string): Promise {
- await this.init();
+ public getValue(category: string, name: string): SCSettingValue | SCSettingValues {
if (this.settingExists(category, name)) {
// return a copy of the settings value
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name].value));
@@ -308,47 +333,6 @@ export class SettingsProvider {
throw new Error(`Setting "${name}" not provided`);
}
- /**
- * Initializes settings from config and stored values if exist
- */
- public async init(): Promise {
- if (!this.needsInit) return;
- this.needsInit = false;
-
- try {
- const settings: SCSetting[] = this.configProvider.getValue('settings') as SCSetting[];
- for (const setting of settings) this.addSetting(setting);
-
- for (const category of Object.keys(this.settingsCache)) {
- if (!this.categoriesOrder.includes(category)) {
- this.categoriesOrder.push(category);
- }
- }
- } catch {
- this.settingsCache = {};
- }
-
- if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
- // get setting values from StorageProvider into settingsCache
- const valuesContainer: SettingValuesContainer = await this.storage.get(
- STORAGE_KEY_SETTING_VALUES,
- );
- // iterate through keys of categories
- for (const categoryKey of Object.keys(this.settingsCache)) {
- // iterate through setting keys of category
- for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
- // if saved setting value exists set it, otherwise set to default value
- this.settingsCache[categoryKey].settings[settingKey].value =
- valuesContainer[categoryKey] !== undefined &&
- valuesContainer[categoryKey][settingKey] !== undefined
- ? valuesContainer[categoryKey][settingKey]
- : this.settingsCache[categoryKey].settings[settingKey].defaultValue;
- }
- }
- await this.saveSettingValues();
- }
- }
-
/**
* Adds given setting and its category if not exist
* @param setting the setting to add
@@ -358,12 +342,10 @@ export class SettingsProvider {
}
/**
- * Deletes saved values and reinitialising the settings
+ * Delete saved values and reinitialize the settings
*/
public async reset(): Promise {
- await this.storage.put(STORAGE_KEY_SETTING_VALUES, {});
- this.needsInit = true;
- await this.init();
+ await this.storageProvider.put(STORAGE_KEY_SETTING_VALUES, {});
}
/**
@@ -383,15 +365,14 @@ export class SettingsProvider {
* Saves cached settings in app storage
*/
public async saveSettingValues(): Promise {
- if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
- const savedSettingsValues: SettingValuesContainer = await this.storage.get(
- STORAGE_KEY_SETTING_VALUES,
- );
+ if (await this.storageProvider.has(STORAGE_KEY_SETTING_VALUES)) {
+ const savedSettingsValues: SettingValuesContainer =
+ await this.storageProvider.get(STORAGE_KEY_SETTING_VALUES);
const cacheSettingsValues = this.getSettingValuesFromCache();
const mergedSettingValues = deepMerge(savedSettingsValues, cacheSettingsValues);
- await this.storage.put(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
+ await this.storageProvider.put(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
} else {
- await this.storage.put(
+ await this.storageProvider.put(
STORAGE_KEY_SETTING_VALUES,
this.getSettingValuesFromCache(),
);
@@ -418,7 +399,6 @@ export class SettingsProvider {
name: string,
value: SCSettingValue | SCSettingValues,
): Promise {
- await this.init();
if (this.settingExists(category, name)) {
const setting: SCSetting = this.settingsCache[category].settings[name];
const isValueValid = SettingsProvider.validateValue(setting, value);
diff --git a/frontend/app/src/app/modules/storage/storage.provider.ts b/frontend/app/src/app/modules/storage/storage.provider.ts
index 324886b1..8e4f9592 100644
--- a/frontend/app/src/app/modules/storage/storage.provider.ts
+++ b/frontend/app/src/app/modules/storage/storage.provider.ts
@@ -14,17 +14,22 @@
*/
import {Injectable} from '@angular/core';
import {Storage} from '@ionic/storage-angular';
+import {BeforeAppInit} from '../../before-app-init';
/**
* Provides interaction with the (ionic) storage on the device (in the browser)
*/
-@Injectable()
-export class StorageProvider {
+@Injectable({providedIn: 'root'})
+export class StorageProvider implements BeforeAppInit {
/**
* @param storage TODO
*/
constructor(private readonly storage: Storage) {}
+ async beforeAppInit() {
+ await this.storage.create();
+ }
+
/**
* Deletes storage entries using keys used to save them
* @param keys Unique identifiers of the resources for deletion
@@ -95,13 +100,6 @@ export class StorageProvider {
return (await this.storage.keys()).includes(key);
}
- /**
- * Initializes the storage (waits until it's ready)
- */
- async init(): Promise {
- await this.storage.create();
- }
-
/**
* Provides information if storage is empty or not
*/
diff --git a/frontend/app/src/app/translation/common-string-pipes.ts b/frontend/app/src/app/translation/common-string-pipes.ts
index 49a1f346..14ae3940 100644
--- a/frontend/app/src/app/translation/common-string-pipes.ts
+++ b/frontend/app/src/app/translation/common-string-pipes.ts
@@ -16,7 +16,6 @@ import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import moment from 'moment';
import {Subscription} from 'rxjs';
-import {logger} from '../_helpers/ts-logger';
@Injectable()
@Pipe({
@@ -163,7 +162,7 @@ export class DurationLocalizedPipe implements PipeTransform, OnDestroy {
updateValue(value: string | unknown, isFrequency = false): void {
if (typeof value !== 'string') {
- logger.warn(`durationLocalized pipe unable to parse input: ${value}`);
+ console.warn('durationLocalized pipe unable to parse input', value);
return;
}
@@ -223,7 +222,7 @@ export class MetersLocalizedPipe implements PipeTransform, OnDestroy {
updateValue(value: string | number | unknown) {
if (typeof value !== 'string' && typeof value !== 'number') {
- logger.warn(`metersLocalized pipe unable to parse input: ${value}`);
+ console.warn('metersLocalized pipe unable to parse input:', value);
return;
}
@@ -321,7 +320,7 @@ export class NumberLocalizedPipe implements PipeTransform, OnDestroy {
updateValue(value: string | number | unknown, formatOptions?: string): void {
if (typeof value !== 'string' && typeof value !== 'number') {
- logger.warn(`numberLocalized pipe unable to parse input: ${value}`);
+ console.warn('numberLocalized pipe unable to parse input:', value);
return;
}
@@ -387,7 +386,7 @@ export class DateLocalizedFormatPipe implements PipeTransform, OnDestroy {
updateValue(value: string | Date | unknown, formatOptions?: string): void {
if (typeof value !== 'string' && Object.prototype.toString.call(value) !== '[object Date]') {
- logger.warn(`dateFormat pipe unable to parse input: ${value}`);
+ console.warn('dateFormat pipe unable to parse input:', value);
return;
}
diff --git a/frontend/app/src/app/translation/translate-service-wrapper.ts b/frontend/app/src/app/translation/translate-service-wrapper.ts
new file mode 100644
index 00000000..c208fe62
--- /dev/null
+++ b/frontend/app/src/app/translation/translate-service-wrapper.ts
@@ -0,0 +1,38 @@
+import {SCLanguageCode, SCSettingValue} from '@openstapps/core';
+import moment from 'moment/moment';
+import {getDateFnsLocale} from './dfns-locale';
+import {setDefaultOptions} from 'date-fns';
+import {SettingsProvider} from '../modules/settings/settings.provider';
+import {TranslateService} from '@ngx-translate/core';
+import {DateFnsConfigurationService} from 'ngx-date-fns';
+import {ConfigProvider} from '../modules/config/config.provider';
+import {BeforeAppInit} from '../before-app-init';
+import {inject, Injectable} from '@angular/core';
+
+@Injectable({providedIn: 'root'})
+export class TranslateServiceWrapper extends TranslateService implements BeforeAppInit {
+ private config = inject(ConfigProvider);
+
+ private settingsProvider = inject(SettingsProvider);
+
+ private dateFnsConfigurationService = inject(DateFnsConfigurationService);
+
+ async beforeAppInit() {
+ if (this.config.isFirstSession) {
+ // set language from browser
+ await this.settingsProvider.setSettingValue(
+ 'profile',
+ 'language',
+ this.getBrowserLang() as SCSettingValue,
+ );
+ }
+ const languageCode = this.settingsProvider.getValue('profile', 'language') as string;
+ // this language will be used as a fallback when a translation isn't found in the current language
+ this.setDefaultLang('en');
+ this.use(languageCode);
+ moment.locale(languageCode);
+ const dateFnsLocale = await getDateFnsLocale(languageCode as SCLanguageCode);
+ setDefaultOptions({locale: dateFnsLocale});
+ this.dateFnsConfigurationService.setLocale(dateFnsLocale);
+ }
+}
diff --git a/frontend/app/src/app/util/internet-connection.service.ts b/frontend/app/src/app/util/internet-connection.service.ts
index e8ed9ea8..4c79b3e8 100644
--- a/frontend/app/src/app/util/internet-connection.service.ts
+++ b/frontend/app/src/app/util/internet-connection.service.ts
@@ -21,6 +21,7 @@ import {
race,
RetryConfig,
share,
+ skip,
Subject,
takeUntil,
} from 'rxjs';
@@ -61,7 +62,7 @@ export class InternetConnectionService {
private doRetry(error: unknown, retryCount: number): ObservableInput {
return race(
this.offline$.pipe(
- tap(it => console.log(it)),
+ skip(1),
filter(it => !it),
take(1),
delay(Math.min(retryCount ** 4 + 100, 10_000)),
diff --git a/frontend/app/src/environments/environment.ts b/frontend/app/src/environments/environment.ts
index 5c13bd28..94eb87ff 100644
--- a/frontend/app/src/environments/environment.ts
+++ b/frontend/app/src/environments/environment.ts
@@ -21,7 +21,7 @@ export const environment = {
backend_url: 'https://mobile.server.uni-frankfurt.de',
app_host: 'mobile.app.uni-frankfurt.de',
custom_url_scheme: 'de.anyschool.app',
- backend_version: '4.0.0',
+ backend_version: '0.0.0',
production: false,
};