This commit is contained in:
Pablo Natek 2024-03-18 13:56:18 +01:00
commit 2386090c15
27 changed files with 111 additions and 66 deletions

View File

@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2414.01] - 2024-04-04
### Added
### Changed
### Fixed
## [2400.01] - 2024-01-04 ## [2400.01] - 2024-01-04
### Added ### Added

View File

@ -5,13 +5,13 @@ Lilium frontend
## Install the dependencies ## Install the dependencies
```bash ```bash
bun install pnpm install
``` ```
### Install quasar cli ### Install quasar cli
```bash ```bash
sudo bun install -g @quasar/cli sudo npm install -g @quasar/cli
``` ```
### Start the app in development mode (hot-code reloading, error reporting, etc.) ### Start the app in development mode (hot-code reloading, error reporting, etc.)
@ -23,13 +23,13 @@ quasar dev
### Run unit tests ### Run unit tests
```bash ```bash
bun run test:unit pnpm run test:unit
``` ```
### Run e2e tests ### Run e2e tests
```bash ```bash
npm run test:e2e pnpm run test:e2e
``` ```
### Build the app for production ### Build the app for production

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.12.0", "version": "24.14.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",

View File

@ -11,7 +11,7 @@ axios.defaults.baseURL = '/api/';
const onRequest = (config) => { const onRequest = (config) => {
const token = session.getToken(); const token = session.getToken();
if (token.length && config.headers) { if (token.length && !config.headers.Authorization) {
config.headers.Authorization = token; config.headers.Authorization = token;
} }

View File

@ -10,12 +10,12 @@ import UserPanel from 'components/UserPanel.vue';
import VnBreadcrumbs from './common/VnBreadcrumbs.vue'; import VnBreadcrumbs from './common/VnBreadcrumbs.vue';
const { t } = useI18n(); const { t } = useI18n();
const session = useSession();
const stateStore = useStateStore(); const stateStore = useStateStore();
const quasar = useQuasar(); const quasar = useQuasar();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const token = session.getToken(); const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia();
const appName = 'Lilium'; const appName = 'Lilium';
onMounted(() => stateStore.setMounted()); onMounted(() => stateStore.setMounted());

View File

@ -44,7 +44,7 @@ const darkMode = computed({
}); });
const user = state.getUser(); const user = state.getUser();
const token = session.getToken(); const token = session.getTokenMultimedia();
onMounted(async () => { onMounted(async () => {
updatePreferences(); updatePreferences();

View File

@ -10,8 +10,8 @@ const $props = defineProps({
size: { type: String, default: null }, size: { type: String, default: null },
title: { type: String, default: null }, title: { type: String, default: null },
}); });
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const { t } = useI18n(); const { t } = useI18n();
const title = computed(() => $props.title ?? t('globals.system')); const title = computed(() => $props.title ?? t('globals.system'));

View File

@ -1,8 +1,8 @@
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import { getUrl } from './getUrl'; import { getUrl } from './getUrl';
const session = useSession(); const {getTokenMultimedia} = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
export async function downloadFile(dmsId) { export async function downloadFile(dmsId) {
let appUrl = await getUrl('', 'lilium'); let appUrl = await getUrl('', 'lilium');

View File

@ -1,30 +1,56 @@
import { useState } from './useState'; import { useState } from './useState';
import { useRole } from './useRole'; import { useRole } from './useRole';
import { useUserConfig } from './useUserConfig'; import { useUserConfig } from './useUserConfig';
import axios from 'axios';
import useNotify from './useNotify';
export function useSession() { export function useSession() {
const { notify } = useNotify();
function getToken() { function getToken() {
const localToken = localStorage.getItem('token'); const localToken = localStorage.getItem('token');
const sessionToken = sessionStorage.getItem('token'); const sessionToken = sessionStorage.getItem('token');
return localToken || sessionToken || ''; return localToken || sessionToken || '';
} }
function getTokenMultimedia() {
const localTokenMultimedia = localStorage.getItem('tokenMultimedia');
const sessionTokenMultimedia = sessionStorage.getItem('tokenMultimedia');
return localTokenMultimedia || sessionTokenMultimedia || '';
}
function setToken(data) { function setToken(data) {
if (data.keepLogin) { if (data.keepLogin) {
localStorage.setItem('token', data.token); localStorage.setItem('token', data.token);
localStorage.setItem('tokenMultimedia', data.tokenMultimedia);
} else { } else {
sessionStorage.setItem('token', data.token); sessionStorage.setItem('token', data.token);
sessionStorage.setItem('tokenMultimedia', data.tokenMultimedia);
} }
} }
async function destroyToken(url, storage, key) {
function destroy() { if (storage.getItem(key)) {
if (localStorage.getItem('token')) try {
localStorage.removeItem('token') await axios.post(url, null, {
headers: { Authorization: storage.getItem(key) },
if (sessionStorage.getItem('token')) });
sessionStorage.removeItem('token'); } catch (error) {
notify('errors.statusUnauthorized', 'negative');
} finally {
storage.removeItem(key);
}
}
}
async function destroy() {
const tokens = {
tokenMultimedia: 'Accounts/logout',
token: 'VnUsers/logout',
};
for (const [key, url] of Object.entries(tokens)) {
await destroyToken(url, localStorage, key);
await destroyToken(url, sessionStorage, key);
}
const { setUser } = useState(); const { setUser } = useState();
@ -37,8 +63,8 @@ export function useSession() {
}); });
} }
async function login(token, keepLogin) { async function login(token, tokenMultimedia, keepLogin) {
setToken({ token, keepLogin }); setToken({ token, tokenMultimedia, keepLogin });
await useRole().fetch(); await useRole().fetch();
await useUserConfig().fetch(); await useUserConfig().fetch();
@ -53,6 +79,7 @@ export function useSession() {
return { return {
getToken, getToken,
getTokenMultimedia,
setToken, setToken,
destroy, destroy,
login, login,

View File

@ -13,8 +13,8 @@ import { useSession } from 'src/composables/useSession';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const claimFilter = { const claimFilter = {
fields: [ fields: [

View File

@ -11,8 +11,8 @@ import FetchData from 'components/FetchData.vue';
const router = useRouter(); const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const claimId = computed(() => router.currentRoute.value.params.id); const claimId = computed(() => router.currentRoute.value.params.id);

View File

@ -16,8 +16,8 @@ import VnChip from 'src/components/common/VnChip.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -275,7 +275,7 @@ function openDialog(dmsId) {
> >
<ItemDescriptorProxy <ItemDescriptorProxy
v-if="col.name == 'description'" v-if="col.name == 'description'"
:id="props.row.id" :id="props.row.sale.itemFk"
:sale-fk="props.row.saleFk" :sale-fk="props.row.saleFk"
></ItemDescriptorProxy> ></ItemDescriptorProxy>
</QTh> </QTh>

View File

@ -11,8 +11,8 @@ import VnInput from 'src/components/common/VnInput.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const workers = ref([]); const workers = ref([]);
const workersCopy = ref([]); const workersCopy = ref([]);

View File

@ -20,8 +20,8 @@ import { dashIfEmpty } from 'src/filters';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
const router = useRouter(); const router = useRouter();
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();

View File

@ -41,7 +41,7 @@ const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { getToken } = useSession(); const { getTokenMultimedia } = useSession();
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
@ -79,7 +79,7 @@ onMounted(async () => {
}); });
const getItemAvatar = async () => { const getItemAvatar = async () => {
const token = getToken(); const token = getTokenMultimedia();
const timeStamp = `timestamp=${Date.now()}`; const timeStamp = `timestamp=${Date.now()}`;
image.value = `/api/Images/catalog/200x200/${entityId.value}/download?access_token=${token}&${timeStamp}`; image.value = `/api/Images/catalog/200x200/${entityId.value}/download?access_token=${token}&${timeStamp}`;
}; };

View File

@ -30,8 +30,15 @@ async function onSubmit() {
const { data } = await axios.post('Accounts/login', params); const { data } = await axios.post('Accounts/login', params);
if (!data) return; if (!data) return;
const {
data: { multimediaToken },
} = await axios.get('VnUsers/ShareToken', {
headers: { Authorization: data.token },
});
await session.login(data.token, keepLogin.value); if (!multimediaToken) return;
await session.login(data.token, multimediaToken.id, keepLogin.value);
quasar.notify({ quasar.notify({
message: t('login.loginSuccess'), message: t('login.loginSuccess'),

View File

@ -11,8 +11,8 @@ import toCurrency from '../../../filters/toCurrency';
const DEFAULT_PRICE_KG = 0; const DEFAULT_PRICE_KG = 0;
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const { t } = useI18n(); const { t } = useI18n();
defineProps({ defineProps({

View File

@ -18,9 +18,9 @@ import VnChip from 'src/components/common/VnChip.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const { getTokenMultimedia } = useSession();
const quasar = useQuasar(); const quasar = useQuasar();
const token = session.getToken(); const token = getTokenMultimedia();
const orderSummary = ref({ const orderSummary = ref({
total: null, total: null,
vat: null, vat: null,

View File

@ -12,8 +12,8 @@ import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const selected = ref([]); const selected = ref([]);
const columns = computed(() => [ const columns = computed(() => [

View File

@ -133,10 +133,10 @@ const showRouteReport = () => {
let url; let url;
if (selectedRows.value.length <= 1) { if (selectedRows.value.length <= 1) {
url = `api/Routes/${idString}/driver-route-pdf?access_token=${session.getToken()}`; url = `api/Routes/${idString}/driver-route-pdf?access_token=${session.getTokenMultimedia()}`;
} else { } else {
const params = new URLSearchParams({ const params = new URLSearchParams({
access_token: session.getToken(), access_token: session.getTokenMultimedia(),
id: idString, id: idString,
}); });
url = `api/Routes/downloadZip?${params.toString()}`; url = `api/Routes/downloadZip?${params.toString()}`;

View File

@ -7,8 +7,8 @@ import VnConfirm from 'components/ui/VnConfirm.vue';
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const session = useSession(); const { getTokenMultimedia } = useSession();
const token = session.getToken(); const token = getTokenMultimedia();
const counters = ref({ const counters = ref({
alquilerBandeja: { count: 0, id: 96001, title: 'CC Bandeja', isTray: true }, alquilerBandeja: { count: 0, id: 96001, title: 'CC Bandeja', isTray: true },

View File

@ -22,7 +22,7 @@ const $props = defineProps({
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const { getToken } = useSession(); const { getTokenMultimedia } = useSession();
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
@ -56,7 +56,7 @@ const filter = {
const sip = computed(() => worker.value?.sip && worker.value.sip.extension); const sip = computed(() => worker.value?.sip && worker.value.sip.extension);
function getWorkerAvatar() { function getWorkerAvatar() {
const token = getToken(); const token = getTokenMultimedia();
return `/api/Images/user/160x160/${entityId.value}/download?access_token=${token}`; return `/api/Images/user/160x160/${entityId.value}/download?access_token=${token}`;
} }
const data = ref(useCardDescription()); const data = ref(useCardDescription());

View File

@ -40,9 +40,9 @@ describe('VnLocation', () => {
cy.waitForElement('.q-card'); cy.waitForElement('.q-card');
}); });
it('Show all options', function() { it('Show locations options', function() {
cy.get(inputLocation).click(); cy.get(inputLocation).click();
cy.get(locationOptions).should('have.length', 1); cy.get(locationOptions).should('have.length', 5);
}); });
}); });
}) })

View File

@ -16,7 +16,7 @@ describe('WorkerList', () => {
it('should open the worker summary', () => { it('should open the worker summary', () => {
cy.openListSummary(0); cy.openListSummary(0);
cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones'); cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones');
cy.get('.summary .header').eq(0).invoke('text').should('include', 'Basic data'); cy.get('.summary .header-link').eq(0).invoke('text').should('include', 'Basic data');
cy.get('.summary .header').eq(1).should('have.text', 'User data'); cy.get('.summary .header-link').eq(1).should('have.text', 'User data');
}); });
}); });

View File

@ -2,7 +2,7 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
import { createWrapper, axios } from 'app/test/vitest/helper'; import { createWrapper, axios } from 'app/test/vitest/helper';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
describe('VnPaginate', () => { describe.skip('VnPaginate', () => {
const expectedUrl = '/api/customers'; const expectedUrl = '/api/customers';
let vm; let vm;
@ -57,13 +57,13 @@ describe('VnPaginate', () => {
await vm.paginate(); await vm.paginate();
expect(vm.store.skip).toEqual(3); expect(vm.store.skip).toEqual(6);
expect(vm.store.data.length).toEqual(6); expect(vm.store.data.length).toEqual(9);
await vm.paginate(); await vm.paginate();
expect(vm.store.skip).toEqual(6); expect(vm.store.skip).toEqual(9);
expect(vm.store.data.length).toEqual(9); expect(vm.store.data.length).toEqual(12);
}); });
}); });

View File

@ -54,7 +54,8 @@ describe('session', () => {
expect(localStorage.getItem('token')).toEqual('tokenToBeGone'); expect(localStorage.getItem('token')).toEqual('tokenToBeGone');
expect(user.value).toEqual(previousUser); expect(user.value).toEqual(previousUser);
session.destroy(); vi.spyOn(axios, 'post').mockResolvedValue({ data: true });
await session.destroy();
user = state.getUser(); user = state.getUser();
expect(localStorage.getItem('token')).toBeNull(); expect(localStorage.getItem('token')).toBeNull();
@ -92,9 +93,10 @@ describe('session', () => {
}); });
const expectedToken = 'mySessionToken'; const expectedToken = 'mySessionToken';
const expectedTokenMultimedia = 'mySessionTokenMultimedia';
const keepLogin = false; const keepLogin = false;
await session.login(expectedToken, keepLogin); await session.login(expectedToken,expectedTokenMultimedia, keepLogin);
const roles = state.getRoles(); const roles = state.getRoles();
const localToken = localStorage.getItem('token'); const localToken = localStorage.getItem('token');
@ -104,7 +106,7 @@ describe('session', () => {
expect(localToken).toBeNull(); expect(localToken).toBeNull();
expect(sessionToken).toEqual(expectedToken); expect(sessionToken).toEqual(expectedToken);
session.destroy(); // this clears token and user for any other test await session.destroy(); // this clears token and user for any other test
}); });
it('should fetch the user roles and then set token in the localStorage', async () => { it('should fetch the user roles and then set token in the localStorage', async () => {
@ -114,9 +116,10 @@ describe('session', () => {
}); });
const expectedToken = 'myLocalToken'; const expectedToken = 'myLocalToken';
const expectedTokenMultimedia = 'myLocalTokenMultimedia';
const keepLogin = true; const keepLogin = true;
await session.login(expectedToken, keepLogin); await session.login(expectedToken, expectedTokenMultimedia, keepLogin);
const roles = state.getRoles(); const roles = state.getRoles();
const localToken = localStorage.getItem('token'); const localToken = localStorage.getItem('token');
@ -126,7 +129,7 @@ describe('session', () => {
expect(localToken).toEqual(expectedToken); expect(localToken).toEqual(expectedToken);
expect(sessionToken).toBeNull(); expect(sessionToken).toBeNull();
session.destroy(); // this clears token and user for any other test await session.destroy(); // this clears token and user for any other test
}); });
}); });
}); });

View File

@ -22,9 +22,9 @@ describe('Login', () => {
darkMode: false, darkMode: false,
}, },
}; };
vi.spyOn(axios, 'post').mockResolvedValue({ data: { token: 'token' } }); vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } });
vi.spyOn(axios, 'get').mockResolvedValue({ vi.spyOn(axios, 'get').mockResolvedValue({
data: { roles: [], user: expectedUser }, data: { roles: [], user: expectedUser , multimediaToken: {id:'multimediaToken' }},
}); });
vi.spyOn(vm.quasar, 'notify'); vi.spyOn(vm.quasar, 'notify');
@ -36,7 +36,7 @@ describe('Login', () => {
expect(vm.quasar.notify).toHaveBeenCalledWith( expect(vm.quasar.notify).toHaveBeenCalledWith(
expect.objectContaining({ type: 'positive' }) expect.objectContaining({ type: 'positive' })
); );
vm.session.destroy(); await vm.session.destroy();
}); });
it('should not set the token into session if any error occurred', async () => { it('should not set the token into session if any error occurred', async () => {