mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-22 09:32:41 +00:00
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2018 StApps
|
* Copyright (C) 2018, 2019 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* 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
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
* Software Foundation, version 3.
|
* Software Foundation, version 3.
|
||||||
@@ -17,6 +17,7 @@ import {HttpClientModule} from '@angular/common/http';
|
|||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
import {IonicModule} from '@ionic/angular';
|
import {IonicModule} from '@ionic/angular';
|
||||||
|
import {StorageModule} from '../storage/storage.module';
|
||||||
import {DataRoutingModule} from './data-routing.module';
|
import {DataRoutingModule} from './data-routing.module';
|
||||||
import {DataProvider} from './data.provider';
|
import {DataProvider} from './data.provider';
|
||||||
import {DataDetailComponent} from './detail/data-detail.component';
|
import {DataDetailComponent} from './detail/data-detail.component';
|
||||||
@@ -48,6 +49,7 @@ import {EventListItemComponent} from './types/event/event-list-item.component';
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
DataRoutingModule,
|
DataRoutingModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
|
StorageModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DataProvider,
|
DataProvider,
|
||||||
|
|||||||
223
src/app/modules/data/data.provider.spec.ts
Normal file
223
src/app/modules/data/data.provider.spec.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018, 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
import {Client} from '@openstapps/api/lib/client';
|
||||||
|
import {SCMessage, SCSaveableThing, SCSearchQuery, SCSearchResponse,
|
||||||
|
SCSearchValueFilter, SCThing, SCThingOriginType, SCThings, SCThingType} from '@openstapps/core';
|
||||||
|
import {StorageProvider} from '../storage/storage.provider';
|
||||||
|
import {DataModule} from './data.module';
|
||||||
|
import {DataProvider, DataScope} from './data.provider';
|
||||||
|
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
||||||
|
|
||||||
|
describe('DataProvider', () => {
|
||||||
|
let dataProvider: DataProvider;
|
||||||
|
let storageProvider: StorageProvider;
|
||||||
|
const sampleResponse: SCSearchResponse = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
categories: ['main dish'],
|
||||||
|
name: 'foo dish',
|
||||||
|
origin: {
|
||||||
|
indexed: '12345',
|
||||||
|
name: 'bar',
|
||||||
|
type: SCThingOriginType.Remote,
|
||||||
|
},
|
||||||
|
type: SCThingType.Dish,
|
||||||
|
uid: '123',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categories: ['dessert'],
|
||||||
|
name: 'foo dessert',
|
||||||
|
origin: {
|
||||||
|
indexed: '12345',
|
||||||
|
name: 'bar',
|
||||||
|
type: SCThingOriginType.Remote,
|
||||||
|
},
|
||||||
|
type: SCThingType.Dish,
|
||||||
|
uid: '123',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
facets: [
|
||||||
|
{
|
||||||
|
buckets: [],
|
||||||
|
field: 'foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
count: 0,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
time: 123,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const sampleFilter: SCSearchValueFilter = {
|
||||||
|
arguments: {
|
||||||
|
field: 'type',
|
||||||
|
value: 'dish',
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
};
|
||||||
|
const sampleQuery: SCSearchQuery = {
|
||||||
|
filter: sampleFilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sampleThing: SCMessage = {
|
||||||
|
audiences: ['students'],
|
||||||
|
message: 'Foo Message',
|
||||||
|
name: 'foo',
|
||||||
|
origin: {
|
||||||
|
indexed: 'SOME-DATE',
|
||||||
|
name: 'some name',
|
||||||
|
type: SCThingOriginType.Remote,
|
||||||
|
},
|
||||||
|
type: SCThingType.Message,
|
||||||
|
uid: '123',
|
||||||
|
};
|
||||||
|
const sampleSavable: SCSaveableThing<SCThings> = {
|
||||||
|
data: sampleThing,
|
||||||
|
name: sampleThing.name,
|
||||||
|
origin: {
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
type: SCThingOriginType.User,
|
||||||
|
},
|
||||||
|
type: SCThingType.Message,
|
||||||
|
uid: sampleThing.uid,
|
||||||
|
};
|
||||||
|
const otherSampleThing: SCMessage = {...sampleThing, uid: '456', name: 'bar'};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [DataModule],
|
||||||
|
providers: [
|
||||||
|
DataProvider,
|
||||||
|
StAppsWebHttpClient,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
storageProvider = TestBed.get(StorageProvider);
|
||||||
|
dataProvider = TestBed.get(DataProvider);
|
||||||
|
await storageProvider.deleteAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate data key', async () => {
|
||||||
|
dataProvider.storagePrefix = 'foo.data';
|
||||||
|
expect(dataProvider.getDataKey('123')).toBe('foo.data.123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide backend data items using search query', async () => {
|
||||||
|
spyOn(Client.prototype, 'search').and.callFake(() => {
|
||||||
|
return {
|
||||||
|
then: (callback: any) => {
|
||||||
|
return callback(sampleResponse);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const response = await dataProvider.search(sampleQuery);
|
||||||
|
expect(response).toEqual(sampleResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should put an data item into the local database (storage)', async () => {
|
||||||
|
let providedThing: SCSaveableThing<SCThing>;
|
||||||
|
spyOn(storageProvider, 'put').and.callFake((_id: any, thing: any) => {
|
||||||
|
providedThing = thing;
|
||||||
|
providedThing.origin.created = sampleSavable.origin.created;
|
||||||
|
});
|
||||||
|
expect(storageProvider.put).not.toHaveBeenCalled();
|
||||||
|
expect(providedThing!).not.toBeDefined();
|
||||||
|
await dataProvider.put(sampleThing);
|
||||||
|
expect(providedThing!).toBeDefined();
|
||||||
|
expect(providedThing!).toEqual(sampleSavable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly set and get single data item from the local database (storage)', async () => {
|
||||||
|
await dataProvider.put(sampleThing);
|
||||||
|
spyOn(storageProvider, 'get').and.callThrough();
|
||||||
|
expect(storageProvider.get).not.toHaveBeenCalled();
|
||||||
|
const providedThing = await dataProvider.get(sampleThing.uid, DataScope.Local);
|
||||||
|
providedThing.origin.created = sampleSavable.origin.created;
|
||||||
|
expect(storageProvider.get).toHaveBeenCalledWith(dataProvider.getDataKey(sampleThing.uid));
|
||||||
|
expect(providedThing).toEqual(sampleSavable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide all data items from the local database (storage)', async () => {
|
||||||
|
await dataProvider.put(sampleThing);
|
||||||
|
await dataProvider.put(otherSampleThing);
|
||||||
|
const result = await dataProvider.getAll();
|
||||||
|
expect(Array.from(result.keys()).sort()).toEqual([
|
||||||
|
dataProvider.getDataKey(sampleThing.uid), dataProvider.getDataKey(otherSampleThing.uid),
|
||||||
|
]);
|
||||||
|
expect(result.get(dataProvider.getDataKey(sampleThing.uid))!.data).toEqual(sampleThing);
|
||||||
|
expect(result.get(dataProvider.getDataKey(otherSampleThing.uid))!.data).toEqual(otherSampleThing);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide single data from the backend', async () => {
|
||||||
|
spyOn(Client.prototype, 'getThing').and.callFake(() => {
|
||||||
|
return {
|
||||||
|
then: (callback: any) => {
|
||||||
|
return callback(sampleThing);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(Client.prototype.getThing).not.toHaveBeenCalled();
|
||||||
|
const providedThing = await dataProvider.get(sampleThing.uid, DataScope.Remote);
|
||||||
|
expect(Client.prototype.getThing).toHaveBeenCalledWith(sampleThing.uid);
|
||||||
|
expect(providedThing).toBe(sampleThing);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get an item from both local and remote database', async () => {
|
||||||
|
spyOn(Client.prototype, 'getThing').and.callFake(() => {
|
||||||
|
return {
|
||||||
|
then: (callback: any) => {
|
||||||
|
return callback(sampleThing);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
spyOn(storageProvider, 'get').and.callFake(() => {
|
||||||
|
return {
|
||||||
|
then: (callback: any) => {
|
||||||
|
return callback(sampleSavable);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const result = await dataProvider.get(sampleThing.uid);
|
||||||
|
expect(result.get(DataScope.Local)).toEqual(sampleSavable);
|
||||||
|
expect(result.get(DataScope.Remote)).toEqual(sampleThing);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly delete a data item from the local database (storage)', async () => {
|
||||||
|
spyOn(storageProvider, 'delete').and.callThrough();
|
||||||
|
await dataProvider.put(sampleThing);
|
||||||
|
expect(await storageProvider.length()).toBe(1);
|
||||||
|
await dataProvider.delete(sampleThing.uid);
|
||||||
|
expect(storageProvider.delete).toHaveBeenCalledWith(dataProvider.getDataKey(sampleThing.uid));
|
||||||
|
expect(await storageProvider.length()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly delete all the data items from the local database (storage)', async () => {
|
||||||
|
spyOn(storageProvider, 'delete').and.callThrough();
|
||||||
|
await dataProvider.put(sampleThing);
|
||||||
|
await dataProvider.put(otherSampleThing);
|
||||||
|
await storageProvider.put('some-uid', {some: 'thing'});
|
||||||
|
expect(await storageProvider.length()).toBe(3);
|
||||||
|
await dataProvider.deleteAll();
|
||||||
|
expect(storageProvider.delete).toHaveBeenCalledWith(
|
||||||
|
dataProvider.getDataKey(sampleThing.uid),
|
||||||
|
dataProvider.getDataKey(otherSampleThing.uid),
|
||||||
|
);
|
||||||
|
const result = await storageProvider.getAll();
|
||||||
|
expect(Array.from(result.keys())).toEqual(['some-uid']);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2018 StApps
|
* Copyright (C) 2018, 2019 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* 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
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
* Software Foundation, version 3.
|
* Software Foundation, version 3.
|
||||||
@@ -13,6 +13,16 @@
|
|||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
|
import {Client} from '@openstapps/api/lib/client';
|
||||||
|
import {SCSearchQuery, SCSearchResponse, SCThingOriginType, SCThings, SCThingType} from '@openstapps/core';
|
||||||
|
import {SCSaveableThing} from '@openstapps/core';
|
||||||
|
import {StorageProvider} from '../storage/storage.provider';
|
||||||
|
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
||||||
|
|
||||||
|
export enum DataScope {
|
||||||
|
Local = 'local',
|
||||||
|
Remote = 'remote',
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Generated class for the DataProvider provider.
|
Generated class for the DataProvider provider.
|
||||||
@@ -22,7 +32,119 @@ import {Injectable} from '@angular/core';
|
|||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DataProvider {
|
export class DataProvider {
|
||||||
constructor() {
|
private _storagePrefix: string = 'stapps.data';
|
||||||
// @TODO
|
// @TODO: get backendUrl and appVersion and storagePrefix from config (module)
|
||||||
|
appVersion: string = '1.0.6';
|
||||||
|
backendUrl: string = 'https://stappsbe01.innocampus.tu-berlin.de';
|
||||||
|
client: Client;
|
||||||
|
storageProvider: StorageProvider;
|
||||||
|
|
||||||
|
constructor(stAppsWebHttpClient: StAppsWebHttpClient, storageProvider: StorageProvider) {
|
||||||
|
this.client = new Client(stAppsWebHttpClient, this.backendUrl, this.appVersion);
|
||||||
|
this.storageProvider = storageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
get storagePrefix(): string {
|
||||||
|
return this._storagePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
set storagePrefix(storagePrefix) {
|
||||||
|
this._storagePrefix = storagePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides key for storing data into the local database
|
||||||
|
*
|
||||||
|
* @param uid Unique identifier of a resource
|
||||||
|
*/
|
||||||
|
getDataKey(uid: string): string {
|
||||||
|
return `${this.storagePrefix}.${uid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a saveable thing from the local database using the provided UID
|
||||||
|
*/
|
||||||
|
async get(uid: string, scope: DataScope.Local): Promise<SCSaveableThing<SCThings>>;
|
||||||
|
/**
|
||||||
|
* Provides a thing from the backend
|
||||||
|
*/
|
||||||
|
async get(uid: string, scope: DataScope.Remote): Promise<SCThings | SCSaveableThing<SCThings>>;
|
||||||
|
/**
|
||||||
|
* Provides a thing from both local database and backend
|
||||||
|
*/
|
||||||
|
async get(uid: string): Promise<Map<DataScope, SCThings | SCSaveableThing<SCThings>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a thing from the local database only, backend only or both, depending on the scope
|
||||||
|
*
|
||||||
|
* @param uid Unique identifier of a thing
|
||||||
|
* @param scope From where data should be provided
|
||||||
|
*/
|
||||||
|
async get(uid: string, scope?: DataScope):
|
||||||
|
Promise<SCThings | SCSaveableThing<SCThings> | Map<DataScope, SCThings | SCSaveableThing<SCThings>>> {
|
||||||
|
if (scope === DataScope.Local) {
|
||||||
|
return this.storageProvider.get<SCSaveableThing<SCThings>>(this.getDataKey(uid));
|
||||||
|
}
|
||||||
|
if (scope === DataScope.Remote) {
|
||||||
|
return this.client.getThing(uid);
|
||||||
|
}
|
||||||
|
const map: Map<DataScope, SCThings | SCSaveableThing<SCThings>> = new Map();
|
||||||
|
map.set(DataScope.Local, await this.get(uid, DataScope.Local));
|
||||||
|
map.set(DataScope.Remote, await this.get(uid, DataScope.Remote));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides all things saved in the local database
|
||||||
|
*/
|
||||||
|
async getAll(): Promise<Map<string, SCSaveableThing<SCThings>>> {
|
||||||
|
return this.storageProvider.search<SCSaveableThing<SCThings>>(this.storagePrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a data item
|
||||||
|
*
|
||||||
|
* @param item Data item that needs to be saved
|
||||||
|
* @param [type] Saveable type (e.g. 'favorite'); if nothing is provided then type of the thing is used
|
||||||
|
*/
|
||||||
|
async put(item: SCThings, type?: SCThingType): Promise<SCSaveableThing<SCThings>> {
|
||||||
|
const saveableItem: SCSaveableThing<SCThings> = {
|
||||||
|
data: item,
|
||||||
|
name: item.name,
|
||||||
|
origin: {
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
type: SCThingOriginType.User,
|
||||||
|
},
|
||||||
|
type: (typeof type === 'undefined') ? item.type : type,
|
||||||
|
uid: item.uid,
|
||||||
|
};
|
||||||
|
// @TODO: Implementation for saving item into the backend (user's account)
|
||||||
|
return (await this.storageProvider.put<SCSaveableThing<SCThings>>(this.getDataKey(item.uid), saveableItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a data item
|
||||||
|
*
|
||||||
|
* @param uid Unique identifier of the saved data item
|
||||||
|
*/
|
||||||
|
async delete(uid: string): Promise<void> {
|
||||||
|
return this.storageProvider.delete(this.getDataKey(uid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all the previously saved data items
|
||||||
|
*/
|
||||||
|
async deleteAll(): Promise<void> {
|
||||||
|
const keys = Array.from((await this.getAll()).keys());
|
||||||
|
return this.storageProvider.delete(...keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the backend using the provided query and returns response
|
||||||
|
*
|
||||||
|
* @param query - query to send to the backend
|
||||||
|
*/
|
||||||
|
async search(query: SCSearchQuery): Promise<SCSearchResponse> {
|
||||||
|
return (await this.client.search(query));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user