@@ -10,41 +10,43 @@ | |||
"dependencies": { | |||
"@fortawesome/fontawesome-pro": "^5.15.3", | |||
"@mdi/font": "^5.9.55", | |||
"@vue/composition-api": "1.0.0-rc.8", | |||
"@vue/composition-api": "1.0.0-rc.12", | |||
"apollo-cache-inmemory": "^1.6.6", | |||
"apollo-client": "^2.6.10", | |||
"core-js": "^3.6.5", | |||
"core-js": "^3.15.1", | |||
"jot": "git+https://github.com/joshdata/jot.git", | |||
"js-cookie": "^2.2.1", | |||
"lodash": "^4.17.21", | |||
"subscriptions-transport-ws": "^0.9.18", | |||
"moment": "^2.29.1", | |||
"subscriptions-transport-ws": "^0.9.19", | |||
"uuid": "^3.4.0", | |||
"vue": "^2.6.11", | |||
"vue": "^2.6.14", | |||
"vue-apollo": "^3.0.7", | |||
"vue-cookies": "^1.7.4", | |||
"vue-router": "^3.2.0", | |||
"vuetify": "^2.4.0", | |||
"vue-router": "^3.5.2", | |||
"vuedraggable": "^2.24.3", | |||
"vuetify": "^2.5.5", | |||
"vuetify-image-input": "^19.2.2", | |||
"vuex": "^3.4.0" | |||
"vuex": "^3.6.2" | |||
}, | |||
"devDependencies": { | |||
"@vue/cli-plugin-babel": "~4.5.0", | |||
"@vue/cli-plugin-eslint": "~4.5.0", | |||
"@vue/cli-plugin-router": "~4.5.0", | |||
"@vue/cli-plugin-vuex": "~4.5.0", | |||
"@vue/cli-service": "~4.5.0", | |||
"@vue/cli-plugin-babel": "~4.5.13", | |||
"@vue/cli-plugin-eslint": "~4.5.13", | |||
"@vue/cli-plugin-router": "~4.5.13", | |||
"@vue/cli-plugin-vuex": "~4.5.13", | |||
"@vue/cli-service": "~4.5.13", | |||
"@vue/eslint-config-standard": "^5.1.2", | |||
"babel-eslint": "^10.1.0", | |||
"eslint": "^6.7.2", | |||
"eslint-plugin-import": "^2.20.2", | |||
"eslint-plugin-import": "^2.23.4", | |||
"eslint-plugin-node": "^11.1.0", | |||
"eslint-plugin-promise": "^4.2.1", | |||
"eslint-plugin-standard": "^4.0.0", | |||
"eslint-plugin-standard": "^4.1.0", | |||
"eslint-plugin-vue": "^6.2.2", | |||
"sass": "^1.32.0", | |||
"sass": "^1.35.1", | |||
"sass-loader": "^10.0.0", | |||
"vue-cli-plugin-vuetify": "~2.4.0", | |||
"vue-template-compiler": "^2.6.11", | |||
"vuetify-loader": "^1.7.0" | |||
"vue-cli-plugin-vuetify": "~2.4.1", | |||
"vue-template-compiler": "^2.6.14", | |||
"vuetify-loader": "^1.7.2" | |||
} | |||
} |
@@ -0,0 +1,88 @@ | |||
import Vue from 'vue' | |||
import moment from 'moment' | |||
moment.locale('de') | |||
Vue.filter('ts2date', value => { | |||
if (value) { | |||
return (new Date(parseInt(value))).toISOString() | |||
} | |||
}) | |||
Vue.filter('date', value => { | |||
if (value) { | |||
return moment(String(value)).format('DD.MM.YYYY') | |||
} | |||
}) | |||
Vue.filter('dateformat', (value, format) => { | |||
if (!format) format = 'DD.MM.YYYY HH:mm' | |||
if (value) { | |||
return moment(String(value)).format(format) | |||
} | |||
}) | |||
Vue.filter('ms2time', (value) => { | |||
if (!value) value = 0 | |||
value = value / 1000 | |||
const min = Math.floor(value / 60) | |||
const s = value - 60 * min | |||
let ret = '' | |||
if (min) { | |||
ret += min + 'm ' | |||
if (s < 10) ret += '0' | |||
} | |||
ret += s.toFixed(2) + 's' | |||
return ret | |||
}) | |||
Vue.filter('isodate', value => { | |||
if (value) { | |||
return moment(String(value)).format() | |||
} | |||
return null | |||
}) | |||
Vue.filter('float2', function (value) { | |||
const float = parseFloat(value) | |||
if (isNaN(float) || float === 0) return '' | |||
return float.toFixed(2) | |||
}) | |||
Vue.filter('float2force', function (value) { | |||
const float = parseFloat(value) | |||
if (isNaN(float)) return '' | |||
return float.toFixed(2) | |||
}) | |||
Vue.filter('float2_0', function (value) { | |||
const float = parseFloat(value) | |||
if (isNaN(float) || float === 0) return '0.00' | |||
return float.toFixed(2) | |||
}) | |||
Vue.filter('float1', function (value) { | |||
const float = parseFloat(value) | |||
if (isNaN(float) || float === 0) return '' | |||
return float.toFixed(1) | |||
}) | |||
Vue.filter('int', function (value) { | |||
const int = parseInt(value) | |||
if (isNaN(int) || int === 0) return '' | |||
return int.toString() | |||
}) | |||
Vue.filter('int_0', function (value) { | |||
const int = parseInt(value) | |||
if (isNaN(int) || int === 0) return '0' | |||
return int.toString() | |||
}) | |||
Vue.filter('nl2br', function (value) { | |||
return value?.replace(/\n/g, '<br/>') | |||
}) |
@@ -12,6 +12,10 @@ import './plugins/cookies' | |||
import './plugins/compositionAPI' | |||
import './plugins/base' | |||
import './filter' | |||
Vue.component('Draggable', () => import('vuedraggable')) | |||
Vue.component('E403', () => import('./components/e403.vue')) | |||
Vue.component('DateSelector', () => import('./components/DateSelector.vue')) | |||
Vue.component('DateTimeSelector', () => import('./components/DateTimeSelector.vue')) |
@@ -5,5 +5,13 @@ export const useAuth = (context) => { | |||
return context.root.$store.getters.isMaster | |||
}) | |||
return { isMaster } | |||
const isAdmin = (id) => { | |||
return context.root.$store.getters.isAdmin(id) | |||
} | |||
const isOrga = (id) => { | |||
return context.root.$store.getters.isOrga(id) | |||
} | |||
return { isMaster, isAdmin, isOrga } | |||
} |
@@ -28,12 +28,25 @@ const routes = [ | |||
path: 'management/organizer', | |||
name: 'Veranstalter bearbeiten', | |||
component: () => import('../views/components/management/organizer') | |||
}, | |||
{ | |||
path: 'orga/:id', | |||
name: 'Übersicht Events', | |||
component: () => import('../views/components/orga/event'), | |||
props: true | |||
}, | |||
{ | |||
path: 'event/:id', | |||
name: 'Event', | |||
component: () => import('../views/components/event/event'), | |||
props: true | |||
} | |||
] | |||
} | |||
] | |||
const router = new VueRouter({ | |||
mode: 'history', | |||
routes | |||
}) | |||
@@ -35,6 +35,8 @@ export default new Vuex.Store({ | |||
getters: { | |||
profile: (state) => state.profile || {}, | |||
isMaster: (state) => !!state.profile?.master, | |||
isLogin: (state) => !!state.profile?.token | |||
isLogin: (state) => !!state.profile?.token, | |||
isAdmin: (state) => (id) => !!state.profile?.adminOf?.find(a => a._id === id), | |||
isOrga: (state) => (id) => !!state.profile?.adminOf?.find(a => a._id === id) || !!state.profile?.organizerOf?.find(a => a._id === id) | |||
} | |||
}) |
@@ -32,6 +32,42 @@ | |||
] | |||
}" | |||
/> | |||
<base-item | |||
v-for="a in adminOf" | |||
:key="'admin-'+a._id" | |||
:item="{ | |||
// group: '/admin', | |||
icon: 'fa-user-crown', | |||
title: 'Verwaltung', | |||
subtitle: a.name, | |||
to: '/admin/' + a._id | |||
/* children: [ | |||
{ | |||
title: 'Nutzer verwalten', | |||
icon: 'fa-users', | |||
to: 'person' | |||
} | |||
] */ | |||
}" | |||
/> | |||
<base-item | |||
v-for="o in organizerOf" | |||
:key="'orga-'+o._id" | |||
:item="{ | |||
// group: '/orga', | |||
icon: 'fa-calendar-edit', | |||
title: 'Organisation', | |||
subtitle: o.name, | |||
to: '/orga/' + o._id | |||
/* children: [ | |||
{ | |||
title: 'Events verwalten', | |||
icon: 'fa-calendar-day', | |||
to: 'event/' + o._id | |||
} | |||
] */ | |||
}" | |||
/> | |||
</div> | |||
</template> | |||
@@ -42,7 +78,13 @@ export default { | |||
name: 'Menu', | |||
computed: { | |||
...mapGetters(['profile', 'isMaster']) | |||
...mapGetters(['profile', 'isMaster']), | |||
adminOf () { | |||
return this.profile?.adminOf | |||
}, | |||
organizerOf () { | |||
return [...(this.profile?.adminOf || []), ...(this.profile?.organizerOf || [])] | |||
} | |||
} | |||
} | |||
</script> |
@@ -0,0 +1,559 @@ | |||
<template> | |||
<v-container | |||
fluid | |||
tag="section" | |||
> | |||
<v-tabs | |||
v-model="tab" | |||
background-color="primary lighten-1" | |||
fixed-tabs | |||
> | |||
<v-tab | |||
href="#apparatus" | |||
> | |||
Geräte | |||
</v-tab> | |||
<v-tab | |||
href="#timeslot" | |||
> | |||
Zeitslots | |||
</v-tab> | |||
<v-tab | |||
href="#result" | |||
> | |||
Ergebnisse | |||
</v-tab> | |||
</v-tabs> | |||
<v-tabs-items | |||
v-model="tab" | |||
> | |||
<v-tab-item | |||
value="apparatus" | |||
> | |||
<v-card | |||
flat | |||
class="ma-0" | |||
> | |||
<v-card-title class="text-h3"> | |||
Geräte | |||
<v-spacer /> | |||
<v-menu> | |||
<template #activator="{on, attrs}"> | |||
<v-btn | |||
icon | |||
v-bind="attrs" | |||
v-on="on" | |||
> | |||
<v-icon> | |||
fa-plus | |||
</v-icon> | |||
</v-btn> | |||
</template> | |||
<v-list> | |||
<v-list-item | |||
v-for="a in geraete" | |||
:key="a._id" | |||
@click="addApparatus(a._id)" | |||
> | |||
<v-list-item-title>{{ a.name }}</v-list-item-title> | |||
</v-list-item> | |||
</v-list> | |||
</v-menu> | |||
</v-card-title> | |||
<v-card-text> | |||
<draggable | |||
:list="[]" | |||
handle=".handle" | |||
tag="div" | |||
class="row row-dense" | |||
v-bind="dragOptions" | |||
@end="orderApparatus" | |||
> | |||
<v-col | |||
v-for="(a) in (Event || {}).apparatus" | |||
:key="a.name" | |||
cols="6" | |||
lg="3" | |||
class="item" | |||
> | |||
<v-card> | |||
<v-card-title class="text-h4"> | |||
<v-icon class="handle mr-4">far fa-bars</v-icon> | |||
{{ a.apparatus.name }} | |||
</v-card-title> | |||
<v-card-text> | |||
<p><b>Elemente: </b> {{ a.elements }}</p> | |||
<p><b>Bonus: </b> {{ a.bonus | float2_0 }}</p> | |||
<p><b>Malus: </b> {{ a.malus | float2_0 }}</p> | |||
</v-card-text> | |||
<v-card-actions> | |||
<v-spacer /> | |||
<v-btn icon> | |||
<v-icon>far fa-pencil</v-icon> | |||
</v-btn> | |||
<v-btn | |||
icon | |||
@click="deleteApparatus(a._apparatus)" | |||
> | |||
<v-icon>far fa-trash</v-icon> | |||
</v-btn> | |||
</v-card-actions> | |||
</v-card> | |||
</v-col> | |||
</draggable> | |||
</v-card-text> | |||
</v-card> | |||
</v-tab-item> | |||
<v-tab-item | |||
value="timeslot" | |||
> | |||
<v-card | |||
flat | |||
> | |||
<v-btn | |||
v-if="ts.selected.length > 0" | |||
absolute | |||
top | |||
right | |||
fab | |||
small | |||
style="margin-right:60px;" | |||
@click="deleteTimeslots" | |||
> | |||
<v-icon> | |||
fa-minus | |||
</v-icon> | |||
</v-btn> | |||
<v-btn | |||
absolute | |||
top | |||
right | |||
fab | |||
small | |||
@click="ts.dialog.open = true" | |||
> | |||
<v-icon> | |||
fa-plus | |||
</v-icon> | |||
</v-btn> | |||
<v-data-table | |||
:headers="ts.headers" | |||
:items="timeslots" | |||
sort-by="time" | |||
:items-per-page="-1" | |||
mobile-breakpoint="0" | |||
> | |||
<template #header.select> | |||
<v-checkbox | |||
@change="(v) => !v ? ts.selected = [] : ts.selected = timeslots.map(t => t._id)" | |||
/> | |||
</template> | |||
<template #item.select="{item}"> | |||
<v-checkbox | |||
v-if="!item.team" | |||
v-model="ts.selected" | |||
:value="item._id" | |||
multiple | |||
/> | |||
</template> | |||
<template #item.time="{item}"> | |||
{{ `1900-01-01 ${item.time}` | dateformat('HH:mm') }} | |||
</template> | |||
<template #item.end="{item}"> | |||
{{ `1900-01-01 ${addMinutes(item.time, item.duration)}` | dateformat('HH:mm') }} | |||
</template> | |||
<template #item.team="{item}"> | |||
<v-btn | |||
v-if="item.team" | |||
text | |||
x-small | |||
style="float: left;" | |||
@click="deleteTeam(item._id)" | |||
> | |||
<v-icon small> | |||
far fa-trash-alt | |||
</v-icon> | |||
</v-btn> | |||
<base-edit-dialog | |||
:value="item.team ? item.team.name : ''" | |||
label="Team hinzufügen" | |||
@input="(v) => addTeam(item._id, v)" | |||
/> | |||
</template> | |||
</v-data-table> | |||
</v-card> | |||
<base-material-dialog | |||
v-model="ts.dialog.open" | |||
title="Zeitslots hinzufügen" | |||
color="rgb(255, 4, 29)" | |||
:actions="['save','cancel']" | |||
@save="addTimeslots" | |||
@close="ts.dialog.open = false" | |||
@esc="ts.dialog.open = false" | |||
> | |||
<v-row> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
md="3" | |||
> | |||
<v-menu | |||
ref="beginn" | |||
v-model="ts.dialog.beginnmenu" | |||
:close-on-content-click="false" | |||
:nudge-right="40" | |||
:return-value.sync="ts.dialog.beginn" | |||
transition="scale-transition" | |||
offset-y | |||
max-width="290px" | |||
min-width="290px" | |||
> | |||
<template v-slot:activator="{ on, attrs }"> | |||
<v-text-field | |||
v-model="ts.dialog.beginn" | |||
label="Erster Zeitslot" | |||
prepend-icon="far fa-clock" | |||
readonly | |||
v-bind="attrs" | |||
v-on="on" | |||
></v-text-field> | |||
</template> | |||
<v-time-picker | |||
v-if="ts.dialog.beginnmenu" | |||
v-model="ts.dialog.beginn" | |||
full-width | |||
format="24hr" | |||
@click:minute="$refs.beginn.save(`${ts.dialog.beginn}:00`)" | |||
></v-time-picker> | |||
</v-menu> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
md="3" | |||
> | |||
<v-menu | |||
ref="ende" | |||
v-model="ts.dialog.endemenue" | |||
:close-on-content-click="false" | |||
:nudge-right="40" | |||
:return-value.sync="ts.dialog.ende" | |||
transition="scale-transition" | |||
offset-y | |||
max-width="290px" | |||
min-width="290px" | |||
> | |||
<template v-slot:activator="{ on, attrs }"> | |||
<v-text-field | |||
v-model="ts.dialog.ende" | |||
label="Bis" | |||
prepend-icon="far fa-clock" | |||
readonly | |||
v-bind="attrs" | |||
v-on="on" | |||
></v-text-field> | |||
</template> | |||
<v-time-picker | |||
v-if="ts.dialog.endemenue" | |||
v-model="ts.dialog.ende" | |||
full-width | |||
format="24hr" | |||
@click:minute="$refs.ende.save(`${ts.dialog.ende}:00`)" | |||
></v-time-picker> | |||
</v-menu> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
md="3" | |||
> | |||
<v-text-field | |||
v-model="ts.dialog.duration" | |||
label="Dauer eines Zeitslots (in min)" | |||
type="number" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
md="3" | |||
> | |||
<v-text-field | |||
v-model="ts.dialog.pause" | |||
label="Pause nach einem Zeitslot (in min)" | |||
type="number" | |||
/> | |||
</v-col> | |||
</v-row> | |||
<h3>Es werden folgende {{ newtimeslots.length }} Zeitslots angelegt:</h3> | |||
<v-data-table | |||
:headers="ts.dialog.headers" | |||
:items="newtimeslots" | |||
:items-per-page="-1" | |||
> | |||
<template #item.time="{item}"> | |||
{{ `1900-01-01 ${item.time}` | dateformat('HH:mm') }} | |||
</template> | |||
<template #item.end="{item}"> | |||
{{ `1900-01-01 ${addMinutes(item.time, item.duration)}` | dateformat('HH:mm') }} | |||
</template> | |||
</v-data-table> | |||
</base-material-dialog> | |||
</v-tab-item> | |||
<v-tab-item | |||
value="result" | |||
> | |||
Ergebnisse | |||
</v-tab-item> | |||
</v-tabs-items> | |||
</v-container> | |||
</template> | |||
<script> | |||
import gql from 'graphql-tag' | |||
import moment from 'moment' | |||
const query = '_id name date _organizer apparatus { _apparatus apparatus { name logo } elements bonus malus } timeslots { _id time duration team { _id name } }' | |||
export default { | |||
name: 'Event', | |||
props: { | |||
id: { | |||
type: String, | |||
required: true | |||
} | |||
}, | |||
data: () => ({ | |||
Event: null, | |||
ApparatusFind: [], | |||
tab: 'timeslot', | |||
ts: { | |||
headers: [ | |||
{ | |||
text: '', | |||
value: 'select', | |||
sortable: false, | |||
width: 75 | |||
}, | |||
{ | |||
text: 'Beginn', | |||
value: 'time', | |||
sortable: false | |||
}, | |||
{ | |||
text: 'Ende', | |||
value: 'end', | |||
sortable: false | |||
}, | |||
{ | |||
text: 'Team', | |||
value: 'team', | |||
sortable: false | |||
}, | |||
{ | |||
text: 'Ergebnis', | |||
value: 'result', | |||
sortable: false | |||
} | |||
], | |||
selected: [], | |||
dialog: { | |||
open: false, | |||
beginn: '09:00:00', | |||
ende: '12:00:00', | |||
duration: 4, | |||
pause: 1, | |||
beginnmenue: false, | |||
endemenue: false, | |||
headers: [ | |||
{ | |||
text: 'Beginn', | |||
value: 'time', | |||
sortable: false | |||
}, | |||
{ | |||
text: 'Ende', | |||
value: 'end', | |||
sortable: false | |||
} | |||
] | |||
} | |||
} | |||
}), | |||
computed: { | |||
dragOptions () { | |||
return { | |||
animation: 200, | |||
disabled: false, | |||
ghostClass: 'ghost' | |||
} | |||
}, | |||
geraete () { | |||
return this.ApparatusFind?.slice()?.sort((a, b) => a.name < b.name ? -1 : 1)?.filter(g => !this.Event?.apparatus.find(a => a._apparatus === g._id)) || [] | |||
}, | |||
timeslots () { | |||
return this.Event?.timeslots || [] | |||
}, | |||
newtimeslots () { | |||
const neu = [] | |||
if (this.ts.dialog.duration <= 0) return neu | |||
for (let t = this.ts.dialog.beginn; t < this.ts.dialog.ende; t = moment(`1900-01-01 ${t}`).add(parseInt(this.ts.dialog.duration) + parseInt(this.ts.dialog.pause), 'minutes').format('HH:mm:ss')) { | |||
neu.push(t) | |||
} | |||
return neu.filter(n => !this.timeslots.find(o => o.time === n && o.duration === parseInt(this.ts.dialog.duration))).map(t => ({ time: t, duration: this.ts.dialog.duration })) | |||
} | |||
}, | |||
methods: { | |||
addApparatus (id) { | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($id: UUID!, $apparatus: UUID!) { | |||
EventAddApparatus(id: $id, apparatus: $apparatus) { ${query} } | |||
}`, | |||
variables: { | |||
id: this.id, | |||
apparatus: id | |||
} | |||
}) | |||
}, | |||
deleteApparatus (id) { | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($id: UUID!, $apparatus: UUID!) { | |||
EventDeleteApparatus(id: $id, apparatus: $apparatus) { ${query} } | |||
}`, | |||
variables: { | |||
id: this.id, | |||
apparatus: id | |||
} | |||
}) | |||
}, | |||
orderApparatus (a) { | |||
const src = a.oldIndex | |||
const target = a.newIndex | |||
if (src === target) return | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($id: UUID!, $src: Int!, $target: Int!) { | |||
EventOrderApparatus(id: $id, src: $src, target: $target) { ${query} } | |||
}`, | |||
variables: { | |||
id: this.id, | |||
src, | |||
target | |||
} | |||
}).then((r) => { | |||
this.Event.apparatus = r.data.EventOrderApparatus.apparatus | |||
}) | |||
}, | |||
addTimeslots () { | |||
if (this.timeslots.length > 0) { | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($id: UUID!, $timeslots: [ITimeslot!]!) { | |||
EventAddTimeslots(id: $id, timeslots: $timeslots) { ${query} } | |||
}`, | |||
variables: { | |||
id: this.id, | |||
timeslots: this.newtimeslots | |||
} | |||
}).then((r) => { | |||
this.Event.timeslots = r.data.EventAddTimeslots.timeslots | |||
}) | |||
} | |||
this.ts.dialog.open = false | |||
}, | |||
deleteTimeslots () { | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($id: UUID!, $timeslots: [UUID!]!) { | |||
EventDeleteTimeslots(id: $id, timeslots: $timeslots) { ${query} } | |||
}`, | |||
variables: { | |||
id: this.id, | |||
timeslots: this.ts.selected | |||
} | |||
}).then((r) => { | |||
this.Event.timeslots = r.data.EventDeleteTimeslots.timeslots | |||
this.ts.selected = [] | |||
}) | |||
}, | |||
addMinutes (time, min) { | |||
return moment(`1900-01-01 ${time}`).add(min, 'minutes').format('HH:mm:ss') | |||
}, | |||
addTeam (timeslot, name) { | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($id: UUID!, $timeslot: UUID!, $name: String!) { | |||
EventAddTeam(id: $id, timeslot: $timeslot, name: $name) { ${query} } | |||
}`, | |||
variables: { | |||
id: this.id, | |||
timeslot, | |||
name | |||
} | |||
}).then((r) => { | |||
this.Event.timeslots = r.data.EventAddTeam.timeslots | |||
this.ts.selected = [] | |||
}) | |||
}, | |||
async deleteTeam (timeslot) { | |||
if (!this.timeslots.find(t => t._id === timeslot)?.team?.ergebnis || await this.$root.$children[0].$refs.confirm.open('Löschen', 'Bei diesem Team sind bereits Ergebnisse hinterlegt, wirklich löschen?')) { | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($id: UUID!, $timeslot: UUID!) { | |||
EventDeleteTeam(id: $id, timeslot: $timeslot) { ${query} } | |||
}`, | |||
variables: { | |||
id: this.id, | |||
timeslot | |||
} | |||
}).then((r) => { | |||
this.Event.timeslots = r.data.EventDeleteTeam.timeslots | |||
this.ts.selected = [] | |||
}) | |||
} | |||
} | |||
}, | |||
apollo: { | |||
Event: { | |||
query: gql`query($event: UUID!) { Event(id: $event) { ${query} } }`, | |||
variables () { | |||
return { | |||
event: this.id | |||
} | |||
}, | |||
subscribeToMore: { | |||
document: gql`subscription($id: UUID!) { EventUpdated(id: $id) { ${query} } }`, | |||
variables () { | |||
return { | |||
id: this.id | |||
} | |||
} | |||
} | |||
}, | |||
ApparatusFind: { | |||
query: gql`query { ApparatusFind { _id name }}` | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.apparatus { | |||
width: calc(25% - 20px); | |||
margin: 10px; | |||
float: left; | |||
} | |||
.draggable:after { | |||
display: block; | |||
content: ''; | |||
clear: both; | |||
} | |||
</style> |
@@ -0,0 +1,125 @@ | |||
<template> | |||
<base-material-dialog | |||
:value="value" | |||
icon="mdi-home" | |||
title="Person bearbeiten" | |||
:sub-title="id ? id : 'NEU'" | |||
color="rgb(255, 4, 29)" | |||
:actions="[doc && isMaster ? 'del' : '', isMaster ? 'save' : '', 'cancel']" | |||
@del="del" | |||
@save="save" | |||
@close="close" | |||
@esc="close" | |||
> | |||
<v-row v-if="!isMaster"> | |||
Kein Zugriff! {{ isMaster }} | |||
</v-row> | |||
<v-row v-else> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
> | |||
<v-text-field | |||
v-model="data.name" | |||
label="Name" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="4" | |||
sm="2" | |||
> | |||
<v-text-field | |||
v-model="data.plz" | |||
label="PLZ" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="8" | |||
sm="4" | |||
> | |||
<v-text-field | |||
v-model="data.ort" | |||
label="Ort" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="6" | |||
> | |||
<h3>Administratoren:</h3> | |||
{{ admins }} | |||
</v-col> | |||
<v-col | |||
cols="6" | |||
> | |||
<h3>Veranstalter:</h3> | |||
{{ organizers }} | |||
</v-col> | |||
</v-row> | |||
</base-material-dialog> | |||
</template> | |||
<script> | |||
import { useAuth } from '@/plugins/auth' | |||
import { useEditDialog } from '@/plugins/editdialog' | |||
export default { | |||
name: 'EditOrganizer', | |||
props: { | |||
value: { | |||
type: Boolean, | |||
required: true | |||
}, | |||
id: { | |||
type: String, | |||
default: null | |||
} | |||
}, | |||
setup (props, context) { | |||
return { | |||
...useAuth(context), | |||
...useEditDialog(props, context, 'Organizer(id: $id) { _id name plz ort admins { _id givenName familyName } organizers { _id givenName familyName } }', ['name', 'plz', 'ort']) | |||
} | |||
}, | |||
computed: { | |||
admins () { | |||
return this.data.admins?.map(e => e.familyName + ', ' + e.givenName)?.join('<br>') | |||
}, | |||
organizers () { | |||
return this.data.organizers?.map(e => e.familyName + ', ' + e.givenName)?.join('<br>') | |||
} | |||
}, | |||
methods: { | |||
update () { | |||
return { | |||
mutation: `mutation($id: UUID!, $name: String, $plz: Int, $ort: String) { | |||
OrganizerUpdate(id: $id, name: $name, plz: $plz, ort: $ort) { | |||
_id name plz ort | |||
} | |||
}`, | |||
variables: { | |||
plz: parseInt(this.data.plz) || null | |||
} | |||
} | |||
}, | |||
create () { | |||
return { | |||
mutation: `mutation($name: String!, $plz: Int, $ort: String) { | |||
OrganizerCreate(name: $name, plz: $plz, ort: $ort) { | |||
_id name plz ort | |||
} | |||
}`, | |||
variables: { | |||
plz: parseInt(this.data.plz) || null | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="sass"> | |||
$dialog-elevation: 0 | |||
</style> |
@@ -38,23 +38,23 @@ | |||
@click:row="open" | |||
/> | |||
</v-card> | |||
<!--edit-people | |||
<edit-organizer | |||
:id="dialog.id" | |||
v-model="dialog.open" | |||
/--> | |||
/> | |||
</v-container> | |||
</template> | |||
<script> | |||
import { useAuth } from '@/plugins/auth' | |||
import gql from 'graphql-tag' | |||
// import { updateQuery, deleteQuery } from '@/graphql' | |||
import { updateQuery, deleteQuery } from '@/plugins/graphql' | |||
export default { | |||
name: 'People', | |||
components: { | |||
// EditPeople: () => import('./dialogs/EditPeople'), | |||
EditOrganizer: () => import('./dialogs/EditOrganizer') | |||
}, | |||
setup (props, context) { | |||
@@ -91,20 +91,20 @@ export default { | |||
apollo: { | |||
OrganizerFind: { | |||
query: gql`query { OrganizerFind { _id name plz ort } }` | |||
/* subscribeToMore: { | |||
document: gql`subscription { PersonUpdated { _id vorname nachname geburtstag dtbid gymnet { id } email { email typ } tel { nummer typ } adresse { strasse hausnummer plz ort land typ } turnportal { typ gueltigvon gueltigbis lastcheck sperre festgeturnt verein vermerk } } }`, | |||
updateQuery: updateQuery('PersonList', 'PersonUpdated') | |||
} */ | |||
} | |||
/* $subscribe: { | |||
query: gql`query { OrganizerFind { _id name plz ort admins { _id givenName familyName } organizers { _id givenName familyName } } }`, | |||
subscribeToMore: { | |||
document: gql`subscription { OrganizerUpdated { _id name plz ort admins { _id givenName familyName } organizers { _id givenName familyName } } }`, | |||
updateQuery: updateQuery('OrganizerFind', 'OrganizerUpdated') | |||
} | |||
}, | |||
$subscribe: { | |||
PersonDeleted: { | |||
query: gql`subscription { PersonDeleted }`, | |||
query: gql`subscription { OrganizerDeleted }`, | |||
result (id) { | |||
deleteQuery('PersonList', 'PersonUpdated', this.PersonList, id) | |||
}, | |||
}, | |||
}, */ | |||
deleteQuery('OrganizerFind', 'OrganizerDeleted', this.OrganizerFind, id) | |||
} | |||
} | |||
} | |||
}, | |||
methods: { |
@@ -89,7 +89,7 @@ export default { | |||
query: gql`query { PersonFind { _id familyName givenName adminOf { name plz ort } organizerOf { name plz ort } } }`, | |||
subscribeToMore: { | |||
document: gql`subscription { PersonUpdated { _id familyName givenName adminOf { name plz ort } organizerOf { name plz ort } } }`, | |||
updateQuery: updateQuery('PersonList', 'PersonUpdated') | |||
updateQuery: updateQuery('PersonFind', 'PersonUpdated') | |||
} | |||
}, | |||
$subscribe: { |
@@ -0,0 +1,103 @@ | |||
<template> | |||
<base-material-dialog | |||
:value="value" | |||
icon="mdi-home" | |||
title="Event bearbeiten" | |||
:sub-title="id ? id : 'NEU'" | |||
color="rgb(255, 4, 29)" | |||
:actions="[doc && orga ? 'del' : '', !id || orga ? 'save' : '', 'cancel']" | |||
@del="del" | |||
@save="save" | |||
@close="close" | |||
@esc="close" | |||
> | |||
<v-row v-if="id && (!data || !data._organizer)"> | |||
Warte auf Daten... | |||
</v-row> | |||
<v-row v-else-if="id && !isOrga(data._organizer)"> | |||
Kein Zugriff! | |||
</v-row> | |||
<v-row v-else> | |||
<v-col | |||
cols="12" | |||
> | |||
<date-selector | |||
v-model="data.date" | |||
label="Datum" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="data.name" | |||
label="Name" | |||
/> | |||
</v-col> | |||
</v-row> | |||
</base-material-dialog> | |||
</template> | |||
<script> | |||
import { useAuth } from '@/plugins/auth' | |||
import { useEditDialog } from '@/plugins/editdialog' | |||
export default { | |||
name: 'EditEvent', | |||
props: { | |||
value: { | |||
type: Boolean, | |||
required: true | |||
}, | |||
id: { | |||
type: String, | |||
default: null | |||
}, | |||
organizer: { | |||
type: String, | |||
required: true | |||
} | |||
}, | |||
setup (props, context) { | |||
return { | |||
...useAuth(context), | |||
...useEditDialog(props, context, 'Event(id: $id) { _id name date _organizer }', ['name', 'date']) | |||
} | |||
}, | |||
computed: { | |||
orga () { | |||
return this.isOrga(this.data?._organizer) | |||
} | |||
}, | |||
methods: { | |||
update () { | |||
return { | |||
mutation: `mutation($id: UUID!, $name: String, $date: Date) { | |||
EventUpdate(id: $id, name: $name, date: $date) { | |||
_id name date | |||
} | |||
}` | |||
} | |||
}, | |||
create () { | |||
return { | |||
mutation: `mutation($name: String!, $date: Date!, $organizer: UUID!) { | |||
EventCreate(name: $name, date: $date, organizer: $organizer) { | |||
_id name date _organizer | |||
} | |||
}`, | |||
variables: { | |||
organizer: this.organizer | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="sass"> | |||
$dialog-elevation: 0 | |||
</style> |
@@ -0,0 +1,164 @@ | |||
<template> | |||
<e403 v-if="!isOrga(id)" /> | |||
<v-container | |||
v-else | |||
fluid | |||
tag="section" | |||
> | |||
<v-card | |||
flat | |||
> | |||
<v-btn | |||
absolute | |||
top | |||
right | |||
fab | |||
small | |||
@click="open(null)" | |||
> | |||
<v-icon> | |||
fa-plus | |||
</v-icon> | |||
</v-btn> | |||
<v-col> | |||
<v-text-field | |||
v-model="filter" | |||
label="Filter" | |||
/> | |||
</v-col> | |||
<v-data-table | |||
:headers="headers" | |||
:items="EventFind" | |||
sort-by="date" | |||
sort-desc | |||
:items-per-page="15" | |||
mobile-breakpoint="0" | |||
:search="filter" | |||
@click:row="open" | |||
> | |||
<template #item.date="{item}"> | |||
{{ item.date | date }} | |||
</template> | |||
<template #item.open="{item}"> | |||
<v-btn | |||
fab | |||
small | |||
@click.stop="start(item)" | |||
> | |||
<v-icon>far fa-play</v-icon> | |||
</v-btn> | |||
</template> | |||
</v-data-table> | |||
</v-card> | |||
<edit-event | |||
:id="dialog.id" | |||
v-model="dialog.open" | |||
:organizer="id" | |||
/> | |||
</v-container> | |||
</template> | |||
<script> | |||
import { useAuth } from '@/plugins/auth' | |||
import gql from 'graphql-tag' | |||
import { deleteQuery, updateQuery } from '@/plugins/graphql' | |||
export default { | |||
name: 'event', | |||
components: { | |||
EditEvent: () => import('./dialogs/EditEvent') | |||
}, | |||
setup (props, context) { | |||
return { | |||
...useAuth(context) | |||
} | |||
}, | |||
props: { | |||
id: { | |||
type: String, | |||
required: true | |||
} | |||
}, | |||
data: () => ({ | |||
EventFind: [], | |||
headers: [ | |||
{ | |||
text: 'Datum', | |||
value: 'date', | |||
sortable: false | |||
}, | |||
{ | |||
text: 'Name', | |||
value: 'name', | |||
sortable: false | |||
}, | |||
{ | |||
text: 'Öffnen', | |||
value: 'open', | |||
sortable: false, | |||
width: 100, | |||
align: 'center' | |||
} | |||
], | |||
dialog: { | |||
open: false, | |||
id: null | |||
}, | |||
filter: '' | |||
}), | |||
apollo: { | |||
EventFind: { | |||
query: gql`query($organizer: UUID!) { EventFind(organizer: $organizer) { _id name date _organizer } }`, | |||
variables () { | |||
return { | |||
organizer: this.id | |||
} | |||
}, | |||
subscribeToMore: { | |||
document: gql`subscription($organizer: UUID!) { EventUpdated(organizer: $organizer) { _id name date _organizer } }`, | |||
variables () { | |||
return { | |||
organizer: this.id | |||
} | |||
}, | |||
updateQuery: updateQuery('EventFind', 'EventUpdated') | |||
} | |||
}, | |||
$subscribe: { | |||
EventDeleted: { | |||
query: gql`subscription($organizer: UUID!) { EventDeleted(organizer: $organizer) }`, | |||
variables () { | |||
return { | |||
organizer: this.id | |||
} | |||
}, | |||
result (id) { | |||
deleteQuery('EventFind', 'EventDeleted', this.EventFind, id) | |||
} | |||
} | |||
} | |||
}, | |||
methods: { | |||
open (item) { | |||
this.dialog.open = true | |||
this.dialog.id = item?._id | |||
}, | |||
start (item) { | |||
if (!item?._id) return | |||
this.$router.push(`/event/${item._id}`) | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
</style> |
@@ -43,18 +43,34 @@ type Apparatus { | |||
logo: String | |||
} | |||
type Team { | |||
_id: UUID! | |||
name: String! | |||
} | |||
type Timeslot { | |||
_id: UUID! | |||
time: Time! | |||
duration: Int! | |||
team: Team | |||
} | |||
"""A time string HH:MM:SS""" | |||
scalar Time | |||
type EventApparatus { | |||
_apparatus: UUID! | |||
elements: Int! | |||
bonus: Int! | |||
malus: Int! | |||
apparatus: Apparatus! | |||
} | |||
type Event { | |||
_id: UUID! | |||
name: String | |||
date: Date! | |||
apparatus: [EventApparatus!]! | |||
timeslots: [Timeslot!]! | |||
_organizer: UUID! | |||
organizer: Organizer! | |||
@@ -86,7 +102,24 @@ type Mutation { | |||
ApparatusCreate(logo: String, name: String!): Apparatus! | |||
ApparatusDelete(id: UUID!): UUID! | |||
OrganizerRegister(ort: String, plz: Int, name: String!): Organizer! | |||
OrganizerUpdate(ort: String, plz: Int, name: String, id: UUID!): Organizer! | |||
OrganizerCreate(ort: String, plz: Int, name: String!): Organizer! | |||
OrganizerDelete(id: UUID!): UUID! | |||
EventCreate(name: String, date: Date!, organizer: UUID!): Event! | |||
EventUpdate(date: Date, name: String, id: UUID!): Event! | |||
EventAddApparatus(apparatus: UUID!, id: UUID!): Event! | |||
EventDeleteApparatus(apparatus: UUID!, id: UUID!): Event! | |||
EventOrderApparatus(target: Int!, src: Int!, id: UUID!): Event! | |||
EventAddTimeslots(timeslots: [ITimeslot!]!, id: UUID!): Event! | |||
EventDeleteTimeslots(timeslots: [UUID!]!, id: UUID!): Event! | |||
EventAddTeam(name: String!, timeslot: UUID!, id: UUID!): Event! | |||
EventDeleteTeam(timeslot: UUID!, id: UUID!): Event! | |||
EventDelete(id: UUID!): UUID! | |||
} | |||
input ITimeslot { | |||
time: Time! | |||
duration: Int! | |||
} | |||
type Subscription { | |||
@@ -94,4 +127,8 @@ type Subscription { | |||
PersonDeleted: UUID! | |||
ApparatusUpdated: Apparatus | |||
ApparatusDeleted: UUID! | |||
OrganizerUpdated: Organizer! | |||
OrganizerDeleted: UUID! | |||
EventUpdated(organizer: UUID, id: UUID): Event! | |||
EventDeleted(organizer: UUID!): UUID! | |||
} |
@@ -3,12 +3,15 @@ import {EventResolver} from './resolver/event' | |||
import {EventService} from './event.service' | |||
import {EventResolverQ} from './resolver/event.query' | |||
import {EventResolverM} from './resolver/event.mutation' | |||
import {EventResolverS} from './resolver/event.subscription' | |||
import {EventApparatusResolver} from './resolver/eventapparatus' | |||
@Module({ | |||
providers: [ | |||
EventResolverQ, EventResolverM, | |||
EventResolverQ, EventResolverM, EventResolverS, | |||
EventService, | |||
EventResolver, | |||
EventApparatusResolver, | |||
], | |||
}) | |||
export class EventModule {} |
@@ -2,7 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
import { Date } from '../../global/scalars/Date' | |||
import { Timeslot } from './Timeslot' | |||
import { Organizer } from '../../organizer/models/Organizer' | |||
import {EventApparatus} from './EventApparatus' | |||
@ObjectType() | |||
export class Event { | |||
@@ -15,6 +15,9 @@ export class Event { | |||
@Field(() => Date, { nullable: false }) | |||
date: Date | |||
@Field(() => [EventApparatus], { nullable: false }) | |||
apparatus: EventApparatus[] | |||
@Field(() => [Timeslot], { nullable: false }) | |||
timeslots: Timeslot[] | |||
@@ -0,0 +1,17 @@ | |||
import {Field, Int, ObjectType} from '@nestjs/graphql'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
@ObjectType() | |||
export class EventApparatus { | |||
@Field(() => UUID,{ nullable: false }) | |||
_apparatus: UUID | |||
@Field(() => Int, { nullable: false }) | |||
elements: number | |||
@Field(() => Int, { nullable: false }) | |||
bonus: number | |||
@Field(() => Int, { nullable: false }) | |||
malus: number | |||
} |
@@ -0,0 +1,11 @@ | |||
import {Field, ObjectType} from '@nestjs/graphql'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
@ObjectType() | |||
export class Team { | |||
@Field(() => UUID,{ nullable: false }) | |||
_id: UUID | |||
@Field(() => String,{ nullable: false }) | |||
name: string | |||
} |
@@ -1,6 +1,7 @@ | |||
import { Field, ObjectType } from '@nestjs/graphql'; | |||
import {Field, InputType, Int, ObjectType} from '@nestjs/graphql'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
import { Time } from '../../global/scalars/Time' | |||
import {Team} from './Team' | |||
@ObjectType() | |||
export class Timeslot { | |||
@@ -9,4 +10,19 @@ export class Timeslot { | |||
@Field(() => Time, { nullable: false }) | |||
time: Time | |||
@Field(() => Int, { nullable: false }) | |||
duration: number | |||
@Field(() => Team, { nullable: true }) | |||
team?: Team | |||
} | |||
@InputType() | |||
export class ITimeslot { | |||
@Field(() => Time, { nullable: false }) | |||
time: Time | |||
@Field(() => Int, { nullable: false }) | |||
duration: number | |||
} |
@@ -1,4 +1,4 @@ | |||
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql'; | |||
import {Args, Context, Int, Mutation, Resolver} from '@nestjs/graphql'; | |||
import { Event } from '../models/Event'; | |||
import { Client } from '../../client'; | |||
import { EventService } from '../event.service'; | |||
@@ -6,6 +6,9 @@ import { HttpException } from '@nestjs/common'; | |||
import {Date} from '../../global/scalars/Date' | |||
import {UUID} from '../../global/scalars/UUID' | |||
import {OrganizerService} from '../../organizer/organizer.service' | |||
import {pubsub} from '../../main' | |||
import {ITimeslot} from '../models/Timeslot' | |||
import { v4 as uuid } from 'uuid' | |||
@Resolver(() => Event) | |||
export class EventResolverM { | |||
@@ -31,6 +34,254 @@ export class EventResolverM { | |||
throw new HttpException('Organizer-ID not found!', 404); | |||
} | |||
return this.service.create(client, date, organizer, name); | |||
const neu = await this.service.create(client, date, organizer, name); | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventUpdate( | |||
@Context('client') client, Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('name', { type: () => String, nullable: true }) name: string, | |||
@Args('date', { type: () => Date, nullable: true }) date: Date | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
const set: { | |||
name?: string, | |||
date?: Date, | |||
} = {}; | |||
if (name !== undefined && name !== tmp.name) set.name = name; | |||
if (date !== undefined && date !== tmp.date) set.date = date; | |||
if (Object.values(set).length === 0) return tmp; | |||
const neu = await this.service.update(client, tmp._id, {$set: set}); | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventAddApparatus( | |||
@Context('client') client, Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('apparatus', { type: () => UUID, nullable: false }) apparatus: UUID, | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
let neu: Event; | |||
if (!tmp.apparatus) { | |||
neu = await this.service.update(client, tmp._id, {$set: {apparatus: [{ _apparatus: apparatus, elements: 3, bonus: 3.00, malus: 5.00 }]}}); | |||
} else { | |||
neu = await this.service.update(client, tmp._id, {$push: { apparatus: { _apparatus: apparatus, elements: 3, bonus: 3.00, malus: 5.00 }}}) | |||
} | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventDeleteApparatus( | |||
@Context('client') client, Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('apparatus', { type: () => UUID, nullable: false }) apparatus: UUID, | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
let neu: Event; | |||
if (!tmp.apparatus) { | |||
neu = tmp; | |||
} else { | |||
neu = await this.service.update(client, tmp._id, {$pull: { apparatus: { _apparatus: apparatus }}}) | |||
} | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventOrderApparatus( | |||
@Context('client') client: Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('src', { type: () => Int, nullable: false }) src: number, | |||
@Args('target', { type: () => Int, nullable: false }) target: number, | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
let neu: Event; | |||
if (!tmp.apparatus) { | |||
neu = tmp; | |||
} else { | |||
const app = tmp.apparatus.splice(src, 1) | |||
if (app?.length === 1) tmp.apparatus.splice(target, 0, app[0]) | |||
neu = await this.service.update(client, tmp._id, {$set: { apparatus: tmp.apparatus }}) | |||
} | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventAddTimeslots( | |||
@Context('client') client: Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('timeslots', { type: () => [ITimeslot], nullable: false}) timeslots: ITimeslot[], | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
let neu: Event; | |||
if (!tmp.timeslots) { | |||
neu = await this.service.update(client, tmp._id, {$set: {timeslots: timeslots.map(t => ({_id: uuid(), ...t }))}}); | |||
} else { | |||
timeslots = timeslots.filter(n => !tmp.timeslots.find(o => o.time === n.time && o.duration === n.duration)); | |||
neu = await this.service.update(client, tmp._id, {$push: { timeslots: { $each: timeslots.map(t => ({_id: uuid(), ...t })) }}}); | |||
} | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventDeleteTimeslots( | |||
@Context('client') client: Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('timeslots', { type: () => [UUID], nullable: false}) timeslots: UUID[], | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
let neu: Event; | |||
if (!tmp.timeslots) { | |||
neu = tmp; | |||
} else { | |||
neu = await this.service.update(client, tmp._id, {$pull: { timeslots: { _id: { $in: timeslots } }}}); | |||
} | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventAddTeam( | |||
@Context('client') client: Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID, | |||
@Args('name', { type: () => String, nullable: false }) name: string | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
if (!tmp?.timeslots?.find(t => t._id === timeslot)) { | |||
throw new HttpException('Timeslot-ID not found', 404); | |||
} | |||
let neu: Event; | |||
if (tmp.timeslots.find(t => t._id === timeslot).team) { | |||
neu = await this.service.update( | |||
client, | |||
tmp._id, | |||
{ $set: { 'timeslots.$[ts].team,name': name }}, | |||
{ arrayFilters: [{ 'ts._id': timeslot }]} | |||
); | |||
} else { | |||
neu = await this.service.update( | |||
client, | |||
tmp._id, | |||
{ $set: { 'timeslots.$[ts].team': { _id: uuid(), name }}}, | |||
{ arrayFilters: [{ 'ts._id': timeslot }]} | |||
); | |||
} | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Event, { nullable: false }) | |||
async EventDeleteTeam( | |||
@Context('client') client: Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('timeslot', { type: () => UUID, nullable: false }) timeslot: UUID, | |||
): Promise<Event> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
if (!tmp?.timeslots?.find(t => t._id === timeslot)) { | |||
throw new HttpException('Timeslot-ID not found', 404); | |||
} else if (!tmp.timeslots.find(t => t._id === timeslot).team) { | |||
throw new HttpException('Timeslot empty', 404); | |||
} | |||
const neu: Event = await this.service.update( | |||
client, | |||
tmp._id, | |||
{ $unset: { 'timeslots.$[ts].team': '' }}, | |||
{ arrayFilters: [{ 'ts._id': timeslot }]} | |||
); | |||
pubsub.publish('EventUpdated', { EventUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => UUID, { nullable: false }) | |||
async EventDelete( | |||
@Context('client') client, Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
): Promise<UUID> { | |||
const tmp = await this.service.findOneById(id); | |||
if (!tmp) throw new HttpException('Event-ID not found!', 404); | |||
if (!client.isMaster() && !client.isOrganizer(tmp._organizer)) { | |||
throw new HttpException('Access denied', 403); | |||
} | |||
this.service.delete(client, id); | |||
pubsub.publish('EventDeleted', { EventDeleted: id, organizer: tmp._organizer }); | |||
return id; | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
import {Args, Resolver, Subscription} from '@nestjs/graphql' | |||
import {Event} from '../models/Event' | |||
import {pubsub} from '../../main' | |||
import {UUID} from '../../global/scalars/UUID' | |||
import {EventService} from '../event.service' | |||
@Resolver(() => Event) | |||
export class EventResolverS { | |||
constructor( | |||
private readonly service: EventService | |||
) {} | |||
@Subscription(() => Event, { | |||
nullable: false, | |||
filter: async (payload, variables, context) => { | |||
let ret = true; | |||
if (ret && !!variables.organizer) | |||
ret = payload?.EventUpdated?._organizer === variables?.organizer | |||
&& (context?.client?.isMaster() || context?.client?.isAdmin(variables.organizer)); | |||
if (ret && !!variables.id) | |||
ret = payload?.EventUpdated?._id === variables?.id | |||
return ret; | |||
} | |||
}) | |||
async EventUpdated( | |||
@Args('id', { type: () => UUID, nullable: true }) id: UUID, | |||
@Args('organizer', { type: () => UUID, nullable: true }) organizer: UUID, | |||
) { | |||
return pubsub.asyncIterator('EventUpdated'); | |||
} | |||
@Subscription(() => UUID, { | |||
nullable: false, | |||
filter: async (payload, variables, context) => { | |||
return payload?.organizer === variables?.organizer | |||
&& (context?.client?.isMaster() || context?.client?.isAdmin(variables.organizer)) | |||
} | |||
}) | |||
async EventDeleted( | |||
@Args('organizer', { type: () => UUID, nullable: false }) organizer: UUID | |||
) { | |||
return pubsub.asyncIterator('EventDeleted'); | |||
} | |||
} |
@@ -6,6 +6,7 @@ import {Date} from '../../global/scalars/Date' | |||
import {Timeslot} from '../models/Timeslot' | |||
import {Organizer} from '../../organizer/models/Organizer' | |||
import {OrganizerService} from '../../organizer/organizer.service' | |||
import {EventApparatus} from '../models/EventApparatus' | |||
@Resolver(() => Event) | |||
export class EventResolver { | |||
@@ -33,7 +34,15 @@ export class EventResolver { | |||
return parent.date; | |||
} | |||
@ResolveField(() => [Timeslot], { nullable: true }) | |||
@ResolveField(() => [EventApparatus], { nullable: false }) | |||
async apparatus( | |||
@Context('client') client: Client, | |||
@Parent() parent: Event | |||
): Promise<EventApparatus[]> { | |||
return parent.apparatus || []; | |||
} | |||
@ResolveField(() => [Timeslot], { nullable: false }) | |||
async timeslots( | |||
@Context('client') client: Client, | |||
@Parent() parent: Event |
@@ -0,0 +1,49 @@ | |||
import {Context, Float, Int, Parent, ResolveField, Resolver} from '@nestjs/graphql'; | |||
import { Client } from '../../client'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
import {EventApparatus} from '../models/EventApparatus' | |||
import {Apparatus} from '../../apparatus/models/Apparatus' | |||
import {ApparatusService} from '../../apparatus/apparatus.service' | |||
@Resolver(() => EventApparatus) | |||
export class EventApparatusResolver { | |||
@ResolveField(() => UUID, { nullable: false }) | |||
async _apparatus( | |||
@Context('client') client: Client, | |||
@Parent() parent: EventApparatus | |||
): Promise<UUID> { | |||
return parent._apparatus as UUID; | |||
} | |||
@ResolveField(() => Apparatus, { nullable: false }) | |||
async apparatus( | |||
@Context('client') client: Client, | |||
@Parent() parent: EventApparatus | |||
): Promise<Apparatus> { | |||
return new ApparatusService().findOneById(parent._apparatus); | |||
} | |||
@ResolveField(() => Int, { nullable: false }) | |||
async elements( | |||
@Context('client') client: Client, | |||
@Parent() parent: EventApparatus | |||
): Promise<number> { | |||
return parent.elements; | |||
} | |||
@ResolveField(() => Float, { nullable: false }) | |||
async bonus( | |||
@Context('client') client: Client, | |||
@Parent() parent: EventApparatus | |||
): Promise<number> { | |||
return parent.bonus; | |||
} | |||
@ResolveField(() => Float, { nullable: false }) | |||
async malus( | |||
@Context('client') client: Client, | |||
@Parent() parent: EventApparatus | |||
): Promise<number> { | |||
return parent.malus; | |||
} | |||
} |
@@ -1,12 +1,13 @@ | |||
import { Module } from '@nestjs/common'; | |||
import { OrganizerResolverQ } from './resolver/organizer.query'; | |||
import { OrganizerResolverM } from './resolver/organizer.mutation'; | |||
import { OrganizerResolverS } from './resolver/organizer.subscription' | |||
import { OrganizerService } from './organizer.service'; | |||
import { OrganizerResolver } from './resolver/organizer'; | |||
@Module({ | |||
providers: [ | |||
OrganizerResolverQ, OrganizerResolverM, | |||
OrganizerResolverQ, OrganizerResolverM, OrganizerResolverS, | |||
OrganizerService, | |||
OrganizerResolver, | |||
], |
@@ -3,6 +3,8 @@ import { Organizer } from '../models/Organizer'; | |||
import { Client } from '../../client'; | |||
import { OrganizerService } from '../organizer.service'; | |||
import { HttpException } from '@nestjs/common'; | |||
import {UUID} from '../../global/scalars/UUID' | |||
import {pubsub} from '../../main' | |||
@Resolver(() => Organizer) | |||
export class OrganizerResolverM { | |||
@@ -28,4 +30,63 @@ export class OrganizerResolverM { | |||
return this.service.create(client, name, plz, ort); | |||
return tmp[0]; | |||
} | |||
@Mutation(() => Organizer, { nullable: false }) | |||
async OrganizerUpdate( | |||
@Context('client') client: Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||
@Args('name', { nullable: true }) name?: string, | |||
@Args('plz', { type: () => Int, nullable: true }) plz?: number, | |||
@Args('ort', { nullable: true }) ort?: string | |||
): Promise<Organizer> { | |||
if (!client.isMaster() && !client.isAdmin(id)) throw new HttpException('Access denied', 403) | |||
const tmp = await this.service.findOneById(id); | |||
const set: { | |||
name?: string, | |||
plz?: number, | |||
ort?: string, | |||
} = {}; | |||
if (name !== undefined && name !== tmp.name) set.name = name | |||
if (plz !== undefined && plz !== tmp.plz) set.plz = plz | |||
if (ort !== undefined && ort !== tmp.ort) set.ort = ort | |||
if (Object.values(set).length === 0) return tmp; | |||
const neu = await this.service.update(client, tmp._id, {$set: set}); | |||
pubsub.publish('OrganizerUpdated', { OrganizerUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => Organizer, { nullable: false }) | |||
async OrganizerCreate( | |||
@Context('client') client: Client, | |||
@Args('name') name: string, | |||
@Args('plz', { type: () => Int, nullable: true }) plz?: number, | |||
@Args('ort', { nullable: true }) ort?: string, | |||
): Promise<Organizer> { | |||
if (!client.isMaster()) throw new HttpException('Access denied', 403); | |||
const neu = await this.service.insert(client, { name, plz, ort }); | |||
pubsub.publish('OrganizerUpdated', { OrganizerUpdated: neu }); | |||
return neu; | |||
} | |||
@Mutation(() => UUID, { nullable: false }) | |||
async OrganizerDelete( | |||
@Context('client') client: Client, | |||
@Args('id', { type: () => UUID, nullable: false }) id: UUID | |||
): Promise<UUID> { | |||
if (!client.isMaster() && !client.isAdmin(id)) throw new HttpException('Access denied', 403); | |||
this.service.delete(client, id); | |||
pubsub.publish('OrganizerDeleted', { OrganizerDeleted: id }); | |||
return id; | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
import {Resolver, Subscription} from '@nestjs/graphql' | |||
import {Organizer} from '../models/Organizer' | |||
import {pubsub} from '../../main' | |||
import {UUID} from '../../global/scalars/UUID' | |||
import {OrganizerService} from '../organizer.service' | |||
@Resolver(() => Organizer) | |||
export class OrganizerResolverS { | |||
constructor( | |||
private readonly service: OrganizerService | |||
) {} | |||
@Subscription(() => Organizer, { nullable: false }) | |||
async OrganizerUpdated() { | |||
return pubsub.asyncIterator('OrganizerUpdated'); | |||
} | |||
@Subscription(() => UUID, { nullable: false }) | |||
async OrganizerDeleted() { | |||
return pubsub.asyncIterator('OrganizerDeleted'); | |||
} | |||
} |
@@ -18,6 +18,6 @@ export class PersonResolverS { | |||
@Subscription(() => UUID, { nullable: false }) | |||
async PersonDeleted() { | |||
return pubsub.asyncIterator('PersonDeleted') | |||
return pubsub.asyncIterator('PersonDeleted'); | |||
} | |||
} |