import Vue from 'vue'; import Vuex from 'vuex'; import AxiosBootstrap from 'axios'; import * as _ from 'lodash/fp'; import router from '../router'; import * as base64 from 'base-64'; import * as utf8 from 'utf8'; import {ticketStateColorLookup, ticketStateIconLookup} from "@/utils"; import createMutationsSharer from "vuex-shared-mutations"; Vue.use(Vuex); const axios = AxiosBootstrap.create({ baseURL: '/api', }); axios.interceptors.response.use(response => response, error => { if (error.response.status === 401) { console.log('401 interceptor fired'); store.dispatch('reloadToken').then((ok) => { if (ok) { error.config.headers['Authorization'] = `Token ${store.state.token}`; return axios.request(error.config); } }); } else if (error.response.status === 403) { const message = `

Access denied.

url: ${error.config.url}
method: ${error.config.method}
response-body: ${error.response && error.response.body}

`; store.commit('createToast', {title: 'Error: Access denied', message, color: 'danger'}); return Promise.reject(error) } else { console.error('error interceptor fired', error.message); if (error.isAxiosError) { const message = `

A HTTP ${error.config.method} request failed.

url: ${error.config.url}
timeout: ${!!error.request.timeout}
response-body: ${error.response && error.response.body}

`; store.commit('createToast', {title: 'Error: HTTP', message, color: 'danger'}); } else { store.commit('createToast', {title: 'Error: Unknown', message: error.toString(), color: 'danger'}); } return Promise.reject(error); } }); const store = new Vuex.Store({ state: { keyIncrement: 0, events: [], layout: 'cards', loadedItems: [], itemCache: {}, loadedBoxes: [], toasts: [], tickets: [], users: [], groups: [], lastEvent: localStorage.getItem('lf_lastEvent') || '37C3', lastUsed: JSON.parse(localStorage.getItem('lf_lastUsed') || '{}'), remember: false, user: null, password: null, userPermissions: [], token: null, state_options: [], token_expiry: null, local_loaded: false, showAddBoxModal: false, }, getters: { getEventSlug: state => state.route && state.route.params.event ? state.route.params.event : state.lastEvent, getActiveView: state => state.route.name || 'items', getFilters: state => state.route.query, getBoxes: state => state.loadedBoxes, checkPermission: state => (event, perm) => state.userPermissions.includes(`${event}:${perm}`) || state.userPermissions.includes(`*:${perm}`), hasPermissions: state => state.userPermissions.length > 0, stateInfo: state => (slug) => { const obj = state.state_options.filter((s) => s.value === slug)[0]; if (obj) { return { color: ticketStateColorLookup(obj.value), icon: ticketStateIconLookup(obj.value), slug: obj.value, text: obj.text, } } else { return { color: 'danger', icon: 'exclamation', slug: slug, text: 'Unknown' } } }, isLoggedIn(state) { if (!state.local_loaded) { state.remember = localStorage.getItem('remember') === 'true'; state.user = localStorage.getItem('user'); //state.password = localStorage.getItem('password'); state.userPermissions = JSON.parse(localStorage.getItem('permissions') || '[]'); state.token = localStorage.getItem('token'); state.token_expiry = localStorage.getItem('token_expiry'); state.local_loaded = true; axios.defaults.headers.common['Authorization'] = `Token ${state.token}`; } return state.user !== null && state.token !== null; }, }, mutations: { updateLastUsed(state, diff) { state.lastUsed = _.extend(state.lastUsed, diff); localStorage.setItem('lf_lastUsed', JSON.stringify(state.lastUsed)); }, updateLastEvent(state, slug) { state.lastEvent = slug; localStorage.setItem('lf_lastEvent', slug); }, replaceEvents(state, events) { state.events = events; }, replaceTicketStates(state, states) { state.state_options = states; }, changeView(state, {view, slug}) { router.push({path: `/${slug}/${view}`}); }, replaceLoadedItems(state, newItems) { state.loadedItems = newItems; }, setItemCache(state, {slug, items}) { state.itemCache[slug] = items; }, setLayout(state, layout) { state.layout = layout; }, replaceBoxes(state, loadedBoxes) { state.loadedBoxes = loadedBoxes; }, updateItem(state, updatedItem) { const item = state.loadedItems.filter(({uid}) => uid === updatedItem.uid)[0]; Object.assign(item, updatedItem); }, removeItem(state, item) { state.loadedItems = state.loadedItems.filter(it => it !== item); }, appendItem(state, item) { state.loadedItems.push(item); }, replaceTickets(state, tickets) { state.tickets = tickets; }, replaceUsers(state, users) { state.users = users; }, replaceGroups(state, groups) { state.groups = groups; }, updateTicket(state, updatedTicket) { const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0]; Object.assign(ticket, updatedTicket); }, openAddBoxModal(state) { state.showAddBoxModal = true; }, closeAddBoxModal(state) { state.showAddBoxModal = false; }, createToast(state, {title, message, color}) { var toast = {title, message, color, key: state.keyIncrement} state.toasts.push(toast); state.keyIncrement += 1; return toast; }, removeToast(state, key) { state.toasts = state.toasts.filter(toast => toast.key !== key); }, setRemember(state, remember) { state.remember = remember; localStorage.setItem('remember', remember); }, setUser(state, user) { state.user = user; if (user) localStorage.setItem('user', user); }, setPassword(state, password) { state.password = password; }, setPermissions(state, permissions) { state.userPermissions = permissions; if (permissions) localStorage.setItem('permissions', JSON.stringify(permissions)); }, setToken(state, {token, expiry}) { state.token = token; state.token_expiry = expiry; if (token) localStorage.setItem('token', token); localStorage.setItem('token_expiry', expiry); }, logout(state) { state.user = null; state.token = null; localStorage.removeItem('user'); localStorage.removeItem('permissions'); localStorage.removeItem('token'); localStorage.removeItem('token_expiry'); if (router.currentRoute.name !== 'login') router.push('/login'); }, }, actions: { async login({commit, dispatch, state}, {username, password, remember}) { commit('setRemember', remember); try { const data = await fetch('/api/2/login/', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username: username, password: password}), credentials: 'omit' }).then(r => r.json()) if (data && data.token) { commit('setToken', data); commit('setUser', username); commit('setPassword', password); axios.defaults.headers.common['Authorization'] = `Token ${data.token}`; dispatch('afterLogin'); return true; } else { return false; } } catch (e) { console.error(e); return false; } }, async reloadToken({commit, state}) { try { if (state.password) { const data = await fetch('/api/2/login/', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username: state.user, password: state.password}), credentials: 'omit' }).then(r => r.json()).catch(e => console.error(e)) if (data && data.token) { commit('setToken', data); axios.defaults.headers.common['Authorization'] = `Token ${data.token}`; return true; } } } catch (e) { console.error(e); } //credentials failed, logout store.commit('logout'); }, //async verifyToken({commit, state}) { async afterLogin({dispatch}) { const boxes = dispatch('loadBoxes'); const states = dispatch('fetchTicketStates'); const items = dispatch('loadEventItems'); const tickets = dispatch('loadTickets'); const user = dispatch('loadUserInfo'); await Promise.all([boxes, items, tickets, user, states]); }, async fetchImage({state}, url) { return await fetch(url, {headers: {'Authorization': `Token ${state.token}`}}); }, async loadUserInfo({commit}) { const {data} = await axios.get('/2/self/'); commit('setUser', data.username); commit('setPermissions', data.permissions); }, async loadEvents({commit}) { const {data} = await axios.get('/2/events/'); commit('replaceEvents', data); }, async fetchTicketStates({commit}) { const {data} = await axios.get('/2/tickets/states/'); commit('replaceTicketStates', data); }, changeEvent({dispatch, getters, commit}, eventName) { router.push({path: `/${eventName.slug}/${getters.getActiveView}/`}); dispatch('loadEventItems'); }, changeView({getters}, link) { router.push({path: `/${getters.getEventSlug}/${link.path}/`}); }, showBoxContent({getters}, box) { router.push({path: `/${getters.getEventSlug}/items/`, query: {box}}); }, async loadEventItems({commit, getters, state}) { try { commit('replaceLoadedItems', []); const slug = getters.getEventSlug; if (slug in state.itemCache) { commit('replaceLoadedItems', state.itemCache[slug]); } const {data} = await axios.get(`/2/${slug}/items/`); commit('replaceLoadedItems', data); commit('setItemCache', {slug, items: data}); } catch (e) { console.error("Error loading items"); } }, async searchEventItems({commit, getters}, query) { const foo = utf8.encode(query); const bar = base64.encode(foo); const {data} = await axios.get(`/2/${getters.getEventSlug}/items/${bar}/`); commit('replaceLoadedItems', data); }, async loadBoxes({commit}) { const {data} = await axios.get('/2/boxes/'); commit('replaceBoxes', data); }, async createBox({commit, dispatch}, box) { const {data} = await axios.post('/2/boxes/', box); commit('replaceBoxes', data); dispatch('loadBoxes').then(() => { commit('closeAddBoxModal'); }); }, async deleteBox({commit, dispatch}, box_id) { await axios.delete(`/2/boxes/${box_id}/`); dispatch('loadBoxes'); }, async updateItem({commit, getters}, item) { const {data} = await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item); commit('updateItem', data); }, async markItemReturned({commit, getters}, item) { await axios.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}); commit('removeItem', item); }, async deleteItem({commit, getters}, item) { await axios.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item); commit('removeItem', item); }, async postItem({commit, getters}, item) { commit('updateLastUsed', {box: item.box, cid: item.cid}); const {data} = await axios.post(`/2/${getters.getEventSlug}/item/`, item); commit('appendItem', data); }, async loadTickets({commit}) { const {data} = await axios.get('/2/tickets/'); commit('replaceTickets', data); }, async sendMail({commit, dispatch}, {id, message}) { const {data} = await axios.post(`/2/tickets/${id}/reply/`, {message}); await dispatch('loadTickets'); }, async postManualTicket({commit, dispatch}, {sender, message, title,}) { const {data} = await axios.post(`/2/tickets/manual/`, { name: title, sender, body: message, recipient: 'mail@c3lf.de' }); await dispatch('loadTickets'); }, async postComment({commit, dispatch}, {id, message}) { const {data} = await axios.post(`/2/tickets/${id}/comment/`, {comment: message}); await dispatch('loadTickets'); }, async loadUsers({commit}) { const {data} = await axios.get('/2/users/'); commit('replaceUsers', data); }, async loadGroups({commit}) { const {data} = await axios.get('/2/groups/'); commit('replaceGroups', data); }, async updateTicket({commit}, ticket) { const {data} = await axios.put(`/2/tickets/${ticket.id}/`, ticket); commit('updateTicket', data); }, async updateTicketPartial({commit}, {id, ...ticket}) { const {data} = await axios.patch(`/2/tickets/${id}/`, ticket); commit('updateTicket', data); } }, plugins: [createMutationsSharer({ predicate: [ 'replaceLoadedItems', 'setItemCache', 'setLayout', 'replaceBoxes', 'updateItem', 'removeItem', 'appendItem', 'replaceTickets', 'replaceUsers', 'replaceGroups', 'updateTicket', 'openAddBoxModal', 'closeAddBoxModal', 'createToast', 'removeToast', 'setRemember', 'setUser', 'setPermissions', 'setToken', 'logout', ] })], }); export default store; store.dispatch('loadEvents').then(() => { if (store.getters.isLoggedIn) { axios.defaults.headers.common['Authorization'] = `Token ${store.state.token}`; store.dispatch('afterLogin'); } });