...
 
Commits (2)
import { ObservableArray } from 'tns-core-modules/data/observable-array';
import { View } from 'tns-core-modules/ui/core/view';
import { Controller } from './controller';
import { BarcodeScanner } from 'nativescript-barcodescanner';
import { AccountsController } from '../controllers/accounts';
......@@ -16,14 +17,17 @@ import {
WrongLoginPassword,
Cookies,
Manager,
Iso8601Date
Iso8601Date,
PortalActionResult
} from '../models/';
import { RadSideDrawer } from 'nativescript-ui-sidedrawer';
import { RadListView } from 'nativescript-ui-listview';
import { WebView } from 'tns-core-modules/ui/web-view';
import { OAuthWebViewClient } from './utils/oauth-webview-client';
import { MbaWebView } from './utils/mba-web-view';
import { WebViewUtils } from 'nativescript-webview-utils';
import * as imageSource from "tns-core-modules/image-source";
import * as AppVersion from 'nativescript-appversion'
export class LibraryController extends Controller {
protected _account: Account;
......@@ -33,7 +37,7 @@ export class LibraryController extends Controller {
protected _on_loans_page_loaded = null;
protected _on_successful_login = null;
protected _manager: Manager;
protected _view;
protected _view: View;
public setPortal(portal: Portal): LibraryController {
this._portal = portal;
......@@ -337,22 +341,25 @@ export class LibraryController extends Controller {
public onWebsitePageLoaded(args) {
this._view = args.object;
let webview = args.object.getViewById("webView");
let activity_indicator = args.object.getViewById("activity-indicator");
let webview = args.object.getViewById("webView")
let activity_indicator = args.object.getViewById("activity-indicator")
this._addHeadersToWebView(webview);
new Cookies().syncHttpToWebView(webview).then(() => {
webview.src = this.get('website_url');
webview.src = this.get('website_url')
})
activity_indicator.busy = true;
activity_indicator.busy = true
webview.on(WebView.loadFinishedEvent, () => {
activity_indicator.busy = false;
});
activity_indicator.busy = false
})
}
public onOauthPageLoaded(args) {
let webview = args.object.getViewById("webView");
let oauth_web_view = OAuthWebViewClient.newWithWebView(webview);
this._doOAuthLogin(oauth_web_view);
}
......@@ -377,6 +384,51 @@ export class LibraryController extends Controller {
}
public renewLoanAction(args) {
const loan = <Loan>args.object.bindingContext;
return this._portal
.renewLoan(loan.getAccount(), loan)
.then((result: PortalActionResult) => {
const message = result.isSuccess()
? L('loan_renewed', new Iso8601Date(loan.getDateDue()).asLongDate())
: result.getMessage()
if (result.isSuccess())
Database.current().save(loan);
this.dialogs.alert({
title: L('renew'),
message: message,
okButtonText: L('OK')
})
})
}
public cancelHoldAction(args) {
const hold = <Hold>args.object.bindingContext;
this.dialogs
.confirm({
title: hold.getTitle(),
message: L('confirm_cancel_hold'),
okButtonText: L('OK'),
cancelButtonText: L('cancel')
})
.then((result) => {
if (!result) return;
return this._portal
.cancelHold(hold.getAccount(), hold)
.then((result: PortalActionResult) => {
if (result.isSuccess()) {
Database.current().delete(hold);
this._loadLoansAndHolds()
}
})
})
}
protected _authenticationDone(account: Account): LibraryController {
Database.current().save(account);
this.set('busy', true);
......@@ -505,9 +557,22 @@ export class LibraryController extends Controller {
protected _loadLoansAndHolds(): this {
const holds = this._account.findHolds(Database.current())
holds.forEach((hold) => {
hold.set('cancellable', this._portal.isCancellable(this._account,
hold))
})
const loans = this._account.findLoans(Database.current())
loans.forEach((loan) => {
loan.set('renewable', this._portal.isRenewable(this._account,
loan))
})
return this
._updateItems(this._loans, this._account.findLoans(Database.current()))
._updateItems(this._holds, this._account.findHolds(Database.current()))
._updateItems(this._loans, loans)
._updateItems(this._holds, holds)
}
......@@ -528,13 +593,13 @@ export class LibraryController extends Controller {
if (!this._view)
return this;
let loans = this._view.getViewById("loans")
let loans: RadListView = this._view.getViewById("loans")
if (loans != undefined) {
loans.notifyPullToRefreshFinished()
loans.refresh()
}
let holds = this._view.getViewById("holds")
let holds: RadListView = this._view.getViewById("holds")
if (holds != undefined) {
holds.notifyPullToRefreshFinished()
holds.refresh()
......@@ -542,4 +607,13 @@ export class LibraryController extends Controller {
return this;
}
protected _addHeadersToWebView(webview): this {
let headers: Map<string, string> = new Map()
headers.set('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 MyBibApp/' + AppVersion.getVersionNameSync())
WebViewUtils.addHeaders(webview, headers);
return this;
}
}
......@@ -14,6 +14,8 @@ export class OAuthWebViewClient {
public constructor(view: WebView) {
this._view = view;
view.android.getSettings().setUserAgentString(L('app_name'));
let WVClient = (<any>android.webkit.WebViewClient).extend({
shouldOverrideUrlLoading: (_webview, page_url) => {
if (typeof page_url === 'object') // android 7.1.1 compatibility
......
......@@ -10,38 +10,51 @@ var NSWKNavigationDelegate = (NSObject as any).extend(
_onError: null,
webViewDidStartLoad: function(webview: WKWebView) {
console.log('start Load');
return this._origDelegate.webViewDidStartLoad(webview);
},
webViewDidStartProvisionalNavigation: function(webview, navigation) {
console.log('start prov url:' + webview.URL.absoluteString);
return this._origDelegate.webViewDidStartProvisionalNavigation(webview, navigation);
},
webViewDidFailNavigationWithError: function(webview: WKWebView, navigation: WKNavigation, error: NSError) {
console.log('didFailNavigationWithError');
return this._origDelegate.webViewDidFailNavigationWithError(webview, navigation, error);
},
webViewDecidePolicyForNavigationActionDecisionHandler: function(webview: WKWebView, navigationAction: WKNavigationAction, decisionHandler: WKNavigationActionPolicy) {
console.log('decidePolicyForNavigationActionDecisionHandler:' + webview.URL.absoluteString);
webViewDecidePolicyForNavigationActionDecisionHandler: function(webview: WKWebView, navigationAction: WKNavigationAction, decisionHandler: (p1: WKNavigationActionPolicy) => void) {
if (!webview.URL)
return this._origDelegate.webViewDecidePolicyForNavigationActionDecisionHandler(webview, navigationAction, decisionHandler);
let page_url = webview.URL.absoluteString;
const user_agent = L('app_name');
if (webview.customUserAgent !== user_agent) {
webview.customUserAgent = user_agent;
const customRequest = navigationAction.request.mutableCopy();
customRequest.setValueForHTTPHeaderField('User-Agent', user_agent);
webview.loadRequest(customRequest);
decisionHandler(WKNavigationActionPolicy.Cancel);
return;
}
let matches = page_url.match(/.*token=(.*)$/);
if (matches) {
let token = matches[1];
this._onTokenSet(token);
new Cookies().clearCookies();
decisionHandler(WKNavigationActionPolicy.Cancel);
return;
}
if (page_url == 'bibapp://authorize') {
this._onError();
decisionHandler(WKNavigationActionPolicy.Cancel);
return;
}
return this._origDelegate.webViewDecidePolicyForNavigationActionDecisionHandler(webview, navigationAction, decisionHandler);
......@@ -49,7 +62,6 @@ var NSWKNavigationDelegate = (NSObject as any).extend(
webViewShouldStartLoadWithRequestNavigationType: function(webview: WKWebView, request: NSURLRequest, navigationType: number) {
console.log('start loading');
return this._origDelegate.webViewShouldStartLoadWithRequestNavigationType(webview, request, navigationType);
},
},
......
......@@ -94,4 +94,6 @@
</string>
<string name="used_software">Software used</string>
<string name="used_software_content">The application used following software libraries:</string>
<string name="renew">Renew</string>
<string name="confirm_cancel_hold">Are you sure to cancel this hold ?</string>
</resources>
......@@ -94,4 +94,6 @@
</string>
<string name="used_software">Software utilizado</string>
<string name="used_software_content">La aplicación utiliza las siguientes bibliotecas de software:</string>
<string name="renew">Extender</string>
<string name="confirm_cancel_hold">Cancelar reserva ?</string>
</resources>
......@@ -94,4 +94,7 @@
</string>
<string name="used_software">Logiciels utilisés</string>
<string name="used_software_content">L\'application utilise les bibliothèques logicielles suivantes:</string>
<string name="renew">Prolonger</string>
<string name="confirm_cancel_hold">Voulez-vous annuler cette réservation ?</string>
<string name="loan_renewed">Prêt prolongé jusqu\'au %s</string>
</resources>
import {Loan,
Hold,
Card,
Persistable,
Serializable,
DataSource} from '../models/'
import {
Loan,
Hold,
Card,
Persistable,
Serializable,
DataSource
} from '../models/'
export class Account extends Persistable {
......@@ -27,8 +29,8 @@ export class Account extends Persistable {
return this._cookies
}
public setLabel(label:string): Account {
public setLabel(label: string): Account {
this._label = label
return this
}
......@@ -66,7 +68,7 @@ export class Account extends Persistable {
.forEach((loan) => {
datasource.delete(loan)
})
this._number_of_loans = 0
return this
}
......@@ -78,7 +80,7 @@ export class Account extends Persistable {
.forEach((hold) => {
datasource.delete(hold)
})
this._number_of_holds = 0
return this
}
......@@ -88,7 +90,7 @@ export class Account extends Persistable {
this._number_of_loans = number_of_loans
return this
}
public setNumberOfHolds(number_of_holds: number): this {
this._number_of_holds = number_of_holds
......@@ -100,7 +102,10 @@ export class Account extends Persistable {
return this
._findAll(datasource, Loan)
.sort((a, b) => {
return a.getDateDue().localeCompare(b.getDateDue())
const date_compare = a.getDateDue().localeCompare(b.getDateDue())
return (date_compare !== 0)
? date_compare
: a.getTitle().localeCompare(b.getTitle())
})
}
......@@ -108,12 +113,15 @@ export class Account extends Persistable {
public findHolds(datasource: DataSource): Array<Hold> {
return this._findAll(datasource, Hold)
.sort((a, b) => {
return a.getStatus().localeCompare(b.getStatus())
const status_compare = a.getStatus().localeCompare(b.getStatus())
return (status_compare !== 0)
? status_compare
: a.getTitle().localeCompare(b.getTitle())
})
}
protected _findAll<T extends Persistable>(datasource: DataSource, modelClass:{new():T}): Array<T> {
protected _findAll<T extends Persistable>(datasource: DataSource, modelClass: { new(): T }): Array<T> {
return datasource
.findAll(modelClass)
.filter((obj) => {
......@@ -136,7 +144,7 @@ export class Account extends Persistable {
this._url.replace(new RegExp('/$'), '')
if (!this._url.includes('://'))
this._url = 'http://' + this._url
return this._url
return this._url
}
......@@ -164,7 +172,7 @@ export class Account extends Persistable {
public beforeSave(datasource: DataSource) {
this._cacheCounters(datasource)
datasource.save(this._card)
datasource.save(this._card)
}
......@@ -174,7 +182,7 @@ export class Account extends Persistable {
}
public getAdapterIdentifier():string {
public getAdapterIdentifier(): string {
return this._credentials['portal']
? this._credentials['portal']
: 'autodetect'
......@@ -204,7 +212,7 @@ export class Account extends Persistable {
public daysToNextLoanAction(to_date: Date = new Date()): number {
if (!this.loanEarliestDateDue())
return null
let from_date: Date = new Date(this.loanEarliestDateDue())
let time_diff = to_date.getTime() - from_date.getTime()
return Math.ceil(time_diff / (1000 * 3600 * 24))
......@@ -231,7 +239,7 @@ export class Account extends Persistable {
this._card = serializable.get('card', Card)
if (!this._card)
this._card = new Card()
this._number_of_loans = serializable.get('number_of_loans')
if (!this._number_of_loans)
this._number_of_loans = 0
......
import {ItemOperation, Serializable} from '../models';
import { ItemOperation, Serializable } from '../models';
export class Hold extends ItemOperation {
protected _hold_id: string;
protected _status: string;
public setHoldId(hold_id: string): this {
this._hold_id = hold_id;
return this;
......@@ -33,11 +33,10 @@ export class Hold extends ItemOperation {
.set('status', this._status);
}
public materializeFrom(serializable: Serializable) {
super.materializeFrom(serializable);
this._hold_id = serializable.get('hold_id');
this._status = serializable.get('status');
}
}
export { Persistable, Serializable, DataSource } from './persistable';
export { Database } from './database';
export { Account } from './account';
export { ItemOperation } from './item-operation';
export { Loan } from './loan';
export { Hold } from './hold';
export { Portal } from './portal';
export { HTTPClient } from './http/http-client';
export { NSHTTPClient } from './http/ns-http-client';
export { NullHTTPClient } from './http/null-http-client';
export { PortalAdapter } from './portal/adapter';
export { OAuthTokenNotSet } from './portal/oauth-token-not-set';
export { WrongLoginPassword } from './portal/wrong-login-password';
export { PortalDetectionFail } from './portal/portal-detection-fail';
export { Cookies } from './cookies/cookies';
export { Manager, MBPortal } from './manager';
export { Record } from './record';
export { Item } from './item';
export { Novelties } from './novelties';
export { Card } from './card';
export { Iso8601Date } from './iso8601_date';
export { BackgroundJobs } from './background-jobs/background-jobs';
export { Form } from './form';
export { Persistable, Serializable, DataSource } from './persistable'
export { Database } from './database'
export { Account } from './account'
export { ItemOperation } from './item-operation'
export { Loan } from './loan'
export { Hold } from './hold'
export { Portal } from './portal'
export { HTTPClient } from './http/http-client'
export { NSHTTPClient } from './http/ns-http-client'
export { NullHTTPClient } from './http/null-http-client'
export { PortalAdapter } from './portal/adapter'
export { OAuthTokenNotSet } from './portal/oauth-token-not-set'
export { WrongLoginPassword } from './portal/wrong-login-password'
export { PortalDetectionFail } from './portal/portal-detection-fail'
export { PortalActionResult } from './portal/portal-action-result'
export { Cookies } from './cookies/cookies'
export { Manager, MBPortal } from './manager'
export { Record } from './record'
export { Item } from './item'
export { Novelties } from './novelties'
export { Card } from './card'
export { Iso8601Date } from './iso8601_date'
export { BackgroundJobs } from './background-jobs/background-jobs'
export { Form } from './form'
import {Account, Persistable, Serializable} from '../models/';
import { Account, Persistable, Serializable } from '../models/';
export abstract class ItemOperation extends Persistable {
protected _account: Account;
......@@ -38,24 +38,24 @@ export abstract class ItemOperation extends Persistable {
}
public setTitle(title:string): this {
public setTitle(title: string): this {
this._item.title = title;
return this;
}
public getTitle():string {
return this._item.title;
public getTitle(): string {
return this._item.title || '';
}
public setAuthor(author:string): this {
public setAuthor(author: string): this {
this._item.author = author;
return this;
}
public getAuthor():string {
public getAuthor(): string {
return this._item.author;
}
......@@ -93,14 +93,14 @@ export abstract class ItemOperation extends Persistable {
return this._account && this._account.getId() == other.getId();
}
public serializeOn(serializable: Serializable) {
serializable
.set('item', this._item)
.set('account', this._account.getId());
}
public materializeFrom(serializable: Serializable) {
this._item = serializable.get("item") || {};
this._account = serializable.get('account', Account);
......
......@@ -45,15 +45,18 @@ export abstract class Persistable extends Observable {
export interface Serializable {
interface Serializable {
set(name, value): Serializable;
get(name, modelClass?: any): any;
serialize(): Object;
}
export interface DataSource {
interface DataSource {
delete(model: Persistable): DataSource;
save(model: Persistable): DataSource;
findAll(modelClass: any): any;
}
export { DataSource, Serializable }
import * as PortalAdapters from './portal/index';
import {PortalAdapter} from './portal/adapter';
import {Database, Account, Item} from '../models';
import { PortalAdapter } from './portal/adapter';
import { Database, Account, Hold, Loan, Item } from '../models';
export class Portal {
protected _adapters: Map<string, PortalAdapter> = new Map<string, PortalAdapter>();
get adapters(): Map<string, PortalAdapter> {
return this._adapters.size > 0
? this._adapters
......@@ -39,7 +40,7 @@ export class Portal {
.searchUrl(account, terms);
}
public canFetchNovelties(account: Account): boolean {
return this
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
......@@ -68,10 +69,38 @@ export class Portal {
}
public cancelHold(account: Account, hold: Hold): Promise<any> {
return this
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
.cancelHold(account, hold)
}
public renewLoan(account: Account, loan: Loan): Promise<any> {
return this
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
.renewLoan(account, loan)
}
public isCancellable(account: Account, hold: Hold): boolean {
return this
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
.isCancellable(hold)
}
public isRenewable(account: Account, loan: Loan): boolean {
return this
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
.isRenewable(loan)
}
public updateAccountInfo(account: Account): Promise<any> {
return this
.findAdapterIdentifiedBy(account.getAdapterIdentifier())
.updateCard(account).then( () => {
.updateCard(account).then(() => {
Database.current().save(account)
return account
});
......@@ -79,7 +108,7 @@ export class Portal {
protected _populateAdapters(): Map<string, PortalAdapter> {
for(let key in PortalAdapters) {
for (let key in PortalAdapters) {
let adapter: PortalAdapter = new PortalAdapters[key]();
this._adapters.set(adapter.getIdentifier(), adapter);
}
......
import * as http from 'tns-core-modules/http';
import * as http from 'tns-core-modules/http'
import { ImageSource } from 'tns-core-modules/image-source'
import {
Cookies,
......@@ -9,116 +9,127 @@ import {
Loan,
Item,
Form,
Hold
} from '../../models';
Hold,
PortalActionResult
} from '../../models'
export abstract class PortalAdapter {
private _http: any;
private _cookies: Cookies;
private _http: any
private _cookies: Cookies
public constructor() {
this._http = new NSHTTPClient();
this._cookies = new Cookies();
this._http = new NSHTTPClient()
this._cookies = new Cookies()
}
public setHTTP(http: any): PortalAdapter {
this._http = http;
return this;
public setHTTP(http: any): this {
this._http = http
return this
}
public updateAdapterHTTPClient(adapter: PortalAdapter): PortalAdapter {
adapter.setHTTP(this._http);
return this;
adapter.setHTTP(this._http)
return this
}
public signIn(account): Promise<any> {
this._cookies.clearCookies();
this._cookies.clearCookies()
return this
._signIn(account)
.then(() => {
return this.updateAccountInfo(account);
});
return this.updateAccountInfo(account)
})
}
public refresh(account): Promise<any> {
if (!account.isConnected()) {
account.setCookies(new Array<any>());
account.setCookies(new Array<any>())
return this._signIn(account).then(() => {
return this.refresh(account);
});
return this.refresh(account)
})
}
let db: Database = Database.current();
let db: Database = Database.current()
return this
._fetchLoans(account)
.then((loans) => {
account.deleteLoans(db);
account.deleteLoans(db)
loans.forEach((loan) => {
db.save(loan.setAccount(account));
});
db.save(loan.setAccount(account))
})
db.save(account);
db.save(account)
})
.then(() => {
return this._fetchHolds(account);
return this._fetchHolds(account)
})
.then((holds) => {
account.deleteHolds(db);
account.deleteHolds(db)
holds.forEach((hold) => {
db.save(hold.setAccount(account));
});
db.save(hold.setAccount(account))
})
account.setNumberOfHolds(holds.length);
db.save(account);
account.setNumberOfHolds(holds.length)
db.save(account)
})
}
public request(account: Account, options: any): Promise<http.HttpResponse> {
this._cookies.setCookies(account.getCookies());;
this._cookies.setCookies(account.getCookies())
return this._http
.request(options)
.then((response) => {
account.setCookies(this._cookies.getCookies());
return response;
});
account.setCookies(this._cookies.getCookies())
return response
})
}
public searchUrl(account: Account, terms: string): Promise<string> {
return Promise.resolve('');
return Promise.resolve('')
}
public canSearch(): boolean {
return false;
return false
}
public canFetchNovelties(): boolean {
return false;
return false
}
public fetchNovelties(account: Account, page: number): Promise<any> {
return Promise.resolve(new Array());
return Promise.resolve(new Array())
}
public updateCard(account: Account): Promise<any> {
return Promise.resolve();
return Promise.resolve()
}
public cancelHold(account: Account, hold: Hold): Promise<PortalActionResult> {
return Promise.reject(new PortalActionResult)
}
public renewLoan(account: Account, loan: Loan): Promise<PortalActionResult> {
return Promise.reject(new PortalActionResult)
}
......@@ -127,37 +138,47 @@ export abstract class PortalAdapter {
? Promise.resolve()
: this._fetchAccountLabel(account).then(
(label) => {
account.setLabel(label);
});
account.setLabel(label)
})
}
public fetchItem(account, barcode): Promise<Item> {
return Promise.reject(null);
return Promise.reject(null)
}
public isCancellable(hold: Hold): boolean {
return false;
}
public isRenewable(loan: Loan): boolean {
return false;
}
protected _fetchAccountLabel(account: Account): Promise<any> {
return Promise.resolve(account.credentials['login']);
};
return Promise.resolve(account.credentials['login'])
}
protected _fetchHolds(account: Account): Promise<Array<Hold>> {
return Promise.resolve(new Array<Hold>());
return Promise.resolve(new Array<Hold>())
}
protected _fetchThumbnails<T extends ItemOperation>(account: Account,
items: Array<T>): Promise<Array<T>> {
return Promise.all(items.map((item) => {
return this._fetchThumbnail(account, item);
return this._fetchThumbnail(account, item)
}))
}
protected _fetchThumbnail<T extends ItemOperation>(account: Account, item: T): Promise<T> {
if (!item.getRecordThumbnail())
return Promise.resolve(item);
return Promise.resolve(item)
return this
.request(account,
......@@ -179,7 +200,7 @@ export abstract class PortalAdapter {
return response.content
.toImage()
.then((image_source: ImageSource) => {
return item.setRecordThumbnail('data:image/jpg;base64,' + image_source.toBase64String('jpg'));
return item.setRecordThumbnail('data:image/jpg;base64,' + image_source.toBase64String('jpg'))
})
})
}
......@@ -189,15 +210,15 @@ export abstract class PortalAdapter {
return new Form(form).toUrlEncodedString()
}
protected abstract _signIn(account: Account): Promise<any>;
protected abstract _signIn(account: Account): Promise<any>
public abstract getIdentifier(): string;
public abstract getIdentifier(): string
public abstract getLabel(): string;
public abstract getLabel(): string
public abstract canHandleWebsite($): boolean;
public abstract canHandleWebsite($): boolean
protected abstract _fetchLoans(account: Account): Promise<Array<Loan>>;
protected abstract _fetchLoans(account: Account): Promise<Array<Loan>>
}
import {PortalAdapter} from './adapter';
import {Database, Account, Hold, Loan, Record, Item} from '../../models';
import {OAuthTokenNotSet} from './oauth-token-not-set';
import { PortalAdapter } from './adapter';
import { Database, Account, Hold, Loan, Record, Item, PortalActionResult } from '../../models';
import { OAuthTokenNotSet } from './oauth-token-not-set';
export class Bokeh extends PortalAdapter {
......@@ -8,18 +8,24 @@ export class Bokeh extends PortalAdapter {
return 'bokeh';
}
public getLabel(): string {
return 'Bokeh';
}
public requestUser(account: Account, action: string): Promise<any> {
const authorization = "Bearer " + account.credentials['token']
return this
.request(account,
{ url: account.getUrl() + '/api/user/' + action,
headers: {"authorization": "Bearer " + account.credentials['token']} })
.then( (response) => {
{
url: account.getUrl() + '/api/user/' + action,
headers: {
"Authorization": authorization,
"authorization": authorization // compatibility Bokeh =< 8.0.25
}
})
.then((response) => {
let datas = response.content.toJSON();
if (datas['error']) {
......@@ -36,9 +42,9 @@ export class Bokeh extends PortalAdapter {
public updateCard(account: Account): Promise<any> {
return this
.requestUser(account, 'account')
.then( (datas) => {
.then((datas) => {
account.credentials['login'] = datas['account']['login'];
let card = account.getCard();
card.setNumber(datas['account']['card']['id']);
card.setExpirationDate(datas['account']['card']['expire_at']);
......@@ -46,26 +52,51 @@ export class Bokeh extends PortalAdapter {
}
public cancelHold(account: Account, hold: Hold): Promise<PortalActionResult> {
return this
.requestUser(account, 'cancel-hold/id/' + hold.getHoldId())
.then((datas) => {
if (datas['status'] == 'canceled')
return new PortalActionResult().beSuccess()
return new PortalActionResult().beError(datas['error']);
})
}
public renewLoan(account: Account, loan: Loan): Promise<PortalActionResult> {
return this
.requestUser(account, 'renew-loan/id/' + loan.getLoanId())
.then((datas) => {
loan.setDateDue(datas['date_due'])
return new PortalActionResult().beSuccess()
})
.catch((error) => {
return new PortalActionResult().beError(error.name);
})
}
public fetchNovelties(account: Account, page: number): Promise<any> {
return this
.request(account,
{ url: account.getUrl() + '/recherche/simple/tri/date_creation+desc/format/json/page/' + page })
.then( (response) => {
let search_result = response.content.toJSON();
{ url: account.getUrl() + '/recherche/simple/tri/date_creation+desc/format/json/page/' + page })
.then((response) => {
let search_result = response.content.toJSON();
let records = new Array();
for(let record of search_result.records) {
for (let record of search_result.records) {
if (record.thumbnail) {
let author: string = (record.creators && record.creators.length )
let author: string = (record.creators && record.creators.length)
? record.creators[0]
: '';
records.push( new Record()
.setUrl(record.permalink)
.setThumbnailUrl(record.thumbnail)
.setTitle(record.titles[0])
.setDocType(record.type.label)
.setAuthor(author.replace('|', ' ').trim()) );
records.push(new Record()
.setUrl(record.permalink)
.setThumbnailUrl(record.thumbnail)
.setTitle(record.titles[0])
.setDocType(record.type.label)
.setAuthor(author.replace('|', ' ').trim()));
}
}
......@@ -87,8 +118,8 @@ export class Bokeh extends PortalAdapter {
public fetchItem(account, barcode): Promise<Item> {
return this
.request(account,
{ url: account.getUrl() + '/api/catalog/item/barcode/' + barcode })
.then( (response) => {
{ url: account.getUrl() + '/api/catalog/item/barcode/' + barcode })
.then((response) => {
let datas = response.content.toJSON();
let item = new Item(datas['barcode'])
item
......@@ -115,20 +146,30 @@ export class Bokeh extends PortalAdapter {
return true;
}
public isCancellable(hold: Hold): boolean {
return true;
}
public isRenewable(loan: Loan): boolean {
return true;
}
protected _fetchAccountLabel(account: Account): Promise<any> {
return this
.requestUser(account, 'account')
.then( (datas) => {
.then((datas) => {
return datas['account']['label'];
})
.catch( () => {
.catch(() => {
let regex = new RegExp("https?://(www\.)?([^/\.?]+)");
return regex.exec(account.getUrl())[2];
});
}
public _fetchLoans(account: Account): Promise<Array<Loan>> {
let record_url = account.getUrl() + '/recherche/viewnotice/retour_abonne/prets/id/';
let loans_url = account.getUrl() + '/abonne/prets'
......@@ -136,18 +177,18 @@ export class Bokeh extends PortalAdapter {
.requestUser(account, 'loans')
.then(
(datas) => {
return datas['loans'].map( (loan) => {
return datas['loans'].map((loan) => {
return new Loan()
.setLoanId(loan['id'])
.setTitle(loan['title'])
.setAuthor(loan['author'])
.setDateDue(loan['date_due'])
.setLibrary(loan['library'])
.setRecordId(loan['record'] ? loan['record']['id']: null)
.setRecordId(loan['record'] ? loan['record']['id'] : null)
.setRecordUrl(loan['record']
? record_url + loan['record']['id']
: loans_url)
.setRecordThumbnail(loan['record'] ? loan['record']['thumbnail']: null)
? record_url + loan['record']['id']
: loans_url)
.setRecordThumbnail(loan['record'] ? loan['record']['thumbnail'] : null)
});
});
}
......@@ -160,25 +201,25 @@ export class Bokeh extends PortalAdapter {
return this
.requestUser(account, 'holds')
.then( (datas) => {
return datas['holds'].map( (hold) => {
.then((datas) => {
return datas['holds'].map((hold) => {
return new Hold()
.setHoldId(hold['id'])
.setTitle(hold['title'])
.setAuthor(hold['author'])
.setStatus(hold['status'])
.setLibrary(hold['library'])
.setRecordId(hold['record'] ? hold['record']['id']: null)
.setRecordId(hold['record'] ? hold['record']['id'] : null)
.setRecordUrl(hold['record']
? record_url + hold['record']['id']
: holds_url)
.setRecordThumbnail(hold['record'] ? hold['record']['thumbnail']: null);
? record_url + hold['record']['id']
: holds_url)
.setRecordThumbnail(hold['record'] ? hold['record']['thumbnail'] : null);
})
});
}
protected _signIn(account): Promise<any> {
if (!account.credentials['token'])
......
export {Bokeh} from './bokeh';
export {PMB} from './pmb';
export {OrpheeMedia} from './orpheemedia';
export {DecalogOpac} from './decalog-opac';
export {DecalogOpacV2} from './decalog-opac-v2';
export {Ermes} from './ermes';
export {Koha} from './koha';
export {KohaCas} from './koha-cas';
export {InMedia} from './inmedia';
export {Symphony} from './symphony';
export {InMediaV2} from './inmedia-v2';
export {IguanaV3} from './iguana-v3';
export {Autodetect} from './autodetect';
export { Bokeh } from './bokeh';
export { PMB } from './pmb';
export { OrpheeMedia } from './orpheemedia';
export { DecalogOpac } from './decalog-opac';
export { DecalogOpacV2 } from './decalog-opac-v2';
export { Ermes } from './ermes';
export { Koha } from './koha';
export { KohaCas } from './koha-cas';
export { InMedia } from './inmedia';
export { Symphony } from './symphony';
export { InMediaV2 } from './inmedia-v2';
export { IguanaV3 } from './iguana-v3';
export { Autodetect } from './autodetect';
export class PortalActionResult {
static readonly SUCCESS: string = 'SUCCESS'
static readonly ERROR: string = 'ERROR'
static readonly UNSUPPORTED: string = 'UNSUPPORTED'
protected _status: string = PortalActionResult.UNSUPPORTED
protected _message: string
public beSuccess(): this {
this._status = PortalActionResult.SUCCESS
return this
}
public beError(message: string): this {
this._status = PortalActionResult.ERROR
this._message = message
return this
}
public isSuccess(): boolean {
return this._status == PortalActionResult.SUCCESS
}
public getMessage(): string {
return this._message
}
}
declare var expect: any;
import {HTTPScenario} from './http-scenario';
import { HTTPScenario } from './http-scenario';
abstract class BokehScenario extends HTTPScenario {
protected _headers = {
"Authorization": "Bearer 123456789",
"authorization": "Bearer 123456789"
}
}
abstract class BokehWithValidToken extends HTTPScenario {
abstract class BokehWithValidToken extends BokehScenario {
public constructor(base_url: string) {
super();
this.setup(base_url);
......@@ -11,14 +20,16 @@ abstract class BokehWithValidToken extends HTTPScenario {
public setup(base_url: string) {
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.accountJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.accountJSON() });
}
public loansJSON(): string {
return JSON.stringify( { loans:[] } );
return JSON.stringify({ loans: [] });
}
......@@ -44,9 +55,11 @@ export class BokehSignInWithValidToken extends BokehWithValidToken {
public constructor(base_url: string) {
super(base_url);
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.accountJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.accountJSON() });
}
}
......@@ -55,9 +68,11 @@ export class BokehSignInWithValidToken extends BokehWithValidToken {
export class BokehAccountInfoWithValidToken extends BokehWithValidToken {
public setup(base_url) {
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.accountJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.accountJSON() });
}
}
......@@ -68,13 +83,17 @@ export class BokehLoansAndHoldsWithValidToken extends BokehWithValidToken {
public constructor(base_url: string) {
super(base_url);
this
.expect({ url: base_url + '/api/user/loans',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.loansJSON() })
.expect({
url: base_url + '/api/user/loans',
headers: this._headers
},
{ content: this.loansJSON() })
this
.expect({ url: base_url + '/api/user/holds',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.holdsJSON() });
.expect({
url: base_url + '/api/user/holds',
headers: this._headers
},
{ content: this.holdsJSON() });
}
......@@ -82,24 +101,27 @@ export class BokehLoansAndHoldsWithValidToken extends BokehWithValidToken {
return JSON.stringify(
{
loans: [
{ id: '13137_180337',
{
id: '13137_180337',
title: 'Harry Potter',
author: 'J.K. Rowling',
date_due: '2016-08-12',
loaned_by: 'Captain Haddock',
library: 'Annecy',
record: {
record: {
id: 9876,
thumbnail: 'https://images.fr/SL300_.jpg'
}
},
{ id: '13137_193847',
{
id: '13137_193847',
title: 'Millenium',
author: 'Stieg Larsson',
date_due: '2016-09-11',
loaned_by: 'Captain Haddock',
library: 'Annecy' }
library: 'Annecy'
}
]
});
}
......@@ -149,16 +171,20 @@ export class BokehLoansAndHoldsWithValidToken extends BokehWithValidToken {
export class BokehWithValidTokenButNoHTTPS extends HTTPScenario {
export class BokehWithValidTokenButNoHTTPS extends BokehScenario {
public constructor(base_url: string) {
super();
this
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.errorJSON() })
.expect({ url: base_url + '/api/user/account',
headers: {"authorization": "Bearer 123456789"} },
{ content: this.errorJSON() });
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.errorJSON() })
.expect({
url: base_url + '/api/user/account',
headers: this._headers
},
{ content: this.errorJSON() });
}
......@@ -173,16 +199,16 @@ export class BokehWithValidTokenButNoHTTPS extends HTTPScenario {
export class BokehWithNovelties extends HTTPScenario {
export class BokehWithNovelties extends BokehScenario {
public constructor(base_url: string) {
super();
this.expect({ url: base_url + '/recherche/simple/tri/date_creation+desc/format/json/page/1' },
{ content: this.recordsJSON() })
{ content: this.recordsJSON() })
}
public recordsJSON(): string {
return JSON.stringify (
return JSON.stringify(
{
"url": "http:\/\/bokeh-enligne.fr\/recherche\/simple\/format\/json",
"total": 6144,
......@@ -352,3 +378,85 @@ export class BokehWithNovelties extends HTTPScenario {
);
}
}
export class BokehRenewLoanSuccessful extends BokehScenario {
public constructor(base_url: string) {
super();
this.expect(
{
url: base_url + '/api/user/renew-loan/id/547',
headers: this._headers
},
{
content: JSON.stringify({
"status": "renewed",
"date_due": "2019-02-27"
})
})
}
}
export class BokehRenewLoanError extends BokehScenario {
public constructor(base_url: string) {
super();
this.expect(
{
url: base_url + '/api/user/renew-loan/id/547',
headers: this._headers
},
{
content: JSON.stringify({
"status": "error",
"error": "Could not renew"
})
})
}
}
export class BokehCancelHoldSuccessful extends BokehScenario {
public constructor(base_url: string) {
super();
this.expect(
{
url: base_url + '/api/user/cancel-hold/id/547',
headers: this._headers
},
{
content: JSON.stringify({
"status": "canceled",
})
})
}
}
export class BokehCancelHoldError extends BokehScenario {
public constructor(base_url: string) {
super();
this.expect(
{
url: base_url + '/api/user/cancel-hold/id/547',
headers: this._headers
},
{
content: JSON.stringify({
"status": "error",
"error": "Could not cancel"
})
})
}
}
This diff is collapsed.
This diff is collapsed.
declare var describe, expect, it, before, beforeEach, chai: any
import {Loan, Account, Database, WrongLoginPassword} from '../../models/'
import {Ermes} from '../../models/portal/ermes'
import {ErmesLoansWithSuccessfulLoginCSV,
ErmesSignInWithSuccessfulLogin,
ErmesOldSignInWithSuccessfulLogin,
ErmesWithLoginError,
ErmesPartialLoansWithSuccessfulLogin,
ErmesLoansWithSuccessfulLoginV1,
ErmesLoansWithSuccessfulLoginV2,
ErmesLoansWithSuccessfulLoginV3} from './ermes-fixtures'
import { Loan, Account, Database, WrongLoginPassword } from '../../models/'
import { Ermes } from '../../models/portal/ermes'
import {
ErmesLoansWithSuccessfulLoginCSV,
ErmesSignInWithSuccessfulLogin,
ErmesOldSignInWithSuccessfulLogin,
ErmesWithLoginError,
ErmesPartialLoansWithSuccessfulLogin,
ErmesLoansWithSuccessfulLoginV1,
ErmesLoansWithSuccessfulLoginV2,
ErmesLoansWithSuccessfulLoginV3
} from './ermes-fixtures'
describe('Account on Ermes', () => {
let adapter: Ermes
let db: Database
let adapter: Ermes
let db: Database
let account: Account
let time: number
beforeEach( () => {
db = Database.open('testdb').clear()
beforeEach(() => {
db = Database.open('testdb').clear()
time = (new Date()).getTime()
adapter = (new Ermes()).setTime(time)
adapter = (new Ermes()).setTime(time)
account = (new Account())
.setUrl('ermes.fr')
.setCredentials({login: 'marco',
password: 'polo',
portal: 'ermes'})
.setUrl('ermes.fr')
.setCredentials({
login: 'marco',
password: 'polo',
portal: 'ermes'
})
db.save(account)
})
db.save(account)
})
describe('With successful Login scenario', () => {
describe('With successful Login scenario', () => {
it('sign in should set account label to Marco Polo', () => {
let http = new ErmesSignInWithSuccessfulLogin('http://ermes.fr', time)
return adapter.setHTTP(http).signIn(account).then(() => {
let http = new ErmesSignInWithSuccessfulLogin('http://ermes.fr', time)
return adapter.setHTTP(http).signIn(account).then(() => {
expect(account.label).to.equals('Marco POLO')
})
})
})
})
it('sign in on older Ermes should set account label to Marco Polo', () => {
let http = new ErmesOldSignInWithSuccessfulLogin('http://ermes.fr', time)
return adapter.setHTTP(http).signIn(account).then(() => {
let http = new ErmesOldSignInWithSuccessfulLogin('http://ermes.fr', time)
return adapter.setHTTP(<