580 lines
14 KiB
JavaScript
580 lines
14 KiB
JavaScript
/**
|
|
* Servicio de comunicación API
|
|
*
|
|
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
import axios from 'axios';
|
|
import { reactive, ref } from 'vue';
|
|
|
|
axios.defaults.withXSRFToken = true;
|
|
// axios.defaults.withCredentials = true;
|
|
|
|
/**
|
|
* Códigos de falla
|
|
*/
|
|
const failCodes = [
|
|
400,
|
|
409,
|
|
422
|
|
];
|
|
|
|
/**
|
|
* Servidor a utilizar
|
|
*/
|
|
const token = ref(localStorage.token);
|
|
const csrfToken = ref(localStorage.csrfToken);
|
|
|
|
/**
|
|
* Define el token de la api
|
|
*/
|
|
const defineApiToken = (x) => {
|
|
token.value = x;
|
|
localStorage.token = x;
|
|
}
|
|
|
|
/**
|
|
* Define CSRF token
|
|
*/
|
|
const defineCsrfToken = (x) => {
|
|
csrfToken.value = x;
|
|
localStorage.csrfToken = x;
|
|
}
|
|
|
|
/**
|
|
* Define el token de la api
|
|
*/
|
|
const resetApiToken = () => {
|
|
token.value = undefined;
|
|
localStorage.removeItem('token');
|
|
}
|
|
|
|
/**
|
|
* Reset CSRF token
|
|
*/
|
|
const resetCsrfToken = () => {
|
|
csrfToken.value = undefined;
|
|
localStorage.removeItem('csrfToken');
|
|
}
|
|
|
|
/**
|
|
* Determina si el token tiene algo o no
|
|
*/
|
|
const hasToken = () => {
|
|
return token.value !== undefined;
|
|
}
|
|
|
|
/**
|
|
* Fuerza el cierre de la sesión
|
|
*/
|
|
const closeSession = () => {
|
|
resetApiToken()
|
|
resetCsrfToken()
|
|
|
|
Notify.info(Lang('session.closed'))
|
|
|
|
location.replace('auth.html')
|
|
}
|
|
|
|
/**
|
|
* Composición de llaves
|
|
*
|
|
* Utilizado para transformar llaves en FormData
|
|
*/
|
|
function composeKey(parent, key) {
|
|
return parent ? parent + '[' + key + ']' : key
|
|
}
|
|
|
|
/**
|
|
* Instancia de la API de uso directo
|
|
*/
|
|
const api = {
|
|
errors: {},
|
|
hasErrors: false,
|
|
processing: false,
|
|
wasSuccessful: false,
|
|
async load({
|
|
method,
|
|
url,
|
|
apiToken = token.value,
|
|
options = {
|
|
data:{},
|
|
params:{}
|
|
}
|
|
}) {
|
|
this.errors = {};
|
|
this.hasErrors = false;
|
|
this.processing = true;
|
|
this.wasSuccessful = false;
|
|
|
|
try {
|
|
if(options.hasOwnProperty('onStart')) {
|
|
options.onStart();
|
|
}
|
|
|
|
let { data } = await axios({
|
|
method: method,
|
|
url,
|
|
data: options.data,
|
|
params: options.params,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${apiToken}`
|
|
}
|
|
});
|
|
|
|
if(data.status == 'success') {
|
|
this.wasSuccessful = true;
|
|
|
|
if(options.hasOwnProperty('onSuccess')) {
|
|
options.onSuccess(data.data, data);
|
|
}
|
|
} else if(data.status == 'fail') {
|
|
if(options.hasOwnProperty('onFail')) {
|
|
options.onFail(data.data);
|
|
}
|
|
|
|
console.log(data.data);
|
|
}
|
|
|
|
if(options.hasOwnProperty('onFinish')) {
|
|
options.onFinish(data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
|
|
this.hasErrors = true;
|
|
|
|
let { response } = error
|
|
|
|
// Código de sesión invalida
|
|
if(response.status === 401 && response.data?.message == 'Unauthenticated.') {
|
|
Notify.error(Lang('session.expired'));
|
|
|
|
closeSession();
|
|
|
|
return
|
|
}
|
|
|
|
// Fallas
|
|
if(failCodes.includes(response.status)) {
|
|
options.hasOwnProperty('onFail')
|
|
? options.onFail(response.data.data)
|
|
: Notify.warning(response.data.data.message);
|
|
|
|
return
|
|
}
|
|
|
|
if(options.hasOwnProperty('onError')) {
|
|
options.onError(response.data);
|
|
}
|
|
|
|
if(response.data != null) {
|
|
this.errors = response.data.errors;
|
|
}
|
|
}
|
|
|
|
this.processing = false;
|
|
},
|
|
get(url, options) {
|
|
this.load({
|
|
method: 'get',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
post(url, options) {
|
|
this.load({
|
|
method: 'post',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
put(url, options) {
|
|
this.load({
|
|
method: 'put',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
patch(url, options) {
|
|
this.load('patch', {
|
|
method: 'patch',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
delete(url, options) {
|
|
this.load({
|
|
method: 'delete',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
resource(resources, options) {
|
|
this.post('resources/get', {
|
|
...options,
|
|
data: resources
|
|
})
|
|
},
|
|
download(url, file, params = {}) {
|
|
axios({
|
|
url: url,
|
|
params: params,
|
|
method: 'GET',
|
|
responseType: 'blob',
|
|
headers: {
|
|
'Authorization': `Bearer ${token.value}`
|
|
}
|
|
}).then((res) => {
|
|
const href = URL.createObjectURL(res.data);
|
|
const link = document.createElement('a');
|
|
|
|
link.href = href;
|
|
link.setAttribute('download', file);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
|
|
document.body.removeChild(link);
|
|
|
|
URL.revokeObjectURL(href);
|
|
});
|
|
},
|
|
}
|
|
|
|
/**
|
|
* Instancia de la API
|
|
*/
|
|
const useApi = () => reactive(api);
|
|
|
|
/**
|
|
* Instancia de la API para formularios
|
|
*/
|
|
const useForm = (form = {}) => {
|
|
// Permite agregar datos mediante una transformación
|
|
let transform = (data) => data
|
|
|
|
// Contador de archivos
|
|
let filesCounter = 0
|
|
|
|
// Procesar elementos del formulario
|
|
const append = (formData, key, value) => {
|
|
if(Array.isArray(value)) {
|
|
return Array.from(value.keys()).forEach((index) => append(formData, composeKey(key, index), value[index]));
|
|
} else if(value instanceof Date) {
|
|
return formData.append(key, value.toISOString())
|
|
} else if(value instanceof File) {
|
|
filesCounter++
|
|
return formData.append(key, value, value.name)
|
|
} else if (value instanceof Blob) {
|
|
return formData.append(key, value)
|
|
} else if(typeof value === 'boolean') {
|
|
return formData.append(key, value ? '1' : '0')
|
|
} else if (typeof value === 'string') {
|
|
return formData.append(key, value)
|
|
} else if (typeof value === 'number') {
|
|
return formData.append(key, `${value}`)
|
|
} else if(value === null || value === undefined) {
|
|
return formData.append(key, '')
|
|
} else if (typeof value === 'object') {
|
|
objectToFormData(formData, key, value);
|
|
}
|
|
}
|
|
|
|
// Convertir objeto a elemento de FormData
|
|
const objectToFormData = (formData, parentKey = null, value) => {
|
|
value = value || {}
|
|
for (const key in value) {
|
|
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
append(formData, composeKey(parentKey, key), value[key])
|
|
}
|
|
}
|
|
|
|
return formData
|
|
}
|
|
|
|
// Transforma todos los datos
|
|
const prepareData = (data) => {
|
|
let formData = new FormData();
|
|
|
|
for (let i in data) {
|
|
append(formData, i, data[i]);
|
|
}
|
|
|
|
return formData;
|
|
}
|
|
|
|
return reactive({
|
|
...form,
|
|
errors: {},
|
|
hasErrors: false,
|
|
processing: false,
|
|
wasSuccessful: false,
|
|
_inputs: Object.keys(form),
|
|
_original: {
|
|
...form
|
|
},
|
|
reset() {
|
|
for(let i in this._original) {
|
|
this[i] = this._original[i]
|
|
}
|
|
},
|
|
data() {
|
|
let data = {};
|
|
|
|
for (let i in this) {
|
|
if(typeof this[i] !== 'function' && this._inputs.includes(i)){
|
|
data[i] = this[i]
|
|
}
|
|
}
|
|
|
|
return data;
|
|
},
|
|
transform(callback) {
|
|
transform = callback
|
|
|
|
return this
|
|
},
|
|
async load({
|
|
method,
|
|
url,
|
|
apiToken = token.value,
|
|
options = {
|
|
data:{},
|
|
params:{}
|
|
}
|
|
}) {
|
|
this.errors = {};
|
|
this.hasErrors = false;
|
|
this.processing = true;
|
|
this.wasSuccessful = false;
|
|
|
|
try {
|
|
if(options.hasOwnProperty('onStart')) {
|
|
options.onStart(options);
|
|
}
|
|
|
|
let { data } = await axios({
|
|
method: method,
|
|
url,
|
|
data: prepareData(transform(this.data())),
|
|
headers: {
|
|
'Content-Type': (filesCounter > 0)
|
|
? 'multipart/form-data boundary='
|
|
: 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${apiToken}`,
|
|
'X-CSRF-TOKEN': csrfToken.value
|
|
}
|
|
});
|
|
|
|
if(data.status == 'success') {
|
|
this.wasSuccessful = true;
|
|
|
|
if(options.hasOwnProperty('onSuccess')) {
|
|
options.onSuccess(data?.data);
|
|
}
|
|
} else if(data.status == 'fail') {
|
|
if(options.hasOwnProperty('onFail')) {
|
|
options.onFail(data?.data);
|
|
}
|
|
}
|
|
|
|
if(options.hasOwnProperty('onFinish')) {
|
|
options.onFinish(data?.data);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
this.hasErrors = true;
|
|
|
|
let { response } = error
|
|
|
|
if(options.hasOwnProperty('onError')) {
|
|
options.onError(response);
|
|
}
|
|
|
|
if(response.data?.errors != null) {
|
|
this.errors = response.data.errors;
|
|
|
|
for(let e in this.errors) {
|
|
Notify.error(this.errors[e])
|
|
}
|
|
}
|
|
}
|
|
|
|
this.processing = false;
|
|
},
|
|
fill(model) {
|
|
this._inputs.forEach(element => {
|
|
this[element] = (element == 'is_active')
|
|
? (model[element] == 1)
|
|
: model[element] ?? this[element]
|
|
});
|
|
},
|
|
get(url, options) {
|
|
this.load({
|
|
method: 'get',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
post(url, options) {
|
|
this.load({
|
|
method: 'post',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
put(url, options) {
|
|
this.load({
|
|
method: 'put',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
patch(url, options) {
|
|
this.load('patch', {
|
|
method: 'patch',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
delete(url, options) {
|
|
this.load({
|
|
method: 'delete',
|
|
url,
|
|
options
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Instancia de a API para buscador
|
|
*/
|
|
const useSearcher = (options = {
|
|
url: '',
|
|
filters: ''
|
|
}) => reactive({
|
|
query: '',
|
|
errors: {},
|
|
hasErrors: false,
|
|
processing: false,
|
|
wasSuccessful: false,
|
|
async load({
|
|
url,
|
|
apiToken = token.value,
|
|
filters,
|
|
}) {
|
|
this.errors = {};
|
|
this.processing = true;
|
|
this.hasErrors = false;
|
|
this.wasSuccessful = false;
|
|
|
|
try {
|
|
if(options.hasOwnProperty('onStart')) {
|
|
options.onStart();
|
|
}
|
|
|
|
let { data } = await axios({
|
|
method: 'get',
|
|
url,
|
|
params: {
|
|
q: this.query,
|
|
...filters
|
|
},
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${apiToken}`
|
|
}
|
|
});
|
|
|
|
if(data.status == 'success') {
|
|
this.wasSuccessful = true;
|
|
|
|
if(options.hasOwnProperty('onSuccess')) {
|
|
options.onSuccess(data.data);
|
|
}
|
|
} else if(data.status == 'fail') {
|
|
if(options.hasOwnProperty('onFail')) {
|
|
options.onFail(data.data);
|
|
}
|
|
}
|
|
|
|
if(options.hasOwnProperty('onFinish')) {
|
|
options.onFinish(data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
this.hasErrors = true;
|
|
|
|
let { response } = error
|
|
|
|
// Código de sesión invalida
|
|
if(response.status === 401 && response.data.message == 'Unauthenticated.') {
|
|
Notify.error(Lang('session.expired'));
|
|
closeSession();
|
|
return
|
|
}
|
|
|
|
if(options.hasOwnProperty('onError')) {
|
|
options.onError(response);
|
|
}
|
|
|
|
if(response.data?.errors != null) {
|
|
this.errors = response.data.errors;
|
|
for(let e in this.errors) {
|
|
Notify.error(this.errors[e])
|
|
}
|
|
}
|
|
}
|
|
|
|
this.processing = false;
|
|
},
|
|
pagination(url, filter = {}) {
|
|
this.load({
|
|
url,
|
|
filters : {
|
|
...options.filters,
|
|
...filter,
|
|
}
|
|
})
|
|
},
|
|
search(q = '', filter = {}) {
|
|
this.query = q
|
|
this.load({
|
|
url: options.url,
|
|
filters : {
|
|
...options.filters,
|
|
...filter,
|
|
}
|
|
})
|
|
},
|
|
refresh(filter = {}) {
|
|
this.load({
|
|
url: options.url,
|
|
filters : {
|
|
...options.filters,
|
|
...filter,
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
export {
|
|
api,
|
|
token,
|
|
closeSession,
|
|
defineCsrfToken,
|
|
defineApiToken,
|
|
hasToken,
|
|
resetApiToken,
|
|
useApi,
|
|
useForm,
|
|
useSearcher
|
|
} |