Преглед на файлове

DB, Client, Init, GraphQL-Init

tags/v0.9.1
akimmig преди 4 години
родител
ревизия
fce5ef0272
променени са 19 файла, в които са добавени 5394 реда и са изтрити 614 реда
  1. +1
    -0
      .idea/TurnenAufZeit.iml
  2. +3
    -1
      server/.gitignore
  3. +4626
    -593
      server/package-lock.json
  4. +25
    -13
      server/package.json
  5. +52
    -5
      server/src/app.module.ts
  6. +121
    -0
      server/src/client.ts
  7. +164
    -0
      server/src/db.ts
  8. +9
    -0
      server/src/generate.ts
  9. +11
    -0
      server/src/global/global.module.ts
  10. +38
    -0
      server/src/global/scalars/EmailAddress.ts
  11. +38
    -0
      server/src/global/scalars/UUID.ts
  12. +86
    -0
      server/src/init.ts
  13. +21
    -2
      server/src/main.ts
  14. +18
    -0
      server/src/person/models/Person.ts
  15. +14
    -0
      server/src/person/person.module.ts
  16. +22
    -0
      server/src/person/person.service.ts
  17. +51
    -0
      server/src/person/resolver/person.mutation.ts
  18. +44
    -0
      server/src/person/resolver/person.query.ts
  19. +50
    -0
      server/src/person/resolver/person.ts

+ 1
- 0
.idea/TurnenAufZeit.iml Целия файл

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

+ 3
- 1
server/.gitignore Целия файл

@@ -31,4 +31,6 @@ lerna-debug.log*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/extensions.json

/assets/keys

+ 4626
- 593
server/package-lock.json
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 25
- 13
server/package.json Целия файл

@@ -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": [

+ 52
- 5
server/src/app.module.ts Целия файл

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

+ 121
- 0
server/src/client.ts Целия файл

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

+ 164
- 0
server/src/db.ts Целия файл

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

+ 9
- 0
server/src/generate.ts Целия файл

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

+ 11
- 0
server/src/global/global.module.ts Целия файл

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

+ 38
- 0
server/src/global/scalars/EmailAddress.ts Целия файл

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

+ 38
- 0
server/src/global/scalars/UUID.ts Целия файл

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

+ 86
- 0
server/src/init.ts Целия файл

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

+ 21
- 2
server/src/main.ts Целия файл

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

+ 18
- 0
server/src/person/models/Person.ts Целия файл

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

+ 14
- 0
server/src/person/person.module.ts Целия файл

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

+ 22
- 0
server/src/person/person.service.ts Целия файл

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

+ 51
- 0
server/src/person/resolver/person.mutation.ts Целия файл

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

+ 44
- 0
server/src/person/resolver/person.query.ts Целия файл

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

+ 50
- 0
server/src/person/resolver/person.ts Целия файл

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

Loading…
Отказ
Запис