@@ -5,6 +5,7 @@ | |||
<excludeFolder url="file://$MODULE_DIR$/temp" /> | |||
<excludeFolder url="file://$MODULE_DIR$/.tmp" /> | |||
<excludeFolder url="file://$MODULE_DIR$/tmp" /> | |||
<excludeFolder url="file://$MODULE_DIR$/server/dist" /> | |||
</content> | |||
<orderEntry type="inheritedJdk" /> | |||
<orderEntry type="sourceFolder" forTests="false" /> |
@@ -31,4 +31,6 @@ lerna-debug.log* | |||
!.vscode/settings.json | |||
!.vscode/tasks.json | |||
!.vscode/launch.json | |||
!.vscode/extensions.json | |||
!.vscode/extensions.json | |||
/assets/keys |
@@ -1,5 +1,5 @@ | |||
{ | |||
"name": "server", | |||
"name": "turnenaufzeit", | |||
"version": "0.0.1", | |||
"description": "", | |||
"author": "", | |||
@@ -21,34 +21,46 @@ | |||
"test:e2e": "jest --config ./test/jest-e2e.json" | |||
}, | |||
"dependencies": { | |||
"@apollo/gateway": "^0.26.1", | |||
"@nestjs/common": "^7.6.15", | |||
"@nestjs/core": "^7.6.15", | |||
"@nestjs/graphql": "^7.10.6", | |||
"@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", | |||
"rimraf": "^3.0.2", | |||
"rxjs": "^6.6.6" | |||
"rxjs": "^6.6.7", | |||
"subscriptions-transport-ws": "^0.9.18", | |||
"ws": "^7.4.5" | |||
}, | |||
"devDependencies": { | |||
"@nestjs/cli": "^7.6.0", | |||
"@nestjs/schematics": "^7.3.0", | |||
"@nestjs/schematics": "^7.3.1", | |||
"@nestjs/testing": "^7.6.15", | |||
"@types/express": "^4.17.11", | |||
"@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", | |||
"prettier": "^2.2.1", | |||
"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", | |||
"tsconfig-paths": "^3.9.0", | |||
"typescript": "^4.2.3" | |||
"typescript": "^4.2.4" | |||
}, | |||
"jest": { | |||
"moduleFileExtensions": [ |
@@ -1,10 +1,57 @@ | |||
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({ | |||
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 {} |
@@ -0,0 +1,121 @@ | |||
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; | |||
} | |||
} |
@@ -0,0 +1,164 @@ | |||
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(); |
@@ -0,0 +1,9 @@ | |||
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); | |||
} |
@@ -0,0 +1,11 @@ | |||
import { Module } from '@nestjs/common'; | |||
import { UUID } from './scalars/UUID'; | |||
import { EmailAddress } from './scalars/EmailAddress'; | |||
@Module({ | |||
providers: [ | |||
UUID, | |||
EmailAddress, | |||
] | |||
}) | |||
export class GlobalModule {} |
@@ -0,0 +1,38 @@ | |||
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) | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
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) | |||
} | |||
} |
@@ -0,0 +1,86 @@ | |||
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!'); | |||
} |
@@ -1,8 +1,27 @@ | |||
import { NestFactory } from '@nestjs/core'; | |||
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); | |||
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(); |
@@ -0,0 +1,18 @@ | |||
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 | |||
} |
@@ -0,0 +1,14 @@ | |||
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 {} |
@@ -0,0 +1,22 @@ | |||
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) | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
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); | |||
} */ | |||
} |
@@ -0,0 +1,44 @@ | |||
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 | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
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; | |||
} | |||
} |