"apollo-cache-inmemory": "^1.6.6", | "apollo-cache-inmemory": "^1.6.6", | ||||
"apollo-client": "^2.6.10", | "apollo-client": "^2.6.10", | ||||
"core-js": "^3.6.5", | "core-js": "^3.6.5", | ||||
"jot": "git+https://github.com/joshdata/jot.git", | |||||
"js-cookie": "^2.2.1", | "js-cookie": "^2.2.1", | ||||
"lodash": "^4.17.21", | "lodash": "^4.17.21", | ||||
"subscriptions-transport-ws": "^0.9.18", | "subscriptions-transport-ws": "^0.9.18", |
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> | <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||||
<title><%= htmlWebpackPlugin.options.title %></title> | <title><%= htmlWebpackPlugin.options.title %></title> | ||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> | ||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous"> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<noscript> | <noscript> |
<template> | |||||
<div> | |||||
T2: {{ get('pub_person','b77038e7-9f17-4ef2-951c-958aeba8a5b5') }} | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { getData } from '../plugins/useSDB' | |||||
export default { | |||||
name: 'Demo', | |||||
setup () { | |||||
return { ...getData() } | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped> | |||||
</style> |
import Vue from 'vue' | |||||
import { ref } from '@vue/composition-api' | |||||
import * as jot from 'jot' | |||||
import gql from 'graphql-tag' | |||||
export const useEditDialog = (props, context, query, fields, lists = {}) => { | |||||
const doc = ref(null) | |||||
const data = ref({}) | |||||
const coll = query.match(/^\s*[^{(\s]*/)?.[0]?.replace(/^\s/, '') | |||||
context.parent.$apollo.addSmartQuery(coll, { | |||||
query: gql`query($id: UUID!) { ${query} }`, | |||||
variables () { | |||||
return { | |||||
id: !props.value || props.id | |||||
} | |||||
}, | |||||
skip () { | |||||
return !props.value || !props.id | |||||
}, | |||||
update (d) { | |||||
_update(d[coll]) | |||||
} | |||||
}) | |||||
const addfields = () => { | |||||
for (const field of fields) { | |||||
const p = field.split('.') | |||||
let d = data.value | |||||
for (const i in p) { | |||||
if (!d[p[i]]) { | |||||
if (i < p.length - 1) { | |||||
Vue.set(d, p[i], {}) | |||||
} else { | |||||
Vue.set(d, p[i], '') | |||||
} | |||||
} | |||||
d = d[p[i]] | |||||
} | |||||
} | |||||
} | |||||
addfields() | |||||
const removeimg = () => { | |||||
for (const field of fields) { | |||||
if (context.refs[field] && context?.refs[field]?.$vnode?.tag?.indexOf('VImageInput') !== -1) { | |||||
context.refs[field].internalImageData = '' | |||||
} | |||||
} | |||||
} | |||||
/* let arrays = {} | |||||
for (const name in lists) { | |||||
arrays = { ...arrays, ...useEditList(props, context, doc, data, name, lists[name]) } | |||||
} */ | |||||
const del = async () => { | |||||
if (doc?.value?._id && await context.root.$children[0].$refs.confirm.open('Löschen', 'Sind Sie sicher?')) { | |||||
context.parent.$apollo.mutate({ | |||||
mutation: gql`mutation($id: UUID!) { ${coll}Delete(id: $id) }`, | |||||
variables: { | |||||
id: doc?.value?._id | |||||
} | |||||
}) | |||||
close() | |||||
} | |||||
} | |||||
const close = () => { | |||||
context.emit('input', false) | |||||
Vue.set(doc, 'value', null) | |||||
Vue.set(data, 'value', {}) | |||||
addfields() | |||||
removeimg() | |||||
const self = context?.parent?.$children?.find(e => e?.$el?._prevClass === 'v-dialog__container') | |||||
if (self && typeof self.onClose === 'function') self.onClose() | |||||
} | |||||
const save = () => { | |||||
const self = context?.parent?.$children?.find(e => e?.$el?._prevClass === 'v-dialog__container') | |||||
let func = null | |||||
if (self.data._id) { | |||||
if (!self && typeof self.update !== 'function') return | |||||
func = self.update | |||||
} else { | |||||
if (!self && typeof self.create !== 'function') return | |||||
func = self.create | |||||
} | |||||
let { mutation, variables } = func() | |||||
if (!mutation) return | |||||
if (!variables) variables = {} | |||||
if (self.data._id) { | |||||
variables.id = self.data._id | |||||
} | |||||
for (const field of fields) { | |||||
if (variables[field] === undefined && self.data[field] !== self.doc?.[field]) { | |||||
variables[field] = self.data[field] | |||||
} | |||||
} | |||||
context.parent.$apollo.mutate({ | |||||
mutation: gql(mutation), | |||||
variables | |||||
}).catch(e => { | |||||
context.root.$store.commit('OPEN_SNACKBAR', e) | |||||
}) | |||||
if (self && typeof self.onSave === 'function') self.onSave() | |||||
close() | |||||
} | |||||
/* const savelist = async (id, l, listdata) => { | |||||
let subcoll = l.split('') | |||||
subcoll[0] = l[0].toUpperCase() | |||||
subcoll = subcoll.join('') | |||||
listdata = listdata.map(d => { | |||||
const neu = {} | |||||
for (const k of lists[l]) { | |||||
neu[k] = d[k] | |||||
} | |||||
return neu | |||||
}) | |||||
const up = `mutation { Update${coll}${subcoll}(id: "${id}", ${l}: ${JSON.stringify(listdata).replace(/"([^"]+)":/g, '$1:')}) ${q} }` | |||||
console.log(up) | |||||
return context.parent.$apollo.mutate({ | |||||
mutation: gql(up), | |||||
}) | |||||
} | |||||
watch(props, () => { | |||||
if (props.id && props.value) { | |||||
removeimg() | |||||
addfields() | |||||
const self = context?.parent?.$children?.find(e => e?.$el?._prevClass === 'v-dialog__container') | |||||
if (self && typeof self.onInit === 'function') self.onInit() | |||||
} else if (props.id) { | |||||
removeimg() | |||||
addfields() | |||||
} | |||||
if (!props.value) { | |||||
close() | |||||
} | |||||
}) */ | |||||
const _update = (neu) => { | |||||
if (doc.value === null) { | |||||
Vue.set(doc, 'value', neu) | |||||
Vue.set(data, 'value', JSON.parse(JSON.stringify(neu))) | |||||
addfields() | |||||
} else { | |||||
const user1 = jot.diff(doc.value, neu) | |||||
const user2 = jot.diff(doc.value, data.value) | |||||
const user3 = user2.rebase(user1, { document: doc.value }) | |||||
Vue.set(data, 'value', user1.compose(user3).apply(JSON.parse(JSON.stringify(doc.value)))) | |||||
Vue.set(doc, 'value', JSON.parse(JSON.stringify(neu))) | |||||
addfields() | |||||
} | |||||
} | |||||
// return { doc, data, del, close, save, ...arrays, [coll]: apollodoc } | |||||
return { doc, data, close, del, save } | |||||
} |
return { connected, login } | return { connected, login } | ||||
} | } | ||||
export const updateQuery = (a, b) => (alt, neu) => { | |||||
const i = alt[a].findIndex(old => old._id === neu.subscriptionData.data[b]._id) | |||||
if (i !== -1) return | |||||
return { [a]: [...alt[a], neu.subscriptionData.data[b]] } | |||||
} | |||||
export const deleteQuery = (a, b, alt, data) => { | |||||
const id = data?.data[b] | |||||
const i = alt.findIndex(old => old._id === id) | |||||
if (i !== -1) { | |||||
alt.splice(i, 1) | |||||
} | |||||
} |
md="auto" | md="auto" | ||||
> | > | ||||
<div class="body-1 font-weight-light pt-6 pt-md-0 text-center"> | <div class="body-1 font-weight-light pt-6 pt-md-0 text-center"> | ||||
© 2020 IT Kimmig | |||||
© 2021 IT Kimmig | |||||
</div> | </div> | ||||
</v-col> | </v-col> | ||||
</v-row> | </v-row> |
<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="6" | |||||
> | |||||
<v-text-field | |||||
v-model="data.givenName" | |||||
label="Vorname" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="6" | |||||
> | |||||
<v-text-field | |||||
v-model="data.familyName" | |||||
label="Nachname" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="12" | |||||
> | |||||
<v-text-field | |||||
v-model="data.email" | |||||
label="E-Mail-Adresse" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="12" | |||||
> | |||||
<v-checkbox | |||||
v-model="data.master" | |||||
label="System-Administrator" | |||||
/> | |||||
</v-col> | |||||
<v-col | |||||
cols="6" | |||||
> | |||||
<h3>Administrator von:</h3> | |||||
{{ adminOf }} | |||||
</v-col> | |||||
<v-col | |||||
cols="6" | |||||
> | |||||
<h3>Veranstalter von:</h3> | |||||
{{ organizerOf }} | |||||
</v-col> | |||||
</v-row> | |||||
</base-material-dialog> | |||||
</template> | |||||
<script> | |||||
import { useAuth } from '@/plugins/auth' | |||||
import { useEditDialog } from '@/plugins/editdialog' | |||||
export default { | |||||
name: 'EditPerson', | |||||
props: { | |||||
value: { | |||||
type: Boolean, | |||||
required: true | |||||
}, | |||||
id: { | |||||
type: String, | |||||
default: null | |||||
} | |||||
}, | |||||
setup (props, context) { | |||||
return { | |||||
...useAuth(context), | |||||
...useEditDialog(props, context, 'Person(id: $id) { _id givenName familyName email master adminOf { name plz ort } organizerOf { name plz ort } }', ['givenName', 'familyName', 'email', 'master']) | |||||
} | |||||
}, | |||||
computed: { | |||||
adminOf () { | |||||
return this.data.adminOf?.map(e => e.name + ' (' + e.plz + ' ' + e.ort + ')')?.join('<br>') | |||||
}, | |||||
organizerOf () { | |||||
return this.data.organizerOf?.map(e => e.name + ' (' + e.plz + ' ' + e.ort + ')')?.join('<br>') | |||||
} | |||||
}, | |||||
methods: { | |||||
update () { | |||||
return { | |||||
mutation: `mutation($id: UUID!, $givenName: String, $familyName: String, $email: EmailAddress, $master: Boolean) { | |||||
PersonUpdate(id: $id, givenName: $givenName, familyName: $familyName, email: $email, master: $master) { | |||||
_id givenName familyName email master | |||||
} | |||||
}`, | |||||
variables: { | |||||
master: !!this.data.master | |||||
} | |||||
} | |||||
}, | |||||
create () { | |||||
return { | |||||
mutation: `mutation($givenName: String!, $familyName: String!, $email: EmailAddress!, $master: Boolean) { | |||||
PersonCreate(givenName: $givenName, familyName: $familyName, email: $email, master: $master) { | |||||
_id givenName familyName email master | |||||
} | |||||
}`, | |||||
variables: { | |||||
master: !!this.data.master | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="sass"> | |||||
$dialog-elevation: 0 | |||||
</style> |
@click:row="open" | @click:row="open" | ||||
/> | /> | ||||
</v-card> | </v-card> | ||||
<!--edit-people | |||||
<edit-person | |||||
:id="dialog.id" | :id="dialog.id" | ||||
v-model="dialog.open" | v-model="dialog.open" | ||||
/--> | |||||
/> | |||||
</v-container> | </v-container> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import { useAuth } from '@/plugins/auth' | import { useAuth } from '@/plugins/auth' | ||||
import gql from 'graphql-tag' | import gql from 'graphql-tag' | ||||
// import { updateQuery, deleteQuery } from '@/graphql' | |||||
import { updateQuery, deleteQuery } from '@/plugins/graphql' | |||||
export default { | export default { | ||||
name: 'People', | name: 'People', | ||||
components: { | components: { | ||||
// EditPeople: () => import('./dialogs/EditPeople'), | |||||
EditPerson: () => import('./dialogs/EditPerson') | |||||
}, | }, | ||||
setup (props, context) { | setup (props, context) { | ||||
apollo: { | apollo: { | ||||
PersonFind: { | 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 } } }`, | |||||
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('PersonList', 'PersonUpdated') | ||||
} */ | |||||
} | |||||
/* $subscribe: { | |||||
} | |||||
}, | |||||
$subscribe: { | |||||
PersonDeleted: { | PersonDeleted: { | ||||
query: gql`subscription { PersonDeleted }`, | query: gql`subscription { PersonDeleted }`, | ||||
result (id) { | result (id) { | ||||
deleteQuery('PersonList', 'PersonUpdated', this.PersonList, id) | |||||
}, | |||||
}, | |||||
}, */ | |||||
deleteQuery('PersonFind', 'PersonDeleted', this.PersonFind, id) | |||||
} | |||||
} | |||||
} | |||||
}, | }, | ||||
methods: { | methods: { |
PersonRegister(passwort: String!, email: EmailAddress!, familyName: String!, givenName: String!, organizer: UUID!): Person! | PersonRegister(passwort: String!, email: EmailAddress!, familyName: String!, givenName: String!, organizer: UUID!): Person! | ||||
PersonConfirmMail(confirmCode: String!, email: String!): Person | PersonConfirmMail(confirmCode: String!, email: String!): Person | ||||
ChangePassword(newPassword: String!, oldPassword: String!): Boolean! | ChangePassword(newPassword: String!, oldPassword: String!): Boolean! | ||||
PersonUpdate(master: Boolean, email: EmailAddress, familyName: String, givenName: String, id: UUID!): Person! | |||||
PersonCreate(master: Boolean, email: EmailAddress!, familyName: String!, givenName: String!): Person! | |||||
PersonDelete(id: UUID!): UUID! | |||||
OrganizerRegister(ort: String, plz: Int, name: String!): Organizer! | OrganizerRegister(ort: String, plz: Int, name: String!): Organizer! | ||||
EventCreate(name: String, date: Date!, organizer: UUID!): Event! | EventCreate(name: String, date: Date!, organizer: UUID!): Event! | ||||
} | } | ||||
type Subscription { | type Subscription { | ||||
PersonUpdated: Person! | |||||
PersonUpdated: Person | |||||
PersonDeleted: UUID! | |||||
} | } |
return db.doOps(this.collection, id, ops, filter, client); | return db.doOps(this.collection, id, ops, filter, client); | ||||
} | } | ||||
protected async insert(client: Client, doc: any): Promise<E> { | |||||
public async insert(client: Client, doc: any): Promise<E> { | |||||
doc._id = uuid(); | doc._id = uuid(); | ||||
return db.insert(this.collection, doc, client); | return db.insert(this.collection, doc, client); | ||||
} | } | ||||
public async delete(client: Client, id: UUID): Promise<UUID> { | |||||
return db.delete(this.collection, id, client); | |||||
} | |||||
} | } |
import {OrganizerService} from '../../organizer/organizer.service' | import {OrganizerService} from '../../organizer/organizer.service' | ||||
import {EmailAddress} from '../../global/scalars/EmailAddress' | import {EmailAddress} from '../../global/scalars/EmailAddress' | ||||
import {checkPassword, secureHash} from '../../generate' | import {checkPassword, secureHash} from '../../generate' | ||||
import {pubsub} from '../../main' | |||||
@Resolver(() => Person) | @Resolver(() => Person) | ||||
export class PersonResolverM { | export class PersonResolverM { | ||||
const tmp = await this.service.create(client, givenName, familyName, email, passwort); | const tmp = await this.service.create(client, givenName, familyName, email, passwort); | ||||
if (!o._admins) { | if (!o._admins) { | ||||
organizerService.update(client, o._id, {$set: {_admins: [ tmp._id ] }}, {}); | |||||
await organizerService.update(client, o._id, {$set: {_admins: [ tmp._id ] }}, {}); | |||||
} else if (o._admins.length === 0) { | } else if (o._admins.length === 0) { | ||||
organizerService.update(client, o._id, {$push: {_admins: tmp._id }}, {}); | |||||
await organizerService.update(client, o._id, {$push: {_admins: tmp._id }}, {}); | |||||
} else if (!o._pending) { | } else if (!o._pending) { | ||||
organizerService.update(client, o._id, {$set: {_pending: [ tmp._id ] }}, {}); | |||||
await organizerService.update(client, o._id, {$set: {_pending: [ tmp._id ] }}, {}); | |||||
} else { | } else { | ||||
organizerService.update(client, o._id, {$push: {_pending: tmp._id }}, {}); | |||||
await organizerService.update(client, o._id, {$push: {_pending: tmp._id }}, {}); | |||||
} | } | ||||
// TODO: Mail verschicken | // TODO: Mail verschicken | ||||
pubsub.publish('PersonUpdated', { PersonUpdated: tmp }); | |||||
return tmp; | return tmp; | ||||
} | } | ||||
this.service.update(client, tmp._id, {$set: { passwort: await secureHash(newPassword) }}); | this.service.update(client, tmp._id, {$set: { passwort: await secureHash(newPassword) }}); | ||||
return true; | return true; | ||||
} | } | ||||
@Mutation(() => Person, { nullable: false }) | |||||
async PersonUpdate( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID, | |||||
@Args('givenName', { nullable: true }) givenName?: string, | |||||
@Args('familyName', { nullable: true }) familyName?: string, | |||||
@Args('email', { type: () => EmailAddress, nullable: true }) email?: EmailAddress, | |||||
@Args('master', { nullable: true }) master?: boolean | |||||
): Promise<Person> { | |||||
if (!client.isMaster() && !client.isSelf(id)) throw new HttpException('Access denied', 403); | |||||
const tmp = await this.service.findOneById(id); | |||||
const set: { | |||||
givenName?: string | |||||
familyName?: string | |||||
email?: EmailAddress | |||||
master?: boolean | |||||
} = {}; | |||||
if (givenName !== undefined && givenName !== tmp.givenName) set.givenName = givenName | |||||
if (familyName !== undefined && familyName !== tmp.familyName) set.familyName = familyName | |||||
if (email !== undefined && email !== tmp.email) set.email = email | |||||
if (client.isMaster() && master !== undefined && master !== tmp.master) set.master = master | |||||
if (Object.values(set).length === 0) return tmp; | |||||
const neu = await this.service.update(client, tmp._id, {$set: set}); | |||||
pubsub.publish('PersonUpdated', { PersonUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => Person, { nullable: false }) | |||||
async PersonCreate( | |||||
@Context('client') client: Client, | |||||
@Args('givenName', { nullable: false }) givenName: string, | |||||
@Args('familyName', { nullable: false }) familyName: string, | |||||
@Args('email', { type: () => EmailAddress, nullable: false }) email: EmailAddress, | |||||
@Args('master', { nullable: true }) master?: boolean | |||||
): Promise<Person> { | |||||
if (!client.isMaster()) throw new HttpException('Access denied', 403); | |||||
const neu = await this.service.insert(client, { givenName, familyName, email, master }); | |||||
pubsub.publish('PersonUpdated', { PersonUpdated: neu }); | |||||
return neu; | |||||
} | |||||
@Mutation(() => UUID, { nullable: false }) | |||||
async PersonDelete( | |||||
@Context('client') client: Client, | |||||
@Args('id', { type: () => UUID, nullable: false }) id: UUID | |||||
): Promise<UUID> { | |||||
if (!client.isMaster()) throw new HttpException('Access denied', 403); | |||||
this.service.delete(client, id); | |||||
pubsub.publish('PersonDeleted', { PersonDeleted: id }); | |||||
return id; | |||||
} | |||||
} | } |
import {Person} from '../models/Person' | import {Person} from '../models/Person' | ||||
import { pubsub } from '../../main' | import { pubsub } from '../../main' | ||||
import { UUID } from '../../global/scalars/UUID' | |||||
@Resolver(() => Person) | @Resolver(() => Person) | ||||
export class PersonResolverS { | export class PersonResolverS { | ||||
private readonly service: PersonService, | private readonly service: PersonService, | ||||
) {} | ) {} | ||||
@Subscription(returns => Person,{}) | |||||
@Subscription(() => Person,{ nullable: true }) | |||||
async PersonUpdated() { | async PersonUpdated() { | ||||
return pubsub.asyncIterator('PersonUpdated'); | return pubsub.asyncIterator('PersonUpdated'); | ||||
} | } | ||||
@Subscription(() => UUID, { nullable: false }) | |||||
async PersonDeleted() { | |||||
return pubsub.asyncIterator('PersonDeleted') | |||||
} | |||||
} | } |
const organizerService = new OrganizerService(); | const organizerService = new OrganizerService(); | ||||
const o = await organizerService.find({ _admins: client.getUser()._id }); | |||||
const o = await organizerService.find({ _admins: parent._id }); | |||||
return o.map(e => e._id); | return o.map(e => e._id); | ||||
} | } | ||||
const organizerService = new OrganizerService(); | const organizerService = new OrganizerService(); | ||||
return organizerService.find({ _admins: client.getUser()._id }); | |||||
return organizerService.find({ _admins: parent._id }); | |||||
} | } | ||||
@ResolveField(() => [UUID], { nullable: true }) | @ResolveField(() => [UUID], { nullable: true }) | ||||
const organizerService = new OrganizerService(); | const organizerService = new OrganizerService(); | ||||
const o = await organizerService.find({ $or: [{_organizers: client.getUser()._id}] }); | |||||
const o = await organizerService.find({ $or: [{_organizers: parent._id}] }); | |||||
return o.map(e => e._id); | return o.map(e => e._id); | ||||
} | } | ||||
const organizerService = new OrganizerService(); | const organizerService = new OrganizerService(); | ||||
return await organizerService.find({ $or: [{_organizers: client.getUser()._id}] }); | |||||
return await organizerService.find({ $or: [{_organizers: parent._id}] }); | |||||
} | } | ||||
} | } |