import router from './plugins/router' | import router from './plugins/router' | ||||
import store from './plugins/store' | import store from './plugins/store' | ||||
import vuetify from './plugins/vuetify' | import vuetify from './plugins/vuetify' | ||||
import apolloProvider from './plugins/graphql' | |||||
import '@mdi/font/css/materialdesignicons.css' | import '@mdi/font/css/materialdesignicons.css' | ||||
import '@fortawesome/fontawesome-pro/css/all.css' | import '@fortawesome/fontawesome-pro/css/all.css' | ||||
router, | router, | ||||
store, | store, | ||||
vuetify, | vuetify, | ||||
apolloProvider, | |||||
render: h => h(App) | render: h => h(App) | ||||
}).$mount('#app') | }).$mount('#app') |
import { computed } from '@vue/composition-api' | |||||
export const useAuth = (context) => { | |||||
const isMaster = computed(() => { | |||||
return context.root.$store.getters.isMaster | |||||
}) | |||||
return { isMaster } | |||||
} |
import Vue from 'vue' | |||||
import { v4 as uuid } from 'uuid' | import { v4 as uuid } from 'uuid' | ||||
import { SubscriptionClient } from 'subscriptions-transport-ws' | import { SubscriptionClient } from 'subscriptions-transport-ws' | ||||
import ApolloClient from 'apollo-client' | import ApolloClient from 'apollo-client' | ||||
import { ref } from '@vue/composition-api' | import { ref } from '@vue/composition-api' | ||||
import gql from 'graphql-tag' | import gql from 'graphql-tag' | ||||
Vue.use(VueApollo) | |||||
export const clientId = uuid() | export const clientId = uuid() | ||||
const server = process.env.NODE_ENV === 'production' ? 'wss://turnenaufzeit.de/gql' : 'ws://localhost:3000/graphql' | const server = process.env.NODE_ENV === 'production' ? 'wss://turnenaufzeit.de/gql' : 'ws://localhost:3000/graphql' | ||||
givenName | givenName | ||||
familyName | familyName | ||||
adminOf { _id name plz ort } | adminOf { _id name plz ort } | ||||
master | |||||
}}`, | }}`, | ||||
variables: { | variables: { | ||||
token: !email || !passwort ? Cookie.get('token') : undefined, | token: !email || !passwort ? Cookie.get('token') : undefined, | ||||
Cookie.remove('token') | Cookie.remove('token') | ||||
if (email) { | if (email) { | ||||
context.root.$store.commit('OPEN_SNACKBAR', 'Zugangsdaten falsch') | |||||
// context.root.$store.commit('OPEN_SNACKBAR', 'Zugangsdaten falsch') | |||||
} | } | ||||
} | } | ||||
} | } |
{ | { | ||||
path: '/', | path: '/', | ||||
name: 'Home', | name: 'Home', | ||||
component: () => import('../views/Index') | |||||
component: () => import('../views/Index'), | |||||
children: [ | |||||
{ | |||||
path: 'login', | |||||
name: 'Login', | |||||
component: () => import('../views/Login') | |||||
}, | |||||
{ | |||||
path: 'management/person', | |||||
name: 'Personen bearbeiten', | |||||
component: () => import('../views/components/management/person') | |||||
}, | |||||
{ | |||||
path: 'management/apparatus', | |||||
name: 'Geräte bearbeiten', | |||||
component: () => import('../views/components/management/apparatus') | |||||
}, | |||||
{ | |||||
path: 'management/organizer', | |||||
name: 'Veranstalter bearbeiten', | |||||
component: () => import('../views/components/management/organizer') | |||||
} | |||||
] | |||||
} | } | ||||
] | ] | ||||
}, | }, | ||||
getters: { | getters: { | ||||
profile: (state) => state.profile || {}, | profile: (state) => state.profile || {}, | ||||
isMaster: (state) => !!state.profile?.master | |||||
isMaster: (state) => !!state.profile?.master, | |||||
isLogin: (state) => !!state.profile?.token | |||||
} | } | ||||
}) | }) |
<template> | |||||
<v-container | |||||
id="login" | |||||
class="fill-height justify-center" | |||||
tag="section" | |||||
> | |||||
<v-row justify="center"> | |||||
<base-material-card | |||||
color="red" | |||||
light | |||||
max-width="100%" | |||||
width="400" | |||||
class="px-5 py-3" | |||||
> | |||||
<template #heading> | |||||
<div class="text-center"> | |||||
<h1 class="display-2 font-weight-bold mb-2"> | |||||
Anmelden | |||||
</h1> | |||||
</div> | |||||
</template> | |||||
<v-card-text class="text-center"> | |||||
<v-form | |||||
ref="login" | |||||
v-model="valid" | |||||
lazy-validation | |||||
> | |||||
<v-text-field | |||||
v-model="email" | |||||
required | |||||
:rules="emailRules" | |||||
color="red" | |||||
label="E-Mail..." | |||||
prepend-icon="mdi-email" | |||||
name="email" | |||||
@keydown.enter="_login" | |||||
/> | |||||
<v-text-field | |||||
v-model="password" | |||||
required | |||||
:rules="passwordRules" | |||||
:type="pwdshow ? 'text' : 'password'" | |||||
class="mb-5" | |||||
color="red" | |||||
label="Passwort..." | |||||
prepend-icon="mdi-lock" | |||||
name="password" | |||||
:append-icon="pwdshow ? 'mdi-eye' : 'mdi-eye-off'" | |||||
@click:append="pwdshow = !pwdshow" | |||||
@keydown.enter="_login" | |||||
/> | |||||
<v-btn | |||||
class="mb-5" | |||||
color="red" | |||||
tile | |||||
@click="_login" | |||||
width="100%" | |||||
> | |||||
<v-icon class="mr-4"> | |||||
fa-sign-in-alt | |||||
</v-icon> | |||||
Anmelden | |||||
</v-btn> | |||||
<v-btn | |||||
class="mb-5" | |||||
color="white" | |||||
tile | |||||
to="/register" | |||||
width="100%" | |||||
> | |||||
<v-icon class="mr-4"> | |||||
far fa-user-plus | |||||
</v-icon> | |||||
Neu registrieren | |||||
</v-btn> | |||||
<v-btn | |||||
class="mb-5" | |||||
color="white" | |||||
tile | |||||
to="/reset" | |||||
width="100%" | |||||
> | |||||
<v-icon class="mr-4"> | |||||
far fa-key | |||||
</v-icon> | |||||
Passwort vergessen | |||||
</v-btn> | |||||
</v-form> | |||||
</v-card-text> | |||||
</base-material-card> | |||||
</v-row> | |||||
</v-container> | |||||
</template> | |||||
<script> | |||||
import { mapGetters } from 'vuex' | |||||
import { useGraphQL } from '@/plugins/graphql' | |||||
export default { | |||||
name: 'PagesLogin', | |||||
setup (props, context) { | |||||
return { | |||||
...useGraphQL(context) | |||||
} | |||||
}, | |||||
data: () => ({ | |||||
valid: true, | |||||
email: '', | |||||
password: '', | |||||
emailRules: [ | |||||
v => !!v || 'E-Mail wird benötigt!' | |||||
], | |||||
passwordRules: [ | |||||
v => !!v || 'Passwort wird benötigt!' | |||||
], | |||||
pwdshow: false | |||||
}), | |||||
computed: { | |||||
...mapGetters(['profile', 'isLogin']) | |||||
}, | |||||
watch: { | |||||
isLogin () { | |||||
if (this.isLogin) { | |||||
this.cl() | |||||
} | |||||
} | |||||
}, | |||||
created () { | |||||
if (this.isLogin) { | |||||
this.cl() | |||||
} | |||||
}, | |||||
methods: { | |||||
async _login () { | |||||
if (!this.$refs.login.validate()) { | |||||
return | |||||
} | |||||
await this.login(this.email, this.password) | |||||
if (!this.isLogin) { | |||||
this.$store.commit('OPEN_SNACKBAR', 'Falsche Zugangsdaten!') | |||||
} | |||||
}, | |||||
cl () { | |||||
let target = this.$route.query.origin | |||||
if (!target) target = '/' | |||||
this.$router.replace({ path: target }) | |||||
} | |||||
} | |||||
} | |||||
</script> |
<v-icon>mdi-view-dashboard</v-icon> | <v-icon>mdi-view-dashboard</v-icon> | ||||
</v-btn> | </v-btn> | ||||
<v-menu | |||||
bottom | |||||
left | |||||
offset-y | |||||
origin="top right" | |||||
transition="scale-transition" | |||||
> | |||||
<template #activator="{ attrs, on }"> | |||||
<v-btn | |||||
class="ml-2" | |||||
min-width="0" | |||||
text | |||||
v-bind="attrs" | |||||
v-on="on" | |||||
> | |||||
<v-badge | |||||
color="rgb(255, 4, 29)" | |||||
overlap | |||||
bordered | |||||
> | |||||
<template #badge> | |||||
<span>{{ messages.length }}</span> | |||||
</template> | |||||
<v-icon>mdi-bell</v-icon> | |||||
</v-badge> | |||||
</v-btn> | |||||
</template> | |||||
<v-list | |||||
:tile="false" | |||||
nav | |||||
> | |||||
<div> | |||||
<app-bar-item | |||||
v-for="(n, i) in messages" | |||||
:key="`item-${i}`" | |||||
> | |||||
<v-list-item-title v-text="n.message" /> | |||||
</app-bar-item> | |||||
</div> | |||||
</v-list> | |||||
</v-menu> | |||||
<v-btn | <v-btn | ||||
v-if="!profile._id" | v-if="!profile._id" | ||||
class="ml-2" | class="ml-2" | ||||
min-width="0" | min-width="0" | ||||
text | text | ||||
to="/pages/login" | |||||
to="/login" | |||||
> | > | ||||
<v-icon>far fa-user</v-icon> | <v-icon>far fa-user</v-icon> | ||||
</v-btn> | </v-btn> | ||||
}, | }, | ||||
computed: { | computed: { | ||||
...mapState(['drawer', 'messages']), | |||||
...mapState(['drawer']), | |||||
...mapGetters(['profile']), | ...mapGetters(['profile']), | ||||
title () { | title () { | ||||
if (this.$route.name) { | if (this.$route.name) { |
:item="{ | :item="{ | ||||
group: '/management', | group: '/management', | ||||
icon: 'fa-user-crown', | icon: 'fa-user-crown', | ||||
title: 'Admin', | |||||
title: 'Administration', | |||||
children: [ | children: [ | ||||
{ | { | ||||
title: 'Wettkampforte', | |||||
to: 'places', | |||||
icon: 'mdi-home-group', | |||||
}, | |||||
{ | |||||
title: 'Hauptevents', | |||||
to: 'events', | |||||
icon: 'mdi-calendar-multiple', | |||||
title: 'Geräte', | |||||
to: 'apparatus', | |||||
icon: 'fa-dumbbell', | |||||
}, | }, | ||||
{ | { | ||||
title: 'Vereine', | |||||
to: 'clubs', | |||||
title: 'Veranstalter', | |||||
to: 'organizer', | |||||
icon: 'mdi-account-supervisor-circle', | icon: 'mdi-account-supervisor-circle', | ||||
}, | }, | ||||
{ | { | ||||
title: 'Disziplinen', | |||||
to: 'disciplines', | |||||
icon: 'fa-dumbbell', | |||||
}, | |||||
{ | |||||
title: 'Personen verwalten', | |||||
to: 'people', | |||||
title: 'Personen', | |||||
to: 'person', | |||||
icon: 'mdi-account-edit', | icon: 'mdi-account-edit', | ||||
}, | |||||
{ | |||||
title: 'Personen zusammenführen', | |||||
to: 'merge', | |||||
icon: 'mdi-account-switch', | |||||
}, | |||||
{ | |||||
title: 'Turnportalabfrage', | |||||
to: 'turnportal', | |||||
icon: 'mdi-account-question', | |||||
}, | |||||
{ | |||||
title: 'Serverübersicht', | |||||
to: 'server', | |||||
icon: 'mdi-server', | |||||
}, | |||||
], | |||||
} | |||||
] | |||||
}" | }" | ||||
/> | /> | ||||
</div> | </div> |
<template> | |||||
<e403 v-if="!isMaster" /> | |||||
<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="ApparatusFind" | |||||
sort-by="name" | |||||
:items-per-page="15" | |||||
mobile-breakpoint="0" | |||||
:search="filter" | |||||
@click:row="open" | |||||
/> | |||||
</v-card> | |||||
<!--edit-people | |||||
: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' | |||||
export default { | |||||
name: 'People', | |||||
components: { | |||||
// EditPeople: () => import('./dialogs/EditPeople'), | |||||
}, | |||||
setup (props, context) { | |||||
return { | |||||
...useAuth(context) | |||||
} | |||||
}, | |||||
data: () => ({ | |||||
ApparatusFind: [], | |||||
headers: [ | |||||
{ | |||||
text: '', | |||||
value: 'logo', | |||||
sortable: false | |||||
}, | |||||
{ | |||||
text: 'Name', | |||||
value: 'name', | |||||
sortable: true | |||||
} | |||||
], | |||||
dialog: { | |||||
open: false, | |||||
id: null | |||||
}, | |||||
filter: '' | |||||
}), | |||||
apollo: { | |||||
ApparatusFind: { | |||||
query: gql`query { ApparatusFind { _id name logo } }` | |||||
/* 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: { | |||||
PersonDeleted: { | |||||
query: gql`subscription { PersonDeleted }`, | |||||
result (id) { | |||||
deleteQuery('PersonList', 'PersonUpdated', this.PersonList, id) | |||||
}, | |||||
}, | |||||
}, */ | |||||
}, | |||||
methods: { | |||||
open (item) { | |||||
this.dialog.open = true | |||||
this.dialog.id = item?._id | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
</style> |
<template> | |||||
<e403 v-if="!isMaster" /> | |||||
<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="OrganizerFind" | |||||
sort-by="plz" | |||||
:items-per-page="15" | |||||
mobile-breakpoint="0" | |||||
:search="filter" | |||||
@click:row="open" | |||||
/> | |||||
</v-card> | |||||
<!--edit-people | |||||
: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' | |||||
export default { | |||||
name: 'People', | |||||
components: { | |||||
// EditPeople: () => import('./dialogs/EditPeople'), | |||||
}, | |||||
setup (props, context) { | |||||
return { | |||||
...useAuth(context) | |||||
} | |||||
}, | |||||
data: () => ({ | |||||
OrganizerFind: [], | |||||
headers: [ | |||||
{ | |||||
text: 'Name', | |||||
value: 'name', | |||||
sortable: true | |||||
}, | |||||
{ | |||||
text: 'PLZ', | |||||
value: 'plz', | |||||
sortable: true | |||||
}, | |||||
{ | |||||
text: 'Ort', | |||||
value: 'ort', | |||||
sortable: true | |||||
} | |||||
], | |||||
dialog: { | |||||
open: false, | |||||
id: null | |||||
}, | |||||
filter: '' | |||||
}), | |||||
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: { | |||||
PersonDeleted: { | |||||
query: gql`subscription { PersonDeleted }`, | |||||
result (id) { | |||||
deleteQuery('PersonList', 'PersonUpdated', this.PersonList, id) | |||||
}, | |||||
}, | |||||
}, */ | |||||
}, | |||||
methods: { | |||||
open (item) { | |||||
this.dialog.open = true | |||||
this.dialog.id = item?._id | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
</style> |
<template> | |||||
<e403 v-if="!isMaster" /> | |||||
<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="PersonFind" | |||||
sort-by="familyName" | |||||
:items-per-page="15" | |||||
mobile-breakpoint="0" | |||||
:search="filter" | |||||
@click:row="open" | |||||
/> | |||||
</v-card> | |||||
<!--edit-people | |||||
: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' | |||||
export default { | |||||
name: 'People', | |||||
components: { | |||||
// EditPeople: () => import('./dialogs/EditPeople'), | |||||
}, | |||||
setup (props, context) { | |||||
return { | |||||
...useAuth(context) | |||||
} | |||||
}, | |||||
data: () => ({ | |||||
PersonFind: [], | |||||
headers: [ | |||||
{ | |||||
text: 'Nachname', | |||||
value: 'familyName', | |||||
sortable: true | |||||
}, | |||||
{ | |||||
text: 'Vorname', | |||||
value: 'givenName', | |||||
sortable: true | |||||
} | |||||
], | |||||
dialog: { | |||||
open: false, | |||||
id: null | |||||
}, | |||||
filter: '' | |||||
}), | |||||
apollo: { | |||||
PersonFind: { | |||||
query: gql`query { PersonFind { _id familyName givenName } }` | |||||
/* 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: { | |||||
PersonDeleted: { | |||||
query: gql`subscription { PersonDeleted }`, | |||||
result (id) { | |||||
deleteQuery('PersonList', 'PersonUpdated', this.PersonList, id) | |||||
}, | |||||
}, | |||||
}, */ | |||||
}, | |||||
methods: { | |||||
open (item) { | |||||
this.dialog.open = true | |||||
this.dialog.id = item?._id | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
</style> |
givenName: String! | givenName: String! | ||||
familyName: String! | familyName: String! | ||||
email: EmailAddress! | email: EmailAddress! | ||||
master: Boolean | |||||
token: String | token: String | ||||
_adminOf: [UUID!] | _adminOf: [UUID!] | ||||
adminOf: [Organizer!] | adminOf: [Organizer!] |
@Field(() => EmailAddress , { nullable: false }) | @Field(() => EmailAddress , { nullable: false }) | ||||
email: EmailAddress | email: EmailAddress | ||||
@Field(() => Boolean, { nullable: true }) | |||||
master?: boolean | |||||
} | } |
return parent.email; | return parent.email; | ||||
} | } | ||||
@ResolveField(() => Boolean, { nullable: true }) | |||||
async master( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: Person | |||||
): Promise<boolean> { | |||||
if (!client.isMaster() && !client.isSelf(parent._id)) return null; | |||||
return parent.master; | |||||
} | |||||
@ResolveField(() => [UUID], { nullable: true }) | @ResolveField(() => [UUID], { nullable: true }) | ||||
async _adminOf( | async _adminOf( | ||||
@Context('client') client: Client, | @Context('client') client: Client, |