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.
181 lines
5.0 KiB
181 lines
5.0 KiB
2 years ago
|
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
|
||
|
}
|