Procházet zdrojové kódy

Personen bearbeiten

tags/v0.9.1
akimmig před 4 roky
rodič
revize
ab235e7f68
14 změnil soubory, kde provedl 850 přidání a 118 odebrání
  1. +422
    -71
      client/package-lock.json
  2. +1
    -0
      client/package.json
  3. +0
    -1
      client/public/index.html
  4. +0
    -20
      client/src/components/Demo.vue
  5. +177
    -0
      client/src/plugins/editdialog.js
  6. +14
    -0
      client/src/plugins/graphql.js
  7. +1
    -1
      client/src/views/components/core/Footer.vue
  8. +130
    -0
      client/src/views/components/management/dialogs/EditPerson.vue
  9. +14
    -14
      client/src/views/components/management/person.vue
  10. +5
    -1
      server/schema.gql
  11. +5
    -1
      server/src/global/service.ts
  12. +70
    -4
      server/src/person/resolver/person.mutation.ts
  13. +7
    -1
      server/src/person/resolver/person.subscription.ts
  14. +4
    -4
      server/src/person/resolver/person.ts

+ 422
- 71
client/package-lock.json
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


+ 1
- 0
client/package.json Zobrazit soubor

@@ -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",

+ 0
- 1
client/public/index.html Zobrazit soubor

@@ -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>

+ 0
- 20
client/src/components/Demo.vue Zobrazit soubor

@@ -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>

+ 177
- 0
client/src/plugins/editdialog.js Zobrazit soubor

@@ -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 }
}

+ 14
- 0
client/src/plugins/graphql.js Zobrazit soubor

@@ -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)
}
}

+ 1
- 1
client/src/views/components/core/Footer.vue Zobrazit soubor

@@ -28,7 +28,7 @@
md="auto"
>
<div class="body-1 font-weight-light pt-6 pt-md-0 text-center">
&copy; 2020 IT Kimmig
&copy; 2021 IT Kimmig
</div>
</v-col>
</v-row>

+ 130
- 0
client/src/views/components/management/dialogs/EditPerson.vue Zobrazit soubor

@@ -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>

+ 14
- 14
client/src/views/components/management/person.vue Zobrazit soubor

@@ -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: {

+ 5
- 1
server/schema.gql Zobrazit soubor

@@ -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!
}

+ 5
- 1
server/src/global/service.ts Zobrazit soubor

@@ -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);
}
}

+ 70
- 4
server/src/person/resolver/person.mutation.ts Zobrazit soubor

@@ -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;
}
}

+ 7
- 1
server/src/person/resolver/person.subscription.ts Zobrazit soubor

@@ -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')
}
}

+ 4
- 4
server/src/person/resolver/person.ts Zobrazit soubor

@@ -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}] });
}
}

Načítá se…
Zrušit
Uložit