You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
frontend/src/app/service/api.service.ts

225 lines
6.3 KiB

import {Injectable} from '@angular/core';
import {environment} from '../../environments/environment';
import {HttpClient} from '@angular/common/http';
import {BehaviorSubject, Observable} from 'rxjs';
import ApiResponse from '../structures/ApiResponse';
import {MenuItem} from './db/MenuItem';
import {DatabaseService} from './db/database.service';
import {ConnectionService} from 'ng-connection-service';
@Injectable({
providedIn: 'root'
})
export class ApiService {
protected baseEndpoint: string = environment.backendBase;
protected statUrl: string = environment.statUrl;
protected versionUrl: string = environment.versionUrl;
protected offline = false;
public readonly offline$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
get isOffline() {
return this.offline;
}
constructor(
protected http: HttpClient,
protected db: DatabaseService,
protected connection: ConnectionService,
) {
connection.monitor().subscribe(isConnected => {
if ( !isConnected ) {
this.makeOffline();
} else {
this.makeOnline(); // TODO add checks for server.
}
});
}
public makeOffline() {
this.offline = true;
this.offline$.next(true);
}
public makeOnline() {
this.offline = false;
this.offline$.next(false);
}
public get(endpoint, params = {}): Observable<ApiResponse> {
return this.request(endpoint, params, 'get');
}
public post(endpoint, body = {}): Observable<ApiResponse> {
return this.request(endpoint, body, 'post');
}
public request(endpoint, params = {}, method: 'get'|'post' = 'get'): Observable<ApiResponse> {
return this._request(this._build_url(endpoint), params, method);
}
public stat(): Observable<ApiResponse> {
return new Observable<ApiResponse>(sub => {
(async () => {
const statKV = await this.db.getKeyValue('host_stat');
// If offline, look up the last stored stat for information
if ( this.isOffline ) {
if ( typeof statKV !== 'object' ) {
throw new Error('No locally stored host stat found.');
}
sub.next(new ApiResponse(statKV.data));
sub.complete();
}
// Otherwise, fetch the stat and cache it locally
this._request(this.statUrl).subscribe(apiResponse => {
statKV.data = {status: apiResponse.status, message: apiResponse.message, data: apiResponse.data};
statKV.save().then(() => {
sub.next(statKV.data);
sub.complete();
});
});
})();
});
}
public version(): Promise<string> {
return new Promise(async (res, rej) => {
const versionKV = await this.db.getKeyValue('app_version');
// If offline, look up the local app version.
if ( this.isOffline ) {
if ( versionKV ) {
return res(versionKV.data);
} else {
return rej(new Error('No local app version found.'));
}
}
// Otherwise, look up the app version and store it locally
this._request(this.versionUrl).subscribe({
next: async result => {
const version = result.data.text.trim();
versionKV.data = version;
await versionKV.save();
res(version);
},
error: rej,
});
});
}
protected _request(endpoint, params = {}, method: 'get'|'post' = 'get'): Observable<ApiResponse> {
return new Observable<ApiResponse>(sub => {
let data: any = {}
if ( method === 'get' ) {
data.params = params;
} else {
data = params;
}
this.http[method](endpoint, data).subscribe({
next: (response: any) => {
sub.next(new ApiResponse(response));
},
error: (err) => {
const response = {
status: err.status,
message: err.message,
data: err.error,
};
sub.next(new ApiResponse(response));
},
});
});
}
public _build_url(endpoint) {
if ( !endpoint.startsWith('/') ) {
endpoint = `/${endpoint}`;
}
return `${this.baseEndpoint.endsWith('/') ? this.baseEndpoint.slice(0, -1) : this.baseEndpoint}${endpoint}`;
}
public getMenuItems(): Promise<any[]> {
return new Promise(async (res, rej) => {
await this.db.createSchemata();
// If offline, fetch the menu from the database
if ( this.isOffline ) {
const items = await this.db.menuItems.toArray();
const nodes = MenuItem.inflateTree(items as MenuItem[]);
return res(nodes);
}
// Download the latest menu items
const tree: any[] = await new Promise(res2 => {
this.get('/menu/items').subscribe({
next: async result => {
const nodes = result.data as any[];
const items = MenuItem.deflateTree(nodes);
// Update the locally stored nodes
await this.db.menuItems.clear();
await Promise.all(items.map(item => item.save()));
res2(nodes);
},
error: rej,
});
});
res(tree);
});
}
public getSessionData(): Promise<any> {
return new Promise(async (res, rej) => {
const sessionKV = await this.db.getKeyValue('session_data');
// If offline, just return the locally cached session data
if ( this.isOffline ) {
if ( typeof sessionKV.data !== 'object' ) {
return rej(new Error('No locally cached session data found.'));
}
return res(sessionKV.data);
}
// Otherwise, fetch the session data from the server and cache it locally
this.get('/session').subscribe(async result => {
sessionKV.data = result.data;
await sessionKV.save();
res(sessionKV.data);
});
});
}
public saveSessionData(data: any): Promise<void> {
return new Promise(async (res, rej) => {
// Update the local session data
const sessionKV = await this.db.getKeyValue('session_data');
sessionKV.data = data;
await sessionKV.save();
// If we're not offline, then update the data on the server
if ( !this.isOffline ) {
await new Promise(res2 => {
this.post('/session', data || {}).subscribe({
next: res2,
error: rej,
});
});
}
res();
});
}
}