<excludeFolder url="file://$MODULE_DIR$/temp" /> | <excludeFolder url="file://$MODULE_DIR$/temp" /> | ||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" /> | <excludeFolder url="file://$MODULE_DIR$/.tmp" /> | ||||
<excludeFolder url="file://$MODULE_DIR$/tmp" /> | <excludeFolder url="file://$MODULE_DIR$/tmp" /> | ||||
<excludeFolder url="file://$MODULE_DIR$/server/dist" /> | |||||
</content> | </content> | ||||
<orderEntry type="inheritedJdk" /> | <orderEntry type="inheritedJdk" /> | ||||
<orderEntry type="sourceFolder" forTests="false" /> | <orderEntry type="sourceFolder" forTests="false" /> |
!.vscode/settings.json | !.vscode/settings.json | ||||
!.vscode/tasks.json | !.vscode/tasks.json | ||||
!.vscode/launch.json | !.vscode/launch.json | ||||
!.vscode/extensions.json | |||||
!.vscode/extensions.json | |||||
/assets/keys |
{ | { | ||||
"name": "server", | |||||
"name": "turnenaufzeit", | |||||
"version": "0.0.1", | "version": "0.0.1", | ||||
"description": "", | "description": "", | ||||
"author": "", | "author": "", | ||||
"test:e2e": "jest --config ./test/jest-e2e.json" | "test:e2e": "jest --config ./test/jest-e2e.json" | ||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"@apollo/gateway": "^0.26.1", | |||||
"@nestjs/common": "^7.6.15", | "@nestjs/common": "^7.6.15", | ||||
"@nestjs/core": "^7.6.15", | "@nestjs/core": "^7.6.15", | ||||
"@nestjs/graphql": "^7.10.6", | |||||
"@nestjs/platform-express": "^7.6.15", | "@nestjs/platform-express": "^7.6.15", | ||||
"apollo-server-express": "^2.23.0", | |||||
"bcrypt": "^5.0.1", | |||||
"crypto": "^1.0.1", | |||||
"graphql": "^15.5.0", | |||||
"js-base64": "^3.6.0", | |||||
"jsonwebtoken": "^8.5.1", | |||||
"mongodb": "^3.6.6", | |||||
"prompt-async": "^0.9.9", | |||||
"reflect-metadata": "^0.1.13", | "reflect-metadata": "^0.1.13", | ||||
"rimraf": "^3.0.2", | "rimraf": "^3.0.2", | ||||
"rxjs": "^6.6.6" | |||||
"rxjs": "^6.6.7", | |||||
"subscriptions-transport-ws": "^0.9.18", | |||||
"ws": "^7.4.5" | |||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@nestjs/cli": "^7.6.0", | "@nestjs/cli": "^7.6.0", | ||||
"@nestjs/schematics": "^7.3.0", | |||||
"@nestjs/schematics": "^7.3.1", | |||||
"@nestjs/testing": "^7.6.15", | "@nestjs/testing": "^7.6.15", | ||||
"@types/express": "^4.17.11", | "@types/express": "^4.17.11", | ||||
"@types/jest": "^26.0.22", | "@types/jest": "^26.0.22", | ||||
"@types/node": "^14.14.36", | |||||
"@types/supertest": "^2.0.10", | |||||
"@typescript-eslint/eslint-plugin": "^4.19.0", | |||||
"@typescript-eslint/parser": "^4.19.0", | |||||
"eslint": "^7.22.0", | |||||
"eslint-config-prettier": "^8.1.0", | |||||
"eslint-plugin-prettier": "^3.3.1", | |||||
"@types/node": "^14.14.41", | |||||
"@types/supertest": "^2.0.11", | |||||
"@typescript-eslint/eslint-plugin": "^4.22.0", | |||||
"@typescript-eslint/parser": "^4.22.0", | |||||
"eslint": "^7.24.0", | |||||
"eslint-config-prettier": "^8.2.0", | |||||
"eslint-plugin-prettier": "^3.4.0", | |||||
"jest": "^26.6.3", | "jest": "^26.6.3", | ||||
"prettier": "^2.2.1", | "prettier": "^2.2.1", | ||||
"supertest": "^6.1.3", | "supertest": "^6.1.3", | ||||
"ts-jest": "^26.5.4", | |||||
"ts-loader": "^8.0.18", | |||||
"ts-jest": "^26.5.5", | |||||
"ts-loader": "^9.1.0", | |||||
"ts-node": "^9.1.1", | "ts-node": "^9.1.1", | ||||
"tsconfig-paths": "^3.9.0", | "tsconfig-paths": "^3.9.0", | ||||
"typescript": "^4.2.3" | |||||
"typescript": "^4.2.4" | |||||
}, | }, | ||||
"jest": { | "jest": { | ||||
"moduleFileExtensions": [ | "moduleFileExtensions": [ |
import { Module } from '@nestjs/common'; | import { Module } from '@nestjs/common'; | ||||
import { AppController } from './app.controller'; | |||||
import { AppService } from './app.service'; | |||||
import { GraphQLModule} from '@nestjs/graphql'; | |||||
import { ConnectionContext } from 'subscriptions-transport-ws'; | |||||
import * as WebSocket from 'ws'; | |||||
import { Client } from './client'; | |||||
import { UUID } from './global/scalars/UUID'; | |||||
import { GlobalModule } from './global/global.module'; | |||||
import {PersonModule} from './person/person.module' | |||||
@Module({ | @Module({ | ||||
imports: [], | |||||
controllers: [AppController], | |||||
providers: [AppService], | |||||
imports: [ | |||||
GlobalModule, | |||||
PersonModule, | |||||
GraphQLModule.forRoot({ | |||||
installSubscriptionHandlers: true, | |||||
autoSchemaFile: 'schema.gql', | |||||
debug: false, | |||||
playground: true, | |||||
path: '/gql', | |||||
context: async ({ req, connection }) => { | |||||
if (connection?.context?.client) { | |||||
return { client: connection.context.client }; | |||||
} | |||||
const client = new Client(); | |||||
if (req?.headers?.token) { | |||||
await client.login({token: req.headers.token}); | |||||
} | |||||
return { client } | |||||
}, | |||||
subscriptions: { | |||||
onConnect: (headers: { token: string, clientId: UUID }, websocket: any) => { | |||||
const ip = websocket?.upgradeReq?.headers?.['x-forwarded-for'] || websocket._socket.remoteAddress; | |||||
const client = new Client(headers.clientId, ip); | |||||
if (headers.token) { | |||||
client.login({token: headers.token}).then(); | |||||
} | |||||
return { ...headers, client}; | |||||
}, | |||||
onDisconnect: (websocket: WebSocket, context: ConnectionContext) => { | |||||
context.initPromise.then(() => { | |||||
}) | |||||
}, | |||||
}, | |||||
resolvers: {}, | |||||
}) | |||||
], | |||||
controllers: [], | |||||
providers: [], | |||||
}) | }) | ||||
export class AppModule {} | export class AppModule {} |
import * as jwt from 'jsonwebtoken'; | |||||
import { Base64 } from 'js-base64'; | |||||
import * as fs from 'fs'; | |||||
import { checkPassword } from './generate'; | |||||
import { db } from './db'; | |||||
import { UUID } from './global/scalars/UUID'; | |||||
const SECRET = fs.readFileSync('./assets/keys/jwt.key'); | |||||
const PUBLIC = fs.readFileSync('./assets/keys/jwt.key.pub'); | |||||
export class Client { | |||||
private readonly id: UUID; | |||||
private readonly connectTime: number; | |||||
private readonly ip: string; | |||||
private token: string = null; | |||||
private master: boolean = false; | |||||
constructor(id?: UUID, ip?: string) { | |||||
this.id = id; | |||||
this.ip = ip; | |||||
this.connectTime = Date.now(); | |||||
} | |||||
public async login(data: { | |||||
email?: string, | |||||
passwort?: string, | |||||
token?: string, | |||||
}): Promise<void> { | |||||
if (data.token) { | |||||
let token = null; | |||||
try { | |||||
token = jwt.verify(data.token, PUBLIC); | |||||
} catch (e) { | |||||
return this.LoginFailed(); | |||||
} | |||||
this.token = data.token; | |||||
return this.LoginSuccessful(token._id); | |||||
} | |||||
if (data.email && data.passwort) { | |||||
console.log(data); | |||||
const entries: any[] = (await Promise.all( | |||||
(await db.fetch('person', { 'email': data.email } )) | |||||
.map(async (e) => ({ | |||||
...e, | |||||
authOK: await checkPassword(data.passwort, e.passwort) | |||||
})))) | |||||
.filter(e => e.authOK); | |||||
console.log(entries); | |||||
if (entries.length !== 1) { | |||||
return this.LoginFailed(); | |||||
} | |||||
return this.LoginSuccessful(entries[0]._id); | |||||
} | |||||
return this.LoginFailed(); | |||||
} | |||||
private async LoginFailed(): Promise<void> { | |||||
this.token = null; | |||||
this.master = false; | |||||
} | |||||
private async LoginSuccessful(userid: string): Promise<void> { | |||||
const entry = (await db.fetch('person', {_id: userid}))?.[0] | |||||
if (!entry) { | |||||
return; | |||||
} | |||||
this.token = jwt.sign({ | |||||
vorname: entry.givenName, | |||||
nachname: entry.familyName, | |||||
_id: userid, | |||||
iat: Math.floor(Date.now() / 1000), | |||||
}, SECRET, { | |||||
algorithm: 'RS256', | |||||
expiresIn: 3600*12, | |||||
}) | |||||
this.master = entry.master; | |||||
return; | |||||
} | |||||
/*public getID(): UUID { | |||||
return this.id | |||||
} | |||||
public getIP(): string { | |||||
return this.ip | |||||
}*/ | |||||
public getConnectTime(): number { | |||||
return this.connectTime; | |||||
} | |||||
public getToken(): string { | |||||
return this.token; | |||||
} | |||||
public getUser(): any { | |||||
if (!this.token) { return null; } | |||||
return JSON.parse(Base64.decode(this.token?.split('.')?.[1] || '')); | |||||
} | |||||
public isSelf(id: string | UUID): boolean { | |||||
return this.getUser()?._id === id; | |||||
} | |||||
public isMaster(): boolean { | |||||
return this.master; | |||||
} | |||||
} |
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', | |||||
}; | |||||
private mongo: MongoClient; | |||||
private db: MongoClient.Db; | |||||
public getDB(): MongoClient.Db { | |||||
return this.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, | |||||
useNewUrlParser: true, | |||||
useUnifiedTopology: true, | |||||
poolSize: 20, | |||||
}, (err, database) => { | |||||
if (err) { | |||||
reject(err); | |||||
} | |||||
resolve(database); | |||||
}); | |||||
}); | |||||
this.db = this.mongo.db(this.DBDATA.db); | |||||
} | |||||
public collection(ops): MongoClient.Collection { | |||||
return this.db.collection(ops); | |||||
} | |||||
public async fetch(collection: string, filter: any = {}, limit?: number, offset?: number): Promise<any[]> { | |||||
if (!limit || limit > 1000) limit = 1001; | |||||
if (limit < 0) limit = -limit; | |||||
if (offset) { | |||||
return new Promise<any[]>((resolve, reject) => this.db.collection(collection).find(filter).skip(offset).limit(limit).toArray((e, d) => { | |||||
if (e) { | |||||
return reject(e); | |||||
} | |||||
resolve(d); | |||||
})) | |||||
} else { | |||||
return new Promise<any[]>((resolve, reject) => this.db.collection(collection).find(filter).limit(limit).toArray((e, d) => { | |||||
if (e) { | |||||
return reject(e); | |||||
} | |||||
resolve(d); | |||||
})) | |||||
} | |||||
} | |||||
public async fetchAll(collection: string): Promise<any[]> { | |||||
return new Promise<any[]>((resolve, reject) => this.db.collection(collection).find(true).toArray((e, d) => { | |||||
if (e) { | |||||
return reject(e); | |||||
} | |||||
resolve(d); | |||||
})) | |||||
} | |||||
public async insert(collection: string, doc: any, client?: Client): Promise<any> { | |||||
const d = await this.db.collection(collection).insertOne({ ...doc, _v: 1, _m: { ctime: Date.now(), mtime: Date.now() } }) | |||||
if (d.insertedCount !== 1) { return null; } | |||||
const insertOp = { | |||||
id: doc._id, | |||||
create: { ...doc }, | |||||
user: client?.getUser()?._id, | |||||
ts: Date.now(), | |||||
} | |||||
delete insertOp.create._id; | |||||
delete insertOp.create._v; | |||||
await this.db.collection(`o_${collection}`).insertOne(insertOp); | |||||
return d?.ops?.[0]; | |||||
} | |||||
public async update(collection: string, doc: any, client?: Client): Promise<any> { | |||||
const old = await this.db.collection(collection).findOne({ _id: doc._id }); | |||||
if (!old) return null; | |||||
const neu = { ...old, ...doc, _m: { ...old?._m, mtime: Date.now() } }; | |||||
const d = await this.db.collection(collection).replaceOne({_id: doc._id}, neu); | |||||
if (d.modifiedCount !== 1 || !client) { return null; } | |||||
const insertOp = { | |||||
id: doc._id, | |||||
old: { ...old }, | |||||
update: { ...doc }, | |||||
user: client?.getUser()?._id, | |||||
ts: Date.now(), | |||||
} | |||||
delete insertOp.update._id; | |||||
delete insertOp.update._v; | |||||
await this.db.collection(`o_${collection}`).insertOne(insertOp); | |||||
return d?.ops?.[0]; | |||||
} | |||||
public async delete(collection: string, id: UUID, client?: Client): Promise<UUID> { | |||||
const old = await this.db.collection(collection).findOne({ _id: id }); | |||||
if (!old) { return null; } | |||||
const del = await this.db.collection(collection).deleteOne({ _id: id }); | |||||
if (del.deletedCount !== 1) { return null; } | |||||
const insertOp = { | |||||
id: old._id, | |||||
delete: { ...old }, | |||||
user: client?.getUser()?._id, | |||||
ts: Date.now(), | |||||
} | |||||
delete insertOp.delete._id; | |||||
delete insertOp.delete._v; | |||||
await this.db.collection(`o_${collection}`).insertOne(insertOp); | |||||
return id; | |||||
} | |||||
public async doOps(collection: string, id: UUID, ops: any, filter: any, client?: Client): Promise<any> { | |||||
const doc: any = await this.fetch(collection, { _id: id }); | |||||
if (!doc) return null; | |||||
await db.collection(collection).updateOne({_id: id}, ops, filter); | |||||
const neu = await this.fetch(collection, { _id: id }); | |||||
const insertOp = { | |||||
id: doc._id, | |||||
old: { ...doc }, | |||||
ops: JSON.stringify(ops), | |||||
filter: JSON.stringify(filter), | |||||
user: client?.getUser()?._id, | |||||
ts: Date.now(), | |||||
} | |||||
this.db.collection(`o_${collection}`).insertOne(insertOp); | |||||
return neu[0]; | |||||
} | |||||
} | |||||
export const db = new DB(); |
import * as bcrypt from 'bcrypt'; | |||||
export const secureHash = async (password: string): Promise<string> => { | |||||
return bcrypt.hash(password, 10); | |||||
} | |||||
export const checkPassword = async (password: string, hash: string): Promise<boolean> => { | |||||
return bcrypt.compare(password, hash); | |||||
} |
import { Module } from '@nestjs/common'; | |||||
import { UUID } from './scalars/UUID'; | |||||
import { EmailAddress } from './scalars/EmailAddress'; | |||||
@Module({ | |||||
providers: [ | |||||
UUID, | |||||
EmailAddress, | |||||
] | |||||
}) | |||||
export class GlobalModule {} |
import { CustomScalar, Scalar } from '@nestjs/graphql'; | |||||
import { GraphQLError, Kind, ValueNode } from 'graphql'; | |||||
const validate = (value: string): string => { | |||||
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 (typeof value !== 'string') { | |||||
throw new TypeError(`Value is not string: ${value}`); | |||||
} | |||||
if (!EMAIL_ADDRESS_REGEX.test(value)) { | |||||
throw new TypeError(`Value is not a valid email address: ${value}`); | |||||
} | |||||
return value; | |||||
}; | |||||
@Scalar('EmailAddress', () => EmailAddress) | |||||
export class EmailAddress implements CustomScalar<string, string> { | |||||
description = 'A field whose value conforms to the standard internet email address format as specified in RFC822: https://www.w3.org/Protocols/rfc822/.' | |||||
parseValue(value: string): string { | |||||
return validate(value) | |||||
} | |||||
serialize(value: string): string { | |||||
return validate(value) | |||||
} | |||||
parseLiteral(ast: ValueNode): string { | |||||
if (ast.kind !== Kind.STRING) { | |||||
throw new GraphQLError( | |||||
`Can only validate strings as email addresses but got a: ${ast.kind}`, | |||||
); | |||||
} | |||||
return validate(ast.value) | |||||
} | |||||
} |
import { CustomScalar, Scalar } from '@nestjs/graphql'; | |||||
import { GraphQLError, Kind, ValueNode } from 'graphql'; | |||||
const validate = (value: string): string => { | |||||
const UUID_REGEX = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/; | |||||
if (typeof value !== 'string') { | |||||
throw new TypeError(`Value is not string: ${value}`); | |||||
} | |||||
if (!UUID_REGEX.test(value)) { | |||||
throw new TypeError(`Value is not a valid UUID: ${value}`); | |||||
} | |||||
return value; | |||||
}; | |||||
@Scalar('UUID', () => UUID) | |||||
export class UUID implements CustomScalar<string, string> { | |||||
description = 'UUID' | |||||
parseValue(value: string): string { | |||||
return validate(value) | |||||
} | |||||
serialize(value: string): string { | |||||
return validate(value) | |||||
} | |||||
parseLiteral(ast: ValueNode): string { | |||||
if (ast.kind !== Kind.STRING) { | |||||
throw new GraphQLError( | |||||
`Can only validate strings as UUID but got a: ${ast.kind}`, | |||||
); | |||||
} | |||||
return validate(ast.value) | |||||
} | |||||
} |
import * as prompt from 'prompt-async'; | |||||
import { v4 as uuid } from 'uuid'; | |||||
import { secureHash } from './generate'; | |||||
import { db } from './db'; | |||||
const pr = async (message) => { | |||||
const { test } = (await prompt.get({ | |||||
properties: { | |||||
test: { | |||||
message, | |||||
}, | |||||
}, | |||||
})); | |||||
return test; | |||||
}; | |||||
export const initDB = async (init: boolean, reset: boolean): Promise<void> => { | |||||
prompt.start(); | |||||
console.log('initialisiere Datenbank...'); | |||||
const ed = db.getDB(); | |||||
const colls = ['person','organizer','event']; | |||||
if (reset) { | |||||
const test = await pr('Wirklich löschen? Bitte mit "JA!" bestätigen!'); | |||||
if (test === 'JA!') { | |||||
console.log('setze Collections zurück...'); | |||||
await Promise.all(colls.map(c => new Promise<void>(async (resolve) => { | |||||
if (await ed.collection(c).findOne() !== null) { | |||||
await new Promise(resolve2 => ed.collection(c).drop(resolve2)); | |||||
} | |||||
if (await ed.collection(`o_${c}`).findOne() !== null) { | |||||
await new Promise(resolve2 => ed.collection(`o_${c}`).drop(resolve2)); | |||||
} | |||||
resolve(); | |||||
}))); | |||||
console.log(' Collections geleert!'); | |||||
} | |||||
} | |||||
const collections: any[] = await new Promise(resolve => ed.listCollections().toArray((e, i) => resolve(i))); | |||||
console.log('erstelle Collections...'); | |||||
colls.forEach(c => { | |||||
if (!collections.find(C => C.name === c)) { | |||||
ed.createCollection(c); | |||||
} | |||||
if (!collections.find(C => C.name === `o_${c}`)) { | |||||
ed.createCollection(`o_${c}`); | |||||
} | |||||
}); | |||||
console.log(' Collections erstellt!'); | |||||
console.log('Überprüfe auf Master-Zugänge...'); | |||||
const master = await db.fetch('person', {'master': true}) | |||||
console.log(` ${master.length} Zugänge gefunden`); | |||||
if (init) { | |||||
const anlegen = await pr(' Weiterer Zugang anlegen? (y/j)'); | |||||
if (['y', 'j', 'yes', 'ja'].find(t => t === anlegen.toLowerCase())) { | |||||
const email = await pr(' neue E-Mail-Adresse eingeben'); | |||||
const passwort = await pr(' neues Passwort eingeben'); | |||||
const hash = await secureHash(passwort); | |||||
const neu = { | |||||
_id: uuid(), | |||||
givenName: 'AAA_MASTER', | |||||
familyName: 'AAA_MASTER', | |||||
passwort: hash, | |||||
email: email, | |||||
master: true, | |||||
}; | |||||
db.insert('person', neu, null).then() | |||||
console.log(' Nutzer angelegt!'); | |||||
} | |||||
} | |||||
console.log('Datenbank bereit!'); | |||||
} |
import { NestFactory } from '@nestjs/core'; | import { NestFactory } from '@nestjs/core'; | ||||
import { AppModule } from './app.module'; | import { AppModule } from './app.module'; | ||||
async function bootstrap() { | |||||
import { db } from './db'; | |||||
import { initDB } from './init'; | |||||
async function bootstrap(argv: string[]) { | |||||
await db.connect(); | |||||
await initDB(argv.findIndex(a => a.toLowerCase() === 'init') !== -1, argv.findIndex(a => a.toLowerCase() === 'reset') !== -1); | |||||
const app = await NestFactory.create(AppModule); | const app = await NestFactory.create(AppModule); | ||||
await app.listen(3000); | await app.listen(3000); | ||||
} | } | ||||
bootstrap(); | |||||
let args: string[]; | |||||
if (process.env.npm_config_argv) { | |||||
args = JSON.parse(process.env.npm_config_argv || '').remain || []; | |||||
} else { | |||||
args = process.argv.slice(2); | |||||
} | |||||
process.on('uncaughtException', (error) => { | |||||
// tslint:disable-next-line:no-console | |||||
console.error(error); | |||||
}); | |||||
bootstrap(args).then(); |
import { Field, ObjectType } from '@nestjs/graphql'; | |||||
import { UUID } from '../../global/scalars/UUID'; | |||||
import { EmailAddress } from '../../global/scalars/EmailAddress' | |||||
@ObjectType() | |||||
export class Person { | |||||
@Field(() => UUID,{ nullable: false }) | |||||
_id: UUID | |||||
@Field(() => String, { nullable: false }) | |||||
givenName: string | |||||
@Field(() => String, { nullable: false }) | |||||
familyName: string | |||||
@Field(() => EmailAddress , { nullable: false }) | |||||
email: EmailAddress | |||||
} |
import { Module } from '@nestjs/common'; | |||||
import { PersonResolverQ } from './resolver/person.query'; | |||||
import { PersonResolverM } from './resolver/person.mutation'; | |||||
import { PersonService } from './person.service'; | |||||
import { PersonResolver } from './resolver/person'; | |||||
@Module({ | |||||
providers: [ | |||||
PersonResolverQ, PersonResolverM, | |||||
PersonService, | |||||
PersonResolver, | |||||
], | |||||
}) | |||||
export class PersonModule {} |
import { Injectable } from '@nestjs/common'; | |||||
import { db } from '../db'; | |||||
import { Person } from './models/Person'; | |||||
import { Client } from '../client'; | |||||
import { UUID } from '../global/scalars/UUID'; | |||||
@Injectable() | |||||
export class PersonService { | |||||
async findOneById(id: UUID): Promise<Person> { | |||||
const data = await db.fetch('person', { _id: id }); | |||||
return data?.[0] || null as Person; | |||||
} | |||||
async find(filter: any, limit?: number, offset?: number): Promise<Person[]> { | |||||
return db.fetch('person', filter, limit, offset); | |||||
} | |||||
async update(id: UUID, ops: any, filter: any, client: Client): Promise<Person> { | |||||
return db.doOps('person', id, ops, filter, client) | |||||
} | |||||
} |
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql'; | |||||
import { Person } from '../models/Person'; | |||||
import { Client } from '../../client'; | |||||
import { PersonService } from '../person.service'; | |||||
import { HttpException } from '@nestjs/common'; | |||||
@Resolver(() => Person) | |||||
export class PersonResolverM { | |||||
constructor( | |||||
private readonly service: PersonService, | |||||
) {} | |||||
@Mutation(() => Person, { nullable: true }) | |||||
async login( | |||||
@Context('client') client: Client, | |||||
@Args('email', { nullable: true }) email?: string, | |||||
@Args('passwort', { nullable: true }) passwort?: string, | |||||
@Args('token', { nullable: true }) token?: string | |||||
): Promise<Person> { | |||||
await client.login({email, passwort, token}); | |||||
const newtoken: string = client.getToken(); | |||||
if (email && passwort && !newtoken) { | |||||
throw new HttpException('Logindaten falsch', 403); | |||||
} | |||||
return this.service.findOneById(client.getUser()?._id); | |||||
} | |||||
/* @Mutation(() => Person, { nullable: true }) | |||||
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('birthDate', { type: () => Date, nullable: true }) birthDate?: Date, | |||||
): Promise<Person> { | |||||
if (!client.isMaster() && !client.isSelf(id)) return null; | |||||
const p = await this.service.findOneById(id); | |||||
if (!p) return null; | |||||
const ops: any = {$set:{}}; | |||||
if (givenName) ops['$set'].givenName = givenName; | |||||
if (familyName) ops['$set'].familyName = familyName; | |||||
if (birthDate) ops['$set'].birthDate = birthDate; | |||||
return this.service.update(id, ops, null, client); | |||||
} */ | |||||
} |
import { Args, Context, Int, Query, Resolver } from '@nestjs/graphql'; | |||||
import { Person } from '../models/Person'; | |||||
import { PersonService } from '../person.service'; | |||||
import { Client } from '../../client'; | |||||
import { HttpException } from '@nestjs/common'; | |||||
import { UUID } from '../../global/scalars/UUID'; | |||||
@Resolver(() => Person) | |||||
export class PersonResolverQ { | |||||
constructor( | |||||
private readonly service: PersonService, | |||||
) {} | |||||
@Query(() => Person, { nullable: true }) | |||||
async Person( | |||||
@Context('client') client: Client, | |||||
@Args('id') id: UUID, | |||||
): Promise<Person> { | |||||
return await this.service.findOneById(id) as Person; | |||||
} | |||||
@Query(() => [Person], { nullable: true }) | |||||
async PersonFind( | |||||
@Context('client') client: Client, | |||||
@Args('givenName', { nullable: true }) givenName?: string, | |||||
@Args('familyName', { nullable: true }) familyName?: string, | |||||
@Args('email', { nullable: true }) email?: string, | |||||
@Args('limit', { type: () => Int, nullable: true }) limit?: number, | |||||
@Args('offset', { type: () => Int, nullable: true }) offset?: number, | |||||
): Promise<Person[]> { | |||||
if (!client.isMaster()) return null; | |||||
const filter: any = {} | |||||
if (givenName) filter.givenName = givenName; | |||||
if (familyName) filter.familyName = familyName; | |||||
if (email) filter.email = email; | |||||
let tmp = await this.service.find(filter, limit, offset); | |||||
if (tmp.length > 1000) throw new HttpException('too many results', 413); | |||||
return tmp | |||||
} | |||||
} |
import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql'; | |||||
import { Person } from '../models/Person'; | |||||
import { Client } from '../../client'; | |||||
import { UUID } from '../../global/scalars/UUID'; | |||||
import {EmailAddress} from '../../global/scalars/EmailAddress' | |||||
@Resolver(() => Person) | |||||
export class PersonResolver { | |||||
@ResolveField(() => UUID, { nullable: false }) | |||||
async _id( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: Person | |||||
): Promise<UUID> { | |||||
return parent._id as UUID; | |||||
} | |||||
@ResolveField(() => String, { nullable: false }) | |||||
async givenName( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: Person | |||||
): Promise<string> { | |||||
return parent.givenName; | |||||
} | |||||
@ResolveField(() => String, { nullable: false }) | |||||
async familyName( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: Person | |||||
): Promise<string> { | |||||
return parent.familyName; | |||||
} | |||||
@ResolveField(() => String, { nullable: true }) | |||||
async token( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: Person | |||||
): Promise<string> { | |||||
if (!client.isSelf(parent._id)) return null; | |||||
return client.getToken(); | |||||
} | |||||
@ResolveField(() => EmailAddress, { nullable: true }) | |||||
async email( | |||||
@Context('client') client: Client, | |||||
@Parent() parent: Person | |||||
): Promise<EmailAddress> { | |||||
if (!client.isMaster() && !client.isSelf(parent._id)) return null; | |||||
return parent.email; | |||||
} | |||||
} |