diff --git a/backend/backend/config/default/app/menu.js b/backend/backend/config/default/app/menu.js index 23b257a0..db0887af 100644 --- a/backend/backend/config/default/app/menu.js +++ b/backend/backend/config/default/app/menu.js @@ -29,19 +29,6 @@ const menus = [ }, }, }, - { - icon: 'local_library', - route: '/hebis-search', - title: 'library catalog', - translations: { - de: { - title: 'Bibliothekskatalog', - }, - en: { - title: 'library catalog', - }, - }, - }, { icon: 'inventory_2', route: '/catalog', @@ -137,20 +124,6 @@ const menus = [ }, }, }, - { - authProvider: 'paia', - icon: 'badge', - route: '/library-account', - title: 'library account', - translations: { - de: { - title: 'Bibliothekskonto', - }, - en: { - title: 'library account', - }, - }, - }, { icon: 'settings', route: '/settings', diff --git a/backend/backend/config/f-u/backendrc.js b/backend/backend/config/f-u/backendrc.js index a16e0e72..8138cdc2 100644 --- a/backend/backend/config/f-u/backendrc.js +++ b/backend/backend/config/f-u/backendrc.js @@ -32,25 +32,6 @@ const config = { userinfo: 'https://cas.rz.uni-frankfurt.de/cas/oauth2.0/profile', }, }, - paia: { - client: { - clientId: '', - scopes: '', - url: 'https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php', - }, - endpoints: { - authorization: - 'https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php', - endSession: 'https://ubffm.hds.hebis.de/Shibboleth.sso/Logout', - mapping: { - id: '$.email', - name: '$.name', - role: '$.type', - }, - token: 'https://hds.hebis.de/paia/auth/login', - userinfo: 'https://hds.hebis.de/paia/core', - }, - }, }, app: { ...defaultApp, @@ -60,16 +41,6 @@ const config = { authProvider: 'default', url: 'https://his-self-service.rz.uni-frankfurt.de', }, - daia: { - url: 'https://daia.hebis.de/DAIA2/UB_Frankfurt', - }, - hebisProxy: { - url: 'https://proxy.ub.uni-frankfurt.de/login?qurl=', - }, - paia: { - authProvider: 'paia', - url: 'https://hds.hebis.de/paia/core', - }, /** TODO: idCards: { authProvider: 'default', url: 'TODO', diff --git a/frontend/app/cypress/fixtures/config/default-config.json b/frontend/app/cypress/fixtures/config/default-config.json index 6ca009e0..e4101297 100644 --- a/frontend/app/cypress/fixtures/config/default-config.json +++ b/frontend/app/cypress/fixtures/config/default-config.json @@ -175,16 +175,6 @@ "hisometry": { "authProvider": "default", "url": "https://his-self-service.rz.uni-frankfurt.de" - }, - "daia": { - "url": "https://daia.hebis.de/DAIA2/UB_Frankfurt" - }, - "hebisProxy": { - "url": "https://proxy.ub.uni-frankfurt.de/login?qurl=" - }, - "paia": { - "authProvider": "paia", - "url": "https://hds.hebis.de/paia/core" } }, "plugins": { @@ -193,9 +183,6 @@ }, "feedback": { "urlPath": "/feedback" - }, - "hebis-plugin": { - "urlPath": "/hebissearch" } } }, @@ -229,19 +216,6 @@ } } }, - { - "icon": "local_library", - "route": "/hebis-search", - "title": "library catalog", - "translations": { - "de": { - "title": "Bibliothekskatalog" - }, - "en": { - "title": "library catalog" - } - } - }, { "icon": "inventory_2", "route": "/catalog", @@ -324,20 +298,6 @@ } } }, - { - "authProvider": "paia", - "icon": "badge", - "route": "/library-account", - "title": "library account", - "translations": { - "de": { - "title": "Bibliothekskonto" - }, - "en": { - "title": "library account" - } - } - }, { "icon": "settings", "route": "/settings", @@ -473,24 +433,6 @@ "token": "https://cas.rz.uni-frankfurt.de/cas/oauth2.0/accessToken", "userinfo": "https://cas.rz.uni-frankfurt.de/cas/oauth2.0/profile" } - }, - "paia": { - "client": { - "clientId": "", - "scopes": "", - "url": "https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php" - }, - "endpoints": { - "authorization": "https://hds.hebis.de/Shibboleth.sso/UBFFM?target=https://hds.hebis.de/ubffm/paia_login_stub.php", - "endSession": "https://ubffm.hds.hebis.de/Shibboleth.sso/Logout", - "mapping": { - "id": "$.email", - "name": "$.name", - "role": "$.type" - }, - "token": "https://hds.hebis.de/paia/auth/login", - "userinfo": "https://hds.hebis.de/paia/core" - } } }, "backend": { diff --git a/frontend/app/src/app/app.component.ts b/frontend/app/src/app/app.component.ts index ff4a37a3..d39d8022 100644 --- a/frontend/app/src/app/app.component.ts +++ b/frontend/app/src/app/app.component.ts @@ -121,11 +121,8 @@ export class AppComponent implements AfterContentInit { private async authNotificationsInit() { this.authHelper - .getProvider('default') + .getProvider() .events$.subscribe(action => this.showMessage(this.authHelper.getAuthMessage('default', action))); - this.authHelper - .getProvider('paia') - .events$.subscribe(action => this.showMessage(this.authHelper.getAuthMessage('paia', action))); } private async showMessage(message?: string) { diff --git a/frontend/app/src/app/app.module.ts b/frontend/app/src/app/app.module.ts index 857a3712..42fe3b10 100644 --- a/frontend/app/src/app/app.module.ts +++ b/frontend/app/src/app/app.module.ts @@ -33,7 +33,6 @@ import {CatalogModule} from './modules/catalog/catalog.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'; import {MapModule} from './modules/map/map.module'; import {MenuModule} from './modules/menu/menu.module'; import {NewsModule} from './modules/news/news.module'; @@ -53,14 +52,12 @@ import {FeedbackModule} from './modules/feedback/feedback.module'; import {DebugDataCollectorService} from './modules/data/debug-data-collector.service'; 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 {NavigationModule} from './modules/menu/navigation/navigation.module'; import {browserFactory, SimpleBrowser} from './util/browser.factory'; import {getDateFnsLocale} from './translation/dfns-locale'; @@ -86,7 +83,6 @@ export function initializerFactory( translateService: TranslateService, _routingStackService: RoutingStackService, defaultAuthService: DefaultAuthService, - paiaAuthService: PAIAAuthService, dateFnsConfigurationService: DateFnsConfigurationService, ) { return async () => { @@ -115,7 +111,6 @@ export function initializerFactory( dateFnsConfigurationService.setLocale(dateFnsLocale); await defaultAuthService.init(); - await paiaAuthService.init(); } catch (error) { logger.warn(error); } @@ -154,10 +149,8 @@ export function createTranslateLoader(http: HttpClient) { CommonModule, DashboardModule, DataModule, - HebisModule, JobModule, FavoritesModule, - LibraryModule, ProfilePageModule, FeedbackModule, MapModule, @@ -208,7 +201,6 @@ export function createTranslateLoader(http: HttpClient) { TranslateService, RoutingStackService, DefaultAuthService, - PAIAAuthService, DateFnsConfigurationService, ], useFactory: initializerFactory, diff --git a/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts b/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts index cd45070e..c90a1438 100644 --- a/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts +++ b/frontend/app/src/app/modules/auth/auth-callback/page/auth-callback-page.component.ts @@ -19,7 +19,6 @@ import {AuthActions, IAuthAction} from 'ionic-appauth'; import {AuthHelperService} from '../../auth-helper.service'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {Observable} from 'rxjs'; -import {IPAIAAuthAction} from '../../paia/paia-auth-action'; import {DefaultAuthService} from '../../default-auth.service'; @Component({ @@ -33,7 +32,7 @@ export class AuthCallbackPageComponent { private authHelper: AuthHelperService, private auth: DefaultAuthService, ) { - const events: Observable = this.auth.events$; + const events: Observable = this.auth.events$; events.pipe(takeUntilDestroyed()).subscribe((action: IAuthAction) => this.postCallback(action)); this.auth.authorizationCallback(window.location.origin + this.router.url); diff --git a/frontend/app/src/app/modules/auth/auth-guard.service.ts b/frontend/app/src/app/modules/auth/auth-guard.service.ts index 03a570eb..d2f29bc9 100644 --- a/frontend/app/src/app/modules/auth/auth-guard.service.ts +++ b/frontend/app/src/app/modules/auth/auth-guard.service.ts @@ -33,7 +33,7 @@ export class AuthGuardService implements CanActivate { } try { - await this.authHelper.getProvider(route.data.authProvider).getValidToken(); + await this.authHelper.getProvider().getValidToken(); } catch { const originNavigation = this.router.getCurrentNavigation(); let extras: NavigationExtras = {}; @@ -42,7 +42,7 @@ export class AuthGuardService implements CanActivate { extras = {queryParams: {origin_path: url}}; } this.router.navigate(['profile'], extras); - await this.authHelper.getProvider(route.data.authProvider).signIn(); + await this.authHelper.getProvider().signIn(); return false; } 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 bb3a6db4..97099880 100644 --- a/frontend/app/src/app/modules/auth/auth-helper.service.ts +++ b/frontend/app/src/app/modules/auth/auth-helper.service.ts @@ -14,7 +14,6 @@ */ 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'; @@ -27,7 +26,6 @@ import { import {ConfigProvider} from '../config/config.provider'; 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/standalone'; @@ -44,7 +42,6 @@ export class AuthHelperService { private configProvider: ConfigProvider, private storageProvider: StorageProvider, private defaultAuth: DefaultAuthService, - private paiaAuth: PAIAAuthService, private browser: SimpleBrowser, private alertController: AlertController, ) { @@ -56,7 +53,7 @@ export class AuthHelperService { ).default?.endpoints.mapping ?? {}; } - public getAuthMessage(provider: SCAuthorizationProviderType, action: IAuthAction | IPAIAAuthAction) { + public getAuthMessage(provider: SCAuthorizationProviderType, action: IAuthAction) { let message: string | undefined; switch (action.action) { case AuthActions.SignInSuccess: { @@ -108,16 +105,11 @@ export class AuthHelperService { return originPath; } - getProvider( - providerType: B, - ): B extends 'paia' ? PAIAAuthService : DefaultAuthService; /** * Provides appropriate auth service instance based on type (string) parameter */ - getProvider(providerType: SCAuthorizationProviderType): DefaultAuthService | PAIAAuthService { - return providerType === 'paia' - ? (this.paiaAuth as PAIAAuthService) - : (this.defaultAuth as DefaultAuthService); + getProvider(): DefaultAuthService { + return this.defaultAuth; } /** @@ -125,7 +117,7 @@ export class AuthHelperService { * @param providerType Type of the provider (e.g. 'default' or 'paia') */ async endBrowserSession(providerType: SCAuthorizationProviderType) { - const endSessionEndpoint = (await this.getProvider(providerType).configuration).endSessionEndpoint; + const endSessionEndpoint = (await this.getProvider().configuration).endSessionEndpoint; if (endSessionEndpoint) { const alert: HTMLIonAlertElement = await this.alertController.create({ diff --git a/frontend/app/src/app/modules/auth/auth-routing.module.ts b/frontend/app/src/app/modules/auth/auth-routing.module.ts index a7a0c265..3f52edb0 100644 --- a/frontend/app/src/app/modules/auth/auth-routing.module.ts +++ b/frontend/app/src/app/modules/auth/auth-routing.module.ts @@ -17,19 +17,12 @@ import {RouterModule, Routes} from '@angular/router'; import {NgModule} from '@angular/core'; import {authPaths} from './auth-paths'; import {AuthCallbackPageComponent} from './auth-callback/page/auth-callback-page.component'; -import {DefaultAuthService} from './default-auth.service'; -import {PAIAAuthService} from './paia/paia-auth.service'; const authRoutes: Routes = [ { path: authPaths.default.redirect_path, component: AuthCallbackPageComponent, }, - { - path: authPaths.paia.redirect_path, - component: AuthCallbackPageComponent, - providers: [{provide: DefaultAuthService, useExisting: PAIAAuthService}], - }, ]; /** diff --git a/frontend/app/src/app/modules/auth/auth.module.ts b/frontend/app/src/app/modules/auth/auth.module.ts index 9b47b366..0fd9b82a 100644 --- a/frontend/app/src/app/modules/auth/auth.module.ts +++ b/frontend/app/src/app/modules/auth/auth.module.ts @@ -11,7 +11,6 @@ import {AuthRoutingModule} from './auth-routing.module'; import {TranslateModule} from '@ngx-translate/core'; import {AuthCallbackPageComponent} from './auth-callback/page/auth-callback-page.component'; import {DefaultAuthService} from './default-auth.service'; -import {PAIAAuthService} from './paia/paia-auth.service'; @NgModule({ declarations: [AuthCallbackPageComponent], @@ -32,7 +31,6 @@ import {PAIAAuthService} from './paia/paia-auth.service'; useClass: CapacitorBrowser, }, DefaultAuthService, - PAIAAuthService, ], }) export class AuthModule {} diff --git a/frontend/app/src/app/modules/auth/paia/authorization-request-handler.ts b/frontend/app/src/app/modules/auth/paia/authorization-request-handler.ts deleted file mode 100644 index 5c85b329..00000000 --- a/frontend/app/src/app/modules/auth/paia/authorization-request-handler.ts +++ /dev/null @@ -1,197 +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 { - StorageBackend, - BasicQueryStringUtils, - DefaultCrypto, - AuthorizationServiceConfiguration, - AuthorizationRequest, - StringMap, - AuthorizationError, - AuthorizationErrorJson, - BUILT_IN_PARAMETERS, -} from '@openid/appauth'; -import {Browser} from 'ionic-appauth'; -import {PAIAAuthorizationNotifier} from './paia-authorization-notifier'; -import {PAIAAuthorizationRequestResponse} from './authorization-request-response'; -import {PAIAAuthorizationResponse, PAIAAuthorizationResponseJson} from './paia-authorization-response'; - -/** key for authorization request. */ -const authorizationRequestKey = (handle: string) => { - return `${handle}_appauth_authorization_request`; -}; - -/** key in local storage which represents the current authorization request. */ -const AUTHORIZATION_REQUEST_HANDLE_KEY = 'appauth_current_authorization_request'; -export const AUTHORIZATION_RESPONSE_KEY = 'auth_response'; - -export class PAIAAuthorizationRequestHandler { - notifier: PAIAAuthorizationNotifier; - - constructor( - private browser: Browser, - private storage: StorageBackend, - public utils = new BasicQueryStringUtils(), - protected crypto = new Crypto(), - private generateRandom = new DefaultCrypto(), - ) {} - - public async performAuthorizationRequest( - configuration: AuthorizationServiceConfiguration, - request: AuthorizationRequest, - ): Promise { - const handle = this.generateRandom.generateRandom(10); - await this.storage.setItem(AUTHORIZATION_REQUEST_HANDLE_KEY, handle); - await this.storage.setItem(authorizationRequestKey(handle), JSON.stringify(await request.toJson())); - const url = this.buildRequestUrl(configuration, request); - const returnedUrl: string | undefined = await this.browser.showWindow(url, request.redirectUri); - - // callback may come from showWindow or via another method - if (returnedUrl !== undefined) { - await this.storage.setItem(AUTHORIZATION_RESPONSE_KEY, returnedUrl); - await this.completeAuthorizationRequestIfPossible(); - } - } - - protected async completeAuthorizationRequest(): Promise { - const handle = await this.storage.getItem(AUTHORIZATION_REQUEST_HANDLE_KEY); - - if (!handle) { - throw new Error('Handle Not Available'); - } - - const request: AuthorizationRequest = this.getAuthorizationRequest( - await this.storage.getItem(authorizationRequestKey(handle)), - ); - const queryParameters = this.getQueryParams(await this.storage.getItem(AUTHORIZATION_RESPONSE_KEY)); - void this.removeItemsFromStorage(handle); - - const state: string | undefined = queryParameters['state']; - const error: string | undefined = queryParameters['error']; - - if (state !== request.state) { - throw new Error('State Does Not Match'); - } - - return { - request: request, // request - response: error ? undefined : this.getAuthorizationResponse(queryParameters), - error: error ? this.getAuthorizationError(queryParameters) : undefined, - }; - } - - private getAuthorizationRequest(authRequest: string | null): AuthorizationRequest { - if (authRequest == undefined) { - throw new Error('No Auth Request Available'); - } - - return new AuthorizationRequest(JSON.parse(authRequest)); - } - - private getAuthorizationError(queryParameters: StringMap): AuthorizationError { - const authorizationErrorJSON: AuthorizationErrorJson = { - error: queryParameters['error'], - error_description: queryParameters['error_description'], - error_uri: undefined, - state: queryParameters['state'], - }; - return new AuthorizationError(authorizationErrorJSON); - } - - private getAuthorizationResponse(queryParameters: StringMap): PAIAAuthorizationResponse { - const authorizationResponseJSON: PAIAAuthorizationResponseJson = { - code: queryParameters['code'], - patron: queryParameters['patron'], - // TODO: currently PAIA is not providing state - state: queryParameters['state'] ?? '', - }; - return new PAIAAuthorizationResponse(authorizationResponseJSON); - } - - private async removeItemsFromStorage(handle: string): Promise { - await this.storage.removeItem(AUTHORIZATION_REQUEST_HANDLE_KEY); - await this.storage.removeItem(authorizationRequestKey(handle)); - await this.storage.removeItem(AUTHORIZATION_RESPONSE_KEY); - } - - private getQueryParams(authResponse: string | null): StringMap { - if (authResponse == undefined) { - return {}; - } else { - const querySide: string = authResponse.split('#')[0]; - const parts: string[] = querySide.split('?'); - if (parts.length !== 2) throw new Error('Invalid auth response string'); - const hash = parts[1]; - return this.utils.parseQueryString(hash); - } - } - - setAuthorizationNotifier(notifier: PAIAAuthorizationNotifier): PAIAAuthorizationRequestHandler { - this.notifier = notifier; - return this; - } - - completeAuthorizationRequestIfPossible(): Promise { - // call complete authorization if possible to see there might - // be a response that needs to be delivered. - console.log(`Checking to see if there is an authorization response to be delivered.`); - if (!this.notifier) { - console.log(`Notifier is not present on AuthorizationRequest handler. - No delivery of result will be possible`); - } - return this.completeAuthorizationRequest().then(result => { - if (!result) { - console.log(`No result is available yet.`); - } - if (result && this.notifier) { - this.notifier.onAuthorizationComplete(result.request, result.response, result.error); - } - }); - } - - /** - * A utility method to be able to build the authorization request URL. - */ - protected buildRequestUrl(configuration: AuthorizationServiceConfiguration, request: AuthorizationRequest) { - // build the query string - // coerce to any type for convenience - const requestMap: StringMap = { - redirect_uri: request.redirectUri, - client_id: request.clientId, - response_type: request.responseType, - state: request.state, - scope: request.scope, - }; - - // copy over extras - if (request.extras) { - for (const extra in request.extras) { - if ( - request.extras.hasOwnProperty(extra) && // check before inserting to requestMap - !BUILT_IN_PARAMETERS.includes(extra) - ) { - requestMap[extra] = request.extras[extra]; - } - } - } - - const query = this.utils.stringify(requestMap); - const baseUrl = configuration.authorizationEndpoint; - - // required encoding (PAIA specific) - return `${baseUrl}?${encodeURIComponent(query)}`; - } -} diff --git a/frontend/app/src/app/modules/auth/paia/authorization-request-response.ts b/frontend/app/src/app/modules/auth/paia/authorization-request-response.ts deleted file mode 100644 index d9dd73ac..00000000 --- a/frontend/app/src/app/modules/auth/paia/authorization-request-response.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021 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 {AuthorizationError, AuthorizationRequest} from '@openid/appauth'; -import {PAIAAuthorizationResponse} from './paia-authorization-response'; - -/** - * Represents a structural type holding both authorization request and response. - */ -export interface PAIAAuthorizationRequestResponse { - request: AuthorizationRequest; - response: PAIAAuthorizationResponse | null; - error: AuthorizationError | null; -} diff --git a/frontend/app/src/app/modules/auth/paia/paia-auth-action.ts b/frontend/app/src/app/modules/auth/paia/paia-auth-action.ts deleted file mode 100644 index ec8cae9d..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-auth-action.ts +++ /dev/null @@ -1,75 +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 {PAIATokenResponse} from './paia-token-response'; -import {AuthActionBuilder, IAuthAction} from 'ionic-appauth'; - -export interface IPAIAAuthAction extends IAuthAction { - tokenResponse?: PAIATokenResponse; -} - -export class PAIAAuthActionBuilder extends AuthActionBuilder { - public static Init(): IPAIAAuthAction { - return AuthActionBuilder.Init() as IPAIAAuthAction; - } - - public static SignOutSuccess(): IPAIAAuthAction { - return AuthActionBuilder.SignOutSuccess() as IPAIAAuthAction; - } - - public static SignOutFailed(error: Error): IPAIAAuthAction { - return AuthActionBuilder.SignOutFailed(error) as IPAIAAuthAction; - } - - public static RefreshSuccess(tokenResponse: PAIATokenResponse): IPAIAAuthAction { - return AuthActionBuilder.RefreshSuccess(tokenResponse) as IPAIAAuthAction; - } - - public static RefreshFailed(error: Error): IPAIAAuthAction { - return AuthActionBuilder.RefreshFailed(error) as IPAIAAuthAction; - } - - public static SignInSuccess(tokenResponse: PAIATokenResponse): IPAIAAuthAction { - return AuthActionBuilder.SignInSuccess(tokenResponse) as IPAIAAuthAction; - } - - public static SignInFailed(error: Error): IPAIAAuthAction { - return AuthActionBuilder.SignInFailed(error) as IPAIAAuthAction; - } - - public static LoadTokenFromStorageSuccess(tokenResponse: PAIATokenResponse): IPAIAAuthAction { - return AuthActionBuilder.LoadTokenFromStorageSuccess(tokenResponse) as IPAIAAuthAction; - } - - public static LoadTokenFromStorageFailed(error: Error): IPAIAAuthAction { - return AuthActionBuilder.LoadTokenFromStorageFailed(error) as IPAIAAuthAction; - } - - public static RevokeTokensSuccess(): IPAIAAuthAction { - return AuthActionBuilder.RevokeTokensSuccess() as IPAIAAuthAction; - } - - public static RevokeTokensFailed(error: Error): IPAIAAuthAction { - return AuthActionBuilder.RevokeTokensFailed(error) as IPAIAAuthAction; - } - - public static LoadUserInfoSuccess(user: Error): IPAIAAuthAction { - return AuthActionBuilder.LoadUserInfoSuccess(user) as IPAIAAuthAction; - } - - public static LoadUserInfoFailed(error: Error): IPAIAAuthAction { - return AuthActionBuilder.LoadUserInfoFailed(error) as IPAIAAuthAction; - } -} 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 deleted file mode 100644 index 6de1bf6a..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-auth.service.ts +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright (C) 2023 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 { - AuthorizationError, - AuthorizationRequest, - AuthorizationRequestJson, - AuthorizationServiceConfiguration, - BasicQueryStringUtils, - DefaultCrypto, - JQueryRequestor, - LocalStorageBackend, - Requestor, - StorageBackend, - StringMap, - TokenResponse, -} from '@openid/appauth'; -import { - AuthActions, - AUTHORIZATION_RESPONSE_KEY, - AuthSubject, - Browser, - DefaultBrowser, - EndSessionHandler, - IAuthConfig, - IonicEndSessionHandler, - IonicUserInfoHandler, - UserInfoHandler, -} from 'ionic-appauth'; -import {BehaviorSubject, Observable} from 'rxjs'; -import {PAIATokenRequestHandler} from './token-request-handler'; -import {PAIAAuthorizationRequestHandler} from './authorization-request-handler'; -import {PAIATokenRequest, PAIATokenRequestJson} from './paia-token-request'; -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'; - -const TOKEN_RESPONSE_KEY = 'paia_token_response'; -const AUTH_EXPIRY_BUFFER = 10 * 60 * -1; // 10 minutes in seconds - -export interface IAuthService { - signIn(authExtras?: StringMap, state?: string): void; - signOut(state?: string, revokeTokens?: boolean): void; - loadUserInfo(): void; - authorizationCallback(callbackUrl: string): void; - loadTokenFromStorage(): void; - getValidToken(buffer?: number): Promise; -} - -@Injectable({ - providedIn: 'root', -}) -export class PAIAAuthService { - private _authConfig?: IAuthConfig; - - private _authSubject: AuthSubject = new AuthSubject(); - - private _authSubjectV2 = new BehaviorSubject(PAIAAuthActionBuilder.Init()); - - private _tokenSubject = new BehaviorSubject(undefined); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _userSubject = new BehaviorSubject(undefined); - - private _authenticatedSubject = new BehaviorSubject(false); - - private _loggedIn = new BehaviorSubject(false); - - private _initComplete = new BehaviorSubject(false); - - protected tokenHandler: PAIATokenRequestHandler; - - protected userInfoHandler: UserInfoHandler; - - protected requestHandler: PAIAAuthorizationRequestHandler; - - protected endSessionHandler: EndSessionHandler; - - public localConfiguration: AuthorizationServiceConfiguration; - - constructor( - protected browser: Browser = new DefaultBrowser(), - protected storage: StorageBackend = new LocalStorageBackend(), - protected requestor: Requestor = new JQueryRequestor(), - private readonly configProvider: ConfigProvider, - ) { - this.tokenHandler = new PAIATokenRequestHandler(requestor); - this.userInfoHandler = new IonicUserInfoHandler(requestor); - this.requestHandler = new PAIAAuthorizationRequestHandler( - browser, - storage, - new BasicQueryStringUtils(), - crypto, - ); - this.endSessionHandler = new IonicEndSessionHandler(browser); - } - - get token$(): Observable { - return this._tokenSubject.asObservable(); - } - - get isAuthenticated$(): Observable { - return this._authenticatedSubject.asObservable(); - } - - /** - * Similar to isAuthenticated$, but will also return true if the token is expired - */ - get isLoggedIn$(): Observable { - return this._loggedIn.asObservable(); - } - - get initComplete$(): Observable { - return this._initComplete.asObservable(); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get user$(): Observable { - return this._userSubject.asObservable(); - } - - get events$(): Observable { - return this._authSubjectV2.asObservable(); - } - - get authConfig(): IAuthConfig { - if (!this._authConfig) throw new Error('AuthConfig Not Defined'); - - return this._authConfig; - } - - set authConfig(value: IAuthConfig) { - this._authConfig = value; - } - - get configuration(): Promise { - if (!this.localConfiguration) throw new Error('Local Configuration Not Defined'); - - 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) { - case AuthActions.SignInFailed: - case AuthActions.SignOutSuccess: - case AuthActions.SignOutFailed: { - this._tokenSubject.next(undefined); - this._userSubject.next(undefined); - this._authenticatedSubject.next(false); - this._loggedIn.next(false); - break; - } - case AuthActions.LoadTokenFromStorageFailed: { - this._tokenSubject.next(undefined); - this._userSubject.next(undefined); - this._authenticatedSubject.next(false); - this._loggedIn.next(false); - this._initComplete.next(true); - break; - } - case AuthActions.SignInSuccess: { - this._tokenSubject.next(action.tokenResponse); - this._authenticatedSubject.next(true); - this._loggedIn.next(true); - break; - } - case AuthActions.LoadTokenFromStorageSuccess: { - this._tokenSubject.next(action.tokenResponse); - this._authenticatedSubject.next((action.tokenResponse as TokenResponse).isValid(0)); - this._loggedIn.next(true); - this._initComplete.next(true); - break; - } - case AuthActions.RevokeTokensSuccess: { - this._tokenSubject.next(undefined); - break; - } - case AuthActions.LoadUserInfoSuccess: { - this._userSubject.next(action.user); - break; - } - case AuthActions.LoadUserInfoFailed: { - this._userSubject.next(undefined); - break; - } - } - - this._authSubjectV2.next(action); - this._authSubject.notify(action); - } - - protected setupAuthorizationNotifier() { - const notifier = new PAIAAuthorizationNotifier(); - this.requestHandler.setAuthorizationNotifier(notifier); - notifier.setAuthorizationListener((request, response, error) => - this.onAuthorizationNotification(request, response, error), - ); - } - - protected onAuthorizationNotification( - request: AuthorizationRequest, - response: PAIAAuthorizationResponse | null, - error: AuthorizationError | null, - ) { - const codeVerifier: string | undefined = - request.internal != undefined && this.authConfig.pkce ? request.internal.code_verifier : undefined; - - if (response != undefined) { - this.requestAccessToken(response.code, response.patron, codeVerifier); - } else if (error == undefined) { - throw new Error('Unknown Error With Authentication'); - } else { - throw new Error(error.errorDescription); - } - } - - protected async internalAuthorizationCallback(url: string) { - this.browser.closeWindow(); - await this.storage.setItem(AUTHORIZATION_RESPONSE_KEY, url); - return this.requestHandler.completeAuthorizationRequestIfPossible(); - } - - protected async performAuthorizationRequest(authExtras?: StringMap, state?: string): Promise { - const requestJson: AuthorizationRequestJson = { - response_type: AuthorizationRequest.RESPONSE_TYPE_CODE, - client_id: this.authConfig.client_id, - redirect_uri: this.authConfig.redirect_url, - scope: this.authConfig.scopes, - extras: authExtras, - state: state || undefined, - }; - - const request = new AuthorizationRequest(requestJson, new DefaultCrypto(), this.authConfig.pkce); - - if (this.authConfig.pkce) await request.setupCodeVerifier(); - - return this.requestHandler.performAuthorizationRequest(await this.configuration, request); - } - - protected async requestAccessToken(code: string, patron: string, codeVerifier?: string): Promise { - const requestJSON: PAIATokenRequestJson = { - code: code, - patron: patron, - extras: codeVerifier - ? { - code_verifier: codeVerifier, - } - : {}, - }; - - const token: PAIATokenResponse = await this.tokenHandler.performTokenRequest( - await this.configuration, - new PAIATokenRequest(requestJSON), - ); - await this.storage.setItem(TOKEN_RESPONSE_KEY, JSON.stringify(token.toJson())); - this.notifyActionListers(PAIAAuthActionBuilder.SignInSuccess(token)); - } - - public async revokeTokens() { - // Note: only locally - await this.storage.removeItem(TOKEN_RESPONSE_KEY); - this.notifyActionListers(PAIAAuthActionBuilder.RevokeTokensSuccess()); - } - - public async signOut() { - await this.revokeTokens().catch(error => - this.notifyActionListers(PAIAAuthActionBuilder.SignOutFailed(error)), - ); - this.notifyActionListers(PAIAAuthActionBuilder.SignOutSuccess()); - } - - protected async internalLoadTokenFromStorage() { - let token: PAIATokenResponse | undefined; - const tokenResponseString: string | null = await this.storage.getItem(TOKEN_RESPONSE_KEY); - - if (tokenResponseString != undefined) { - token = new PAIATokenResponse(JSON.parse(tokenResponseString)); - - if (token) { - return this.notifyActionListers(PAIAAuthActionBuilder.LoadTokenFromStorageSuccess(token)); - } - } - - throw new Error('No Token In Storage'); - } - - protected async internalRequestUserInfo() { - if (this._tokenSubject.value) { - const userInfo = await this.userInfoHandler.performUserInfoRequest( - await this.configuration, - this._tokenSubject.value, - ); - this.notifyActionListers(PAIAAuthActionBuilder.LoadUserInfoSuccess(userInfo)); - } else { - throw new Error('No Token Available'); - } - } - - public async loadTokenFromStorage() { - await this.internalLoadTokenFromStorage().catch(error => { - this.notifyActionListers(PAIAAuthActionBuilder.LoadTokenFromStorageFailed(error)); - }); - } - - public async signIn(authExtras?: StringMap, state?: string) { - await this.performAuthorizationRequest(authExtras, state).catch(error => { - this.notifyActionListers(PAIAAuthActionBuilder.SignInFailed(error)); - }); - } - - public async loadUserInfo() { - await this.internalRequestUserInfo().catch(error => { - this.notifyActionListers(PAIAAuthActionBuilder.LoadUserInfoFailed(error)); - }); - } - - public authorizationCallback(callbackUrl: string): void { - this.internalAuthorizationCallback(callbackUrl).catch(error => { - this.notifyActionListers(PAIAAuthActionBuilder.SignInFailed(error)); - }); - } - - public async getValidToken(buffer: number = AUTH_EXPIRY_BUFFER): Promise { - if (this._tokenSubject.value && this._tokenSubject.value.isValid(buffer)) { - return this._tokenSubject.value; - } - - const error = new Error('Unable To Obtain Valid Token'); - this.notifyActionListers(PAIAAuthActionBuilder.SignInFailed(error)); - - throw error; - } -} diff --git a/frontend/app/src/app/modules/auth/paia/paia-authorization-listener.ts b/frontend/app/src/app/modules/auth/paia/paia-authorization-listener.ts deleted file mode 100644 index c2d346d0..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-authorization-listener.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2021 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 {AuthorizationError, AuthorizationRequest} from '@openid/appauth'; -import {PAIAAuthorizationResponse} from './paia-authorization-response'; - -export type PAIAAuthorizationListener = ( - request: AuthorizationRequest, - response: PAIAAuthorizationResponse | null, - error: AuthorizationError | null, -) => void; diff --git a/frontend/app/src/app/modules/auth/paia/paia-authorization-notifier.ts b/frontend/app/src/app/modules/auth/paia/paia-authorization-notifier.ts deleted file mode 100644 index 4b2b5613..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-authorization-notifier.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021 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 {PAIAAuthorizationListener} from './paia-authorization-listener'; -import {AuthorizationError, AuthorizationRequest} from '@openid/appauth'; -import {PAIAAuthorizationResponse} from './paia-authorization-response'; - -export class PAIAAuthorizationNotifier { - // eslint-disable-next-line unicorn/no-null - private listener: PAIAAuthorizationListener | null = null; - - setAuthorizationListener(listener: PAIAAuthorizationListener) { - this.listener = listener; - } - - /** - * The authorization complete callback. - */ - onAuthorizationComplete( - request: AuthorizationRequest, - response: PAIAAuthorizationResponse | null, - error: AuthorizationError | null, - ): void { - if (this.listener) { - // complete authorization request - this.listener(request, response, error); - } - } -} diff --git a/frontend/app/src/app/modules/auth/paia/paia-authorization-response.ts b/frontend/app/src/app/modules/auth/paia/paia-authorization-response.ts deleted file mode 100644 index e71999dd..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-authorization-response.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 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 . - */ - -export interface PAIAAuthorizationResponseJson { - code: string; - state: string; - patron: string; -} - -export class PAIAAuthorizationResponse { - code: string; - - state: string; - - patron: string; - - constructor(response: PAIAAuthorizationResponseJson) { - this.code = response.code; - this.state = response.state; - this.patron = response.patron; - } - - toJson(): PAIAAuthorizationResponseJson { - return {code: this.code, state: this.state, patron: this.patron}; - } -} diff --git a/frontend/app/src/app/modules/auth/paia/paia-token-request.ts b/frontend/app/src/app/modules/auth/paia/paia-token-request.ts deleted file mode 100644 index 4b12b2f8..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-token-request.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2021 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 {StringMap} from '@openid/appauth'; - -// TODO: add documentation -export interface PAIATokenRequestJson { - code: string; - patron: string; - extras?: StringMap; -} - -export class PAIATokenRequest { - code: string; - - patron: string; - - extras?: StringMap; - - constructor(request: PAIATokenRequestJson) { - this.code = request.code; - this.patron = request.patron; - this.extras = request.extras; - } - - /** - * Serializes a TokenRequest to a JavaScript object. - */ - toJson(): PAIATokenRequestJson { - return { - code: this.code, - patron: this.patron, - extras: this.extras, - }; - } - - toStringMap(): StringMap { - const map: StringMap = { - patron: this.patron, - code: this.code, - }; - - // copy over extras - if (this.extras) { - for (const extra in this.extras) { - if (this.extras.hasOwnProperty(extra) && !map.hasOwnProperty(extra)) { - // check before inserting to requestMap - map[extra] = this.extras[extra]; - } - } - } - return map; - } -} diff --git a/frontend/app/src/app/modules/auth/paia/paia-token-response.ts b/frontend/app/src/app/modules/auth/paia/paia-token-response.ts deleted file mode 100644 index f08ee811..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-token-response.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2021 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 {TokenResponse, TokenResponseJson} from '@openid/appauth'; -import {nowInSeconds} from '@openid/appauth'; - -const AUTH_EXPIRY_BUFFER = 10 * 60 * -1; // 10 mins in seconds - -export interface PAIATokenResponseJson extends TokenResponseJson { - patron: string; -} - -export class PAIATokenResponse extends TokenResponse { - patron: string; - - constructor(response: PAIATokenResponseJson) { - super(response); - this.patron = response.patron; - } - - toJson(): PAIATokenResponseJson { - return { - access_token: this.accessToken, - id_token: this.idToken, - refresh_token: this.refreshToken, - scope: this.scope, - token_type: this.tokenType, - issued_at: this.issuedAt, - expires_in: this.expiresIn?.toString(), - patron: this.patron, - }; - } - - isValid(buffer: number = AUTH_EXPIRY_BUFFER): boolean { - if (this.expiresIn) { - const now = nowInSeconds(); - return now < this.issuedAt + this.expiresIn + buffer; - } else { - return true; - } - } -} diff --git a/frontend/app/src/app/modules/auth/paia/paia-user-info-handler.ts b/frontend/app/src/app/modules/auth/paia/paia-user-info-handler.ts deleted file mode 100644 index 7ab2101f..00000000 --- a/frontend/app/src/app/modules/auth/paia/paia-user-info-handler.ts +++ /dev/null @@ -1,42 +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 {AuthorizationServiceConfiguration, Requestor} from '@openid/appauth'; -import {PAIATokenResponse} from './paia-token-response'; - -export interface UserInfoHandler { - performUserInfoRequest( - configuration: AuthorizationServiceConfiguration, - token: PAIATokenResponse, - ): Promise; -} - -export class PAIAUserInfoHandler implements UserInfoHandler { - constructor(private requestor: Requestor) {} - - public async performUserInfoRequest( - configuration: AuthorizationServiceConfiguration, - token: PAIATokenResponse, - ): Promise { - const settings: JQueryAjaxSettings = { - url: `${configuration.userInfoEndpoint}/${token.patron}`, - method: 'GET', - headers: { - Authorization: `${token.tokenType == 'bearer' ? 'Bearer' : token.tokenType} ${token.accessToken}`, - }, - }; - - return this.requestor.xhr(settings); - } -} diff --git a/frontend/app/src/app/modules/auth/paia/token-request-handler.ts b/frontend/app/src/app/modules/auth/paia/token-request-handler.ts deleted file mode 100644 index cf0da436..00000000 --- a/frontend/app/src/app/modules/auth/paia/token-request-handler.ts +++ /dev/null @@ -1,81 +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 { - TokenErrorJson, - AuthorizationServiceConfiguration, - RevokeTokenRequest, - Requestor, - JQueryRequestor, - BasicQueryStringUtils, - QueryStringUtils, - AppAuthError, - TokenError, -} from '@openid/appauth'; -import {PAIATokenRequest} from './paia-token-request'; -import {PAIATokenResponse, PAIATokenResponseJson} from './paia-token-response'; - -export class PAIATokenRequestHandler { - constructor( - public readonly requestor: Requestor = new JQueryRequestor(), - public readonly utils: QueryStringUtils = new BasicQueryStringUtils(), - ) {} - - private isTokenResponse( - response: PAIATokenResponseJson | TokenErrorJson, - ): response is PAIATokenResponseJson { - return (response as TokenErrorJson).error === undefined; - } - - performRevokeTokenRequest( - configuration: AuthorizationServiceConfiguration, - request: RevokeTokenRequest, - ): Promise { - const revokeTokenResponse = this.requestor.xhr({ - url: configuration.revocationEndpoint, - method: 'GET', - // headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - data: this.utils.stringify(request.toStringMap()), - }); - - return revokeTokenResponse.then(_response => { - return true; - }); - } - - performTokenRequest( - configuration: AuthorizationServiceConfiguration, - request: PAIATokenRequest, - ): Promise { - const tokenResponse = this.requestor.xhr({ - url: configuration.tokenEndpoint, - method: 'POST', - data: { - patron: request.patron, - grant_type: 'authorization_code', - ...request.toStringMap(), - }, - headers: { - 'Authorization': `Basic ${request.code}`, - 'Content-Type': 'application/json', - }, - }); - - return tokenResponse.then(response => { - return this.isTokenResponse(response) - ? new PAIATokenResponse(response) - : Promise.reject(new AppAuthError(response.error, new TokenError(response))); - }); - } -} diff --git a/frontend/app/src/app/modules/data/data.module.ts b/frontend/app/src/app/modules/data/data.module.ts index 498e7418..7d497c8b 100644 --- a/frontend/app/src/app/modules/data/data.module.ts +++ b/frontend/app/src/app/modules/data/data.module.ts @@ -114,10 +114,6 @@ import {SkeletonListComponent} from './list/skeleton-list.component'; import {TreeListFragmentComponent} from './list/tree-list-fragment.component'; import {TreeListComponent} from './list/tree-list.component'; import {StAppsWebHttpClient} from './stapps-web-http-client.provider'; -import {ArticleDetailContentComponent} from './types/article/article-detail-content.component'; -import {ArticleListItemComponent} from './types/article/article-item.component'; -import {BookDetailContentComponent} from './types/book/book-detail-content.component'; -import {BookListItemComponent} from './types/book/book-list-item.component'; import {CatalogDetailContentComponent} from './types/catalog/catalog-detail-content.component'; import {CatalogListItemComponent} from './types/catalog/catalog-list-item.component'; import {DateSeriesDetailContentComponent} from './types/date-series/date-series-detail-content.component'; @@ -136,8 +132,6 @@ import {MessageDetailContentComponent} from './types/message/message-detail-cont import {MessageListItemComponent} from './types/message/message-list-item.component'; import {OrganizationDetailContentComponent} from './types/organization/organization-detail-content.component'; import {OrganizationListItemComponent} from './types/organization/organization-list-item.component'; -import {PeriodicalDetailContentComponent} from './types/periodical/periodical-detail-content.component'; -import {PeriodicalListItemComponent} from './types/periodical/periodical-list-item.component'; import {PersonDetailContentComponent} from './types/person/person-detail-content.component'; import {PersonListItemComponent} from './types/person/person-list-item.component'; import {PlaceDetailContentComponent} from './types/place/place-detail-content.component'; @@ -216,12 +210,6 @@ import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive'; SimpleDataListComponent, TitleCardComponent, ExternalLinkComponent, - ArticleListItemComponent, - ArticleDetailContentComponent, - BookListItemComponent, - BookDetailContentComponent, - PeriodicalListItemComponent, - PeriodicalDetailContentComponent, ShareButtonComponent, ], exports: [ @@ -241,9 +229,6 @@ import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive'; FavoriteButtonComponent, TreeListComponent, ExternalLinkComponent, - ArticleDetailContentComponent, - BookDetailContentComponent, - PeriodicalDetailContentComponent, TitleCardComponent, ], imports: [ diff --git a/frontend/app/src/app/modules/data/detail/data-detail-content.html b/frontend/app/src/app/modules/data/detail/data-detail-content.html index 5e0ff24b..e2fa527b 100644 --- a/frontend/app/src/app/modules/data/detail/data-detail-content.html +++ b/frontend/app/src/app/modules/data/detail/data-detail-content.html @@ -33,9 +33,6 @@
@switch (item.type) { - @case ('article') { - - } @case ('catalog') { } diff --git a/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts b/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts index 7b839536..b70db932 100644 --- a/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts +++ b/frontend/app/src/app/modules/data/list/data-list-item-host.directive.ts @@ -14,7 +14,6 @@ */ import {ComponentRef, Directive, Input, Type, ViewContainerRef} from '@angular/core'; import {SCThings, SCThingType} from '@openstapps/core'; -import {BookListItemComponent} from '../types/book/book-list-item.component'; import {CatalogListItemComponent} from '../types/catalog/catalog-list-item.component'; import {DateSeriesListItemComponent} from '../types/date-series/date-series-list-item.component'; import {EventListItemComponent} from '../types/event/event-list-item.component'; @@ -25,9 +24,7 @@ import {PersonListItemComponent} from '../types/person/person-list-item.componen import {PlaceListItemComponent} from '../types/place/place-list-item.component'; import {SemesterListItemComponent} from '../types/semester/semester-list-item.component'; import {VideoListItemComponent} from '../types/video/video-list-item.component'; -import {PeriodicalListItemComponent} from '../types/periodical/periodical-list-item.component'; import {DataListItemHostDefaultComponent} from './data-list-item-host-default.component'; -import {ArticleListItemComponent} from '../types/article/article-item.component'; import {DishListItemComponent} from '../types/dish/dish-list-item.component'; import {JobPostingListItemComponent} from '../types/job-posting/job-posting-list-item.component'; @@ -51,9 +48,6 @@ const DataListItemIndex: Partial>> = { [SCThingType.Room]: PlaceListItemComponent, [SCThingType.Semester]: SemesterListItemComponent, [SCThingType.Video]: VideoListItemComponent, - [SCThingType.Periodical]: PeriodicalListItemComponent, - [SCThingType.Book]: BookListItemComponent, - [SCThingType.Article]: ArticleListItemComponent, [SCThingType.JobPosting]: JobPostingListItemComponent, }; diff --git a/frontend/app/src/app/modules/data/list/food-data-list.html b/frontend/app/src/app/modules/data/list/food-data-list.html index ef5be677..fe3aba51 100644 --- a/frontend/app/src/app/modules/data/list/food-data-list.html +++ b/frontend/app/src/app/modules/data/list/food-data-list.html @@ -1,6 +1,5 @@ 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 9d41c9de..1958dd88 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 @@ -22,14 +22,7 @@ import { ModalController, } from '@ionic/angular/standalone'; 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'; @@ -67,14 +60,6 @@ export class SearchPageComponent implements OnInit { */ loading = false; - /** - * Navigation elements between search pages - */ - @Input() navigation: Array<{label: string; routerLink?: string[]}> = [ - {label: 'search.type'}, - {label: 'hebisSearch.type', routerLink: ['/hebis-search']}, - ]; - /** * Show default data (e.g. when there is user interaction) */ @@ -353,17 +338,6 @@ export class SearchPageComponent implements OnInit { } }); } - try { - // TODO: make this hack more generic - if (this.navigation[1]?.routerLink?.[0] === '/hebis-search') { - const features = this.configProvider.getValue('features') as SCFeatureConfiguration; - if (features.plugins?.['hebis-plugin']?.urlPath === undefined) { - this.navigation = []; - } - } - } catch (error) { - this.logger.error(error); - } } /** diff --git a/frontend/app/src/app/modules/data/list/search-page.html b/frontend/app/src/app/modules/data/list/search-page.html index e45a8748..4276520c 100644 --- a/frontend/app/src/app/modules/data/list/search-page.html +++ b/frontend/app/src/app/modules/data/list/search-page.html @@ -43,26 +43,6 @@ } - @if (navigation.length > 0) { - - - @for (target of navigation; track target) { - @if (target.routerLink) { - {{ target.label | translate }} - - } @else { - {{ target.label | translate }} - } - } - - - } diff --git a/frontend/app/src/app/modules/data/types/article/article-detail-content.component.ts b/frontend/app/src/app/modules/data/types/article/article-detail-content.component.ts deleted file mode 100644 index 2d9b0f2b..00000000 --- a/frontend/app/src/app/modules/data/types/article/article-detail-content.component.ts +++ /dev/null @@ -1,24 +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 {Component, Input} from '@angular/core'; -import {SCArticle} from '@openstapps/core'; - -@Component({ - selector: 'stapps-article-detail-content', - templateUrl: 'article-detail-content.html', -}) -export class ArticleDetailContentComponent { - @Input() item: SCArticle; -} diff --git a/frontend/app/src/app/modules/data/types/article/article-detail-content.html b/frontend/app/src/app/modules/data/types/article/article-detail-content.html deleted file mode 100644 index 47071efb..00000000 --- a/frontend/app/src/app/modules/data/types/article/article-detail-content.html +++ /dev/null @@ -1,105 +0,0 @@ - - -@if (item.sameAs) { - - {{ 'hebisSearch.detail.title' | translate | sentencecase }} - - - - -} -@if (!item.sameAs) { - - -} -@if (item.description) { - -} -@if (item.sourceOrganization) { - -} -@if (item.authors && item.authors.length > 0) { - - {{ 'authors' | propertyNameTranslate: item | sentencecase }} - - @for (author of item.authors; track author) { - {{ 'name' | thingTranslate: author }} - } - - -} -@if (item.firstPublished && !item.lastPublished) { - - -} -@if (item.firstPublished && item.lastPublished) { - - -} -@if (item.publications) { - - {{ 'publications' | propertyNameTranslate: item | sentencecase }} - - @for (publication of item.publications; track publication) { -

- {{ publication.locations | join: ', ' }} - {{ publication.locations && publication.publisher ? ':' : '' }} {{ publication.publisher }} -

- } -
-
-} -@if (item.reference) { - -} -@if (item.isPartOf) { - -} -@if (item.categories) { - - {{ 'categories' | propertyNameTranslate: item | sentencecase }} - - - - {{ 'categories' | thingTranslate: item }} - - - -} diff --git a/frontend/app/src/app/modules/data/types/article/article-item.component.ts b/frontend/app/src/app/modules/data/types/article/article-item.component.ts deleted file mode 100644 index 391f37cd..00000000 --- a/frontend/app/src/app/modules/data/types/article/article-item.component.ts +++ /dev/null @@ -1,32 +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 {Component, Input} from '@angular/core'; -import {SCArticle} from '@openstapps/core'; -import {DataListItemComponent} from '../../list/data-list-item.component'; - -/** - * TODO - */ -@Component({ - selector: 'stapps-article-item', - templateUrl: 'article-list-item.html', - styleUrl: 'article-list-item.scss', -}) -export class ArticleListItemComponent extends DataListItemComponent { - /** - * TODO - */ - @Input() item: SCArticle; -} diff --git a/frontend/app/src/app/modules/data/types/article/article-list-item.html b/frontend/app/src/app/modules/data/types/article/article-list-item.html deleted file mode 100644 index f32c9065..00000000 --- a/frontend/app/src/app/modules/data/types/article/article-list-item.html +++ /dev/null @@ -1,32 +0,0 @@ - - -{{ 'name' | thingTranslate: item }} -

- @for (author of item.authors; track author) { - {{ 'name' | thingTranslate: author }} - } - @if (item.authors && item.authors && item.firstPublished) { - ,  - } - @if (item.firstPublished && !item.lastPublished) { - {{ item.firstPublished }} - } @else { - @if (item.firstPublished && item.lastPublished) { - {{ [item.firstPublished, item.lastPublished] | join: ' - ' }} - } - } -

- {{ 'categories' | thingTranslate: item }} diff --git a/frontend/app/src/app/modules/data/types/article/article-list-item.scss b/frontend/app/src/app/modules/data/types/article/article-list-item.scss deleted file mode 100644 index 5c3cde6d..00000000 --- a/frontend/app/src/app/modules/data/types/article/article-list-item.scss +++ /dev/null @@ -1,3 +0,0 @@ -p.title-sub { - white-space: unset; -} diff --git a/frontend/app/src/app/modules/data/types/book/book-detail-content.component.ts b/frontend/app/src/app/modules/data/types/book/book-detail-content.component.ts deleted file mode 100644 index 64d0ffbe..00000000 --- a/frontend/app/src/app/modules/data/types/book/book-detail-content.component.ts +++ /dev/null @@ -1,30 +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 {Component, Input} from '@angular/core'; -import {SCBook} from '@openstapps/core'; - -/** - * TODO - */ -@Component({ - selector: 'stapps-book-detail-content', - templateUrl: 'book-detail-content.html', -}) -export class BookDetailContentComponent { - /** - * TODO - */ - @Input() item: SCBook; -} diff --git a/frontend/app/src/app/modules/data/types/book/book-detail-content.html b/frontend/app/src/app/modules/data/types/book/book-detail-content.html deleted file mode 100644 index b8004814..00000000 --- a/frontend/app/src/app/modules/data/types/book/book-detail-content.html +++ /dev/null @@ -1,104 +0,0 @@ - - -@if (item.sameAs) { - - {{ 'hebisSearch.detail.title' | translate | sentencecase }} - - - - -} -@if (!item.sameAs) { - - -} -@if (item.edition) { - - -} -@if (item.description) { - -} -@if (item.sourceOrganization) { - -} -@if (item.authors && item.authors.length > 0) { - - {{ 'authors' | propertyNameTranslate: item | sentencecase }} - - @for (author of item.authors; track author) { - {{ 'name' | thingTranslate: author }} - } - - -} -@if (item.ISBNs) { - - -} -@if (item.firstPublished && !item.lastPublished) { - - -} -@if (item.firstPublished && item.lastPublished) { - - -} -@if (item.publications) { - - {{ 'publications' | propertyNameTranslate: item | sentencecase }} - - @for (publication of item.publications; track publication) { -

- {{ publication.locations | join: ', ' }} - {{ publication.locations && publication.publisher ? ':' : '' }} {{ publication.publisher }} -

- } -
-
-} -@if (item.categories) { - - {{ 'categories' | propertyNameTranslate: item | sentencecase }} - - - - {{ 'categories' | thingTranslate: item }} - - - -} diff --git a/frontend/app/src/app/modules/data/types/book/book-list-item.component.ts b/frontend/app/src/app/modules/data/types/book/book-list-item.component.ts deleted file mode 100644 index dd324058..00000000 --- a/frontend/app/src/app/modules/data/types/book/book-list-item.component.ts +++ /dev/null @@ -1,32 +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 {Component, Input} from '@angular/core'; -import {SCBook} from '@openstapps/core'; -import {DataListItemComponent} from '../../list/data-list-item.component'; - -/** - * TODO - */ -@Component({ - selector: 'stapps-book-list-item', - templateUrl: 'book-list-item.html', - styleUrl: 'book-list-item.scss', -}) -export class BookListItemComponent extends DataListItemComponent { - /** - * TODO - */ - @Input() item: SCBook; -} diff --git a/frontend/app/src/app/modules/data/types/book/book-list-item.html b/frontend/app/src/app/modules/data/types/book/book-list-item.html deleted file mode 100644 index f32c9065..00000000 --- a/frontend/app/src/app/modules/data/types/book/book-list-item.html +++ /dev/null @@ -1,32 +0,0 @@ - - -{{ 'name' | thingTranslate: item }} -

- @for (author of item.authors; track author) { - {{ 'name' | thingTranslate: author }} - } - @if (item.authors && item.authors && item.firstPublished) { - ,  - } - @if (item.firstPublished && !item.lastPublished) { - {{ item.firstPublished }} - } @else { - @if (item.firstPublished && item.lastPublished) { - {{ [item.firstPublished, item.lastPublished] | join: ' - ' }} - } - } -

- {{ 'categories' | thingTranslate: item }} diff --git a/frontend/app/src/app/modules/data/types/book/book-list-item.scss b/frontend/app/src/app/modules/data/types/book/book-list-item.scss deleted file mode 100644 index 5c3cde6d..00000000 --- a/frontend/app/src/app/modules/data/types/book/book-list-item.scss +++ /dev/null @@ -1,3 +0,0 @@ -p.title-sub { - white-space: unset; -} diff --git a/frontend/app/src/app/modules/data/types/periodical/periodical-detail-content.component.ts b/frontend/app/src/app/modules/data/types/periodical/periodical-detail-content.component.ts deleted file mode 100644 index 908e5d67..00000000 --- a/frontend/app/src/app/modules/data/types/periodical/periodical-detail-content.component.ts +++ /dev/null @@ -1,30 +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 {Component, Input} from '@angular/core'; -import {SCPeriodical} from '@openstapps/core'; - -/** - * TODO - */ -@Component({ - selector: 'stapps-periodical-detail-content', - templateUrl: 'periodical-detail-content.html', -}) -export class PeriodicalDetailContentComponent { - /** - * TODO - */ - @Input() item: SCPeriodical; -} diff --git a/frontend/app/src/app/modules/data/types/periodical/periodical-detail-content.html b/frontend/app/src/app/modules/data/types/periodical/periodical-detail-content.html deleted file mode 100644 index 03a7edc8..00000000 --- a/frontend/app/src/app/modules/data/types/periodical/periodical-detail-content.html +++ /dev/null @@ -1,100 +0,0 @@ - - -@if (item.sameAs) { - - {{ 'hebisSearch.detail.title' | translate | sentencecase }} - - - - -} -@if (!item.sameAs) { - - -} -@if (item.description) { - -} -@if (item.sourceOrganization) { - -} -@if (item.authors && item.authors.length > 0) { - - {{ 'authors' | propertyNameTranslate: item | sentencecase }} - - @for (author of item.authors; track author) { - {{ 'name' | thingTranslate: author }} - } - - -} -@if (item.firstPublished && !item.lastPublished) { - - -} -@if (item.firstPublished && item.lastPublished) { - - -} -@if (item.publications) { - - {{ 'publications' | propertyNameTranslate: item | sentencecase }} - - @for (publication of item.publications; track publication) { -

- {{ publication.locations | join: ', ' }} - {{ publication.locations && publication.publisher ? ':' : '' }} {{ publication.publisher }} -

- } -
-
-} -@if (item.categories) { - - {{ 'categories' | propertyNameTranslate: item | sentencecase }} - - - - {{ 'categories' | thingTranslate: item }} - - - -} -@if (item.ISSNs) { - - -} diff --git a/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.component.ts b/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.component.ts deleted file mode 100644 index 951ebe70..00000000 --- a/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.component.ts +++ /dev/null @@ -1,32 +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 {Component, Input} from '@angular/core'; -import {SCPeriodical} from '@openstapps/core'; -import {DataListItemComponent} from '../../list/data-list-item.component'; - -/** - * TODO - */ -@Component({ - selector: 'stapps-periodical-list-item', - templateUrl: 'periodical-list-item.html', - styleUrl: 'periodical-list-item.scss', -}) -export class PeriodicalListItemComponent extends DataListItemComponent { - /** - * TODO - */ - @Input() item: SCPeriodical; -} diff --git a/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.html b/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.html deleted file mode 100644 index f32c9065..00000000 --- a/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.html +++ /dev/null @@ -1,32 +0,0 @@ - - -{{ 'name' | thingTranslate: item }} -

- @for (author of item.authors; track author) { - {{ 'name' | thingTranslate: author }} - } - @if (item.authors && item.authors && item.firstPublished) { - ,  - } - @if (item.firstPublished && !item.lastPublished) { - {{ item.firstPublished }} - } @else { - @if (item.firstPublished && item.lastPublished) { - {{ [item.firstPublished, item.lastPublished] | join: ' - ' }} - } - } -

- {{ 'categories' | thingTranslate: item }} diff --git a/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.scss b/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.scss deleted file mode 100644 index 5c3cde6d..00000000 --- a/frontend/app/src/app/modules/data/types/periodical/periodical-list-item.scss +++ /dev/null @@ -1,3 +0,0 @@ -p.title-sub { - white-space: unset; -} 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 ba219155..75534fb4 100644 --- a/frontend/app/src/app/modules/favorites/favorites-page.component.ts +++ b/frontend/app/src/app/modules/favorites/favorites-page.component.ts @@ -18,7 +18,6 @@ import {ActivatedRoute, Router} from '@angular/router'; import {NGXLogger} from 'ngx-logger'; 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'; @@ -113,17 +112,7 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni .pipe(takeUntilDestroyed(this.destroy$)) .subscribe(item => { if (this.itemRouting) { - if ([SCThingType.Book, SCThingType.Periodical, SCThingType.Article].includes(item.type)) { - void this.router.navigate( - [ - 'hebis-detail', - (item.origin && 'originalId' in item.origin && item.origin['originalId']) || '', - ], - {state: {item}}, - ); - } else { - void this.router.navigate(['data-detail', item.uid], {state: {item}}); - } + void this.router.navigate(['data-detail', item.uid], {state: {item}}); } }); } diff --git a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.component.spec.ts b/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.component.spec.ts deleted file mode 100644 index 4b95cede..00000000 --- a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.component.spec.ts +++ /dev/null @@ -1,120 +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 . - */ - -/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */ -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {ActivatedRoute, RouterModule} from '@angular/router'; -import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core'; -import {sampleThingsMap} from '../../../_helpers/data/sample-things'; -import {HebisRoutingModule} from '../hebis-routing.module'; -import {HebisModule} from '../hebis.module'; -import {DaiaAvailabilityComponent} from './daia-availability.component'; -import {Observable, of} from 'rxjs'; -import {StorageProvider} from '../../storage/storage.provider'; -import {DaiaDataProvider} from '../daia-data.provider'; -import {ConfigProvider} from '../../config/config.provider'; -import {LoggerTestingModule} from 'ngx-logger/testing'; -import {provideIonicAngular} from '@ionic/angular/standalone'; - -const translations: any = {data: {detail: {TITLE: 'Foo'}}}; - -class TranslateFakeLoader implements TranslateLoader { - getTranslation(_lang: string): Observable { - return of(translations); - } -} - -describe('DaiaAvailabilityComponent', () => { - let comp: DaiaAvailabilityComponent; - let fixture: ComponentFixture; - let dataProvider: DaiaDataProvider; - const sampleThing = sampleThingsMap.book[0]; - let translateService: TranslateService; - let configProviderMock: jasmine.SpyObj; - - // @Component({ selector: 'stapps-data-list-item', template: '' }) - // class DataListItemComponent { - // @Input() item; - // } - - const fakeActivatedRoute = { - snapshot: { - paramMap: { - get: () => { - return sampleThing.uid; - }, - }, - }, - }; - - const storageProviderSpy = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put', 'search']); - - beforeEach(() => { - configProviderMock = jasmine.createSpyObj('ConfigProvider', ['init', 'getValue', 'getAnyValue']); - TestBed.configureTestingModule({ - imports: [ - RouterModule.forRoot([]), - HebisRoutingModule, - HebisModule, - TranslateModule.forRoot({ - loader: {provide: TranslateLoader, useClass: TranslateFakeLoader}, - }), - LoggerTestingModule, - ], - providers: [ - provideIonicAngular(), - { - provide: ActivatedRoute, - useValue: fakeActivatedRoute, - }, - { - provide: StorageProvider, - useValue: storageProviderSpy, - }, - { - provide: ConfigProvider, - useValue: configProviderMock, - }, - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); - }); - - beforeEach(async () => { - dataProvider = TestBed.inject(DaiaDataProvider); - const workingDAIAurl = 'https://daia.hebis.de/DAIA2/UB_Frankfurt'; - dataProvider.daiaServiceUrl = workingDAIAurl; - translateService = TestBed.inject(TranslateService); - spyOn(dataProvider, 'getAvailability' as any).and.returnValue(Promise.resolve([])); - spyOn(DaiaAvailabilityComponent.prototype, 'getAvailability').and.callThrough(); - fixture = await TestBed.createComponent(DaiaAvailabilityComponent); - comp = fixture.componentInstance; - translateService.use('foo'); - fixture.detectChanges(); - }); - - it('should create component', () => expect(comp).toBeDefined()); - - it('should get the availability of an item', () => { - comp.getAvailability(sampleThing.uid); - expect(DaiaAvailabilityComponent.prototype.getAvailability).toHaveBeenCalledWith(sampleThing.uid); - }); - - it('should get the availability of an item when the view is entered', () => { - comp.ngOnInit(); - expect(DaiaAvailabilityComponent.prototype.getAvailability).toHaveBeenCalledWith(sampleThing.uid); - }); -}); diff --git a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.component.ts b/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.component.ts deleted file mode 100644 index 89ec1fbe..00000000 --- a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.component.ts +++ /dev/null @@ -1,59 +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 {Component, inject, OnInit} from '@angular/core'; -import {SCUuid} from '@openstapps/core'; -import {DataDetailComponent} from '../../data/detail/data-detail.component'; -import {DaiaDataProvider} from '../daia-data.provider'; -import {DaiaHolding} from '../protocol/response'; -import {groupByStable} from '@openstapps/collection-utils'; - -/** - * A Component to display an SCThing detailed - */ -@Component({ - selector: 'stapps-daia-availability', - styleUrls: ['daia-availability.scss'], - templateUrl: 'daia-availability.html', -}) -export class DaiaAvailabilityComponent extends DataDetailComponent implements OnInit { - holdings?: DaiaHolding[]; - - holdingsByDepartments?: Map; - - private daiaDataProvider = inject(DaiaDataProvider); - - /** - * @override - */ - async ngOnInit() { - const uid = this.route.snapshot.paramMap.get('uid'); - if (uid) { - await this.getAvailability(uid); - } - } - - /** - * Provides data item with given UID - * @param uid Unique identifier of a thing - */ - async getAvailability(uid: SCUuid) { - this.daiaDataProvider.getAvailability(uid).then(holdings => { - if (holdings !== undefined) { - this.holdings = holdings; - this.holdingsByDepartments = groupByStable(holdings, holding => holding.department.id); - } - }); - } -} diff --git a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.html b/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.html deleted file mode 100644 index 01ce08fb..00000000 --- a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - {{ 'hebisSearch.daia.availability' | translate }} - - @if (holdingsByDepartments) { - @for (holdingsByDepartment of holdingsByDepartments | keyvalue; track holdingsByDepartment) { - - @if (holdingsByDepartment.value[0].department.href) { - - - } - - @for (holding of holdingsByDepartment.value; track holding) { - - } - } - } - @if (!holdings) { - {{ 'hebisSearch.daia.unavailableAvailability' | translate }} - } - @if (holdings?.length === 0) { - {{ 'hebisSearch.daia.unknownAvailability' | translate }} - } - - - diff --git a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.scss b/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.scss deleted file mode 100644 index f2c7a719..00000000 --- a/frontend/app/src/app/modules/hebis/daia-availability/daia-availability.scss +++ /dev/null @@ -1,34 +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 . - */ - -:host ion-card { - margin: 0; - box-shadow: none; - - ion-card-content { - padding-bottom: 8px; - - h1 { - margin: 0; - } - } - - ion-card-header { - padding-top: 8px; - padding-bottom: 4px; - font-weight: bold; - color: var(--ion-color-dark); - } -} diff --git a/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.component.ts b/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.component.ts deleted file mode 100644 index f3c84298..00000000 --- a/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 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 {Component, Input, OnInit} from '@angular/core'; -import {DaiaHolding} from '../protocol/response'; -import {DaiaDataProvider} from '../daia-data.provider'; - -@Component({ - selector: 'stapps-daia-holding', - templateUrl: './daia-holding.html', - styleUrls: ['./daia-holding.scss'], -}) -export class DaiaHoldingComponent implements OnInit { - @Input() holding: DaiaHolding; - - constructor(private daiaDataProvider: DaiaDataProvider) {} - - resourceLink?: string; - - ngOnInit(): void { - this.resourceLink = this.daiaDataProvider.getHoldingLink(this.holding, this.holding.open); - } -} diff --git a/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.html b/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.html deleted file mode 100644 index 91be2c3b..00000000 --- a/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.html +++ /dev/null @@ -1,89 +0,0 @@ - - - @if (holding.storage && holding.storage.content) { - - {{ 'hebisSearch.daia.location' | translate }} - - - } - @if (holding.signature && !holding.online) { - - {{ 'hebisSearch.daia.signature' | translate }} - {{ holding.signature }} - - } - @if (holding.holdings) { - - {{ 'hebisSearch.daia.holdings' | translate }} - {{ holding.holdings }} - - } - @if (holding.about) { - - {{ 'hebisSearch.daia.comment' | translate }} - - - } - @if (holding.available && holding.online && resourceLink) { - - {{ 'Online' }} - - - - } - @if (holding.status) { - - {{ 'hebisSearch.daia.status' | translate }} - - @if (holding.status === 'available') { - - } - @if (holding.status === 'library_only') { - - } - @if (['checked_out', 'not_yet_available', 'not_available'].indexOf(holding.status) > -1) { - - } - {{ 'hebisSearch.daia.status_states' + '.' + holding.status | translate }} - @if ( - (holding.status === 'available' || holding.status === 'library_only') && - holding.available && - holding.available.href - ) { - - - } - @if (holding.status === 'checked_out' && holding.unavailable && holding.unavailable.href) { - - - } - - - } - @if (holding.dueDate) { - - {{ 'hebisSearch.daia.dueDate' | translate }} - {{ holding.dueDate | amDateFormat: 'll' }} - - } - diff --git a/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.scss b/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.scss deleted file mode 100644 index 9a43f436..00000000 --- a/frontend/app/src/app/modules/hebis/daia-availability/daia-holding.scss +++ /dev/null @@ -1,42 +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 . - */ -ion-label a { - display: block; - - margin: 20px 0 5px; - - font-weight: 700; - color: var(--ion-color-primary); - text-decoration: none; -} - -ion-grid { - margin: 5px; - padding: 0; - - ion-row { - color: var(--ion-color-light-contrast); - background-color: var(--ion-color-light); - border-bottom: 1px solid #fff; - - ion-col:first-child { - font-weight: 700; - } - } -} - -ion-icon { - --fill: 1; -} diff --git a/frontend/app/src/app/modules/hebis/daia-data.provider.spec.ts b/frontend/app/src/app/modules/hebis/daia-data.provider.spec.ts deleted file mode 100644 index b14abea5..00000000 --- a/frontend/app/src/app/modules/hebis/daia-data.provider.spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2023 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 . - */ -/* eslint-disable @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-explicit-any, unicorn/no-thenable */ -import {TestBed} from '@angular/core/testing'; -import {DaiaDataProvider} from './daia-data.provider'; -import {HebisModule} from './hebis.module'; -import {ConfigProvider} from '../config/config.provider'; -import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; -import {StorageProvider} from '../storage/storage.provider'; -import {LoggerTestingModule} from 'ngx-logger/testing'; -import {MapModule} from '../map/map.module'; -import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http'; -import {StorageModule} from '../storage/storage.module'; -import {DaiaHolding, DaiaService} from './protocol/response'; -import {Observable, of} from 'rxjs'; -import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; - -const translations: any = {data: {detail: {TITLE: 'Foo'}}}; - -class TranslateFakeLoader implements TranslateLoader { - getTranslation(_lang: string): Observable { - return of(translations); - } -} - -describe('DaiaDataProvider', () => { - let daiaDataProvider: DaiaDataProvider; - let configProvider: ConfigProvider; - const proxyUrl = 'https://some-proxy.com?q='; - const getProxifiedUrl = (url: string) => `${proxyUrl}${encodeURIComponent(url)}`; - - beforeEach(async () => { - configProvider = jasmine.createSpyObj('ConfigProvider', ['getValue']); - TestBed.configureTestingModule({ - imports: [ - HebisModule, - MapModule, - StorageModule, - LoggerTestingModule, - TranslateModule.forRoot({ - loader: {provide: TranslateLoader, useClass: TranslateFakeLoader}, - }), - ], - providers: [ - { - provide: ConfigProvider, - useValue: configProvider, - }, - StAppsWebHttpClient, - StorageProvider, - DaiaDataProvider, - provideHttpClient(withInterceptorsFromDi()), - ], - }); - daiaDataProvider = TestBed.inject(DaiaDataProvider); - daiaDataProvider.hebisProxyUrl = proxyUrl; - }); - describe('getResourceLink', () => { - it('should return link with proxy when open property is undefined', () => { - const available: DaiaService = { - delay: '', - expected: '', - href: 'https://some-url.com', - limitations: [], - service: 'presentation', - }; - const holding: DaiaHolding = { - department: {id: '', content: ''}, - id: '', - online: false, - signature: '', - available: available, - }; - - expect(daiaDataProvider.getHoldingLink(holding, holding.open)).toEqual( - getProxifiedUrl(available.href as string), - ); - }); - - it('should return the resource link without proxy when the resource is open', () => { - const available: DaiaService = { - delay: '', - expected: '', - href: 'https://some-url.com', - limitations: [], - service: 'other', - }; - const holding: DaiaHolding = { - department: {id: '', content: ''}, - id: '', - online: false, - open: true, - signature: '', - available: available, - }; - - expect(daiaDataProvider.getHoldingLink(holding, holding.open)).toEqual(available.href as string); - }); - - it('should return the resource link with proxy when the resource is not open', () => { - const available: DaiaService = { - delay: '', - expected: '', - href: 'https://some-url.com', - limitations: [], - service: 'other', - }; - const holding: DaiaHolding = { - department: {id: '', content: ''}, - id: '', - online: false, - open: false, - signature: '', - available: available, - }; - - expect(daiaDataProvider.getHoldingLink(holding, holding.open)).toEqual( - getProxifiedUrl(available.href as string), - ); - }); - }); - - describe('getResourceStatus', () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // let available, unavalable: SCDaiaService[]; - - const checkedOut: DaiaService = { - expected: '2022-09-01', - limitations: [], - service: 'loan', - }; - - const notYetAvailableOnBuy: DaiaService = { - limitations: [{id: 'OnBuy', content: ''}], - service: 'loan', - }; - - const notYetAvailableJustReturned: DaiaService = { - limitations: [{id: 'JustReturned', content: ''}], - service: 'loan', - }; - - const notAvailableCopyIsMissing: DaiaService = { - limitations: [{id: 'CopyIsMissing', content: ''}], - service: 'loan', - }; - - const notAvailableCanceled: DaiaService = { - limitations: [{id: 'Canceled', content: ''}], - service: 'loan', - }; - - const libraryOnlyOnlyInHouse: DaiaService = { - limitations: [{id: 'OnlyInHouse', content: ''}], - service: 'loan', - }; - - const libraryOnlyExternalLoan: DaiaService = { - limitations: [{id: 'ExternalLoan', content: ''}], - service: 'loan', - }; - - const libraryOnlyPresentation: DaiaService = { - service: 'presentation', - }; - - const availableLimitationsUndefined: DaiaService = { - service: 'loan', - }; - - const availableLimitationsEmpty: DaiaService = { - limitations: [], - service: 'loan', - }; - - it('should return check out', () => { - expect(daiaDataProvider.getHoldingStatus([], [checkedOut])).toEqual('checked_out'); - }); - - it('should return not yet available', () => { - expect(daiaDataProvider.getHoldingStatus([], [notYetAvailableOnBuy])).toEqual('not_yet_available'); - expect(daiaDataProvider.getHoldingStatus([], [notYetAvailableJustReturned])).toEqual( - 'not_yet_available', - ); - }); - - it('should return not available', () => { - expect(daiaDataProvider.getHoldingStatus([], [notAvailableCopyIsMissing])).toEqual('not_available'); - expect(daiaDataProvider.getHoldingStatus([], [notAvailableCanceled])).toEqual('not_available'); - }); - - it('should return library only', () => { - expect(daiaDataProvider.getHoldingStatus([], [libraryOnlyOnlyInHouse])).toEqual('library_only'); - expect(daiaDataProvider.getHoldingStatus([libraryOnlyExternalLoan], [])).toEqual('library_only'); - expect(daiaDataProvider.getHoldingStatus([libraryOnlyPresentation], [])).toEqual('library_only'); - }); - - it('should return available', () => { - expect( - daiaDataProvider.getHoldingStatus([availableLimitationsUndefined, libraryOnlyPresentation], []), - ).toEqual('available'); - expect( - daiaDataProvider.getHoldingStatus([availableLimitationsEmpty, libraryOnlyPresentation], []), - ).toEqual('available'); - }); - - it('should return unknown otherwise', () => { - const withoutLoan: DaiaService = { - limitations: [], - service: 'anything else', - }; - - expect(daiaDataProvider.getHoldingStatus([withoutLoan], [])).toEqual('unknown'); - expect(daiaDataProvider.getHoldingStatus([], [withoutLoan])).toEqual('unknown'); - expect(daiaDataProvider.getHoldingStatus([], [availableLimitationsUndefined])).toEqual('unknown'); - expect(daiaDataProvider.getHoldingStatus([], [availableLimitationsEmpty])).toEqual('unknown'); - }); - }); -}); diff --git a/frontend/app/src/app/modules/hebis/daia-data.provider.ts b/frontend/app/src/app/modules/hebis/daia-data.provider.ts deleted file mode 100644 index 860ed3a6..00000000 --- a/frontend/app/src/app/modules/hebis/daia-data.provider.ts +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2023 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 {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'; - -/** - * Generated class for the DataProvider provider. - * - * See https://angular.io/guide/dependency-injection for more info on providers - * and Angular DI. - */ -@Injectable({ - providedIn: 'root', -}) -export class DaiaDataProvider { - /** - * TODO - */ - storageProvider: StorageProvider; - - httpClient: HttpClient; - - daiaServiceUrl?: string; - - hebisProxyUrl?: string; - - 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 readonly logger: NGXLogger, - private translateService: TranslateService, - ) { - this.storageProvider = storageProvider; - this.httpClient = httpClient; - this.clientHeaders = this.clientHeaders.set('Content-Type', 'application/json'); - } - - 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; - } else { - this.logger.error('Daia service url undefined'); - return undefined; - } - if (features.extern?.hebisProxy?.url) { - this.hebisProxyUrl = features.extern?.hebisProxy?.url; - } else { - this.logger.error('HeBIS proxy url undefined'); - return undefined; - } - } catch (error) { - this.logger.error(error); - return undefined; - } - } - - return new Promise(resolve => - this.httpClient - .get(this.daiaServiceUrl as string, { - params: {id, lang: this.translateService.currentLang}, - }) - .subscribe( - (response: DaiaAvailabilityResponse) => { - const holdings: DaiaHolding[] = []; - if (response && Array.isArray(response.document)) { - response.document.map(document => { - Array.isArray(document.item) && - document.item.map(element => { - try { - const { - department: {id: departmentId, content: departmentLabel, href: departmentLink} = { - id: 'noDep', - content: '', - href: '', - }, - label, - about, - available, - storage, - chronology, - unavailable, - } = element; - const holdingStatus = this.holdingHasStatus(available || []) - ? this.getHoldingStatus(available || [], unavailable || []) - : undefined; - - const dueDate = - holdingStatus === 'checked_out' - ? (unavailable.find(item => item.service === 'loan') as DaiaService).expected - : undefined; - - holdings.push({ - id: element.id, - department: { - id: departmentId, - content: departmentLabel, - href: departmentLink, - }, - signature: label, - status: holdingStatus, - dueDate: dueDate, - online: Array.isArray(available) && available.some(item => item.service === 'remote'), - available: - (Array.isArray(available) && - available.find(item => - ['openaccess', 'loan', 'presentation'].includes(item.service), - )) || - undefined, - unavailable: - (Array.isArray(unavailable) && unavailable.find(item => item.service === 'loan')) || - undefined, - storage, - about, - holdings: chronology?.about, - open: - (Array.isArray(available) && - available.some(item => item.service === 'openaccess')) || - undefined, - }); - } catch { - // No element available - } - }); - }); - } - resolve(holdings); - }, - error => { - // handle "availability info not found" separately from the problems with getting the info - if (error.status === 404) resolve([]); - // eslint-disable-next-line unicorn/no-useless-undefined - resolve(undefined); - }, - ), - ); - } - - getHoldingLink(holding: DaiaHolding, open = false) { - if (this.hebisProxyUrl === undefined) { - this.logger.error('HeBIS proxy url undefined'); - - return; - } - const resourceLink = holding.available?.href; - return open ? resourceLink : `${this.hebisProxyUrl}${encodeURIComponent(resourceLink as string)}`; - } - - holdingHasStatus(available: DaiaService[]): boolean { - return !available.some(item => item.service === 'remote'); - } - - getHoldingStatus(available: DaiaService[], unavailable: DaiaService[]): DaiaHolding['status'] { - const loan: {available: number; unavailable: number} = { - available: available.findIndex(item => item.service === 'loan'), - unavailable: unavailable.findIndex(item => item.service === 'loan'), - }; - const presentation: {available: number; unavailable: number} = { - available: available.findIndex(item => item.service === 'presentation'), - unavailable: unavailable.findIndex(item => item.service === 'presentation'), - }; - - if (loan.unavailable !== -1 && unavailable[loan.unavailable].expected !== undefined) { - return 'checked_out'; - } - - if ( - loan.unavailable !== -1 && - unavailable[loan.unavailable].limitations?.some(limitation => - ['OnBuy', 'JustReturned'].includes(limitation.id), - ) - ) - return 'not_yet_available'; - - if ( - loan.unavailable !== -1 && - unavailable[loan.unavailable].limitations?.some(limitation => - ['CopyIsMissing', 'Canceled'].includes(limitation.id), - ) - ) - return 'not_available'; - - if ( - (loan.unavailable !== -1 && - unavailable[loan.unavailable].limitations?.some(limitation => - ['OnlyInHouse'].includes(limitation.id), - )) || - (loan.available !== -1 && - available[loan.available].limitations?.some(limitation => - ['ExternalLoan'].includes(limitation.id), - )) || - (loan.available === -1 && presentation.available !== -1 && presentation.unavailable === -1) - ) - return 'library_only'; - - if (loan.available !== -1) return 'available'; - - return 'unknown'; - } -} diff --git a/frontend/app/src/app/modules/hebis/hebis-data.provider.ts b/frontend/app/src/app/modules/hebis/hebis-data.provider.ts deleted file mode 100644 index ea7f798e..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-data.provider.ts +++ /dev/null @@ -1,114 +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 {Client} from '@openstapps/api'; -import {SCHebisSearchRequest} from './protocol/request'; -import {HebisSearchResponse} from './protocol/response'; -import {StorageProvider} from '../storage/storage.provider'; -import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; -import {HttpClient} from '@angular/common/http'; -import {DataProvider} from '../data/data.provider'; -import {SCHebisSearchRoute} from './protocol/route'; -import {SCSearchRequest, SCSearchResponse} from '@openstapps/core'; - -const HEBIS_PREFIX = 'HEB'; - -/** - * Generated class for the DataProvider provider. - * - * See https://angular.io/guide/dependency-injection for more info on providers - * and Angular DI. - */ -@Injectable({ - providedIn: 'root', -}) -export class HebisDataProvider extends DataProvider { - /** - * TODO - */ - storageProvider: StorageProvider; - - httpClient: HttpClient; - - /** - * Instance of hebis search request route - */ - private readonly hebisSearchRoute = new SCHebisSearchRoute(); - - constructor( - stAppsWebHttpClient: StAppsWebHttpClient, - storageProvider: StorageProvider, - httpClient: HttpClient, - ) { - super(stAppsWebHttpClient, storageProvider); - this.storageProvider = storageProvider; - this.httpClient = httpClient; - this.client = new Client(stAppsWebHttpClient, this.backendUrl, this.appVersion); - } - - override async search(query: SCSearchRequest): Promise { - const response = await this.hebisSearch({ - query: query.query ?? '', - page: query.from, - }); - console.log(response.pagination); - - return { - data: response.data, - facets: [], - pagination: response.pagination, - stats: { - time: Number.NaN, - }, - }; - } - - /** - * Send a search request to the backend - * - * All results will be returned if no size is set in the request. - * @param searchRequest Search request - * @param options Search options - * @param options.addPrefix Add HeBIS prefix (useful when having only an ID, e.g. when using PAIA) - */ - async hebisSearch( - searchRequest: SCHebisSearchRequest, - options?: {addPrefix: boolean}, - ): Promise { - if (options?.addPrefix) { - searchRequest.query = [HEBIS_PREFIX, searchRequest.query].join(''); - } - - let page: number | undefined = searchRequest.page; - - if (page === undefined) { - const preFlightResponse = await this.client.invokeRoute( - this.hebisSearchRoute, - undefined, - { - ...searchRequest, - page: 0, - }, - ); - - page = preFlightResponse.pagination.total; - } - - return this.client.invokeRoute(this.hebisSearchRoute, undefined, { - ...searchRequest, - page, - }); - } -} diff --git a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.component.ts b/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.component.ts deleted file mode 100644 index 291b3957..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.component.ts +++ /dev/null @@ -1,31 +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 {Component, Input} from '@angular/core'; -import {SCThings} from '@openstapps/core'; - -/** - * TODO - */ -@Component({ - selector: 'stapps-hebis-detail-content', - styleUrls: ['hebis-detail-content.scss'], - templateUrl: 'hebis-detail-content.html', -}) -export class HebisDetailContentComponent { - /** - * TODO - */ - @Input() item: SCThings; -} diff --git a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.html b/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.html deleted file mode 100644 index 9b31120c..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.html +++ /dev/null @@ -1,51 +0,0 @@ - - -
- @switch (item.type) { - @case ('book') { - - } - @case ('periodical') { - - } - @case ('article') { - - } - @default { - - - - - - - -
-

{{ item.name }}

- {{ item.type }} -
-
-
-
-
- @if (item.description) { - - } - } - } -
diff --git a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.scss b/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.scss deleted file mode 100644 index a436d38f..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail-content.scss +++ /dev/null @@ -1,50 +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 . - */ - -:host ::ng-deep { - ion-grid, - ion-col { - padding-top: 0; - padding-bottom: 0; - padding-inline-start: 0; - } - - ion-card { - margin: 0; - box-shadow: none; - - ion-card-content { - padding-bottom: 8px; - - h1 { - margin: 0; - } - } - - ion-card-header { - padding-top: 8px; - padding-bottom: 4px; - font-weight: bold; - color: var(--ion-color-dark); - } - - ion-grid, - ion-col { - padding-top: 0; - padding-bottom: 0; - padding-inline-start: 0; - } - } -} diff --git a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.component.spec.ts b/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.component.spec.ts deleted file mode 100644 index 26397055..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.component.spec.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2023 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 . - */ - -/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */ -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {ActivatedRoute, RouterModule} from '@angular/router'; -import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core'; -import {sampleThingsMap} from '../../../_helpers/data/sample-things'; -import {HebisRoutingModule} from '../hebis-routing.module'; -import {HebisModule} from '../hebis.module'; -import {HebisDataProvider} from '../hebis-data.provider'; -import {HebisDetailComponent} from './hebis-detail.component'; -import {Observable, of} from 'rxjs'; -import {StorageProvider} from '../../storage/storage.provider'; -import {LoggerModule, NgxLoggerLevel} from 'ngx-logger'; -import {IonIconDirective} from '../../../util/ion-icon/ion-icon.directive'; -import {provideIonicAngular} from '@ionic/angular/standalone'; - -const translations: any = {data: {detail: {TITLE: 'Foo'}}}; - -class TranslateFakeLoader implements TranslateLoader { - getTranslation(_lang: string): Observable { - return of(translations); - } -} - -describe('HebisDetailComponent', () => { - let comp: HebisDetailComponent; - let fixture: ComponentFixture; - let dataProvider: HebisDataProvider; - const sampleThing = sampleThingsMap.book[0]; - let translateService: TranslateService; - - // @Component({ selector: 'stapps-data-list-item', template: '' }) - // class DataListItemComponent { - // @Input() item; - // } - - const fakeActivatedRoute = { - snapshot: { - paramMap: { - get: () => { - return sampleThing.uid; - }, - }, - }, - }; - - const storageProviderSpy = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put', 'search']); - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - RouterModule.forRoot([]), - HebisRoutingModule, - HebisModule, - IonIconDirective, - TranslateModule.forRoot({ - loader: {provide: TranslateLoader, useClass: TranslateFakeLoader}, - }), - LoggerModule.forRoot({level: NgxLoggerLevel.TRACE}), - ], - providers: [ - provideIonicAngular(), - { - provide: ActivatedRoute, - useValue: fakeActivatedRoute, - }, - { - provide: StorageProvider, - useValue: storageProviderSpy, - }, - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - }).compileComponents(); - }); - - beforeEach(() => { - dataProvider = TestBed.inject(HebisDataProvider); - translateService = TestBed.inject(TranslateService); - spyOn(dataProvider, 'get' as any).and.returnValue(Promise.resolve(sampleThing)); - spyOn(HebisDetailComponent.prototype, 'getItem').and.callThrough(); - fixture = TestBed.createComponent(HebisDetailComponent); - comp = fixture.componentInstance; - translateService.use('foo'); - fixture.detectChanges(); - }); - - it('should create component', () => expect(comp).toBeDefined()); - - it('should get a data item', () => { - comp.getItem(sampleThing.uid, false); - expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false); - }); - - it('should get a data item when initialized', () => { - comp.ngOnInit(); - expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false); - }); -}); diff --git a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.component.ts b/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.component.ts deleted file mode 100644 index b62e7bec..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.component.ts +++ /dev/null @@ -1,55 +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 {Component, inject, OnInit} from '@angular/core'; -import {SCUuid} from '@openstapps/core'; -import {HebisDataProvider} from '../hebis-data.provider'; -import {DataDetailComponent} from '../../data/detail/data-detail.component'; -import {DaiaHolding} from '../protocol/response'; - -/** - * A Component to display an SCThing detailed - */ -@Component({ - selector: 'stapps-hebis-detail', - styleUrls: ['hebis-detail.scss'], - templateUrl: 'hebis-detail.html', -}) -export class HebisDetailComponent extends DataDetailComponent implements OnInit { - holdings: DaiaHolding[]; - - private hebisDataProvider = inject(HebisDataProvider); - - /** - * @override - */ - async ngOnInit() { - const uid = this.route.snapshot.paramMap.get('uid') || ''; - await this.getItem(uid ?? '', false); - } - - /** - * Provides data item with given UID - * @param uid Unique identifier of a thing - * @param _forceReload Ignore any cached data - */ - async getItem(uid: SCUuid, _forceReload: boolean) { - this.item = await (this.inputItem ?? - this.hebisDataProvider.hebisSearch({query: uid, page: 0}).then( - result => - // eslint-disable-next-line unicorn/no-null - (result.data && result.data[0]) || null, - )); - } -} diff --git a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.html b/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.html deleted file mode 100644 index a526f816..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - {{ 'data.detail.TITLE' | translate }} - - @if (item) { - - } - - - - -
- @switch (true) { - @case (!item && (isDisconnected | async)) { -
- - {{ 'data.detail.COULD_NOT_CONNECT' | translate }} -
- } - @case (item === null) { -
- - {{ 'data.detail.NOT_FOUND' | translate }} -
- } - @case (!item && item !== null) { - - - } - @default { - @if (item) { - - } - @if (item) { - - } - } - } -
-
diff --git a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.scss b/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.scss deleted file mode 100644 index 2a8df22b..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-detail/hebis-detail.scss +++ /dev/null @@ -1,5 +0,0 @@ -::ng-deep { - ion-card-header { - font-weight: bold; - } -} diff --git a/frontend/app/src/app/modules/hebis/hebis-routing.module.ts b/frontend/app/src/app/modules/hebis/hebis-routing.module.ts deleted file mode 100644 index 7aad4cb1..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-routing.module.ts +++ /dev/null @@ -1,32 +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 {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {HebisDetailComponent} from './hebis-detail/hebis-detail.component'; -import {HebisSearchPageComponent} from './list/hebis-search-page.component'; - -const hebisRoutes: Routes = [ - {path: 'hebis-search', component: HebisSearchPageComponent}, - {path: 'hebis-detail/:uid', component: HebisDetailComponent}, -]; - -/** - * Module defining routes for data module - */ -@NgModule({ - exports: [RouterModule], - imports: [RouterModule.forChild(hebisRoutes)], -}) -export class HebisRoutingModule {} diff --git a/frontend/app/src/app/modules/hebis/hebis-search.provider.ts b/frontend/app/src/app/modules/hebis/hebis-search.provider.ts deleted file mode 100644 index 5f7c0465..00000000 --- a/frontend/app/src/app/modules/hebis/hebis-search.provider.ts +++ /dev/null @@ -1,21 +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'; - -@Injectable({ - providedIn: 'root', -}) -export class HebisSearchProvider {} diff --git a/frontend/app/src/app/modules/hebis/hebis.module.ts b/frontend/app/src/app/modules/hebis/hebis.module.ts deleted file mode 100644 index 4a61e038..00000000 --- a/frontend/app/src/app/modules/hebis/hebis.module.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2018-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 {ScrollingModule} from '@angular/cdk/scrolling'; -import {CommonModule} from '@angular/common'; -import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http'; -import {NgModule} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {TranslateModule} from '@ngx-translate/core'; -import {MarkdownModule} from 'ngx-markdown'; -import {MomentModule} from 'ngx-moment'; -import {ThingTranslateModule} from '../../translation/thing-translate.module'; -import {MenuModule} from '../menu/menu.module'; -import {StorageModule} from '../storage/storage.module'; -import {HebisDetailComponent} from './hebis-detail/hebis-detail.component'; -import {HebisDetailContentComponent} from './hebis-detail/hebis-detail-content.component'; -import {HebisSearchPageComponent} from './list/hebis-search-page.component'; -import {HebisDataProvider} from './hebis-data.provider'; -import {DaiaDataProvider} from './daia-data.provider'; -import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider'; -import {HebisRoutingModule} from './hebis-routing.module'; -import {DataModule} from '../data/data.module'; -import {DaiaAvailabilityComponent} from './daia-availability/daia-availability.component'; -import {UtilModule} from '../../util/util.module'; -import {DaiaHoldingComponent} from './daia-availability/daia-holding.component'; -import { - IonBackButton, - IonButton, - IonButtons, - IonCard, - IonCardContent, - IonCardHeader, - IonCol, - IonContent, - IonGrid, - IonHeader, - IonItem, - IonLabel, - IonNote, - IonRow, - IonThumbnail, - IonTitle, - IonToolbar, -} from '@ionic/angular/standalone'; -import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive'; - -/** - * Module for handling data - */ -@NgModule({ - declarations: [ - HebisSearchPageComponent, - HebisDetailComponent, - HebisDetailContentComponent, - DaiaAvailabilityComponent, - DaiaHoldingComponent, - ], - imports: [ - CommonModule, - DataModule, - FormsModule, - HebisRoutingModule, - IonIconDirective, - MarkdownModule.forRoot(), - MenuModule, - MomentModule.forRoot({ - relativeTimeThresholdOptions: { - m: 59, - }, - }), - ScrollingModule, - StorageModule, - TranslateModule.forChild(), - ThingTranslateModule.forChild(), - UtilModule, - IonBackButton, - IonLabel, - IonButton, - IonContent, - IonButtons, - IonTitle, - IonToolbar, - IonNote, - IonHeader, - IonGrid, - IonRow, - IonCol, - IonCard, - IonCardContent, - IonCardHeader, - IonItem, - IonThumbnail, - ], - providers: [ - HebisDataProvider, - DaiaDataProvider, - StAppsWebHttpClient, - provideHttpClient(withInterceptorsFromDi()), - ], -}) -export class HebisModule {} diff --git a/frontend/app/src/app/modules/hebis/list/hebis-search-page.component.ts b/frontend/app/src/app/modules/hebis/list/hebis-search-page.component.ts deleted file mode 100644 index 98ea723e..00000000 --- a/frontend/app/src/app/modules/hebis/list/hebis-search-page.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2023 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 {Component} from '@angular/core'; -import {HebisDataProvider} from '../hebis-data.provider'; -import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; -import {DataProvider} from '../../data/data.provider'; -import {DataRoutingService} from '../../data/data-routing.service'; -import {Router} from '@angular/router'; - -/** - * HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu - */ -@Component({ - selector: 'stapps-hebissearch-page', - templateUrl: 'hebis-search-page.html', - styleUrls: ['../../data/list/search-page.scss'], - providers: [{provide: DataProvider, useClass: HebisDataProvider}], -}) -export class HebisSearchPageComponent { - constructor(dataRoutingService: DataRoutingService, router: Router) { - dataRoutingService - .itemSelectListener() - .pipe(takeUntilDestroyed()) - .subscribe(async item => { - void router.navigate( - ['hebis-detail', (item.origin && 'originalId' in item.origin && item.origin['originalId']) || ''], - {state: {item}}, - ); - }); - } -} diff --git a/frontend/app/src/app/modules/hebis/list/hebis-search-page.html b/frontend/app/src/app/modules/hebis/list/hebis-search-page.html deleted file mode 100644 index 616d18cc..00000000 --- a/frontend/app/src/app/modules/hebis/list/hebis-search-page.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/frontend/app/src/app/modules/hebis/protocol/request.ts b/frontend/app/src/app/modules/hebis/protocol/request.ts deleted file mode 100644 index 0a03c761..00000000 --- a/frontend/app/src/app/modules/hebis/protocol/request.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {SCLicensePlate} from '@openstapps/core'; - -export interface SCHebisSearchRequest { - /** - * HDS2 will supply results for the speficied insitute / university if available (Defaults to f-u) - */ - institute?: SCLicensePlate; - /** - * Simple pagination support (Defaults to 0) - */ - page?: number; - /** - * Search query for HDS - */ - query: string; -} - -export interface SCDaiaAvailabilityRequest { - id: string; -} diff --git a/frontend/app/src/app/modules/hebis/protocol/response.ts b/frontend/app/src/app/modules/hebis/protocol/response.ts deleted file mode 100644 index f618af82..00000000 --- a/frontend/app/src/app/modules/hebis/protocol/response.ts +++ /dev/null @@ -1,77 +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 {SCArticle, SCBook, SCPeriodical, SCSearchResultPagination} from '@openstapps/core'; - -export interface HebisSearchResponse { - /** - * Response Array of SCBook type or Object - */ - data: Array; - - /** - * Pagination information - */ - pagination: SCSearchResultPagination; -} - -export interface DaiaAvailabilityResponse { - document: Array<{ - item: Array<{ - id: string; - label: string; - chronology?: { - about?: string; - }; - department: DaiaSimpleContent; - available: DaiaService[]; - unavailable: DaiaService[]; - debugInfo: string; - about?: string; - storage?: DaiaSimpleContent; - }>; - }>; - institution: DaiaSimpleContent; - timestamp: string; -} - -export interface DaiaSimpleContent { - id: string; - content: string; - href?: string; -} - -export interface DaiaService { - delay?: string; - href?: string; - service: string; - expected?: string; - limitations?: DaiaSimpleContent[]; -} - -export interface DaiaHolding { - id: string; - department: DaiaSimpleContent; - signature: string; - storage?: DaiaSimpleContent; - available?: DaiaService; - unavailable?: DaiaService; - about?: string; - online: boolean; - open?: boolean; - dueDate?: string; - holdings?: string; - status?: 'checked_out' | 'not_yet_available' | 'not_available' | 'library_only' | 'available' | 'unknown'; -} diff --git a/frontend/app/src/app/modules/hebis/protocol/route.ts b/frontend/app/src/app/modules/hebis/protocol/route.ts deleted file mode 100644 index 25e1be87..00000000 --- a/frontend/app/src/app/modules/hebis/protocol/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - SCAbstractRoute, - SCRouteHttpVerbs, - SCInternalServerErrorResponse, - SCMethodNotAllowedErrorResponse, - SCRequestBodyTooLargeErrorResponse, - SCSyntaxErrorResponse, - SCUnsupportedMediaTypeErrorResponse, - SCValidationErrorResponse, -} from '@openstapps/core'; - -/** - * Route for searching things - */ -export class SCHebisSearchRoute extends SCAbstractRoute { - constructor() { - super(); - this.errorNames = [ - SCInternalServerErrorResponse, - SCMethodNotAllowedErrorResponse, - SCRequestBodyTooLargeErrorResponse, - SCSyntaxErrorResponse, - SCUnsupportedMediaTypeErrorResponse, - SCValidationErrorResponse, - ]; - this.method = SCRouteHttpVerbs.POST; - this.requestBodyName = 'SCHebisSearchRequest'; - this.responseBodyName = 'SCHebisSearchResponse'; - this.statusCodeSuccess = 200; - this.urlPath = '/hebissearch'; - } -} - -/** - * Route for availability - */ -export class SCDaiaRoute extends SCAbstractRoute { - constructor() { - super(); - this.errorNames = [ - SCInternalServerErrorResponse, - SCMethodNotAllowedErrorResponse, - SCRequestBodyTooLargeErrorResponse, - SCSyntaxErrorResponse, - SCUnsupportedMediaTypeErrorResponse, - SCValidationErrorResponse, - ]; - this.method = SCRouteHttpVerbs.GET; - this.requestBodyName = 'SCDaiaAvailabilityRequest'; - this.responseBodyName = 'SCDaiaAvailabilityResponse'; - this.statusCodeSuccess = 200; - this.urlPath = '/UB_Frankfurt'; - } -} diff --git a/frontend/app/src/app/modules/jobs/page/jobs-page.html b/frontend/app/src/app/modules/jobs/page/jobs-page.html index af61b81d..54c04bce 100644 --- a/frontend/app/src/app/modules/jobs/page/jobs-page.html +++ b/frontend/app/src/app/modules/jobs/page/jobs-page.html @@ -2,7 +2,6 @@ [title]="'jobs.title' | translate" [placeholder]="'jobs.placeholder' | translate" [showDefaultData]="true" - [navigation]="[]" [forcedFilter]="forcedFilter" [backUrl]="'/'" >
diff --git a/frontend/app/src/app/modules/library/account/account.page.html b/frontend/app/src/app/modules/library/account/account.page.html deleted file mode 100644 index cd9e437d..00000000 --- a/frontend/app/src/app/modules/library/account/account.page.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - {{ 'library.account.title' | translate | titlecase }} - - - - - @if (name) { -

- {{ 'library.account.greeting' | translate }} {{ name | firstLastName }}! - {{ 'library.account.login.success' | translate }} -

- } @else { -

- } - - {{ 'library.account.pages.profile.title' | translate | titlecase }} - - - {{ 'library.account.pages.holds.title' | translate | titlecase }} - - - {{ 'library.account.pages.checked_out.title' | translate | titlecase }} - - - {{ 'library.account.pages.fines.title' | translate | titlecase }} - -
diff --git a/frontend/app/src/app/modules/library/account/account.page.scss b/frontend/app/src/app/modules/library/account/account.page.scss deleted file mode 100644 index b498ff0f..00000000 --- a/frontend/app/src/app/modules/library/account/account.page.scss +++ /dev/null @@ -1,4 +0,0 @@ -ion-content { - --padding-start: 8px; - --padding-top: 8px; -} diff --git a/frontend/app/src/app/modules/library/account/account.page.ts b/frontend/app/src/app/modules/library/account/account.page.ts deleted file mode 100644 index efa35da5..00000000 --- a/frontend/app/src/app/modules/library/account/account.page.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {Component} from '@angular/core'; -import {LibraryAccountService} from './library-account.service'; - -@Component({ - templateUrl: './account.page.html', - styleUrls: ['./account.page.scss'], -}) -export class LibraryAccountPageComponent { - name?: string; - - constructor(private readonly libraryAccountService: LibraryAccountService) {} - - async ionViewWillEnter(): Promise { - const patron = await this.libraryAccountService.getPatron(); - this.name = patron?.name; - } -} diff --git a/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.component.ts b/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.component.ts deleted file mode 100644 index 0756b9a2..00000000 --- a/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 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 {Component} from '@angular/core'; -import {DocumentAction, PAIADocument, PAIADocumentStatus} from '../../types'; -import {LibraryAccountService} from '../library-account.service'; - -@Component({ - selector: 'app-checked-out', - templateUrl: './checked-out-page.html', - styleUrls: ['./checked-out-page.scss'], -}) -export class CheckedOutPageComponent { - checkedOutItems?: PAIADocument[]; - - constructor(private readonly libraryAccountService: LibraryAccountService) {} - - async ionViewWillEnter(): Promise { - await this.fetchItems(); - } - - async onDocumentAction(documentAction: DocumentAction) { - const answer = await this.libraryAccountService.handleDocumentAction(documentAction); - - if (answer) await this.fetchItems(); - } - - async fetchItems() { - try { - // Prepare patron (status) for the items - await this.libraryAccountService.getPatron(); - this.checkedOutItems = undefined; - this.checkedOutItems = await this.libraryAccountService.getFilteredItems([PAIADocumentStatus.Held]); - } catch { - await this.libraryAccountService.handleError(); - this.checkedOutItems = []; - } - } -} diff --git a/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.html b/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.html deleted file mode 100644 index 446e946c..00000000 --- a/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - {{ 'library.account.pages.checked_out.title' | translate | titlecase }} - - - - - @if (checkedOutItems) { - @for (checkedOutItem of checkedOutItems; track checkedOutItem) { - - - } @empty { - - {{ 'search.nothing_found' | translate | titlecase }} - - } - } @else { - - } - diff --git a/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.scss b/frontend/app/src/app/modules/library/account/checked-out/checked-out-page.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.component.ts b/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.component.ts deleted file mode 100644 index c526007f..00000000 --- a/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 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 {Component, Input} from '@angular/core'; -import {PAIAFee} from '../../../types'; -import {SCArticle, SCBook, SCPeriodical} from '@openstapps/core'; -import {LibraryAccountService} from '../../library-account.service'; - -@Component({ - selector: 'stapps-fee-item', - templateUrl: './fee-item.html', - styleUrls: ['./fee-item.scss'], -}) -export class FeeItemComponent { - _fee: PAIAFee; - - book?: SCBook | SCPeriodical | SCArticle; - - hasEdition = false; - - @Input() - set fee(fee: PAIAFee) { - this.hasEdition = !fee.edition?.includes('null'); - this._fee = fee; - if (this.hasEdition) { - this.libraryAccountService.getDocumentFromHDS(fee.edition as string).then(book => { - this.book = book; - }); - } - } - - get fee() { - return this._fee; - } - - propertiesToShow: (keyof PAIAFee)[] = ['about', 'date', 'amount']; - - constructor(private readonly libraryAccountService: LibraryAccountService) {} -} diff --git a/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.html b/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.html deleted file mode 100644 index 032f49bc..00000000 --- a/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - @if (hasEdition) { - @if (book) { -

{{ 'library.account.pages.fines.labels.edition' | translate }}: {{ book.name }}

- } @else { -

- -

- } - } - @for (property of propertiesToShow; track property) { - @if (fee[property]) { -

- {{ 'library.account.pages.fines.labels' + '.' + property | translate }}: - @if (!['date'].includes(property)) { - {{ fee[property] }} - } @else { - {{ fee[property] | amDateFormat: 'll' }} - } -

- } - } -
-
diff --git a/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.scss b/frontend/app/src/app/modules/library/account/elements/fee-item/fee-item.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.component.ts b/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.component.ts deleted file mode 100644 index 427d3af9..00000000 --- a/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.component.ts +++ /dev/null @@ -1,76 +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 {Component, EventEmitter, Input, Output} from '@angular/core'; -import {DocumentAction, PAIADocument, PAIADocumentStatus, PAIADocumentVisualStatus} from '../../../types'; -import {LibraryAccountService} from '../../library-account.service'; - -@Component({ - selector: 'stapps-paia-item', - templateUrl: './paiaitem.html', - styleUrls: ['./paiaitem.scss'], -}) -export class PAIAItemComponent { - private _item: PAIADocument; - - renewable: boolean; - - visualStatus?: PAIADocumentVisualStatus; - - constructor(private readonly libraryAccountService: LibraryAccountService) {} - - @Input() - set item(value: PAIADocument) { - this._item = value; - void this.setRenewable(); - this.visualStatus = this.getVisualStatus(Number(this.item.status)); - } - - get item(): PAIADocument { - return this._item; - } - - @Input() - propertiesToShow: (keyof PAIADocument)[]; - - @Input() - listName: string; - - @Output() - documentAction: EventEmitter = new EventEmitter(); - - async onClick(action: DocumentAction['action']) { - this.documentAction.emit({doc: this.item, action}); - } - - private async setRenewable() { - const isActive = await this.libraryAccountService.isActivePatron(); - this.renewable = isActive && Number(this.item.status) === PAIADocumentStatus.Held; - } - - private getVisualStatus(status: PAIADocumentStatus): PAIADocumentVisualStatus | undefined { - switch (status) { - case PAIADocumentStatus.Ordered: { - return {color: 'warning', status: status, statusText: 'ordered'}; - } - case PAIADocumentStatus.Provided: { - return {color: 'success', status: status, statusText: 'ready'}; - } - default: { - return undefined; - } - } - } -} diff --git a/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.html b/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.html deleted file mode 100644 index ab42d701..00000000 --- a/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - @if (item.about) { -

- @if (visualStatus) { - - {{ 'library.account.pages' + '.' + listName + '.' + visualStatus.statusText | translate }} - - } - {{ item.about }} -

- } - @for (property of propertiesToShow; track property) { - @if (item[property]) { -

- {{ 'library.account.pages' + '.' + listName + '.' + 'labels' + '.' + property | translate }}: - @if (!['starttime', 'duedate'].includes(property)) { - {{ item[property] }} - } @else { - {{ $any(item[property]) | amDateFormat: 'll' }} - } -

- } - } - - - - - - - - - - @if (renewable && item.canrenew) { - - {{ 'library.account.actions.renew.header' | translate }} - } - -
-
diff --git a/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.scss b/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.scss deleted file mode 100644 index 317a9763..00000000 --- a/frontend/app/src/app/modules/library/account/elements/paia-item/paiaitem.scss +++ /dev/null @@ -1,17 +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 . - */ -ion-badge { - vertical-align: bottom; -} diff --git a/frontend/app/src/app/modules/library/account/fines/fines-page.component.ts b/frontend/app/src/app/modules/library/account/fines/fines-page.component.ts deleted file mode 100644 index f530bc12..00000000 --- a/frontend/app/src/app/modules/library/account/fines/fines-page.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Component} from '@angular/core'; -import {LibraryAccountService} from '../library-account.service'; -import {PAIAFee} from '../../types'; - -@Component({ - selector: 'app-fines', - templateUrl: './fines-page.html', - styleUrls: ['./fines-page.scss'], -}) -export class FinesPageComponent { - amount?: string; - - fines: PAIAFee[]; - - constructor(private readonly libraryAccountService: LibraryAccountService) {} - - async ionViewWillEnter(): Promise { - await this.fetchItems(); - } - - private async cleanUp(fines: PAIAFee[]): Promise { - for (const fine of fines) { - for (const property in fine) { - // remove properties with "null" included - if (fine[property]?.includes('null')) { - delete fine.item; - } - } - } - return fines; - } - - async fetchItems() { - try { - const fees = await this.libraryAccountService.getFees(); - if (fees) { - this.amount = fees.amount; - this.fines = await this.cleanUp(fees.fee); - } - } catch { - await this.libraryAccountService.handleError(); - } - } -} diff --git a/frontend/app/src/app/modules/library/account/fines/fines-page.html b/frontend/app/src/app/modules/library/account/fines/fines-page.html deleted file mode 100644 index f4c9fed5..00000000 --- a/frontend/app/src/app/modules/library/account/fines/fines-page.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - {{ 'library.account.pages.fines.title' | translate | titlecase }} - - - - @if (fines) { - - @for (fine of fines; track fine) { - - } - - } @else { - @for (_ of [0, 1]; track _) { - - } - } - - @for (_ of [0, 1]; track _) { - - } - - - @if (amount) { - - {{ 'library.account.pages.fines.labels.total_amount' | translate }}: - {{ amount }} - - } @else { - - - - - - } - - diff --git a/frontend/app/src/app/modules/library/account/fines/fines-page.scss b/frontend/app/src/app/modules/library/account/fines/fines-page.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/app/src/app/modules/library/account/first-last-name.pipe.ts b/frontend/app/src/app/modules/library/account/first-last-name.pipe.ts deleted file mode 100644 index 23dc22e7..00000000 --- a/frontend/app/src/app/modules/library/account/first-last-name.pipe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'firstLastName', -}) -export class FirstLastNamePipe implements PipeTransform { - transform(value: string): unknown { - return value.split(', ').reverse().join(' '); - } -} diff --git a/frontend/app/src/app/modules/library/account/holds/holds-page.component.ts b/frontend/app/src/app/modules/library/account/holds/holds-page.component.ts deleted file mode 100644 index 1c34e81d..00000000 --- a/frontend/app/src/app/modules/library/account/holds/holds-page.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2023 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 {Component} from '@angular/core'; -import {DocumentAction, PAIADocument, PAIADocumentStatus} from '../../types'; -import {LibraryAccountService} from '../library-account.service'; - -type Segment = 'orders' | 'reservations'; - -@Component({ - selector: 'stapps-holds', - templateUrl: './holds-page.html', - styleUrls: ['./holds-page.scss'], -}) -export class HoldsPageComponent { - paiaDocuments?: PAIADocument[]; - - paiaDocumentStatus = PAIADocumentStatus; - - activeSegment: Segment = 'orders'; - - constructor(private readonly libraryAccountService: LibraryAccountService) {} - - async ionViewWillEnter(): Promise { - await this.fetchItems(this.activeSegment); - } - - async fetchItems(segment: Segment) { - this.activeSegment = segment; - this.paiaDocuments = undefined; - const itemsStatus = - segment === 'reservations' - ? [PAIADocumentStatus.Reserved] - : [PAIADocumentStatus.Ordered, PAIADocumentStatus.Provided]; - try { - // Prepare patron (status) for the items - await this.libraryAccountService.getPatron(); - this.paiaDocuments = await this.libraryAccountService.getFilteredItems(itemsStatus); - } catch { - await this.libraryAccountService.handleError(); - this.paiaDocuments = []; - } - } - - toNumber = Number; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async segmentChanged(event: any) { - await this.fetchItems(event.detail.value); - } - - async onDocumentAction(documentAction: DocumentAction) { - const answer = await this.libraryAccountService.handleDocumentAction(documentAction); - - if (answer) await this.fetchItems(this.activeSegment); - } -} diff --git a/frontend/app/src/app/modules/library/account/holds/holds-page.html b/frontend/app/src/app/modules/library/account/holds/holds-page.html deleted file mode 100644 index 39b423cf..00000000 --- a/frontend/app/src/app/modules/library/account/holds/holds-page.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - {{ 'library.account.pages.holds.title' | translate | titlecase }} - - - - - - {{ 'library.account.pages.holds.holds' | translate }} - - - {{ 'library.account.pages.holds.reservations' | translate }} - - - @if (paiaDocuments && paiaDocuments.length > 0) { - - @switch (activeSegment) { - @case ('orders') { - @for (hold of paiaDocuments; track hold) { - - - } - } - @case ('reservations') { - @for (hold of paiaDocuments; track hold) { - - - } - } - } - - } @else { - @if (!paiaDocuments) { - - } @else { - @if (paiaDocuments && paiaDocuments.length === 0) { - - {{ 'search.nothing_found' | translate | titlecase }} - - } - } - } - diff --git a/frontend/app/src/app/modules/library/account/holds/holds-page.scss b/frontend/app/src/app/modules/library/account/holds/holds-page.scss deleted file mode 100644 index e69de29b..00000000 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 deleted file mode 100644 index 0c5cd458..00000000 --- a/frontend/app/src/app/modules/library/account/library-account.service.ts +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2023 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 {JQueryRequestor, Requestor} from '@openid/appauth'; -import { - SCAuthorizationProviderType, - SCFeatureConfiguration, - SCFeatureConfigurationExtern, -} from '@openstapps/core'; -import { - DocumentAction, - PAIADocument, - PAIADocumentStatus, - PAIAFees, - PAIAItems, - PAIAPatron, - PAIAPatronStatus, -} 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/standalone'; -import {HebisSearchResponse} from '../../hebis/protocol/response'; - -@Injectable({ - providedIn: 'root', -}) -export class LibraryAccountService { - /** - * Base url of the external service - */ - baseUrl: string; - - /** - * Authorization provider type - */ - authType: SCAuthorizationProviderType; - - /** - * Account (Patron) status - */ - private status?: PAIAPatronStatus; - - 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; - } - - async getPatron() { - const patronId = ((await this.getValidToken()) as PAIATokenResponse).patron; - const patron = { - ...(await this.performRequest(`${this.baseUrl}/{patron}`)), - id: patronId, - } as PAIAPatron; - // Refresh the status - this.status = Number(patron.status); - return patron; - } - - async getPatronStatus() { - return this.status ?? (this.status = Number((await this.getPatron()).status) as PAIAPatronStatus); - } - - async isActivePatron() { - return (await this.getPatronStatus()) === PAIAPatronStatus.Active; - } - - async getItems() { - return this.performRequest(`${this.baseUrl}/{patron}/items`); - } - - async getFees() { - return this.performRequest(`${this.baseUrl}/{patron}/fees`); - } - - async getValidToken() { - return this.authHelper.getProvider(this.authType).getValidToken(); - } - - private async performRequest( - urlTemplate: string, - method = 'GET', - data?: JQuery.PlainObject, - ): Promise { - const token = await this.getValidToken(); - const url = urlTemplate.replace('{patron}', (token as PAIATokenResponse).patron); - const settings: JQueryAjaxSettings = { - url: url, - dataType: 'json', - method: method, - headers: { - 'Authorization': `Bearer: ${token.accessToken}`, - 'Content-Type': 'application/json', - }, - }; - - if (method === 'POST') settings.data = data; - - let result: T; - - try { - result = await this.requestor.xhr(settings); - return result; - } catch { - void this.handleError(); - } - - return; - } - - getRawId(input: string) { - return input.split(':').pop(); - } - - async getFilteredItems(documentStatus: PAIADocumentStatus[]) { - return (await this.getItems())?.doc.filter(document => { - return documentStatus.includes(Number(document.status) as PAIADocumentStatus); - }); - } - - async getDocumentFromHDS(edition: string) { - if (edition === undefined) { - return; - } - - let response: HebisSearchResponse; - - try { - response = await this.hebisDataProvider.hebisSearch( - { - query: this.getRawId(edition) as string, - page: 0, - }, - {addPrefix: true}, - ); - return response.data[0]; - } catch { - await this.handleError(); - } - - return; - } - - async cancelReservation(document: PAIADocument) { - const result = await this.performRequest(`${this.baseUrl}/{patron}/cancel`, 'POST', { - doc: [document], - }); - if (result) { - void this.onSuccess('cancel'); - } - } - - async renewLending(document: PAIADocument) { - const result = await this.performRequest(`${this.baseUrl}/{patron}/renew`, 'POST', { - doc: [document], - }); - if (result) { - void this.onSuccess('renew'); - } - } - - async handleDocumentAction(documentAction: DocumentAction): Promise { - return new Promise(async resolve => { - const handleDocument = () => { - switch (documentAction.action) { - case 'cancel': { - return this.cancelReservation(documentAction.doc); - break; - } - case 'renew': { - return this.renewLending(documentAction.doc); - } - } - }; - const alert = await this.alertController.create({ - buttons: [ - { - role: 'cancel', - text: this.translateService.instant('abort'), - handler: () => { - resolve(false); - }, - }, - { - handler: async () => { - await handleDocument(); - resolve(true); - }, - text: this.translateService.instant('OK'), - }, - ], - header: this.translateService.instant(`library.account.actions.${documentAction.action}.header`), - message: this.translateService.instant(`library.account.actions.${documentAction.action}.text`, { - value: - documentAction.doc.about ?? - this.translateService.instant(`library.account.actions.${documentAction.doc.about}.unknown_book`), - }), - }); - await alert.present(); - }); - } - - async handleError() { - const alert = await this.alertController.create({ - header: this.translateService.instant('app.ui.ERROR'), - message: this.translateService.instant('app.errors.SERVICE'), - buttons: ['OK'], - }); - - await alert.present(); - } - - async onSuccess(action: DocumentAction['action']) { - const toast = await this.toastController.create({ - message: this.translateService.instant(`library.account.actions.${action}.success`), - duration: 2000, - color: 'success', - }); - await toast.present(); - } -} diff --git a/frontend/app/src/app/modules/library/account/profile/profile-page.component.ts b/frontend/app/src/app/modules/library/account/profile/profile-page.component.ts deleted file mode 100644 index 15ab2b4e..00000000 --- a/frontend/app/src/app/modules/library/account/profile/profile-page.component.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2023 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 {Component} from '@angular/core'; -import {LibraryAccountService} from '../library-account.service'; -import {PAIAPatron} from '../../types'; - -@Component({ - selector: 'app-profile', - templateUrl: './profile-page.html', - styleUrls: ['./profile-page.scss'], -}) -export class ProfilePageComponent { - patron?: PAIAPatron; - - constructor(private readonly libraryAccountService: LibraryAccountService) {} - - async ionViewWillEnter(): Promise { - try { - this.patron = await this.libraryAccountService.getPatron(); - } catch { - await this.libraryAccountService.handleError(); - } - } - - isUnlimitedExpiry(date = ''): boolean { - return new Date(date).getFullYear() === 9999; - } -} diff --git a/frontend/app/src/app/modules/library/account/profile/profile-page.html b/frontend/app/src/app/modules/library/account/profile/profile-page.html deleted file mode 100644 index f579edc8..00000000 --- a/frontend/app/src/app/modules/library/account/profile/profile-page.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - {{ 'library.account.pages.profile.title' | translate | titlecase }} - - - - @if (patron) { - - - - - {{ 'library.account.pages.profile.labels.id' | translate }}: - - {{ patron.id }} - - - - {{ 'library.account.pages.profile.labels.name' | translate }}: - {{ patron.name_details?.title ?? '' }} {{ patron.name }} - - @if (patron.email) { - - {{ 'library.account.pages.profile.labels.email' | translate }}: - - {{ patron.email }} - - - } - @if (patron.address) { - - {{ 'library.account.pages.profile.labels.address' | translate }}: - - {{ patron.address }} - - - } - @if (patron.expires) { - - {{ 'library.account.pages.profile.labels.expires' | translate }}: - - @if (isUnlimitedExpiry(patron.expires)) { - {{ 'library.account.pages.profile.values.unlimited' | translate }} - } @else { - {{ 'library.account.pages.profile.values.expires' | translate }}: {{ - patron.expires | amDateFormat: 'll' - }} - } - - - } - - - - } @else { - - } - diff --git a/frontend/app/src/app/modules/library/account/profile/profile-page.scss b/frontend/app/src/app/modules/library/account/profile/profile-page.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/app/src/app/modules/library/library.module.ts b/frontend/app/src/app/modules/library/library.module.ts deleted file mode 100644 index 8f778563..00000000 --- a/frontend/app/src/app/modules/library/library.module.ts +++ /dev/null @@ -1,131 +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 {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; -import {RouterModule, Routes} from '@angular/router'; -import {TranslateModule} from '@ngx-translate/core'; -import {LibraryAccountPageComponent} from './account/account.page'; -import {ProfilePageComponent} from './account/profile/profile-page.component'; -import {CheckedOutPageComponent} from './account/checked-out/checked-out-page.component'; -import {HoldsPageComponent} from './account/holds/holds-page.component'; -import {FinesPageComponent} from './account/fines/fines-page.component'; -import {PAIAItemComponent} from './account/elements/paia-item/paiaitem.component'; -import {FirstLastNamePipe} from './account/first-last-name.pipe'; -import {AuthGuardService} from '../auth/auth-guard.service'; -import {ProtectedRoutes} from '../auth/protected.routes'; -import {MomentModule} from 'ngx-moment'; -import {FeeItemComponent} from './account/elements/fee-item/fee-item.component'; -import {DataModule} from '../data/data.module'; -import {UtilModule} from '../../util/util.module'; -import { - IonHeader, - IonToolbar, - IonButtons, - IonBackButton, - IonTitle, - IonContent, - IonSkeletonText, - IonItem, - IonGrid, - IonCol, - IonRow, - IonLabel, - IonCard, - IonCardContent, - IonList, - IonSegment, - IonSegmentButton, - IonButton, - IonBadge, -} from '@ionic/angular/standalone'; -import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive'; - -const routes: ProtectedRoutes | Routes = [ - { - path: 'library-account', - component: LibraryAccountPageComponent, - data: {authProvider: 'paia'}, - canActivate: [AuthGuardService], - }, - { - path: 'library-account/profile', - component: ProfilePageComponent, - data: {authProvider: 'paia'}, - canActivate: [AuthGuardService], - }, - { - path: 'library-account/checked-out', - component: CheckedOutPageComponent, - data: {authProvider: 'paia'}, - canActivate: [AuthGuardService], - }, - { - path: 'library-account/holds', - component: HoldsPageComponent, - data: {authProvider: 'paia'}, - canActivate: [AuthGuardService], - }, - { - path: 'library-account/fines', - component: FinesPageComponent, - data: {authProvider: 'paia'}, - canActivate: [AuthGuardService], - }, -]; - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - IonIconDirective, - RouterModule.forChild(routes), - TranslateModule, - MomentModule, - DataModule, - UtilModule, - IonHeader, - IonBadge, - IonToolbar, - IonButtons, - IonBackButton, - IonTitle, - IonContent, - IonSkeletonText, - IonItem, - IonGrid, - IonCol, - IonRow, - IonLabel, - IonCard, - IonCardContent, - IonList, - IonSegment, - IonSegmentButton, - IonButton, - ], - declarations: [ - LibraryAccountPageComponent, - ProfilePageComponent, - CheckedOutPageComponent, - HoldsPageComponent, - FinesPageComponent, - PAIAItemComponent, - FirstLastNamePipe, - FeeItemComponent, - ], -}) -export class LibraryModule {} diff --git a/frontend/app/src/app/modules/library/types.ts b/frontend/app/src/app/modules/library/types.ts deleted file mode 100644 index 5bc22bb1..00000000 --- a/frontend/app/src/app/modules/library/types.ts +++ /dev/null @@ -1,103 +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 . - */ - -export interface PAIAPatron { - id: string; - name: string; - email?: string; - address?: string; - expires?: string; - status?: PAIAPatronStatus; - type?: string; - note?: string; - // HeBIS specific property (not in PAIA documentation) - name_details?: { - firstname?: string; - lastname?: string; - gender?: string; - title?: string; - }; -} - -export enum PAIAPatronStatus { - Active = 0, - Inactive = 1, - InactiveExpired = 2, - InactiveOutstandingFees = 3, - InactiveExpiredOutstandingFees = 4, -} - -/* - * Document representing a library item received from the HeBIS PAIA service - * TODO: would be good to standardize the items of HeBIS PAIA to match the official PAIA documentation - * e.g. status should be number (0-5), and queue, renewals, reminder should be numbers too - * https://gbv.github.io/paia/paia.html#documents - */ -export interface PAIADocument { - status: string; - item?: string; - edition?: string; - about?: string; - label?: string; - queue?: string; - renewals?: string; - reminder?: string; - starttime?: string; - duedate?: string; - cancancel?: boolean; - canrenew?: boolean; - storage?: string; - // with locations - condition?: unknown; -} - -export interface PAIAItems { - doc: PAIADocument[]; -} - -export interface PAIAFee { - amount: string; - date?: string; - about?: string; - item?: string; - edition?: string; - feetype?: string; - feeid?: string; -} - -export interface PAIAFees { - amount?: string; - fee: PAIAFee[]; -} - -export enum PAIADocumentStatus { - NoRelation = 0, - Reserved = 1, - Ordered = 2, - Held = 3, - Provided = 4, - Rejected = 5, -} - -export interface DocumentAction { - action: 'cancel' | 'renew'; - doc: PAIADocument; -} - -export interface PAIADocumentVisualStatus { - color: 'warning' | 'success'; - status: PAIADocumentStatus; - statusText: 'ordered' | 'ready'; -} diff --git a/frontend/app/src/app/modules/map/map-page.html b/frontend/app/src/app/modules/map/map-page.html index d8865525..7d051e14 100644 --- a/frontend/app/src/app/modules/map/map-page.html +++ b/frontend/app/src/app/modules/map/map-page.html @@ -17,7 +17,6 @@ [title]="'map.page.TITLE' | translate" [placeholder]="'map.page.search_bar.placeholder' | translate" [forcedFilter]="forcedFilter" - [navigation]="[]" > diff --git a/frontend/app/src/app/modules/profile/id-cards.provider.ts b/frontend/app/src/app/modules/profile/id-cards.provider.ts index 93bf189f..16d5dae3 100644 --- a/frontend/app/src/app/modules/profile/id-cards.provider.ts +++ b/frontend/app/src/app/modules/profile/id-cards.provider.ts @@ -18,7 +18,7 @@ export class IdCardsProvider { getIdCards(): Observable { const feature = this.config.config.app.features.extern?.['idCards']; - const auth = this.authHelper.getProvider(feature?.authProvider ?? 'default'); + const auth = this.authHelper.getProvider(); const storedIdCards = from( this.encryptedStorageProvider.get('id-cards') as Promise, ).pipe(filter(it => it !== undefined)); diff --git a/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts b/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts index d8bf4114..0f2453b1 100644 --- a/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts +++ b/frontend/app/src/app/modules/profile/page/profile-page-section.component.ts @@ -12,6 +12,7 @@ * you should have received a copy of the gnu general public license along with * this program. if not, see . */ +import {Browser} from '@capacitor/browser'; import {Component, inject, input} from '@angular/core'; import {SCSection} from '../../../../config/profile-page-sections'; import {AuthHelperService} from '../../auth/auth-helper.service'; @@ -33,9 +34,7 @@ export class ProfilePageSectionComponent { loggedIn = toSignal( toObservable(this.item).pipe( - mergeMap(item => - item.authProvider ? this.authHelper.getProvider(item.authProvider).isLoggedIn$ : of(false), - ), + mergeMap(item => (item.authProvider ? this.authHelper.getProvider().isLoggedIn$ : of(false))), ), ); @@ -69,10 +68,22 @@ export class ProfilePageSectionComponent { const providerType = this.item().authProvider; if (!providerType) return; if (this.loggedIn()) { - await this.authHelper.getProvider(providerType).signOut(); + await this.authHelper.getProvider().signOut(); await this.authHelper.endBrowserSession(providerType); } else { - await this.authHelper.getProvider(providerType).signIn(); + await this.authHelper.getProvider().signIn(); } } + + isExternalLink(link: string[]): boolean { + return /^https?:\/\//i.test(link[0] ?? ''); + } + + async openExternalLink(link: string[]) { + const url = link[0]; + + if (!url) return; + + await Browser.open({url}); + } } diff --git a/frontend/app/src/app/modules/profile/page/profile-page-section.html b/frontend/app/src/app/modules/profile/page/profile-page-section.html index 5b46f123..5876a034 100644 --- a/frontend/app/src/app/modules/profile/page/profile-page-section.html +++ b/frontend/app/src/app/modules/profile/page/profile-page-section.html @@ -21,25 +21,51 @@ } @else { } - {{ 'profile.buttons.default.log_' + (loggedIn() ? 'out' : 'in') | translate }} + + + {{ 'profile.buttons.default.log_' + (loggedIn() ? 'out' : 'in') | translate }} + } @for (link of item().links; track link) { - -
- @if (link.icon) { - - } - {{ 'name' | translateSimple: link }} -
-
+ @if (isExternalLink(link.link)) { + +
+ @if (link.icon) { + + } + + + {{ 'name' | translateSimple: link }} + +
+
+ } @else { + +
+ @if (link.icon) { + + } + + + {{ 'name' | translateSimple: link }} + +
+
+ } }
diff --git a/frontend/app/src/app/modules/profile/page/profile-page.component.ts b/frontend/app/src/app/modules/profile/page/profile-page.component.ts index 923f1f24..368b3532 100644 --- a/frontend/app/src/app/modules/profile/page/profile-page.component.ts +++ b/frontend/app/src/app/modules/profile/page/profile-page.component.ts @@ -14,7 +14,6 @@ */ import {Component} from '@angular/core'; import {AuthHelperService} from '../../auth/auth-helper.service'; -import {SCAuthorizationProviderType} from '@openstapps/core'; import {ActivatedRoute} from '@angular/router'; import {ScheduleProvider} from '../../calendar/schedule.provider'; import {profilePageSections} from '../../../../config/profile-page-sections'; @@ -33,27 +32,21 @@ export class ProfilePageComponent { readonly scheduleProvider: ScheduleProvider, ) {} - async signIn(providerType: SCAuthorizationProviderType) { + async signIn() { const originPath = this.activatedRoute.snapshot.queryParamMap.get('origin_path'); await (originPath ? this.authHelper.setOriginPath(originPath) : this.authHelper.deleteOriginPath()); - await this.authHelper.getProvider(providerType).signIn(); + await this.authHelper.getProvider().signIn(); } - async signOut(providerType: SCAuthorizationProviderType) { - await this.authHelper.getProvider(providerType).signOut(); + async signOut() { + await this.authHelper.getProvider().signOut(); } ionViewWillEnter() { this.authHelper - .getProvider('default') - .getValidToken() - .then(() => void this.authHelper.getProvider('default').loadUserInfo()) - .catch(() => { - // noop - }); - this.authHelper - .getProvider('paia') + .getProvider() .getValidToken() + .then(() => void this.authHelper.getProvider().loadUserInfo()) .catch(() => { // noop }); diff --git a/frontend/app/src/app/modules/schedule/page/choose-events-page.html b/frontend/app/src/app/modules/schedule/page/choose-events-page.html index fab2de77..eb7c21ba 100644 --- a/frontend/app/src/app/modules/schedule/page/choose-events-page.html +++ b/frontend/app/src/app/modules/schedule/page/choose-events-page.html @@ -13,7 +13,6 @@ ~ this program. If not, see . -->