|
@@ -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) |