From 14b9b374c2fad167e6fbf26dfb3a9f319274ecfb Mon Sep 17 00:00:00 2001 From: Bing Sun Date: Sat, 25 Apr 2020 20:22:43 +0800 Subject: [PATCH] initial import --- src/postgrest.js | 197 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/postgrest.js diff --git a/src/postgrest.js b/src/postgrest.js new file mode 100644 index 0000000..809bf48 --- /dev/null +++ b/src/postgrest.js @@ -0,0 +1,197 @@ +/* api wrapper */ + +// utilities +// generate downloadable csv +const download = (options={}) => { + options.filename = options.filename || 'file' + + if (options.type == 'csv') { + let headers = Object.keys(options.data[0]) + + let body = options.data.map(row => headers.map(key => row[key]).join(',')).join('\n') + + options.data = '\ufeff' + headers.join(',') + '\n' + body + options.type = 'text/csv' + } else if (options.type == 'json') { + } + + let blob = new Blob([options.data], {type: options.type}) + let url = URL.createObjectURL(blob) + + if (options.timestamp) + options.filename = options.filename + (new Date()).toLocaleTimeString(undefined, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false + }) + + let anchor = document.createElement('a') + anchor.href = url + anchor.target = '_blank' + anchor.download = `${options.filename}.csv` + + anchor.click() + + URL.revokeObjectURL(url) + anchor.remove() +} + +// authentication model +const _make_auth = api_root => { + + // members + let _login_name = '', _password = '' + let _token_payload = {} + + // methods + let _token = value => { + if (value) localStorage.setItem('token', value) + return localStorage.getItem('token') + } + + let _load_token_payload = _ => { + try { + _token_payload = JSON.parse(atob(_token().split('.')[1])) + } catch (e) { + console.log('failed parsing token: ', e) + } + } + + let _login = _ => m.request({ + method: 'POST', + url: `${api_root}/rpc/login`, + body: { + login_name: _login_name, + password: _password + }}).then(data => { + _token(data) + _load_token_payload() + + // TODO set to origin page + m.route.set('/') + }).catch(e => alert(e.response.message)) + + // initialize + _load_token_payload() + + // public methods + return { + login: _login, + signed_in: _ => _token() ? true : false, + payload: _ => _token_payload, + token: _token, + login_name: value => { + if (value) _login_name = value + return _login_name + }, + password: value => { + if (value) _password = value + return _password + } + } +} + +// main api +const _make_api = (api_root, need_auth=true) => { + // private state + let _root = api_root + + // if auth is required + let _auth = need_auth ? _make_auth(api_root) : null + + return { + // utilities + download: download, + + // expose authentication module + auth: _auth, + + // wrap m.request with authentication validation + request: (options = {}) => { + /* normalize arguments */ + if (typeof options === 'string') { + options = {url: options} + } + + /* normalize url */ + if (options.url.startsWith('/')) + options.url = _root + options.url + + /* normalize headers */ + // 1. check if auth is required + if (need_auth) { + // insert auth header if availible + if (_auth.token()) { + options.headers = { + Authorization: 'Bearer ' + _auth.token(), + ...options.headers + } + } + // route to login + else { + m.route.set('/login') + } + } + + // 2. return values on Prefer + // if (['POST', 'PATCH'].includes(options.method) && options.headers && !options.headers.Prefer) + if (['POST', 'PATCH'].includes(options.method)) + options.config = xhr => { + xhr.setRequestHeader('Prefer', 'return=representation') + return xhr + } + + // normalize params: since options.params is an object, it cannot support + // duplicated query keys, while postgrest supports that. + // + // Simply build query string by hand. + if (options.queries) { + // build the query string + let _query_string = options.queries.map(query => { + // prepare the query kv pair and optional operator + let k = query.label + let v = query.value + let op = query.op + + // return early if v is empty + if (typeof v == 'undefined' || (!v && v !== 0)) + return undefined + + if (op == 'like') + v = `*${v}*` + else if (op == 'like.right') { + v = `${v}*` + op = 'like' + } else if (op == 'in') + v = `(${v.join(',')})` + else if (k == 'and') { + v = '(' + v.filter(sub_query => sub_query.value == 0 || sub_query.value).map(sub_query => `${sub_query.label}.${sub_query.op}.${typeof sub_query.value == 'function' ? sub_query.value() : sub_query.value}`).join(',') + ')' + } + + return {k: k, v: op ? `${op}.${v}` : v} + }).filter(query => typeof query != 'undefined').map(query => query.k + '=' + encodeURIComponent(query.v)).join('&') + + // append query string to url if necessary + if (_query_string) + options.url = options.url + '?' + _query_string + + delete options.queries + } + + return m.request(options).catch(e => { + if (e.code === 401) { + localStorage.removeItem('token') + m.route.set('/login') + } + + throw e + }) + } + } +} + +export default _make_api('https://api-dev.uidt.net/phanerozoic/v1', false)