diff --git a/src/app/app.component.html b/src/app/app.component.html index 88f2a69..fa18240 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -41,7 +41,6 @@ - Menu diff --git a/src/app/app.component.ts b/src/app/app.component.ts index add8854..9598146 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, ViewChild, HostListener, Host} from '@angular/core'; +import {Component, OnInit, ViewChild, HostListener} from '@angular/core'; import { AlertController, @@ -144,21 +144,6 @@ export class AppComponent implements OnInit { }).then(popover => popover.present()); } - onFilterChange($event) { - const query = $event.detail.value.toLowerCase(); - this.menuTree.treeModel.clearFilter(); - if ( query ) { - this.menuTree.treeModel.filterNodes(node => { - if ( node.data.virtual ) { - // "Virtual" tree nodes should always be shown - return true; - } - - return node.data.name.toLowerCase().indexOf(query) >= 0; - }); - } - } - @HostListener('document:keyup.control./', ['$event']) async handleKeyboardEvent() { if ( this.hasSearchOpen ) { diff --git a/src/app/service/api.service.ts b/src/app/service/api.service.ts index bcf0464..84989ad 100644 --- a/src/app/service/api.service.ts +++ b/src/app/service/api.service.ts @@ -285,12 +285,27 @@ export class ApiService { }); } - public _build_url(endpoint) { + public _build_url(endpoint, base = this.baseEndpoint) { if ( !endpoint.startsWith('/') ) { endpoint = `/${endpoint}`; } - return `${this.baseEndpoint.endsWith('/') ? this.baseEndpoint.slice(0, -1) : this.baseEndpoint}${endpoint}`; + return `${base.endsWith('/') ? base.slice(0, -1) : base}${endpoint}`; + } + + public async getToken(): Promise { + return new Promise(async (res, rej) => { + if ( this.isOffline ) { + return rej(new ResourceNotAvailableOfflineError()); + } + + this.get('token').subscribe({ + next: response => { + return res(response.data); + }, + error: rej, + }); + }); } public async syncOfflineData() { diff --git a/src/app/service/db-api.service.ts b/src/app/service/db-api.service.ts new file mode 100644 index 0000000..2579a90 --- /dev/null +++ b/src/app/service/db-api.service.ts @@ -0,0 +1,139 @@ +import {Injectable} from '@angular/core'; +import {ApiService} from './api.service'; +import {Observable} from 'rxjs'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; +import ApiResponse from '../structures/ApiResponse'; +import {environment} from '../../environments/environment'; +import {debug} from '../utility'; +import * as Structures from '../structures/db-api'; + +@Injectable({ + providedIn: 'root', +}) +export class DbApiService { + protected token?: string; + protected tokenTime?: number; + protected databaseBase: string = environment.databaseBase; + + constructor( + protected readonly api: ApiService, + protected readonly http: HttpClient, + ) { + Structures._setDbApi(this); + } + + public async getDatabase(databaseId: string): Promise { + return new Promise(async (res, rej) => { + + this._authRequest(`/${databaseId}`).subscribe({ + next: async response => { + if ( response.status !== 200 ) { + debug('Invalid get database response.', response); + return rej(new Error('Invalid database ID.')); + } + + return res(new Structures.Database(response.data)); + }, + error: rej, + }); + }); + } + + public async getDatabases(): Promise { + return new Promise(async (res, rej) => { + + this._authRequest('/').subscribe({ + next: async response => { + return res(response.data.map(x => new Structures.Database(x))); + }, + error: rej, + }); + }); + } + + public async getColumns(databaseId: string): Promise { + return new Promise(async (res, rej) => { + + this._authRequest(`${databaseId}/columns`).subscribe({ + next: async response => { + return res(response.data.map(x => new Structures.DatabaseColumn(x))); + }, + error: rej, + }); + }); + } + + public async getColumnOrder(databaseId: string): Promise { + return new Promise(async (res, rej) => { + this._authRequest(`${databaseId}/columns/order`).subscribe({ + next: async response => { + return res(response.data); + }, + error: rej, + }); + }); + } + + public async getData(databaseId: string): Promise { + return new Promise(async (res, rej) => { + this._authRequest(`${databaseId}/data`).subscribe({ + next: async response => { + return res(response.data.map(x => new Structures.DatabaseRecord(x))); + }, + error: rej, + }); + }); + } + + public async getDataRecord(databaseId: string, recordId: string): Promise { + return new Promise(async (res, rej) => { + this._authRequest(`${databaseId}/record/${recordId}`).subscribe({ + next: async response => { + return res(new Structures.DatabaseRecord(response.data)); + }, + error: rej, + }); + }); + } + + protected _authRequest(endpoint, params = {}, method: 'get' | 'post' = 'get') { + return new Observable(sub => { + this.getToken().then(token => { + const headers = new HttpHeaders({ + authorization: `bearer ${token}` + }); + + debug('Auth request headers', headers); + + if ( method === 'get' ) { + const data = { + params, + headers, + }; + + this.http.get(this.api._build_url(endpoint, this.databaseBase), data).subscribe({ + next: (response: any) => { + return sub.next(new ApiResponse(response)); + }, + error: e => { + return sub.error(e); + }, + }); + } + }); + }); + } + + public async getToken(): Promise { + const now = (new Date()).getTime(); + + // 1 hour + if ( this.token && this.tokenTime && (now - this.tokenTime) < (60 * 60 * 1000) ) { + return this.token; + } + + this.token = await this.api.getToken(); + this.tokenTime = now; + return this.token; + } +} diff --git a/src/app/structures/db-api.ts b/src/app/structures/db-api.ts new file mode 100644 index 0000000..8fcd37d --- /dev/null +++ b/src/app/structures/db-api.ts @@ -0,0 +1,102 @@ +import {DbApiService} from '../service/db-api.service'; + +let privDbApi!: DbApiService; +export function _setDbApi(dbApi: DbApiService) { + privDbApi = dbApi; +} + +export function _getDbApi(): DbApiService { + return privDbApi; +} + +export class Database { + public name!: string; + public uuid!: string; + public pageId!: string; + public columnIds: string[] = []; + public versionNum?: number; + + static async all() { + return _getDbApi().getDatabases(); + } + + static async one(databaseId: string) { + return _getDbApi().getDatabase(databaseId); + } + + constructor(record?: any) { + if ( record ) { + this.loadFrom(record); + } + } + + loadFrom(record: any) { + this.name = record.name; + this.uuid = record.uuid; + this.pageId = record.page_id; + this.columnIds = record.column_ids; + + if ( record.version_num ) { + this.versionNum = record.version_num; + } + } + + async columns() { + const order = await this.columnOrder(); + const columns = await _getDbApi().getColumns(this.uuid); + + return order.map(x => columns.find(col => col.uuid === x)); + } + + async columnOrder() { + return _getDbApi().getColumnOrder(this.uuid); + } + + async data() { + return _getDbApi().getData(this.uuid); + } + + async record(id: string) { + return _getDbApi().getDataRecord(this.uuid, id); + } +} + +export class DatabaseColumn { + public name!: string; + public databaseId!: string; + public uuid!: string; + public type!: string; + public metadata: any; + + constructor(record?: any) { + if ( record ) { + this.loadFrom(record); + } + } + + loadFrom(record: any) { + this.name = record.name; + this.databaseId = record.database_id; + this.uuid = record.uuid; + this.type = record.type; + this.metadata = record.metadata; + } +} + +export class DatabaseRecord { + public data: any = {}; + public uuid!: string; + public databaseId!: string; + + constructor(record?: any) { + if ( record ) { + this.loadFrom(record); + } + } + + loadFrom(record: any) { + this.data = record.data; + this.uuid = record.uuid; + this.databaseId = record.database_id; + } +} diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 345f10a..0c527d9 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,6 +1,7 @@ export const environment = { production: true, backendBase: '/api/v1', + databaseBase: '/db_api/v1', statUrl: '/stat?ngsw-bypass', versionUrl: '/i/version.html?ngsw-bypass', outputDebug: false, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 4e6eb22..91b9c3a 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -5,6 +5,7 @@ export const environment = { production: false, backendBase: '/link_api/api/v1', + databaseBase: '/link_api/db_api/v1', statUrl: '/link_api/stat?ngsw-bypass', versionUrl: '/link_api/assets/version.html?ngsw-bypass', outputDebug: true,