import axios from 'axios' import { APIError, callOrReturn, handleApiError } from '../api-helper' import { getISODateString, has } from '@/utilities' import { useAuthStore } from '@/stores/auth' import { createSteeringURL } from '@/api' const cloneMinimalShowObject = function (show) { /* returns a new minimal object from the current show object with all properties needed for a PUT request to the /show/ endpoint */ const s = {} s.name = show.name s.slug = show.slug s.short_description = show.short_description s.fundingcategory = show.fundingcategory s.type = show.type // we do not want the arrays do be passed as references, because the // current show object should not get modified when the update object // gets modified, therefore we use slice to clone the arrays s.category = show.category.slice() s.hosts = show.hosts.slice() s.owners = show.owners.slice() s.language = show.language.slice() s.topic = show.topic.slice() s.musicfocus = show.musicfocus.slice() return s } const state = { shows: [], schedule: null, schedules: [], scheduleTimeslots: [], timeslots: [], notes: [], types: [], fundingcategories: [], categories: [], topics: [], musicfocus: [], languages: [], hosts: [], loaded: { shows: false, timeslots: false, notes: false, schedule: false, scheduleTimeslots: false, schedules: false, types: false, fundingcategories: false, categories: false, topics: false, musicfocus: false, languages: false, hosts: false, }, selected: { index: 0, // index of the currently selected show in our shows array id: 0, // actual id of the currently selected show }, } const getters = { shows: (state) => state.shows, selectedShow: (state) => state.shows[state.selected.index], schedule: (state) => state.schedule, schedules: (state) => state.schedules, scheduleTimeslots: (state) => state.scheduleTimeslots, timeslots: (state) => state.timeslots, notes: (state) => state.notes, types: (state) => state.types, fundingcategories: (state) => state.fundingcategories, categories: (state) => state.categories, topics: (state) => state.topics, musicfocus: (state) => state.musicfocus, languages: (state) => state.languages, hosts: (state) => state.hosts, getShowByDataParam: (state) => (data) => { let show if (data.id !== undefined) { show = state.shows.find((s) => s.id === data.id) if (show === undefined) { console.error('getShowByDataParam: ID not found in store!') } } else if (data.index !== undefined) { show = state.shows[data.index] } else { console.error('getShowByDataParam: no ID or index was provided') } return show }, getTimeslotById: (state) => (id) => { return state.timeslots.find((s) => s.id === id) }, } const mutations = { loading(state, item) { state.loaded[item] = false }, finishLoading(state, item) { state.loaded[item] = true }, setShows(state, shows) { state.shows = shows }, addShow(state, show) { state.shows.push(show) state.shows.sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase()) }, setSchedule(state, schedule) { state.schedule = schedule }, setSchedules(state, schedules) { state.schedules = schedules }, setScheduleTimeslots(state, slots) { state.scheduleTimeslots = slots }, setTimeslots(state, slots) { state.timeslots = slots }, setTimeslot(state, slot) { const index = state.timeslots.findIndex((s) => s.id === slot.id) state.timeslots.splice(index, 1, slot) }, setNotes(state, notes) { state.notes = notes }, addNote(state, note) { state.notes.push(note) }, setNote(state, note) { const index = state.notes.findIndex((n) => n.id === note.id) state.notes.splice(index, 1, note) }, setName(state, data) { const index = state.shows.findIndex((s) => s.id === data.id) state.shows[index].name = data.text }, setShortDescription(state, data) { const index = state.shows.findIndex((s) => s.id === data.id) state.shows[index].short_description = data.text }, setDescription(state, data) { const index = state.shows.findIndex((s) => s.id === data.id) state.shows[index].description = data.text }, setActive(state, data) { const index = state.shows.findIndex((s) => s.id === data.id) state.shows[index].is_active = data.active }, setProperty(state, data) { const index = state.shows.findIndex((s) => s.id === data.id) state.shows[index][data.property] = data.value }, setMetaArray(state, data) { state[data.property] = data.value }, switchShow(state, index) { if (state.loaded.shows) { state.selected.index = index state.selected.id = state.shows[index].id } }, switchShowById(state, id) { if (state.loaded.shows) { state.selected.index = state.shows.findIndex((s) => s.id === id) state.selected.id = id } }, } const actions = { fetchShows(ctx, data) { const authStore = useAuthStore() const user = authStore.steeringUser ctx.commit('loading', 'shows') let uri = createSteeringURL('shows') // normal users should only see their own shows, only superusers see all shows if (!authStore.isSuperuser) { uri += '?owner=' + user.id } axios .get(uri) .then((response) => { ctx.commit('setShows', response.data) ctx.commit('finishLoading', 'shows') if (data && typeof data.callback === 'function') { data.callback() } }) .catch((error) => { handleApiError(this, error, 'could not load shows') console.error(error) if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, fetchSchedule(ctx, data) { ctx.commit('loading', 'schedule') const uri = createSteeringURL('shows', data.show, 'schedules', data.schedule) axios .get(uri) .then((response) => { ctx.commit('setSchedule', response.data) ctx.commit('finishLoading', 'schedule') if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { handleApiError(this, error, 'could not load schedule') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, fetchSchedules(ctx, data) { ctx.commit('loading', 'schedule') const uri = createSteeringURL('shows', data.show, 'schedules') axios .get(uri) .then((response) => { ctx.commit('setSchedules', response.data) ctx.commit('finishLoading', 'schedule') if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { handleApiError(this, error, 'could not load schedule') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, fetchTimeslots(ctx, data) { if (data.schedule !== undefined) { ctx.commit('loading', 'scheduleTimeslots') } else { ctx.commit('loading', 'timeslots') } const query = new URLSearchParams() if (data.surrounding) { const date = data.surrounding instanceof Date ? data.surrounding : new Date() query.set('surrounding', getISODateString(date)) } else { if (has(data, 'start')) query.set('start', data.start) if (has(data, 'end')) query.set('end', data.end) if (has(data, 'limit')) query.set('limit', data.limit) if (has(data, 'offset')) query.set('offset', data.offset) } const uri = data.id && data.schedule ? createSteeringURL('shows', data.id, 'schedules', data.schedule, 'timeslots', query) : data.id ? createSteeringURL('shows', data.id, 'timeslots', query) : createSteeringURL('timeslots', query) axios .get(uri) .then((response) => { if (data.schedule !== undefined) { ctx.commit('setScheduleTimeslots', response.data) ctx.commit('finishLoading', 'scheduleTimeslots') } else { if (data.limit) { ctx.commit('setTimeslots', response.data.results) } else { ctx.commit('setTimeslots', response.data) } ctx.commit('finishLoading', 'timeslots') } if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { handleApiError(this, error, 'could not load timeslots') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, fetchNotes(ctx, data) { ctx.commit('loading', 'notes') // we only have to make an API call if there are actually any notes // otherwise the notes are just and empty array if (data.notes.length === 0) { ctx.commit('setNotes', []) ctx.commit('finishLoading', 'notes') return } const uri = createSteeringURL( 'shows', data.id, 'notes', new URLSearchParams({ ids: data.notes.join(',') }), ) this.$log.debug('fetchNotes: uri:', uri) axios .get(uri) .then((response) => { ctx.commit('setNotes', response.data) ctx.commit('finishLoading', 'notes') if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { handleApiError(this, error, 'could not load notes') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, fetchMetaArray(ctx, data) { ctx.commit('loading', data.property) let uri = createSteeringURL(data.property) if (data.onlyActive === true) { uri += '?active=true' } axios .get(uri) .then((response) => { ctx.commit('setMetaArray', { property: data.property, value: response.data }) ctx.commit('finishLoading', data.property) if (data && typeof data.callback === 'function') { data.callback() } }) .catch((error) => { handleApiError(this, error, 'could not load ' + data.property) if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, submitSchedule(ctx, data) { ctx.commit('loading', 'schedules') const uri = createSteeringURL('shows', data.showId, 'schedules') return axios .post(uri, data.schedule) .then((response) => { ctx.commit('finishLoading', 'schedules') return callOrReturn(response, data?.callback) }) .catch((error) => { APIError.handle(error, 'Unable to submit schedule', data, this) }) }, submitShow(ctx, data) { const uri = createSteeringURL('shows') axios .post(uri, data.show) .then((response) => { ctx.commit('addShow', response.data) if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { handleApiError(this, error, 'could not add new show') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, submitNote(ctx, data) { const NON_UPDATEABLE_PROPERTIES = ['thumbnails', 'width', 'height'] let uri = createSteeringURL( 'shows', data.id, 'schedules', data.scheduleID, 'timeslots', data.timeslotID, 'note', ) if (data.update) { uri += data.note.id + '/' } const method = data.update ? 'put' : 'post' const formData = new FormData() // We serialize the data to a FormData object, so we can send the image // as binary blob to the Steering API. for (const [key, value] of Object.entries(data.note)) { // When we're updating we don't want to include some properties that cause errors. if (data.update && NON_UPDATEABLE_PROPERTIES.includes(key)) { continue } formData.append(key, value) } axios .request({ url: uri, method: method, data: formData, responseType: 'json', // we need this explicitly here, as it does not seem to work automagically as in GET and PUT requests }) .then((response) => { if (data.update) { ctx.commit('setNote', response.data) } else { ctx.commit('addNote', response.data) } if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { const msg = data.update ? 'could not update note' : 'could not add new note' handleApiError(this, error, msg) if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, updateShow(ctx, data) { const uri = createSteeringURL('shows', data.id) axios .put(uri, data.show) .then((response) => { if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { if (data && typeof data.callbackCancel === 'function') { data.callbackCancel(error.response) } }) }, updateProperty(ctx, data) { const show = cloneMinimalShowObject(ctx.getters.getShowByDataParam(data)) show[data.property] = data.value ctx.dispatch('updateShow', { id: data.id, show: show, callback: () => { ctx.commit('setProperty', { id: data.id, property: data.property, value: data.value, }) if (typeof data.callback === 'function') { data.callback() } }, callbackCancel: (error) => { if (typeof data.callbackCancel === 'function') { data.callbackCancel(error) } }, }) }, updateImage(ctx, data) { const show = ctx.getters.getShowByDataParam(data) const uri = createSteeringURL('shows', data.id) const formData = new FormData() // these propoerties have to be sent always (and they must not be null) formData.append('name', show.name) formData.append('slug', show.slug) formData.append('short_description', show.short_description) formData.append('type', show.type) formData.append('fundingcategory', show.fundingcategory) formData.append('is_active', show.is_active) // now we append the new logo/image file (signified by data.type) formData.append(data.type, data.file, data.file.name) axios .put(uri, formData) .then((response) => { ctx.commit('setProperty', { id: data.id, property: data.type, // when updating images of a show the steering API does not return // the full URI but only the path on the server // TODO: create an issue in steering, to create consistency value: import.meta.env.VUE_APP_BASEURI_STEERING + response.data[data.type], }) if (data && typeof data.callback === 'function') { data.callback(response) } }) .catch((error) => { handleApiError(this, error, 'could not update show ' + data.type) if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, updateTimeslot(ctx, data) { const uri = createSteeringURL( 'shows', data.show, 'schedules', data.schedule, 'timeslots', data.timeslot.id, ) axios .put(uri, data.timeslot) .then(() => { ctx.commit('setTimeslot', data.timeslot) if (data && typeof data.callback === 'function') { data.callback() } }) .catch((error) => { handleApiError(this, error, 'could not update timeslot') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, deleteSchedule(ctx, data) { const uri = createSteeringURL('shows', data.show, 'schedules', data.schedule) axios .delete(uri) .then(() => { if (data && typeof data.callback === 'function') { data.callback() } }) .catch((error) => { handleApiError(this, error, 'could not delete full schedule') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, deleteTimeslot(ctx, data) { const uri = createSteeringURL( 'shows', data.show, 'schedules', data.schedule, 'timeslots', data.timeslot, ) axios .delete(uri) .then(() => { if (data && typeof data.callback === 'function') { data.callback() } }) .catch((error) => { handleApiError(this, error, 'could not delete single timeslot') if (data && typeof data.callbackCancel === 'function') { data.callbackCancel() } }) }, } export default { namespaced: true, state, getters, actions, mutations, }