Moisés de Jesús Cortés Castellanos b68bd0c27b
ADD: Historial de acciones (#5)
2025-01-03 12:55:25 -06:00

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
}