c3lf-system-3/web/src/store.js

479 lines
18 KiB
JavaScript
Raw Normal View History

2024-06-18 18:10:10 +00:00
import {createStore} from 'vuex';
import router from './router';
2019-11-13 21:40:14 +00:00
2019-12-28 03:05:58 +00:00
import * as base64 from 'base-64';
import * as utf8 from 'utf8';
2024-06-18 18:10:10 +00:00
import {ticketStateColorLookup, ticketStateIconLookup, http} from "@/utils";
2024-06-22 23:04:32 +00:00
import sharedStatePlugin from "@/shared-state-plugin";
2024-06-22 18:56:51 +00:00
import persistentStatePlugin from "@/persistent-state-plugin";
2024-06-18 18:10:10 +00:00
const store = createStore({
2023-11-27 00:14:52 +00:00
state: {
keyIncrement: 0,
events: [],
loadedItems: [],
2024-01-07 20:38:25 +00:00
itemCache: {},
2023-11-27 00:14:52 +00:00
loadedBoxes: [],
toasts: [],
2024-01-07 20:38:25 +00:00
tickets: [],
users: [],
groups: [],
2024-06-18 18:10:10 +00:00
state_options: [],
2024-01-25 20:06:29 +00:00
2024-06-22 18:56:51 +00:00
lastEvent: '37C3',
2024-06-22 23:04:32 +00:00
lastUsed: {},
2024-01-07 20:38:25 +00:00
remember: false,
2024-06-18 18:10:10 +00:00
user: {
username: null,
password: null,
permissions: [],
token: null,
expiry: null,
},
thumbnailCache: {},
2024-06-22 23:04:32 +00:00
fetchedData: {
events: 0,
items: 0,
boxes: 0,
tickets: 0,
users: 0,
groups: 0,
states: 0,
},
2024-06-22 18:56:51 +00:00
persistent_loaded: false,
2024-06-22 23:04:32 +00:00
shared_loaded: false,
afterInitHandlers: [],
showAddBoxModal: false,
2024-01-25 20:06:29 +00:00
test: ['foo', 'bar', 'baz'],
2023-11-27 00:14:52 +00:00
},
getters: {
2024-06-18 18:10:10 +00:00
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,
2024-01-07 20:38:25 +00:00
getBoxes: state => state.loadedBoxes,
2024-06-22 18:56:51 +00:00
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',
2024-01-02 16:46:34 +00:00
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) => {
2024-06-18 18:10:10 +00:00
if (router.currentRoute.value.query.layout)
return router.currentRoute.value.query.layout;
if (getters.getActiveView === 'items')
return 'cards';
if (getters.getActiveView === 'tickets')
return 'tasks';
},
2024-01-07 20:38:25 +00:00
isLoggedIn(state) {
2024-06-18 18:10:10 +00:00
return state.user && state.user.username !== null && state.user.token !== null;
2024-01-07 20:38:25 +00:00
},
getThumbnail: (state) => (url) => {
if (!url) return null;
if (!(url in state.thumbnailCache))
return null;
return state.thumbnailCache[url];
},
2023-11-27 00:14:52 +00:00
},
mutations: {
2024-06-22 23:04:32 +00:00
updateLastUsed(state, diff) {
state.lastUsed = {...state.lastUsed, ...diff};
},
2024-01-07 20:38:25 +00:00
updateLastEvent(state, slug) {
state.lastEvent = slug;
2023-11-27 00:14:52 +00:00
},
replaceEvents(state, events) {
state.events = events;
2024-06-22 23:04:32 +00:00
state.fetchedData = {...state.fetchedData, events: Date.now()};
2023-11-27 00:14:52 +00:00
},
replaceTicketStates(state, states) {
state.state_options = states;
2024-06-22 23:04:32 +00:00
state.fetchedData = {...state.fetchedData, states: Date.now()};
},
2023-11-27 00:14:52 +00:00
changeView(state, {view, slug}) {
router.push({path: `/${slug}/${view}`});
},
replaceLoadedItems(state, newItems) {
state.loadedItems = newItems;
2024-06-22 23:04:32 +00:00
state.fetchedData = {...state.fetchedData, items: Date.now()}; // TODO: manage caching items for different events and search results correctly
2023-11-27 00:14:52 +00:00
},
2024-01-07 20:38:25 +00:00
setItemCache(state, {slug, items}) {
state.itemCache[slug] = items;
},
2023-11-27 00:14:52 +00:00
replaceBoxes(state, loadedBoxes) {
state.loadedBoxes = loadedBoxes;
2024-06-22 23:04:32 +00:00
state.fetchedData = {...state.fetchedData, boxes: Date.now()};
2023-11-27 00:14:52 +00:00
},
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);
},
2024-01-07 20:38:25 +00:00
replaceTickets(state, tickets) {
state.tickets = tickets;
2024-06-22 23:04:32 +00:00
state.fetchedData = {...state.fetchedData, tickets: Date.now()};
},
updateTicket(state, updatedTicket) {
const ticket = state.tickets.filter(({id}) => id === updatedTicket.id)[0];
Object.assign(ticket, updatedTicket);
//triggerRef(state.tickets);
state.tickets = [...state.tickets];
2024-01-07 20:38:25 +00:00
},
replaceUsers(state, users) {
state.users = users;
2024-06-22 23:04:32 +00:00
state.fetchedData = {...state.fetchedData, users: Date.now()};
2024-01-07 20:38:25 +00:00
},
replaceGroups(state, groups) {
state.groups = groups;
2024-06-22 23:04:32 +00:00
state.fetchedData = {...state.fetchedData, groups: Date.now()};
2024-01-07 20:38:25 +00:00
},
2023-12-28 21:49:55 +00:00
openAddBoxModal(state) {
state.showAddBoxModal = true;
},
closeAddBoxModal(state) {
state.showAddBoxModal = false;
},
2023-11-27 00:14:52 +00:00
createToast(state, {title, message, color}) {
2023-11-27 00:38:43 +00:00
var toast = {title, message, color, key: state.keyIncrement}
state.toasts.push(toast);
2023-11-27 00:14:52 +00:00
state.keyIncrement += 1;
2023-11-27 00:38:43 +00:00
return toast;
2023-11-27 00:14:52 +00:00
},
removeToast(state, key) {
state.toasts = state.toasts.filter(toast => toast.key !== key);
2024-01-07 20:38:25 +00:00
},
setRemember(state, remember) {
state.remember = remember;
},
setUser(state, user) {
2024-06-18 18:10:10 +00:00
state.user.username = user;
2024-01-07 20:38:25 +00:00
},
setPassword(state, password) {
2024-06-18 18:10:10 +00:00
state.user.password = password;
2024-01-07 20:38:25 +00:00
},
setPermissions(state, permissions) {
2024-06-18 18:10:10 +00:00
state.user.permissions = permissions;
2024-01-07 20:38:25 +00:00
},
setToken(state, {token, expiry}) {
2024-06-18 18:10:10 +00:00
const user = {...state.user};
user.token = token;
user.expiry = expiry;
state.user = user;
2024-01-07 20:38:25 +00:00
},
2024-06-18 18:10:10 +00:00
setUserInfo(state, user) {
state.user = user;
},
2024-01-07 20:38:25 +00:00
logout(state) {
2024-06-18 18:10:10 +00:00
const user = {...state.user};
user.user = null;
user.password = null;
user.token = null;
user.expiry = null;
user.permissions = null;
state.user = user;
},
setThumbnail(state, {url, data}) {
state.thumbnailCache[url] = data;
},
2023-11-27 00:14:52 +00:00
},
actions: {
2024-06-22 18:56:51 +00:00
async login({commit}, {username, password, remember}) {
2024-01-07 20:38:25 +00:00
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())
2024-01-07 22:03:22 +00:00
if (data && data.token) {
2024-06-22 18:56:51 +00:00
const {data: {permissions}} = await http.get('/2/self/', data.token);
commit('setUserInfo', {...data, permissions, username, password});
2024-01-07 20:38:25 +00:00
return true;
} else {
return false;
}
} catch (e) {
console.error(e);
return false;
}
},
2024-06-18 18:10:10 +00:00
async reloadToken({commit, state, getters}) {
2024-01-07 20:38:25 +00:00
try {
2024-06-18 18:10:10 +00:00
if (state.user.username && state.user.password) {
2024-01-07 20:38:25 +00:00
const data = await fetch('/api/2/login/', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
2024-06-18 18:10:10 +00:00
body: JSON.stringify({username: state.user.username, password: state.user.password}),
2024-01-07 20:38:25 +00:00
credentials: 'omit'
2024-01-07 22:03:22 +00:00
}).then(r => r.json()).catch(e => console.error(e))
if (data && data.token) {
2024-01-07 20:38:25 +00:00
commit('setToken', data);
return true;
}
}
} catch (e) {
console.error(e);
}
//credentials failed, logout
store.commit('logout');
},
//async verifyToken({commit, state}) {
2024-06-18 18:10:10 +00:00
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);
2024-01-07 20:38:25 +00:00
},
2024-06-22 23:04:32 +00:00
async afterSharedInit({dispatch, state}) {
const handlers = state.afterInitHandlers;
state.afterInitHandlers = [];
await Promise.all(handlers.map(h => h()).flat());
},
scheduleAfterInit({dispatch, state}, handler) {
if (state.shared_loaded) {
Promise.all(handler()).then(() => {
});
} else {
state.afterInitHandlers.push(handler);
}
},
2024-01-07 20:38:25 +00:00
async fetchImage({state}, url) {
2024-06-18 18:10:10 +00:00
return await fetch(url, {headers: {'Authorization': `Token ${state.user.token}`}});
2024-01-07 20:38:25 +00:00
},
2024-06-18 18:10:10 +00:00
async loadUserInfo({commit, state}) {
const {data, success} = await http.get('/2/self/', state.user.token);
2024-01-07 20:38:25 +00:00
commit('setPermissions', data.permissions);
},
2024-06-18 18:10:10 +00:00
async loadEvents({commit, state}) {
2024-06-22 23:04:32 +00:00
if (!state.user.token) return;
if (state.fetchedData.events > Date.now() - 1000 * 60 * 60 * 24) return;
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get('/2/events/', state.user.token);
if (data && success)
commit('replaceEvents', data);
2023-11-27 00:14:52 +00:00
},
2024-06-18 18:10:10 +00:00
async fetchTicketStates({commit, state}) {
2024-06-22 23:04:32 +00:00
if (!state.user.token) return;
if (state.fetchedData.states > Date.now() - 1000 * 60 * 60 * 24) return;
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get('/2/tickets/states/', state.user.token);
if (data && success)
commit('replaceTicketStates', data);
},
2024-01-07 20:38:25 +00:00
changeEvent({dispatch, getters, commit}, eventName) {
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
2023-11-27 00:14:52 +00:00
dispatch('loadEventItems');
},
changeView({getters}, link) {
2024-01-07 20:38:25 +00:00
router.push({path: `/${getters.getEventSlug}/${link.path}/`});
2023-11-27 00:14:52 +00:00
},
showBoxContent({getters}, box) {
2024-01-07 20:38:25 +00:00
router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
2023-11-27 00:14:52 +00:00
},
2024-01-07 20:38:25 +00:00
async loadEventItems({commit, getters, state}) {
2024-06-22 23:04:32 +00:00
if (!state.user.token) return;
if (state.fetchedData.items > Date.now() - 1000 * 60 * 60 * 24) return;
2024-01-07 20:38:25 +00:00
try {
commit('replaceLoadedItems', []);
const slug = getters.getEventSlug;
if (slug in state.itemCache) {
commit('replaceLoadedItems', state.itemCache[slug]);
}
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get(`/2/${slug}/items/`, state.user.token);
if (data && success) {
commit('replaceLoadedItems', data);
commit('setItemCache', {slug, items: data});
}
2024-01-07 20:38:25 +00:00
} catch (e) {
console.error("Error loading items");
}
2023-11-27 00:14:52 +00:00
},
2024-06-18 18:10:10 +00:00
async searchEventItems({commit, getters, state}, query) {
2023-11-27 00:14:52 +00:00
const foo = utf8.encode(query);
const bar = base64.encode(foo);
2019-12-28 03:05:58 +00:00
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get(`/2/${getters.getEventSlug}/items/${bar}/`, state.user.token);
if (data && success)
commit('replaceLoadedItems', data);
2023-11-27 00:14:52 +00:00
},
2024-06-18 18:10:10 +00:00
async loadBoxes({commit, state}) {
2024-06-22 23:04:32 +00:00
if (!state.user.token) return;
if (state.fetchedData.boxes > Date.now() - 1000 * 60 * 60 * 24) return;
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get('/2/boxes/', state.user.token);
if (data && success)
commit('replaceBoxes', data);
2023-11-27 00:14:52 +00:00
},
2024-06-18 18:10:10 +00:00
async createBox({commit, dispatch, state}, box) {
const {data, success} = await http.post('/2/boxes/', box, state.user.token);
2023-12-28 21:49:55 +00:00
commit('replaceBoxes', data);
dispatch('loadBoxes').then(() => {
commit('closeAddBoxModal');
});
},
2024-06-18 18:10:10 +00:00
async deleteBox({commit, dispatch, state}, box_id) {
await http.delete(`/2/boxes/${box_id}/`, state.user.token);
dispatch('loadBoxes');
},
2024-06-18 18:10:10 +00:00
async updateItem({commit, getters, state}, item) {
const {
data,
success
} = await http.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
2023-11-27 00:14:52 +00:00
commit('updateItem', data);
},
2024-06-18 18:10:10 +00:00
async markItemReturned({commit, getters, state}, item) {
await http.patch(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true}, state.user.token);
2023-11-27 00:14:52 +00:00
commit('removeItem', item);
},
2024-06-18 18:10:10 +00:00
async deleteItem({commit, getters, state}, item) {
await http.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item, state.user.token);
2023-11-27 00:14:52 +00:00
commit('removeItem', item);
},
2024-06-18 18:10:10 +00:00
async postItem({commit, getters, state}, item) {
2023-11-27 00:14:52 +00:00
commit('updateLastUsed', {box: item.box, cid: item.cid});
2024-06-18 18:10:10 +00:00
const {data, success} = await http.post(`/2/${getters.getEventSlug}/item/`, item, state.user.token);
2023-11-27 00:14:52 +00:00
commit('appendItem', data);
2024-01-07 20:38:25 +00:00
},
2024-06-18 18:10:10 +00:00
async loadTickets({commit, state}) {
2024-06-22 23:04:32 +00:00
if (!state.user.token) return;
if (state.fetchedData.tickets > Date.now() - 1000 * 60 * 60 * 24) return;
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get('/2/tickets/', state.user.token);
if (data && success)
commit('replaceTickets', data);
2024-01-07 20:38:25 +00:00
},
2024-06-18 18:10:10 +00:00
async sendMail({commit, dispatch, state}, {id, message}) {
const {data, success} = await http.post(`/2/tickets/${id}/reply/`, {message}, state.user.token);
if (data && success) {
2024-06-22 23:04:32 +00:00
state.fetchedData.tickets = 0;
2024-06-18 18:10:10 +00:00
await dispatch('loadTickets');
}
2024-01-07 20:38:25 +00:00
},
2024-06-18 18:10:10 +00:00
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'
2024-06-18 18:10:10 +00:00
}, state.user.token);
2024-01-07 20:38:25 +00:00
await dispatch('loadTickets');
},
2024-06-18 18:10:10 +00:00
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) {
2024-06-22 23:04:32 +00:00
state.fetchedData.tickets = 0;
2024-06-18 18:10:10 +00:00
await dispatch('loadTickets');
}
},
2024-06-18 18:10:10 +00:00
async loadUsers({commit, state}) {
2024-06-22 23:04:32 +00:00
if (!state.user.token) return;
if (state.fetchedData.users > Date.now() - 1000 * 60 * 60 * 24) return;
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get('/2/users/', state.user.token);
if (data && success)
commit('replaceUsers', data);
2024-01-07 20:38:25 +00:00
},
2024-06-18 18:10:10 +00:00
async loadGroups({commit, state}) {
2024-06-22 23:04:32 +00:00
if (!state.user.token) return;
if (state.fetchedData.groups > Date.now() - 1000 * 60 * 60 * 24) return;
2024-06-18 18:10:10 +00:00
const {data, success} = await http.get('/2/groups/', state.user.token);
if (data && success)
commit('replaceGroups', data);
2024-01-07 20:38:25 +00:00
},
2024-06-18 18:10:10 +00:00
async updateTicket({commit, state}, ticket) {
const {data, success} = await http.put(`/2/tickets/${ticket.id}/`, ticket, state.user.token);
commit('updateTicket', data);
},
2024-06-18 18:10:10 +00:00
async updateTicketPartial({commit, state}, {id, ...ticket}) {
const {data, success} = await http.patch(`/2/tickets/${id}/`, ticket, state.user.token);
commit('updateTicket', data);
}
2024-01-14 15:01:32 +00:00
},
2024-06-22 18:56:51 +00:00
plugins: [
persistentStatePlugin({ // TODO change remember to some kind of enable field
prefix: "lf_",
debug: false,
isLoadedKey: "persistent_loaded",
state: [
"remember",
"user",
"events",
2024-06-22 23:04:32 +00:00
"lastUsed",
2024-06-22 18:56:51 +00:00
]
}),
2024-06-22 23:04:32 +00:00
sharedStatePlugin({
debug: true,
isLoadedKey: "shared_loaded",
clearingMutation: "logout",
afterInit: "afterSharedInit",
state: [
"test",
"state_options",
"fetchedData",
"tickets",
"users",
"groups",
"loadedBoxes",
"loadedItems",
],
watch: [
"test",
"state_options",
"fetchedData",
"tickets",
"users",
"groups",
"loadedBoxes",
"loadedItems",
],
mutations: [
//"replaceTickets",
],
}),
2024-06-22 18:56:51 +00:00
],
2019-11-13 21:40:14 +00:00
});
2024-06-18 18:10:10 +00:00
store.watch((state) => state.user, (user) => {
2024-01-07 20:38:25 +00:00
if (store.getters.isLoggedIn) {
2024-06-18 18:10:10 +00:00
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},
});
}
2024-01-07 20:38:25 +00:00
}
2019-12-27 01:06:04 +00:00
});
2024-06-18 18:10:10 +00:00
export default store;