This is the template project that's checked out and configured when you run the bando-up command from ljsthw-bandolier. This is where the code really lives.
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.
bandolier-template/client/api.js

181 lines
5.0 KiB

const MOCK_ROUTES = {};
import { user, cache_reset } from "./stores.js";
import { log } from "$/client/logging.js";
import Validator from 'Validator';
export const validate = (form, extra) => {
if(form._rules) {
let validation = Validator.make(form, form._rules);
form._valid = validation.passes();
if(extra) extra(form);
form._errors = validation.getErrors();
return form;
} else {
return form;
}
}
export const can_submit = (form) => {
return form._rules === undefined || form._valid;
}
export const clean_form = (form, extras=[]) => {
form._errors = {}; // errors is accessed so needs to exist
delete form._valid;
delete form._rules;
for(let field of extras) delete form[field];
}
// use these when you do your own fetch
const fetch_opts = { credentials: 'same-origin',};
export const mock = (config) => {
for(let [route, value] of Object.entries(config)) {
MOCK_ROUTES[route] = value;
}
}
export const raw_mock = (url, raw_method, body, unauthed_action) => {
let error;
const method = raw_method.toLowerCase();
const config = MOCK_ROUTES[url];
const data = config[method];
if(data === undefined) {
error = `Mock ${url}:${method} is not in your mocks list. Did you forget to add it to the api.mock call?`;
} else if(data.length !== 2) {
error = `Mock ${url}:${method} does not have the correct config. Must be [status (int), {data}], like [200, {message: 'OK'}].`
}
if(error) {
log.error(error, MOCK_ROUTES);
return [500, {message: error}];
} else {
// this is the same as res.status below
if(unauthed_action && (data[0] === 401 || data[0] === 403)) unauthed_action();
return data;
}
}
const parse_url_because_js_is_stupid = (url) => {
try {
// javascript is dumb as hell and thinks a typical /this/that is not a URL
return new URL(url);
} catch(error) {
// so fake it for the mock by tacking on ... localhost then ignoring it
return new URL(`http://localhost${url}`);
}
}
export const raw = async (url, method, body, unauthed_action) => {
const parsed = parse_url_because_js_is_stupid(url);
// if there is a rules and valid is exactly false we should just run
// the validation here and avoid the request
if(body && body._rules && body._valid === false) {
const res = validate(body);
if(!can_submit(res)) {
log.debug("Form invalid, won't HTTP submit.");
return [400, res];
} else {
clean_form(body);
}
}
if(parsed.pathname in MOCK_ROUTES) {
return raw_mock(parsed.pathname, method, body);
} else {
let options = {
method,
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
}
if(body) options.body = JSON.stringify(body);
/* this handy little gem exists because fetch likes to go:
* "the string did not match the expected pattern
* when the response body is not json...or at least that's
* what I think it means. Now I get the text, and use it
* for logging.
*/
let res = await fetch(url, options);
let text = await res.text();
try {
if(unauthed_action && (res.status === 401 || res.status === 403)) unauthed_action();
return [res.status, JSON.parse(text)];
} catch(error) {
log.error(error, "Failed to parse reply body as JSON. Text is:", text, "error", error, "URL", url);
return [500, {"message": "Exception processing request. See log.debug."}];
}
}
}
export const get = async (url, data, unauthed_action) => {
const params = new URLSearchParams(data || {});
const param_url = `${url}?${params.toString()}`;
return await raw(param_url, 'GET', undefined, unauthed_action);
}
export const post = async (url, data, unauthed_action) => {
return await raw(url, 'POST', data, unauthed_action);
}
export const put = async (url, data, unauthed_action) => {
return await raw(url, 'PUT', data, unauthed_action);
}
export const del = async (url, unauthed_action) => {
return await raw(url, 'DELETE', undefined, unauthed_action);
}
export const options = async (url, unauthed_action) => {
return await raw(url, 'OPTIONS', undefined, unauthed_action);
}
export const logout_user = async () => {
user.update(() => {
return {authenticated: undefined}
});
// really only a problem for multiple people
// using the same browser, but clear it out
cache_reset();
let [status, data] = await get('/api/logout');
if(status !== 200) log.error("Invalid status from logout", status, data);
window.location.replace("/client/#/login");
}
export const schema = async (table) => {
let [status, tables] = await get('/api/admin/schema');
if(status == 200) {
for(let t of tables) {
if(t.name === table) {
// this exits the function with the schema
return t._columns;
}
}
// this happens if the table isn't in the schema
return undefined;
} else {
return undefined;
}
}
export default {
post, get, put, del, mock, options, fetch_opts,
logout_user, validate, can_submit, clean_form, schema
}