mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 00:23:03 +00:00
313 lines
8.2 KiB
TypeScript
313 lines
8.2 KiB
TypeScript
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
import {Injectable} from '@angular/core';
|
|
import {Client} from '@openstapps/api/lib/client';
|
|
import {
|
|
SCFacet,
|
|
SCIndexableThings,
|
|
SCMultiSearchRequest,
|
|
SCMultiSearchResponse,
|
|
SCSearchRequest,
|
|
SCSearchResponse,
|
|
SCSearchValueFilter,
|
|
SCThing,
|
|
SCThingOriginType,
|
|
SCThings,
|
|
SCThingsField,
|
|
SCThingType,
|
|
SCSaveableThing,
|
|
SCFeedbackRequest,
|
|
SCFeedbackResponse,
|
|
SCUuid,
|
|
} from '@openstapps/core';
|
|
import {environment} from '../../../environments/environment';
|
|
import {StorageProvider} from '../storage/storage.provider';
|
|
import {StAppsWebHttpClient} from './stapps-web-http-client.provider';
|
|
import {chunk} from '../../_helpers/collections/chunk';
|
|
|
|
export enum DataScope {
|
|
Local = 'local',
|
|
Remote = 'remote',
|
|
}
|
|
|
|
interface CacheItem<T> {
|
|
data: Promise<T>;
|
|
timestamp: number;
|
|
}
|
|
|
|
/**
|
|
* 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 DataProvider {
|
|
/**
|
|
* TODO
|
|
*/
|
|
get storagePrefix(): string {
|
|
return this._storagePrefix;
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
set storagePrefix(storagePrefix) {
|
|
this._storagePrefix = storagePrefix;
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
private _storagePrefix = 'stapps.data';
|
|
|
|
readonly cache: Record<SCUuid, CacheItem<SCThings> | undefined> = {};
|
|
|
|
private maxCacheAge = 3600;
|
|
|
|
/**
|
|
* Version of the app (used for the header in communication with the backend)
|
|
*/
|
|
appVersion = environment.backend_version;
|
|
|
|
/**
|
|
* Maximum number of sub-queries in a multi-query allowed by the backend
|
|
*/
|
|
backendQueriesLimit = 5;
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
backendUrl = environment.backend_url;
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
client: Client;
|
|
|
|
/**
|
|
* TODO
|
|
*/
|
|
storageProvider: StorageProvider;
|
|
|
|
/**
|
|
* Simplify creation of a value filter
|
|
*
|
|
* @param field Database field for apply the filter to
|
|
* @param value Value to match with
|
|
*/
|
|
static createValueFilter(field: SCThingsField, value: string): SCSearchValueFilter {
|
|
return {
|
|
type: 'value',
|
|
arguments: {
|
|
field: field,
|
|
value: value,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a facet from data
|
|
*
|
|
* @param items Data to generate facet for
|
|
* @param field Field for which to generate facet
|
|
*/
|
|
static facetForField(items: SCThing[], field: SCThingsField): SCFacet {
|
|
const bucketMap = new Map<string, number>();
|
|
const facet: SCFacet = {buckets: [], field: field};
|
|
|
|
for (const item of items) {
|
|
const value =
|
|
typeof bucketMap.get(item.type) === 'undefined' ? 1 : (bucketMap.get(item.type) as number) + 1;
|
|
bucketMap.set(item.type, value);
|
|
}
|
|
|
|
for (const [key, value] of bucketMap.entries()) {
|
|
facet.buckets.push({key: key, count: value});
|
|
}
|
|
|
|
return facet;
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*
|
|
* @param stAppsWebHttpClient TODO
|
|
* @param storageProvider TODO
|
|
*/
|
|
constructor(stAppsWebHttpClient: StAppsWebHttpClient, storageProvider: StorageProvider) {
|
|
this.client = new Client(stAppsWebHttpClient, this.backendUrl, this.appVersion);
|
|
this.storageProvider = storageProvider;
|
|
}
|
|
|
|
/**
|
|
* Create savable thing from an indexable thing
|
|
*
|
|
* @param item An indexable to create savable thing from
|
|
* @param type The type (falls back to the type of the indexable thing)
|
|
*/
|
|
static createSaveable(item: SCIndexableThings, type?: SCThingType): SCSaveableThing {
|
|
return {
|
|
data: item,
|
|
name: item.name,
|
|
origin: {
|
|
created: new Date().toISOString(),
|
|
type: SCThingOriginType.User,
|
|
},
|
|
type: typeof type === 'undefined' ? item.type : type,
|
|
uid: item.uid,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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 = [...(await this.getAll()).keys()];
|
|
|
|
return this.storageProvider.delete(...keys);
|
|
}
|
|
|
|
/**
|
|
* Provides a savable thing from the local database using the provided UID
|
|
*/
|
|
async get(uid: string, scope: DataScope.Local): Promise<SCSaveableThing>;
|
|
/**
|
|
* Provides a thing from the backend
|
|
*/
|
|
async get(uid: string, scope: DataScope.Remote): Promise<SCThings | SCSaveableThing>;
|
|
/**
|
|
* Provides a thing from both local database and backend
|
|
*/
|
|
async get(uid: string): Promise<Map<DataScope, SCThings | SCSaveableThing>>;
|
|
|
|
/**
|
|
* 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 | Map<DataScope, SCThings | SCSaveableThing>> {
|
|
if (scope === DataScope.Local) {
|
|
return this.storageProvider.get<SCSaveableThing>(this.getDataKey(uid));
|
|
}
|
|
if (scope === DataScope.Remote) {
|
|
const timestamp = Date.now();
|
|
const cacheItem = this.cache[uid];
|
|
if (cacheItem && timestamp - cacheItem.timestamp < this.maxCacheAge) {
|
|
return cacheItem.data;
|
|
}
|
|
const item = this.client.getThing(uid);
|
|
this.cache[uid] = {
|
|
data: item,
|
|
timestamp: timestamp,
|
|
};
|
|
return item;
|
|
}
|
|
const map: Map<DataScope, SCThings | SCSaveableThing> = 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>> {
|
|
return this.storageProvider.search<SCSaveableThing>(this.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 information if something with an UID is saved as a data item
|
|
*
|
|
* @param uid Unique identifier of the saved data item
|
|
*/
|
|
async isSaved(uid: string): Promise<boolean> {
|
|
return this.storageProvider.has(this.getDataKey(uid));
|
|
}
|
|
|
|
/**
|
|
* Performs multiple searches at once and returns their responses
|
|
*
|
|
* @param query - query to send to the backend (auto-splits according to the backend limit)
|
|
*/
|
|
async multiSearch(query: SCMultiSearchRequest): Promise<SCMultiSearchResponse> {
|
|
// partition object into chunks, process those requests in parallel, then merge their responses again
|
|
return Object.assign(
|
|
{},
|
|
...(await Promise.all(
|
|
chunk(Object.entries(query), this.backendQueriesLimit).map(request =>
|
|
this.client.multiSearch(Object.fromEntries(request)),
|
|
),
|
|
)),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Save a data item
|
|
*
|
|
* @param item An item that needs to be saved
|
|
*/
|
|
async put(item: SCIndexableThings): Promise<SCSaveableThing> {
|
|
return this.storageProvider.put<SCSaveableThing>(
|
|
this.getDataKey(item.uid),
|
|
DataProvider.createSaveable(item, item.type),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Send a feedback message (request)
|
|
*
|
|
* @param feedback Feedback message to be sent to the backend
|
|
*/
|
|
async sendFeedback(feedback: SCFeedbackRequest) {
|
|
return this.client.invokePlugin<SCFeedbackResponse>('feedback', undefined, feedback);
|
|
}
|
|
|
|
/**
|
|
* Searches the backend using the provided query and returns response
|
|
*
|
|
* @param query - query to send to the backend
|
|
*/
|
|
async search(query: SCSearchRequest): Promise<SCSearchResponse> {
|
|
return this.client.search(query);
|
|
}
|
|
}
|