@@ -21,3 +21,5 @@ pnpm-debug.log* | |||
*.njsproj | |||
*.sln | |||
*.sw? | |||
/src/env.js |
@@ -9,6 +9,7 @@ | |||
"dependencies": { | |||
"@fortawesome/fontawesome-pro": "^5.15.3", | |||
"@mdi/font": "^5.9.55", | |||
"@pdf-lib/fontkit": "^1.1.1", | |||
"@vue/composition-api": "1.0.0-rc.12", | |||
"apollo-cache-inmemory": "^1.6.6", | |||
"apollo-client": "^2.6.10", | |||
@@ -17,6 +18,7 @@ | |||
"js-cookie": "^2.2.1", | |||
"lodash": "^4.17.21", | |||
"moment": "^2.29.1", | |||
"pdf-lib": "^1.16.0", | |||
"subscriptions-transport-ws": "^0.9.19", | |||
"uuid": "^3.4.0", | |||
"vue": "^2.6.14", | |||
@@ -1473,6 +1475,30 @@ | |||
"node": ">= 6" | |||
} | |||
}, | |||
"node_modules/@pdf-lib/fontkit": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@pdf-lib/fontkit/-/fontkit-1.1.1.tgz", | |||
"integrity": "sha512-KjMd7grNapIWS/Dm0gvfHEilSyAmeLvrEGVcqLGi0VYebuqqzTbgF29efCx7tvx+IEbG3zQciRSWl3GkUSvjZg==", | |||
"dependencies": { | |||
"pako": "^1.0.6" | |||
} | |||
}, | |||
"node_modules/@pdf-lib/standard-fonts": { | |||
"version": "1.0.0", | |||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", | |||
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", | |||
"dependencies": { | |||
"pako": "^1.0.6" | |||
} | |||
}, | |||
"node_modules/@pdf-lib/upng": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", | |||
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", | |||
"dependencies": { | |||
"pako": "^1.0.10" | |||
} | |||
}, | |||
"node_modules/@soda/friendly-errors-webpack-plugin": { | |||
"version": "1.8.0", | |||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz", | |||
@@ -10634,8 +10660,7 @@ | |||
"node_modules/pako": { | |||
"version": "1.0.11", | |||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", | |||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", | |||
"dev": true | |||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" | |||
}, | |||
"node_modules/parallel-transform": { | |||
"version": "1.2.0", | |||
@@ -10837,6 +10862,17 @@ | |||
"node": ">=0.12" | |||
} | |||
}, | |||
"node_modules/pdf-lib": { | |||
"version": "1.16.0", | |||
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.16.0.tgz", | |||
"integrity": "sha512-P/1SSmElOBKrPlbc+Sn7UxikRQbzVA64+4Dh6/uczPscvq/NatP9eryoOguyBTpTnzICNiG8EnMH4Ziqp2TnFA==", | |||
"dependencies": { | |||
"@pdf-lib/standard-fonts": "^1.0.0", | |||
"@pdf-lib/upng": "^1.0.1", | |||
"pako": "^1.0.11", | |||
"tslib": "^1.11.1" | |||
} | |||
}, | |||
"node_modules/performance-now": { | |||
"version": "2.1.0", | |||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", | |||
@@ -17585,6 +17621,30 @@ | |||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", | |||
"dev": true | |||
}, | |||
"@pdf-lib/fontkit": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/@pdf-lib/fontkit/-/fontkit-1.1.1.tgz", | |||
"integrity": "sha512-KjMd7grNapIWS/Dm0gvfHEilSyAmeLvrEGVcqLGi0VYebuqqzTbgF29efCx7tvx+IEbG3zQciRSWl3GkUSvjZg==", | |||
"requires": { | |||
"pako": "^1.0.6" | |||
} | |||
}, | |||
"@pdf-lib/standard-fonts": { | |||
"version": "1.0.0", | |||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", | |||
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", | |||
"requires": { | |||
"pako": "^1.0.6" | |||
} | |||
}, | |||
"@pdf-lib/upng": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", | |||
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", | |||
"requires": { | |||
"pako": "^1.0.10" | |||
} | |||
}, | |||
"@soda/friendly-errors-webpack-plugin": { | |||
"version": "1.8.0", | |||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.0.tgz", | |||
@@ -24932,8 +24992,7 @@ | |||
"pako": { | |||
"version": "1.0.11", | |||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", | |||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", | |||
"dev": true | |||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" | |||
}, | |||
"parallel-transform": { | |||
"version": "1.2.0", | |||
@@ -25108,6 +25167,17 @@ | |||
"sha.js": "^2.4.8" | |||
} | |||
}, | |||
"pdf-lib": { | |||
"version": "1.16.0", | |||
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.16.0.tgz", | |||
"integrity": "sha512-P/1SSmElOBKrPlbc+Sn7UxikRQbzVA64+4Dh6/uczPscvq/NatP9eryoOguyBTpTnzICNiG8EnMH4Ziqp2TnFA==", | |||
"requires": { | |||
"@pdf-lib/standard-fonts": "^1.0.0", | |||
"@pdf-lib/upng": "^1.0.1", | |||
"pako": "^1.0.11", | |||
"tslib": "^1.11.1" | |||
} | |||
}, | |||
"performance-now": { | |||
"version": "2.1.0", | |||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", |
@@ -10,6 +10,7 @@ | |||
"dependencies": { | |||
"@fortawesome/fontawesome-pro": "^5.15.3", | |||
"@mdi/font": "^5.9.55", | |||
"@pdf-lib/fontkit": "^1.1.1", | |||
"@vue/composition-api": "1.0.0-rc.12", | |||
"apollo-cache-inmemory": "^1.6.6", | |||
"apollo-client": "^2.6.10", | |||
@@ -18,6 +19,7 @@ | |||
"js-cookie": "^2.2.1", | |||
"lodash": "^4.17.21", | |||
"moment": "^2.29.1", | |||
"pdf-lib": "^1.16.0", | |||
"subscriptions-transport-ws": "^0.9.19", | |||
"uuid": "^3.4.0", | |||
"vue": "^2.6.14", |
@@ -21,8 +21,13 @@ | |||
class="error_snack" | |||
:timeout="5000" | |||
top | |||
multi-line | |||
> | |||
{{ error_text }} | |||
<template | |||
#default | |||
> | |||
<span v-html="error_text" /> | |||
</template> | |||
<template #action="{ attrs }"> | |||
<v-btn |
@@ -10,11 +10,33 @@ const routes = [ | |||
component: () => import('../views/Index'), | |||
children: [ | |||
{ | |||
path: '', | |||
name: 'Startseite', | |||
component: () => import('../views/components/start') | |||
}, | |||
{ | |||
path: 'login', | |||
name: 'Login', | |||
component: () => import('../views/Login') | |||
}, | |||
{ | |||
path: 'reset/:code?', | |||
name: 'Passwort zurücksetzen', | |||
component: () => import('../views/Reset'), | |||
props: true | |||
}, | |||
{ | |||
path: 'register', | |||
name: 'Registrierung', | |||
component: () => import('../views/Register') | |||
}, | |||
{ | |||
path: 'confirm/:code?', | |||
name: 'Bestätigung', | |||
component: () => import('../views/Confirm'), | |||
props: true | |||
}, | |||
{ | |||
path: 'management/person', | |||
name: 'Personen bearbeiten', | |||
component: () => import('../views/components/management/person') |
@@ -0,0 +1,111 @@ | |||
<template> | |||
<v-container | |||
fluid | |||
tag="section" | |||
> | |||
<v-card | |||
v-if="!code" | |||
flat | |||
> | |||
<h1>Code fehlt. Bitte den Link aus der E-Mail nutzen!</h1> | |||
</v-card> | |||
<v-card | |||
v-else-if="!confirmed" | |||
flat | |||
> | |||
<h1>Bestätigung der Registrierung</h1> | |||
<p>Bitte geben Sie zur Bestätigung nochmals Ihre E-Mail-Adresse ein.</p> | |||
<v-row> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="email" | |||
label="E-Mail-Adresse" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="code" | |||
label="Bestätigungscode" | |||
readonly | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-btn | |||
block | |||
@click="confirm" | |||
> | |||
Bestätigen | |||
</v-btn> | |||
</v-col> | |||
</v-row> | |||
</v-card> | |||
<v-card | |||
v-else | |||
flat | |||
> | |||
<h1>Vielen Dank, Sie können sich nun anmelden.</h1> | |||
</v-card> | |||
</v-container> | |||
</template> | |||
<script> | |||
import { useGraphQL } from '@/plugins/graphql' | |||
import gql from 'graphql-tag' | |||
export default { | |||
name: 'PagesConfirm', | |||
setup (props, context) { | |||
return { | |||
...useGraphQL(context) | |||
} | |||
}, | |||
props: { | |||
code: { | |||
type: String, | |||
required: true | |||
} | |||
}, | |||
data: () => ({ | |||
email: null, | |||
confirmed: false | |||
}), | |||
methods: { | |||
confirm () { | |||
const err = [] | |||
const EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ | |||
if (!this.email) err.push('E-Mail-Adresse fehlt!') | |||
if (this.email && !EMAIL_ADDRESS_REGEX.test(this.email)) err.push('E-Mail-Adresse ungültig!') | |||
if (err.length !== 0) { | |||
this.$store.commit('OPEN_SNACKBAR', `<ul><li>${err.join('</li><li>')}</li></ul>`) | |||
return | |||
} | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($email: String!, $confirmCode: String!) { | |||
PersonConfirmMail(email: $email, confirmCode: $confirmCode) { _id } | |||
}`, | |||
variables: { | |||
email: this.email, | |||
confirmCode: this.code | |||
} | |||
}).then(() => { | |||
this.confirmed = true | |||
}).catch(e => { | |||
this.$store.commit('OPEN_SNACKBAR', e) | |||
}) | |||
} | |||
} | |||
} | |||
</script> |
@@ -16,6 +16,8 @@ | |||
</template> | |||
<script> | |||
import { generateUrkunde } from '@/urkunde' | |||
export default { | |||
name: 'DashboardIndex', | |||
@@ -26,13 +28,20 @@ export default { | |||
}, | |||
data: () => ({ | |||
expandOnHover: false | |||
expandOnHover: false, | |||
image: null | |||
}), | |||
computed: { | |||
loading () { | |||
return false // this.$store.state.loading | |||
} | |||
}, | |||
methods: { | |||
async generatePDF () { | |||
generateUrkunde([{ platz: '1. PLATZ', zeit: '0 MIN. 23 SEK.', team: 'Tolles Team' }, { platz: '2. PLATZ', zeit: '0 MIN. 24 SEK.', team: 'Fast Tolles Team' }, { platz: '3. PLATZ', zeit: '5 MIN. 24 SEK.', team: 'Schlechtes Team' }], 'Droste', 'Alexander Kimmig', this.image, true) | |||
} | |||
} | |||
} | |||
</script> |
@@ -0,0 +1,241 @@ | |||
<template> | |||
<v-container | |||
fluid | |||
tag="section" | |||
> | |||
<v-card | |||
v-if="registered" | |||
flat | |||
> | |||
<h1>Registrierung abgeschlossen</h1> | |||
<p>Vielen Dank für Ihre Registrierung</p> | |||
<p>Sie erhalten eine E-Mail mit einem Link zur Bestätigung der Registrierung.</p> | |||
<p style="font-style: italic;">Sollten Sie keine E-Mail erhalten, so sehen Sie bitte auch im Spam-Ordner nach!</p> | |||
</v-card> | |||
<v-card | |||
v-else | |||
flat | |||
> | |||
<h1>Registrierung</h1> | |||
<p>Hier können Sie sich kostenlos für das System registrieren.</p> | |||
<p>Anmerkung: School-in-Motion ist in erster Linie für Schulen konzipiert. Wird eine Registrierung missbräuchlich verwendet, so behalten wir uns vor, den Nutzer zu sperren bzw. zu löschen.</p> | |||
<v-row> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="plz" | |||
label="Postleitzahl" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-select | |||
v-model="schule" | |||
:items="organizer" | |||
label="Schule auswählen" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
> | |||
<v-text-field | |||
v-if="schule && schule !== -1" | |||
:value="schule.name" | |||
label="Schulname" | |||
disabled | |||
/> | |||
<v-text-field | |||
v-else | |||
v-model="name" | |||
label="Schulname" | |||
:disabled="schule !== -1" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
> | |||
<v-text-field | |||
v-if="schule && schule !== -1" | |||
:value="schule.ort" | |||
label="Ort/Stadt" | |||
disabled | |||
/> | |||
<v-text-field | |||
v-else | |||
v-model="ort" | |||
label="Ort/Stadt" | |||
:disabled="schule !== -1" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
> | |||
<v-text-field | |||
v-model="givenName" | |||
label="Vorname" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
> | |||
<v-text-field | |||
v-model="familyName" | |||
label="Nachname" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="email" | |||
label="E-Mail-Adresse" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
> | |||
<v-text-field | |||
v-model="password" | |||
type="password" | |||
label="Passwort" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
sm="6" | |||
> | |||
<v-text-field | |||
v-model="password2" | |||
type="password" | |||
label="Passwort wiederholen" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-btn | |||
block | |||
@click="register" | |||
> | |||
Registrieren | |||
</v-btn> | |||
</v-col> | |||
</v-row> | |||
</v-card> | |||
</v-container> | |||
</template> | |||
<script> | |||
import { useGraphQL } from '@/plugins/graphql' | |||
import gql from 'graphql-tag' | |||
export default { | |||
name: 'PagesRegister', | |||
setup (props, context) { | |||
return { | |||
...useGraphQL(context) | |||
} | |||
}, | |||
data: () => ({ | |||
plz: null, | |||
ort: null, | |||
name: null, | |||
givenName: null, | |||
familyName: null, | |||
email: null, | |||
password: null, | |||
password2: null, | |||
schule: null, | |||
OrganizerFind: [], | |||
registered: false | |||
}), | |||
computed: { | |||
organizer () { | |||
return [{ value: null, text: 'bitte Schule auswählen...', disabled: true }, ...this.OrganizerFind.map(o => ({ value: o, text: o.name })), { value: -1, text: 'neue Schule anlegen' }] | |||
} | |||
}, | |||
methods: { | |||
async register () { | |||
const err = [] | |||
const EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ | |||
if (!this.plz) err.push('Postleitzahl fehlt!') | |||
if (isNaN(parseInt(this.plz))) err.push('Postleitzahl darf nur aus Ziffern bestehen!') | |||
if (!this.schule) err.push('Schule fehlt!') | |||
if (this.schule === -1 && !this.name) err.push('Schulname fehlt!') | |||
if (this.schule === -1 && !this.ort) err.push('Schulort fehlt!') | |||
if (!this.givenName) err.push('Vorname fehlt!') | |||
if (!this.familyName) err.push('Nachname fehlt!') | |||
if (!this.email) err.push('E-Mail-Adresse fehlt!') | |||
if (this.email && !EMAIL_ADDRESS_REGEX.test(this.email)) err.push('E-Mail-Adresse ungültig!') | |||
if (!this.password) err.push('Passwort fehlt!') | |||
if (this.password !== this.password2) err.push('Passwörter stimmen nicht überein!') | |||
if (err.length !== 0) { | |||
this.$store.commit('OPEN_SNACKBAR', `<ul><li>${err.join('</li><li>')}</li></ul>`) | |||
return | |||
} | |||
let schulid = null | |||
if (this.schule === -1) { | |||
const tmp = await this.$apollo.mutate({ | |||
mutation: gql`mutation($name: String!, $plz: Int!, $ort: String!) { | |||
OrganizerRegister(name: $name, plz: $plz, ort: $ort) { _id } | |||
}`, | |||
variables: { | |||
name: this.name, | |||
plz: parseInt(this.plz), | |||
ort: this.ort | |||
} | |||
}) | |||
schulid = tmp.data.OrganizerRegister._id | |||
} else { | |||
schulid = this.schule._id | |||
} | |||
await this.$apollo.mutate({ | |||
mutation: gql`mutation($organizer: UUID!, $givenName: String!, $familyName: String!, $email: EmailAddress!, $passwort: String!) { | |||
PersonRegister(organizer: $organizer, givenName: $givenName, familyName: $familyName, email: $email, passwort: $passwort) { _id } | |||
}`, | |||
variables: { | |||
organizer: schulid, | |||
givenName: this.givenName, | |||
familyName: this.familyName, | |||
email: this.email, | |||
passwort: this.password | |||
} | |||
}) | |||
this.registered = true | |||
} | |||
}, | |||
watch: { | |||
plz (v) { | |||
const tmp = parseInt(v) | |||
if (isNaN(tmp)) return | |||
this.$apollo.query({ | |||
query: gql`query($plz: Int!) { OrganizerFind(plz: $plz) { _id name plz ort } }`, | |||
variables: { | |||
plz: tmp | |||
} | |||
}).then((r) => { | |||
this.OrganizerFind = r.data.OrganizerFind | |||
}) | |||
} | |||
} | |||
} | |||
</script> |
@@ -0,0 +1,208 @@ | |||
<template> | |||
<v-container | |||
id="login" | |||
class="fill-height justify-center" | |||
tag="section" | |||
> | |||
<v-row justify="center"> | |||
<base-material-card | |||
v-if="resetCode === null" | |||
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"> | |||
Passwort zurücksetzen | |||
</h1> | |||
</div> | |||
</template> | |||
<v-card-text class="text-center"> | |||
<v-row> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="email" | |||
color="red" | |||
label="E-Mail..." | |||
prepend-icon="mdi-email" | |||
name="email" | |||
@keydown.enter="reset" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-btn | |||
class="mb-5" | |||
color="red" | |||
tile | |||
@click="reset" | |||
width="100%" | |||
> | |||
<v-icon class="mr-4"> | |||
fa-sign-in-alt | |||
</v-icon> | |||
Passwort zurücksetzen | |||
</v-btn> | |||
</v-col> | |||
</v-row> | |||
</v-card-text> | |||
</base-material-card> | |||
<base-material-card | |||
v-else | |||
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"> | |||
Passwort zurücksetzen | |||
</h1> | |||
</div> | |||
</template> | |||
<v-card-text class="text-center"> | |||
<v-row> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="email" | |||
color="red" | |||
label="E-Mail-Adresse" | |||
prepend-icon="far fa-envelope" | |||
name="email" | |||
@keydown.enter="reset" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="resetCode" | |||
label="Code" | |||
prepend-icon="far fa-key-skeleton" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="newPassword" | |||
type="password" | |||
label="neues Passwort" | |||
prepend-icon="far fa-key" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-text-field | |||
v-model="newPassword2" | |||
type="password" | |||
label="Passwort wiederholen" | |||
prepend-icon="far fa-key" | |||
/> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
> | |||
<v-btn | |||
class="mb-5" | |||
color="red" | |||
tile | |||
block | |||
@click="reset" | |||
> | |||
<v-icon class="mr-4"> | |||
fa-sign-in-alt | |||
</v-icon> | |||
Passwort zurücksetzen | |||
</v-btn> | |||
</v-col> | |||
</v-row> | |||
</v-card-text> | |||
</base-material-card> | |||
</v-row> | |||
</v-container> | |||
</template> | |||
<script> | |||
import { useGraphQL } from '@/plugins/graphql' | |||
import gql from 'graphql-tag' | |||
export default { | |||
name: 'PagesLogin', | |||
setup (props, context) { | |||
return { | |||
...useGraphQL(context) | |||
} | |||
}, | |||
props: { | |||
code: { | |||
type: String, | |||
required: false, | |||
default: null | |||
} | |||
}, | |||
data: () => ({ | |||
valid: true, | |||
email: '', | |||
resetCode: null, | |||
newPassword: null, | |||
newPassword2: null | |||
}), | |||
methods: { | |||
reset () { | |||
const err = [] | |||
const EMAIL_ADDRESS_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ | |||
if (!this.email) err.push('E-Mail-Adresse fehlt!') | |||
if (this.email && !EMAIL_ADDRESS_REGEX.test(this.email)) err.push('E-Mail-Adresse ungültig!') | |||
if (this.resetCode && !this.newPassword) err.push('Passwort fehlt!') | |||
if (this.newPassword !== this.newPassword2) err.push('Passwörter stimmen nicht überein!') | |||
if (err.length !== 0) { | |||
this.$store.commit('OPEN_SNACKBAR', `<ul><li>${err.join('</li><li>')}</li></ul>`) | |||
return | |||
} | |||
this.$apollo.mutate({ | |||
mutation: gql`mutation($email: EmailAddress!, $resetCode: String, $newPassword: String) { | |||
PersonPasswordReset(email: $email, resetCode: $resetCode, newPassword: $newPassword) | |||
}`, | |||
variables: { | |||
email: this.email, | |||
resetCode: this.resetCode || null, | |||
newPassword: this.newPassword || null | |||
} | |||
}).then(ret => { | |||
this.resetCode = '' | |||
if (this.resetCode !== null && this.newPassword !== null && ret?.data?.PersonPasswordReset === true) { | |||
this.$router.replace('/login') | |||
} | |||
}).catch(e => { | |||
this.$store.commit('OPEN_SNACKBAR', e) | |||
}) | |||
} | |||
}, | |||
mounted () { | |||
if (this.code) this.resetCode = this.code | |||
} | |||
} | |||
</script> |
@@ -19,8 +19,9 @@ | |||
<v-list-item two-line> | |||
<v-list-item-content> | |||
<v-list-item-title class="text-uppercase font-weight-regular display-2"> | |||
<span class="logo-normal">Turnen auf Zeit</span> | |||
<v-list-item-avatar><img src="../../../assets/bogen.png"/></v-list-item-avatar> | |||
<v-list-item-title class="font-weight-regular display-2 text-center"> | |||
<span class="logo-normal">schoolINmotion</span> | |||
</v-list-item-title> | |||
</v-list-item-content> | |||
</v-list-item> |
@@ -23,6 +23,11 @@ | |||
> | |||
Rangliste | |||
</v-tab> | |||
<v-tab | |||
href="#urkunden" | |||
> | |||
Urkunden | |||
</v-tab> | |||
</v-tabs> | |||
<v-tabs-items | |||
v-model="tab" | |||
@@ -472,6 +477,93 @@ | |||
</template> | |||
</v-data-table> | |||
</v-tab-item> | |||
<v-tab-item | |||
value="urkunden" | |||
> | |||
<v-card | |||
flat | |||
> | |||
<v-row> | |||
<v-col | |||
cols="12" | |||
xl="6" | |||
order-xl="2" | |||
> | |||
<v-row> | |||
<v-col cols="12"> | |||
<v-btn | |||
block | |||
@click="generatePDF(true)" | |||
> | |||
Urkunden inklusive Hintergrund | |||
</v-btn> | |||
</v-col> | |||
<v-col cols="12"> | |||
<v-btn | |||
block | |||
@click="generatePDF(false)" | |||
> | |||
Urkunden für Vordruck | |||
</v-btn> | |||
</v-col> | |||
</v-row> | |||
</v-col> | |||
<v-col | |||
cols="12" | |||
xl="6" | |||
order-xl="1" | |||
> | |||
<v-row> | |||
<v-col cols="6"> | |||
<v-text-field | |||
v-model="urkunde.name" | |||
label="Name auf Urkunde" | |||
/> | |||
</v-col> | |||
<v-col cols="6"> | |||
<v-btn | |||
fab | |||
small | |||
@click="urkunde.name = person" | |||
class="mr-2" | |||
> | |||
<v-icon>far fa-long-arrow-alt-left</v-icon> | |||
</v-btn> | |||
übernehme "{{ person }}" | |||
</v-col> | |||
<v-col cols="6"> | |||
<v-text-field | |||
v-model="urkunde.schule" | |||
label="Organisator auf Urkunde" | |||
/> | |||
</v-col> | |||
<v-col cols="6"> | |||
<v-btn | |||
fab | |||
small | |||
@click="urkunde.schule = organizer" | |||
class="mr-2" | |||
> | |||
<v-icon>far fa-long-arrow-alt-left</v-icon> | |||
</v-btn> | |||
übernehme "{{ organizer }}" | |||
</v-col> | |||
<v-col cols="12"> | |||
Unterschrift auf Urkunde:<br> | |||
<v-image-input | |||
v-model="urkunde.unterschrift" | |||
clearable | |||
:image-height="256" | |||
:image-width="512" | |||
image-min-scaling="contain" | |||
hide-actions | |||
/> | |||
</v-col> | |||
</v-row> | |||
</v-col> | |||
</v-row> | |||
</v-card> | |||
</v-tab-item> | |||
</v-tabs-items> | |||
</v-container> | |||
</template> | |||
@@ -479,9 +571,10 @@ | |||
<script> | |||
import gql from 'graphql-tag' | |||
import moment from 'moment' | |||
import { generateUrkunde } from '@/urkunde' | |||
const timeslotquery = '_id time duration team { _id name } result { bonus runtime calctime results { _id teile bonus wechsel } }' | |||
const query = `_id name date _organizer apparatus { _apparatus apparatus { name logo } elements bonus malus } timeslots { ${timeslotquery} }` | |||
const query = `_id name date _organizer organizer { name } apparatus { _apparatus apparatus { name logo } elements bonus malus } timeslots { ${timeslotquery} }` | |||
export default { | |||
name: 'Event', | |||
@@ -577,6 +670,11 @@ export default { | |||
sortable: false | |||
} | |||
] | |||
}, | |||
urkunde: { | |||
name: '', | |||
schule: '', | |||
unterschrift: null | |||
} | |||
}), | |||
@@ -634,6 +732,12 @@ export default { | |||
} | |||
return acc | |||
}, []).sort((a, b) => a.time < b.time ? -1 : 1).map((m, i) => ({ ...m, platz: i + 1 })) | |||
}, | |||
organizer () { | |||
return this.Event?.organizer?.name | |||
}, | |||
person () { | |||
return `${this.$store.getters.profile?.givenName} ${this.$store.getters.profile?.familyName}` | |||
} | |||
}, | |||
@@ -824,6 +928,10 @@ export default { | |||
}) | |||
this.resultclose() | |||
}, | |||
async generatePDF (vorlage = false) { | |||
console.log(this.$store.getters.profile) | |||
generateUrkunde(this.ergebnisse.map(e => ({ platz: `${e.platz}. PLATZ`, zeit: `${Math.floor(e.time / 60)} MIN. ${Math.floor(e.time % 60)} SEK.`, team: e.team })), this.urkunde.schule, this.urkunde.name, this.urkunde.unterschrift, vorlage) | |||
} | |||
}, | |||
@@ -0,0 +1,41 @@ | |||
<template> | |||
<v-container | |||
fluid | |||
tag="section" | |||
> | |||
<img | |||
src="../../assets/logo_sim.jpg" | |||
/> | |||
<h2><b>schoolINmotion</b> ist ein Mannschaftswettbewerb, bei dem eine Anzahl von zwei bis vier Schülerinnen und Schülern in einem Team antreten. Es ist dabei unerheblich, ob das Team aus nur Jungen, nur Mädchen oder als gemischtes Team zusammengestellt wird.</h2> | |||
<h3>Ziel des Wettbewerbs ist es, eine möglichst schnelle Zeit in einem Geräte-Parcours zu erzielen. Zusätzlich sind an den einzelnen Geräten turnerische Elemente zu erfüllen.</h3> | |||
<h3>Der zu absolvierende Parcours besteht aus 4 Geräten, an welchen jeweils drei Elemente gezeigt werden müssen. Jedes Teammitglied kann eine oder zwei Geräte turnen, allerdings nicht direkt hintereinander. Es zählt die Team-Zeit, nicht die beste Ausführung! Auf turnerische Aspekte, wie die korrekte Ausführung von Pflichtübungen wird bewusst weniger Wert gelegt. So garantiert das gemeinsame Turnen Spaß und Action.</h3> | |||
<p style="clear: both;"></p> | |||
</v-container> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'start' | |||
} | |||
</script> | |||
<style scoped> | |||
img { | |||
width: 400px; | |||
height: auto; | |||
float: right; | |||
margin: 0px 24px; | |||
} | |||
h2 { | |||
line-height: 180%; | |||
margin-bottom: 16px; | |||
letter-spacing: 0.2px; | |||
} | |||
h3 { | |||
line-height: 180%; | |||
letter-spacing: 0.2px; | |||
margin-bottom: 12px; | |||
} | |||
</style> |
@@ -34,3 +34,4 @@ lerna-debug.log* | |||
!.vscode/extensions.json | |||
/assets/keys | |||
/src/env.ts |
@@ -21,6 +21,8 @@ | |||
"js-base64": "^3.6.0", | |||
"jsonwebtoken": "^8.5.1", | |||
"mongodb": "^3.6.6", | |||
"nodemailer": "^6.6.3", | |||
"nodemailer-smtp-transport": "^2.7.4", | |||
"prompt-async": "^0.9.9", | |||
"reflect-metadata": "^0.1.13", | |||
"rimraf": "^3.0.2", | |||
@@ -6550,6 +6552,26 @@ | |||
"npm": ">=1.3.7" | |||
} | |||
}, | |||
"node_modules/httpntlm": { | |||
"version": "1.6.1", | |||
"resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", | |||
"integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", | |||
"dependencies": { | |||
"httpreq": ">=0.4.22", | |||
"underscore": "~1.7.0" | |||
}, | |||
"engines": { | |||
"node": ">=0.8.0" | |||
} | |||
}, | |||
"node_modules/httpreq": { | |||
"version": "0.5.2", | |||
"resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.5.2.tgz", | |||
"integrity": "sha512-2Jm+x9WkExDOeFRrdBCBSpLPT5SokTcRHkunV3pjKmX/cx6av8zQ0WtHUMDrYb6O4hBFzNU6sxJEypvRUVYKnw==", | |||
"engines": { | |||
"node": ">= 6.15.1" | |||
} | |||
}, | |||
"node_modules/https-proxy-agent": { | |||
"version": "5.0.0", | |||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", | |||
@@ -8840,6 +8862,42 @@ | |||
"integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", | |||
"dev": true | |||
}, | |||
"node_modules/nodemailer": { | |||
"version": "6.6.3", | |||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.3.tgz", | |||
"integrity": "sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew==", | |||
"engines": { | |||
"node": ">=6.0.0" | |||
} | |||
}, | |||
"node_modules/nodemailer-fetch": { | |||
"version": "1.6.0", | |||
"resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", | |||
"integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=" | |||
}, | |||
"node_modules/nodemailer-shared": { | |||
"version": "1.1.0", | |||
"resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", | |||
"integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=", | |||
"dependencies": { | |||
"nodemailer-fetch": "1.6.0" | |||
} | |||
}, | |||
"node_modules/nodemailer-smtp-transport": { | |||
"version": "2.7.4", | |||
"resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.4.tgz", | |||
"integrity": "sha1-DYmvAZoUSkgP2OzJmZfZ+DjxNoU=", | |||
"dependencies": { | |||
"nodemailer-shared": "1.1.0", | |||
"nodemailer-wellknown": "0.1.10", | |||
"smtp-connection": "2.12.0" | |||
} | |||
}, | |||
"node_modules/nodemailer-wellknown": { | |||
"version": "0.1.10", | |||
"resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", | |||
"integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=" | |||
}, | |||
"node_modules/nopt": { | |||
"version": "5.0.0", | |||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", | |||
@@ -10849,6 +10907,15 @@ | |||
"npm": ">= 3.0.0" | |||
} | |||
}, | |||
"node_modules/smtp-connection": { | |||
"version": "2.12.0", | |||
"resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", | |||
"integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=", | |||
"dependencies": { | |||
"httpntlm": "1.6.1", | |||
"nodemailer-shared": "1.1.0" | |||
} | |||
}, | |||
"node_modules/snapdragon": { | |||
"version": "0.8.2", | |||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", | |||
@@ -12267,6 +12334,11 @@ | |||
"url": "https://github.com/sponsors/ljharb" | |||
} | |||
}, | |||
"node_modules/underscore": { | |||
"version": "1.7.0", | |||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", | |||
"integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" | |||
}, | |||
"node_modules/union-value": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", | |||
@@ -18289,6 +18361,20 @@ | |||
"sshpk": "^1.7.0" | |||
} | |||
}, | |||
"httpntlm": { | |||
"version": "1.6.1", | |||
"resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", | |||
"integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", | |||
"requires": { | |||
"httpreq": ">=0.4.22", | |||
"underscore": "~1.7.0" | |||
} | |||
}, | |||
"httpreq": { | |||
"version": "0.5.2", | |||
"resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.5.2.tgz", | |||
"integrity": "sha512-2Jm+x9WkExDOeFRrdBCBSpLPT5SokTcRHkunV3pjKmX/cx6av8zQ0WtHUMDrYb6O4hBFzNU6sxJEypvRUVYKnw==" | |||
}, | |||
"https-proxy-agent": { | |||
"version": "5.0.0", | |||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", | |||
@@ -20030,6 +20116,39 @@ | |||
"integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", | |||
"dev": true | |||
}, | |||
"nodemailer": { | |||
"version": "6.6.3", | |||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.3.tgz", | |||
"integrity": "sha512-faZFufgTMrphYoDjvyVpbpJcYzwyFnbAMmQtj1lVBYAUSm3SOy2fIdd9+Mr4UxPosBa0JRw9bJoIwQn+nswiew==" | |||
}, | |||
"nodemailer-fetch": { | |||
"version": "1.6.0", | |||
"resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", | |||
"integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=" | |||
}, | |||
"nodemailer-shared": { | |||
"version": "1.1.0", | |||
"resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", | |||
"integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=", | |||
"requires": { | |||
"nodemailer-fetch": "1.6.0" | |||
} | |||
}, | |||
"nodemailer-smtp-transport": { | |||
"version": "2.7.4", | |||
"resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.4.tgz", | |||
"integrity": "sha1-DYmvAZoUSkgP2OzJmZfZ+DjxNoU=", | |||
"requires": { | |||
"nodemailer-shared": "1.1.0", | |||
"nodemailer-wellknown": "0.1.10", | |||
"smtp-connection": "2.12.0" | |||
} | |||
}, | |||
"nodemailer-wellknown": { | |||
"version": "0.1.10", | |||
"resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", | |||
"integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=" | |||
}, | |||
"nopt": { | |||
"version": "5.0.0", | |||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", | |||
@@ -21553,6 +21672,15 @@ | |||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", | |||
"integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" | |||
}, | |||
"smtp-connection": { | |||
"version": "2.12.0", | |||
"resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", | |||
"integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=", | |||
"requires": { | |||
"httpntlm": "1.6.1", | |||
"nodemailer-shared": "1.1.0" | |||
} | |||
}, | |||
"snapdragon": { | |||
"version": "0.8.2", | |||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", | |||
@@ -22670,6 +22798,11 @@ | |||
"which-boxed-primitive": "^1.0.2" | |||
} | |||
}, | |||
"underscore": { | |||
"version": "1.7.0", | |||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", | |||
"integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" | |||
}, | |||
"union-value": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", |
@@ -33,6 +33,8 @@ | |||
"js-base64": "^3.6.0", | |||
"jsonwebtoken": "^8.5.1", | |||
"mongodb": "^3.6.6", | |||
"nodemailer": "^6.6.3", | |||
"nodemailer-smtp-transport": "^2.7.4", | |||
"prompt-async": "^0.9.9", | |||
"reflect-metadata": "^0.1.13", | |||
"rimraf": "^3.0.2", |
@@ -109,6 +109,7 @@ type Mutation { | |||
login(token: String, passwort: String, email: String): Person! | |||
PersonRegister(passwort: String!, email: EmailAddress!, familyName: String!, givenName: String!, organizer: UUID!): Person! | |||
PersonConfirmMail(confirmCode: String!, email: String!): Person | |||
PersonPasswordReset(newPassword: String, resetCode: String, email: EmailAddress!): 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! |
@@ -0,0 +1,54 @@ | |||
import { | |||
DBuser, | |||
DBpwd, | |||
DBhost, | |||
DBport, | |||
DBsource, | |||
DBdb, | |||
MAILhost, | |||
MAILport, | |||
MAILsecure, | |||
MAILuser, | |||
MAILpwd, | |||
SERVERport, | |||
SERVERlisten, | |||
SERVERhost, | |||
ADMINmail, | |||
MAILaddress, MAILname, SYSTEMname | |||
} from './env'; | |||
export const SYSDATA = { | |||
name: SYSTEMname, | |||
} | |||
export const DBDATA = { | |||
user: DBuser, | |||
pwd: DBpwd, | |||
host: DBhost, | |||
port: DBport, | |||
source: DBsource, | |||
db: DBdb, | |||
}; | |||
export const SMTPDATA = { | |||
host: MAILhost, | |||
port: MAILport, | |||
secure: MAILsecure, | |||
user: MAILuser, | |||
pwd: MAILpwd, | |||
}; | |||
export const MAILDATA = { | |||
address: MAILaddress, | |||
name: MAILname, | |||
} | |||
export const SERVERDATA = { | |||
port: SERVERport, // 3000 | |||
listen: SERVERlisten, // '127.0.0.1' | |||
host: SERVERhost, // 'https://www.selbsttest-schule.de' | |||
}; | |||
export const ADMINDATA = { | |||
mail: ADMINmail, | |||
}; |
@@ -2,16 +2,9 @@ import * as MongoClient from 'mongodb'; | |||
import { Client } from './client'; | |||
import { UUID } from './global/scalars/UUID'; | |||
export class DB { | |||
private readonly DBDATA = { | |||
user: 'root', | |||
pwd: 'TurnenAufZeit', | |||
host: 'localhost', | |||
port: 27017, | |||
source: 'admin', | |||
db: 'TurnenAufZeit', | |||
}; | |||
import { DBDATA } from './config'; | |||
export class DB { | |||
private mongo: MongoClient; | |||
private db: MongoClient.Db; | |||
@@ -21,8 +14,8 @@ export class DB { | |||
public async connect(): MongoClient.Db { | |||
this.mongo = await new Promise((resolve, reject) => { | |||
MongoClient.connect(`mongodb://${this.DBDATA.user}:${this.DBDATA.pwd}@${this.DBDATA.host}:${this.DBDATA.port}`, { | |||
authSource: this.DBDATA.source, | |||
MongoClient.connect(`mongodb://${DBDATA.user}:${DBDATA.pwd}@${DBDATA.host}:${DBDATA.port}`, { | |||
authSource: DBDATA.source, | |||
useNewUrlParser: true, | |||
useUnifiedTopology: true, | |||
poolSize: 20, | |||
@@ -34,7 +27,7 @@ export class DB { | |||
}); | |||
}); | |||
this.db = this.mongo.db(this.DBDATA.db); | |||
this.db = this.mongo.db(DBDATA.db); | |||
} | |||
public collection(ops): MongoClient.Collection { |
@@ -0,0 +1,40 @@ | |||
import * as nodemailer from 'nodemailer'; | |||
import * as smtpMail from 'nodemailer-smtp-transport'; | |||
import { SMTPDATA, MAILDATA } from './config' | |||
import {EmailAddress} from './global/scalars/EmailAddress' | |||
export const sendmail = (to: EmailAddress, subject: string, text: string, cc?: string, bcc?: string, from?: string) => { | |||
const transporter = nodemailer.createTransport(smtpMail({ | |||
host: SMTPDATA.host, | |||
port: SMTPDATA.port, | |||
secure: SMTPDATA.secure, // use TLS | |||
auth: { | |||
user: SMTPDATA.user, | |||
pass: SMTPDATA.pwd, | |||
}, | |||
tls: { | |||
rejectUnauthorized: false | |||
} | |||
})) | |||
const sender = `"${MAILDATA.name}" <${MAILDATA.address}>`; | |||
const mailOptions: any = { | |||
from: sender, | |||
to, | |||
cc: cc ? cc : '', | |||
bcc: bcc ? bcc : '', | |||
subject, | |||
text, | |||
}; | |||
if(from) { | |||
mailOptions.replyTo = from; | |||
} | |||
transporter.sendMail(mailOptions, (err) => { | |||
if (err) { | |||
console.log('Fehler: ' + err); | |||
} | |||
}) | |||
} |
@@ -4,6 +4,8 @@ import { AppModule } from './app.module'; | |||
import { db } from './db'; | |||
import { initDB } from './init'; | |||
import { SERVERDATA } from './config'; | |||
import { PubSub } from 'graphql-subscriptions'; | |||
import {NestExpressApplication} from '@nestjs/platform-express' | |||
@@ -14,7 +16,7 @@ async function bootstrap(argv: string[]) { | |||
await initDB(argv.findIndex(a => a.toLowerCase() === 'init') !== -1, argv.findIndex(a => a.toLowerCase() === 'reset') !== -1); | |||
const app = await NestFactory.create<NestExpressApplication>(AppModule); | |||
await app.listen(3000); | |||
await app.listen(SERVERDATA.port, SERVERDATA.listen); | |||
} | |||
let args: string[]; |
@@ -8,6 +8,9 @@ import {OrganizerService} from '../../organizer/organizer.service' | |||
import {EmailAddress} from '../../global/scalars/EmailAddress' | |||
import {checkPassword, secureHash} from '../../generate' | |||
import {pubsub} from '../../main' | |||
import {sendmail} from '../../mail' | |||
import {SERVERDATA, SYSDATA} from '../../config' | |||
import { v4 as uuid } from 'uuid' | |||
@Resolver(() => Person) | |||
export class PersonResolverM { | |||
@@ -56,17 +59,42 @@ export class PersonResolverM { | |||
const tmp = await this.service.create(client, givenName, familyName, email, passwort); | |||
let isAdmin = false; | |||
if (!o._admins) { | |||
await organizerService.update(client, o._id, {$set: {_admins: [ tmp._id ] }}, {}); | |||
isAdmin = true; | |||
} else if (o._admins.length === 0) { | |||
await organizerService.update(client, o._id, {$push: {_admins: tmp._id }}, {}); | |||
isAdmin = true; | |||
} else if (!o._pending) { | |||
await organizerService.update(client, o._id, {$set: {_pending: [ tmp._id ] }}, {}); | |||
} else { | |||
await organizerService.update(client, o._id, {$push: {_pending: tmp._id }}, {}); | |||
} | |||
// TODO: Mail verschicken | |||
const link = `${SERVERDATA.host}/confirm/${(tmp as unknown as any).confirmCode}` | |||
const text = `Vielen Dank für Ihre Registrierung bei ${SYSDATA.name} | |||
Bitte bestätigen Sie unter folgendem Link Ihre Registrierung: | |||
${link}` + (isAdmin?'':` | |||
Sie müssen außerdem zunächst von einem Administrator Ihrer Schule/Ihres Vereins freigeschaltet werden. Dieser erhält ebenfalls eine E-Mail über Ihre Registrierung.`); | |||
sendmail(email,`[${SYSDATA.name}] Neu-Registrierung`, text); | |||
if (!isAdmin) { | |||
const admintext = `${givenName} ${familyName} hat sich neu bei ${SYSDATA.name} für Ihre Schule/Ihren Verein ${o.name} registriert. | |||
Bitte melden Sie sich unter ${SERVERDATA.host} an und bearbeiten die Registrierung.` | |||
for (let i = 0; i < o._admins?.length; i++) { | |||
const p = await this.service.findOneById(o._admins[i]); | |||
sendmail(p.email, `[${SYSDATA.name}] Neu-Registrierung für ${o.name}`, admintext); | |||
} | |||
} | |||
pubsub.publish('PersonUpdated', { PersonUpdated: tmp }); | |||
@@ -82,13 +110,52 @@ export class PersonResolverM { | |||
const tmp = await this.service.find({email, confirmCode}); | |||
if (tmp.length !== 1) { | |||
throw new HttpException('confirmCode not correct', 403); | |||
throw new HttpException('E-Mail-Adresse oder Bestätigungscode ungültig', 403); | |||
} | |||
this.service.update(client, tmp[0]._id, { $unset: { confirmCode } }) | |||
return tmp[0]; | |||
} | |||
@Mutation(() => Boolean, { nullable: true }) | |||
async PersonPasswordReset( | |||
@Context('client') client: Client, | |||
@Args('email') email: EmailAddress, | |||
@Args('resetCode', { type: () => String, nullable: true }) resetCode?: string, | |||
@Args('newPassword', { type: () => String, nullable: true }) newPassword?: string, | |||
): Promise<boolean> { | |||
const tmp = await this.service.find({email}); | |||
console.log((tmp[0] as unknown as any).resetCode) | |||
console.log(resetCode) | |||
if (tmp.length === 1) { | |||
if (!resetCode && !newPassword) { | |||
const code = uuid(); | |||
this.service.update(client, tmp[0]._id, {$set: {resetCode: code}}) | |||
const link = `${SERVERDATA.host}/reset/${code}` | |||
const text = `Unter folgendem Link können Sie Ihr Passwort zurücksetzen: | |||
${link} | |||
Alternativ können Sie auch den folgenden Code in das Eingabefeld kopieren: | |||
${code}` | |||
console.log(text); | |||
sendmail(email,`[${SYSDATA.name}] Passwort zurücksetzen`, text); | |||
} else if ((tmp[0] as unknown as any).resetCode === resetCode) { | |||
this.service.update(client, tmp[0]._id, {$set: { passwort: await secureHash(newPassword) }, $unset: { resetCode: '' } }); | |||
return true; | |||
} | |||
} | |||
return null; | |||
} | |||
@Mutation(() => Boolean, { nullable: false }) | |||
async ChangePassword( | |||
@Context('client') client: Client, |