mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
feat: add api
This commit is contained in:
274
src/client.ts
Normal file
274
src/client.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 {
|
||||
SCAbstractRoute,
|
||||
SCFeedbackRequest,
|
||||
SCFeedbackResponse,
|
||||
SCFeedbackRoute,
|
||||
SCIndexRequest,
|
||||
SCIndexResponse,
|
||||
SCIndexRoute,
|
||||
SCInternalServerErrorResponse,
|
||||
SCMultiSearchRequest,
|
||||
SCMultiSearchResponse,
|
||||
SCMultiSearchRoute,
|
||||
SCNotFoundErrorResponse,
|
||||
SCRequests,
|
||||
SCSearchRequest,
|
||||
SCSearchResponse,
|
||||
SCSearchRoute,
|
||||
SCThings,
|
||||
} from '@openstapps/core';
|
||||
import {ApiError, CoreVersionIncompatibleError, OutOfRangeError} from './errors';
|
||||
import {HttpClientHeaders, HttpClientInterface} from './httpClientInterface';
|
||||
|
||||
/**
|
||||
* StApps-API client
|
||||
*/
|
||||
export class Client {
|
||||
/**
|
||||
* Instance of feedback request route
|
||||
*/
|
||||
private readonly feedbackRoute = new SCFeedbackRoute();
|
||||
|
||||
/**
|
||||
* Instance of index route
|
||||
*/
|
||||
private readonly indexRoute = new SCIndexRoute();
|
||||
|
||||
/**
|
||||
* Instance of multi search request route
|
||||
*/
|
||||
private readonly multiSearchRoute = new SCMultiSearchRoute();
|
||||
|
||||
/**
|
||||
* Instance of search request route
|
||||
*/
|
||||
private readonly searchRoute = new SCSearchRoute();
|
||||
|
||||
/**
|
||||
* Default headers
|
||||
*
|
||||
* TODO: remove headers
|
||||
*/
|
||||
protected readonly headers: HttpClientHeaders = {};
|
||||
|
||||
/**
|
||||
* Create a new search request with altered pagination parameters to move to the next result window
|
||||
*
|
||||
* @param searchRequest Last search request
|
||||
* @param searchResponse Search response for supplied search request
|
||||
* @throws OutOfRangeError Throws an error if the next window is beyond the total number of results
|
||||
*/
|
||||
static nextWindow(searchRequest: SCSearchRequest, searchResponse: SCSearchResponse): SCSearchRequest {
|
||||
// calculate next from
|
||||
const from = searchResponse.pagination.offset + searchResponse.pagination.count;
|
||||
|
||||
// throw an error if the next window is beyond the total number of results
|
||||
if (from >= searchResponse.pagination.total) {
|
||||
throw new OutOfRangeError(searchRequest);
|
||||
}
|
||||
|
||||
// return a search request with the next window
|
||||
return {
|
||||
...searchRequest,
|
||||
from,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new StApps-API client to communicate with a StApps-backend.
|
||||
*
|
||||
* @param httpClient HTTP client to use
|
||||
* @param url URL of the backend
|
||||
* @param version App version to use when requesting data *(only necessary if URL is ambiguous)*
|
||||
*
|
||||
* TODO: remove headers/version
|
||||
*/
|
||||
constructor(protected httpClient: HttpClientInterface, protected url: string, protected version?: string) {
|
||||
if (typeof version === 'string') {
|
||||
// set header to tell proxy to select the correct backend
|
||||
this.headers = {
|
||||
'X-StApps-Version': this.version,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send feedback
|
||||
*
|
||||
* @param feedback Feedback to send
|
||||
*/
|
||||
async feedback(feedback: SCFeedbackRequest): Promise<SCFeedbackResponse> {
|
||||
return await this.invokeRoute<SCFeedbackResponse>(this.feedbackRoute, undefined, feedback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a thing by its UID
|
||||
*
|
||||
* @param uid UID of the thing to fetch
|
||||
*/
|
||||
async getThing(uid: string): Promise<SCThings> {
|
||||
const response = await this.search({
|
||||
filter: {
|
||||
arguments: {
|
||||
field: 'uid',
|
||||
value: uid,
|
||||
},
|
||||
type: 'value',
|
||||
},
|
||||
size: 1,
|
||||
});
|
||||
|
||||
if (response.data.length === 1 && response.data[0].uid === uid) {
|
||||
return response.data[0];
|
||||
}
|
||||
|
||||
throw new SCInternalServerErrorResponse(new SCNotFoundErrorResponse(true), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a handshake with the backend and check StAppsCore version
|
||||
*
|
||||
* @param coreVersion StAppsCore version to check
|
||||
*/
|
||||
async handshake(coreVersion: string): Promise<SCIndexResponse> {
|
||||
const request: SCIndexRequest = {};
|
||||
|
||||
const response = await this.invokeRoute<SCIndexResponse>(this.indexRoute, undefined, request);
|
||||
|
||||
if (response.backend.SCVersion.split('.')[0] !== coreVersion.split('.')[0]) {
|
||||
throw new CoreVersionIncompatibleError(coreVersion, response.backend.SCVersion);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a route
|
||||
*
|
||||
* @param route Route to invoke
|
||||
* @param parameters Parameters for the URL fragment
|
||||
* @param body Body for the request
|
||||
*/
|
||||
async invokeRoute<T>(route: SCAbstractRoute,
|
||||
parameters?: { [k: string]: string },
|
||||
body?: SCRequests): Promise<T> {
|
||||
// make the request
|
||||
const response = await this.httpClient.request({
|
||||
body: body,
|
||||
// TODO: remove headers
|
||||
headers: this.headers,
|
||||
method: route.method,
|
||||
url: new URL(this.url + route.getUrlFragment(parameters)),
|
||||
});
|
||||
|
||||
if (response.statusCode === route.statusCodeSuccess) {
|
||||
return response.body as T;
|
||||
}
|
||||
|
||||
throw new ApiError(response.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a multi search request to the backend
|
||||
*
|
||||
* All results will be returned for requests where no size is set.
|
||||
*
|
||||
* @param multiSearchRequest Multi search request
|
||||
*/
|
||||
async multiSearch(multiSearchRequest: SCMultiSearchRequest): Promise<SCMultiSearchResponse> {
|
||||
const preFlightRequest: SCMultiSearchRequest = {};
|
||||
let preFlightNecessary = false;
|
||||
|
||||
// gather search requests where size is not set
|
||||
Object.keys(multiSearchRequest).forEach((key) => {
|
||||
const searchRequest = multiSearchRequest[key];
|
||||
|
||||
if (typeof searchRequest.size === 'undefined') {
|
||||
preFlightRequest[key] = {
|
||||
...searchRequest,
|
||||
};
|
||||
preFlightRequest[key].size = 0;
|
||||
preFlightNecessary = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (preFlightNecessary) {
|
||||
// copy multi search request
|
||||
multiSearchRequest = {
|
||||
...multiSearchRequest,
|
||||
};
|
||||
|
||||
// make pre flight request
|
||||
const preFlightResponse = await this.invokeRoute<SCMultiSearchResponse>(
|
||||
this.multiSearchRoute,
|
||||
undefined,
|
||||
preFlightRequest,
|
||||
);
|
||||
|
||||
// set size for multi search requests that were in pre flight request
|
||||
Object.keys(preFlightRequest).forEach((key) => {
|
||||
multiSearchRequest[key].size = preFlightResponse[key].pagination.total;
|
||||
});
|
||||
}
|
||||
|
||||
// actually invoke the route
|
||||
return await this.invokeRoute<SCMultiSearchResponse>(this.multiSearchRoute, undefined, multiSearchRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a search request to the backend
|
||||
*
|
||||
* All results will be returned if no size is set in the request.
|
||||
*
|
||||
* @param searchRequest Search request
|
||||
*/
|
||||
async search(searchRequest: SCSearchRequest): Promise<SCSearchResponse> {
|
||||
let size: number | undefined = searchRequest.size;
|
||||
|
||||
if (typeof size === 'undefined') {
|
||||
const preFlightResponse = await this.invokeRoute<SCSearchResponse>(this.searchRoute, undefined, {
|
||||
...searchRequest,
|
||||
size: 0,
|
||||
});
|
||||
|
||||
size = preFlightResponse.pagination.total;
|
||||
}
|
||||
|
||||
return await this.invokeRoute<SCSearchResponse>(this.searchRoute, undefined, {
|
||||
...searchRequest,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next search results
|
||||
*
|
||||
* @param searchRequest Last search request
|
||||
* @param searchResponse Search response for supplied search request
|
||||
*/
|
||||
async searchNext(searchRequest: SCSearchRequest, searchResponse: SCSearchResponse): Promise<{
|
||||
searchRequest: SCSearchRequest;
|
||||
searchResponse: SCSearchResponse;
|
||||
}> {
|
||||
const nextSearchRequest = Client.nextWindow(searchRequest, searchResponse);
|
||||
|
||||
return {
|
||||
searchRequest: nextSearchRequest,
|
||||
searchResponse: await this.search(nextSearchRequest),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user