@@ -14,6 +14,7 @@ | |||
"apollo-cache-inmemory": "^1.6.6", | |||
"apollo-client": "^2.6.10", | |||
"core-js": "^3.6.5", | |||
"jot": "git+https://github.com/joshdata/jot.git", | |||
"js-cookie": "^2.2.1", | |||
"lodash": "^4.17.21", | |||
"subscriptions-transport-ws": "^0.9.18", |
@@ -7,7 +7,6 @@ | |||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> | |||
<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://use.fontawesome.com/releases/v5.8.2/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous"> | |||
</head> | |||
<body> | |||
<noscript> |
@@ -1,20 +0,0 @@ | |||
<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> |
@@ -0,0 +1,177 @@ | |||
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 } | |||
} |
@@ -90,3 +90,17 @@ export const useGraphQL = (context) => { | |||
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) | |||
} | |||
} |
@@ -28,7 +28,7 @@ | |||
md="auto" | |||
> | |||
<div class="body-1 font-weight-light pt-6 pt-md-0 text-center"> | |||
© 2020 IT Kimmig | |||
© 2021 IT Kimmig | |||
</div> | |||
</v-col> | |||
</v-row> |
@@ -0,0 +1,130 @@ | |||
<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> |
@@ -38,23 +38,23 @@ | |||
@click:row="open" | |||
/> | |||
</v-card> | |||
<!--edit-people | |||
<edit-person | |||
: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'), | |||
EditPerson: () => import('./dialogs/EditPerson') | |||
}, | |||
setup (props, context) { | |||
@@ -86,20 +86,20 @@ export default { | |||
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 } } }`, | |||
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') | |||
} */ | |||
} | |||
/* $subscribe: { | |||
} | |||
}, | |||
$subscribe: { | |||
PersonDeleted: { | |||
query: gql`subscription { PersonDeleted }`, | |||
result (id) { | |||
deleteQuery('PersonList', 'PersonUpdated', this.PersonList, id) | |||
}, | |||
}, | |||
}, */ | |||
deleteQuery('PersonFind', 'PersonDeleted', this.PersonFind, id) | |||
} | |||
} | |||
} | |||
}, | |||
methods: { |
@@ -79,10 +79,14 @@ type Mutation { | |||
PersonRegister(passwort: String!, email: EmailAddress!, familyName: String!, givenName: String!, organizer: UUID!): Person! | |||
PersonConfirmMail(confirmCode: String!, email: String!): Person | |||
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! | |||
EventCreate(name: String, date: Date!, organizer: UUID!): Event! | |||
} | |||
type Subscription { | |||
PersonUpdated: Person! | |||
PersonUpdated: Person | |||
PersonDeleted: UUID! | |||
} |
@@ -20,9 +20,13 @@ export abstract class Service<E> { | |||
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(); | |||
return db.insert(this.collection, doc, client); | |||
} | |||
public async delete(client: Client, id: UUID): Promise<UUID> { | |||
return db.delete(this.collection, id, client); | |||
} | |||
} |
@@ -7,6 +7,7 @@ import {UUID} from '../../global/scalars/UUID' | |||
import {OrganizerService} from '../../organizer/organizer.service' | |||
import {EmailAddress} from '../../global/scalars/EmailAddress' | |||
import {checkPassword, secureHash} from '../../generate' | |||
import {pubsub} from '../../main' | |||
@Resolver(() => Person) | |||
export class PersonResolverM { | |||
@@ -56,17 +57,19 @@ export class PersonResolverM { | |||
const tmp = await this.service.create(client, givenName, familyName, email, passwort); | |||
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) { | |||
organizerService.update(client, o._id, {$push: {_admins: tmp._id }}, {}); | |||
await organizerService.update(client, o._id, {$push: {_admins: tmp._id }}, {}); | |||
} else if (!o._pending) { | |||
organizerService.update(client, o._id, {$set: {_pending: [ tmp._id ] }}, {}); | |||
await organizerService.update(client, o._id, {$set: {_pending: [ tmp._id ] }}, {}); | |||
} else { | |||
organizerService.update(client, o._id, {$push: {_pending: tmp._id }}, {}); | |||
await organizerService.update(client, o._id, {$push: {_pending: tmp._id }}, {}); | |||
} | |||
// TODO: Mail verschicken | |||
pubsub.publish('PersonUpdated', { PersonUpdated: tmp }); | |||
return tmp; | |||
} | |||
@@ -105,4 +108,67 @@ export class PersonResolverM { | |||
this.service.update(client, tmp._id, {$set: { passwort: await secureHash(newPassword) }}); | |||
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; | |||
} | |||
} |
@@ -3,6 +3,7 @@ import {PersonService} from '../person.service' | |||
import {Person} from '../models/Person' | |||
import { pubsub } from '../../main' | |||
import { UUID } from '../../global/scalars/UUID' | |||
@Resolver(() => Person) | |||
export class PersonResolverS { | |||
@@ -10,8 +11,13 @@ export class PersonResolverS { | |||
private readonly service: PersonService, | |||
) {} | |||
@Subscription(returns => Person,{}) | |||
@Subscription(() => Person,{ nullable: true }) | |||
async PersonUpdated() { | |||
return pubsub.asyncIterator('PersonUpdated'); | |||
} | |||
@Subscription(() => UUID, { nullable: false }) | |||
async PersonDeleted() { | |||
return pubsub.asyncIterator('PersonDeleted') | |||
} | |||
} |
@@ -68,7 +68,7 @@ export class PersonResolver { | |||
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); | |||
} | |||
@@ -81,7 +81,7 @@ export class PersonResolver { | |||
const organizerService = new OrganizerService(); | |||
return organizerService.find({ _admins: client.getUser()._id }); | |||
return organizerService.find({ _admins: parent._id }); | |||
} | |||
@ResolveField(() => [UUID], { nullable: true }) | |||
@@ -93,7 +93,7 @@ export class PersonResolver { | |||
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); | |||
} | |||
@@ -106,6 +106,6 @@ export class PersonResolver { | |||
const organizerService = new OrganizerService(); | |||
return await organizerService.find({ $or: [{_organizers: client.getUser()._id}] }); | |||
return await organizerService.find({ $or: [{_organizers: parent._id}] }); | |||
} | |||
} |