import {createStore} from 'vuex'; import router from './router'; import * as base64 from 'base-64'; import * as utf8 from 'utf8'; import {ticketStateColorLookup, ticketStateIconLookup, http} from "@/utils"; import persistentStatePlugin from "@/persistent-state-plugin"; const store = createStore({ state: { keyIncrement: 0, events: [], loadedItems: [], itemCache: {}, loadedBoxes: [], toasts: [], tickets: [], users: [], groups: [], state_options: [], lastEvent: '37C3', remember: false, user: { username: null, password: null, permissions: [], token: null, expiry: null, }, showAddBoxModal: false, persistent_loaded: false, }, getters: { route: state => router.currentRoute.value, getEventSlug: state => router.currentRoute.value.params.event ? router.currentRoute.value.params.event : state.lastEvent, getActiveView: state => router.currentRoute.value.name || 'items', getFilters: state => router.currentRoute.value.query, getBoxes: state => state.loadedBoxes, checkPermission: state => (event, perm) => state.user.permissions && (state.user.permissions.includes(`${event}:${perm}`) || state.user.permissions.includes(`*:${perm}`)), hasPermissions: state => state.user.permissions && state.user.permissions.length > 0, activeUser: state => state.user.username || 'anonymous', 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' } } }, layout: (state, getters) => { if (router.currentRoute.value.query.layout) return router.currentRoute.value.query.layout; if (getters.getActiveView === 'items') return 'cards'; if (getters.getActiveView === 'tickets') return 'tasks'; }, isLoggedIn(state) { return state.user && state.user.username !== null && state.user.token !== null; }, }, mutations: { updateLastEvent(state, slug) { state.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; }, 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; }, setUser(state, user) { state.user.username = user; }, setPassword(state, password) { state.user.password = password; }, setPermissions(state, permissions) { state.user.permissions = permissions; }, setToken(state, {token, expiry}) { const user = {...state.user}; user.token = token; user.expiry = expiry; state.user = user; }, setUserInfo(state, user) { state.user = user; }, logout(state) { const user = {...state.user}; user.user = null; user.password = null; user.token = null; user.expiry = null; user.permissions = null; state.user = user; }, }, actions: { async login({commit}, {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) { const {data: {permissions}} = await http.get('/2/self/', data.token); commit('setUserInfo', {...data, permissions, username, password}); return true; } else { return false; } } catch (e) { console.error(e); return false; } }, async reloadToken({commit, state, getters}) { try { if (state.user.username && state.user.password) { const data = await fetch('/api/2/login/', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({username: state.user.username, password: state.user.password}), credentials: 'omit' }).then(r => r.json()).catch(e => console.error(e)) if (data && data.token) { commit('setToken', data); return true; } } } catch (e) { console.error(e); } //credentials failed, logout store.commit('logout'); }, //async verifyToken({commit, state}) { async afterLogin({dispatch, state}) { let promises = []; promises.push(dispatch('loadBoxes')); promises.push(dispatch('fetchTicketStates')); promises.push(dispatch('loadEventItems')); promises.push(dispatch('loadTickets')); if (!state.user.permissions) { promises.push(dispatch('loadUserInfo')); } await Promise.all(promises); }, async fetchImage({state}, url) { return await fetch(url, {headers: {'Authorization': `Token ${state.user.token}`}}); }, async loadUserInfo({commit, state}) { const {data, success} = await http.get('/2/self/', state.user.token); commit('setPermissions', data.permissions); }, async loadEvents({commit, state}) { const {data, success} = await http.get('/2/events/', state.user.token); if (data && success) commit('replaceEvents', data); }, async fetchTicketStates({commit, state}) { const {data, success} = await http.get('/2/tickets/states/', state.user.token); if (data && success) 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, success} = await http.get(`/2/${slug}/items/`, state.user.token); if (data && success) { commit('replaceLoadedItems', data); commit('setItemCache', {slug, items: data}); } } catch (e) { console.error("Error loading items"); } }, async searchEventItems({commit, getters, state}, query) { const foo = utf8.encode(query); const bar = base64.encode(foo); const {data, success} = await http.get(`/2/${getters.getEventSlug}/items/${bar}/`, state.user.token); if (data && success) commit('replaceLoadedItems', data); }, async loadBoxes({commit, state}) { const {data, success} = await http.get('/2/boxes/', state.user.token); if (data && success) commit('replaceBoxes', data); }, async createBox({commit, dispatch, state}, box) { const {data, success} = await http.post('/2/boxes/', box, state.user.token); commit('replaceBoxes', data); dispatch('loadBoxes').then(() => { commit('closeAddBoxModal'); }); }, async deleteBox({commit, dispatch, state}, box_id) { await http.delete(`/2/boxes/${box_id}/`, state.user.token); dispatch('loadBoxes'); }, async updateItem({commit, getters, state}, item) { const { data, success } = await http.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token); commit('updateItem', data); }, async markItemReturned({commit, getters, state}, item) { await http.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}, state.user.token); commit('removeItem', item); }, async deleteItem({commit, getters, state}, item) { await http.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token); commit('removeItem', item); }, async postItem({commit, getters, state}, item) { commit('updateLastUsed', {box: item.box, cid: item.cid}); const {data, success} = await http.post(`/2/${getters.getEventSlug}/item/`, item, state.user.token); commit('appendItem', data); }, async loadTickets({commit, state}) { const {data, success} = await http.get('/2/tickets/', state.user.token); if (data && success) commit('replaceTickets', data); }, async sendMail({commit, dispatch, state}, {id, message}) { const {data, success} = await http.post(`/2/tickets/${id}/reply/`, {message}, state.user.token); if (data && success) { await dispatch('loadTickets'); } }, async postManualTicket({commit, dispatch, state}, {sender, message, title,}) { const {data, success} = await http.post(`/2/tickets/manual/`, { name: title, sender, body: message, recipient: 'mail@c3lf.de' }, state.user.token); await dispatch('loadTickets'); }, async postComment({commit, dispatch, state}, {id, message}) { const {data, success} = await http.post(`/2/tickets/${id}/comment/`, {comment: message}, state.user.token); if (data && success) { await dispatch('loadTickets'); } }, async loadUsers({commit, state}) { const {data, success} = await http.get('/2/users/', state.user.token); if (data && success) commit('replaceUsers', data); }, async loadGroups({commit, state}) { const {data, success} = await http.get('/2/groups/', state.user.token); if (data && success) commit('replaceGroups', data); }, async updateTicket({commit, state}, ticket) { const {data, success} = await http.put(`/2/tickets/${ticket.id}/`, ticket, state.user.token); commit('updateTicket', data); }, async updateTicketPartial({commit, state}, {id, ...ticket}) { const {data, success} = await http.patch(`/2/tickets/${id}/`, ticket, state.user.token); commit('updateTicket', data); } }, plugins: [ persistentStatePlugin({ // TODO change remember to some kind of enable field prefix: "lf_", debug: false, isLoadedKey: "persistent_loaded", state: [ "remember", "user", "events", ] }), ], }); store.watch((state) => state.user, (user) => { if (store.getters.isLoggedIn) { if (router.currentRoute.value.name === 'login' && router.currentRoute.value.query.redirect) router.push(router.currentRoute.value.query.redirect); else if (router.currentRoute.value.name === 'login') router.push('/'); } else { if (router.currentRoute.value.name !== 'login') { router.push({ name: 'login', query: {redirect: router.currentRoute.value.fullPath}, }); } } }); export default store;