@@ -32,6 +32,24 @@ type Organizer { | |||
_organizers: [UUID!] | |||
} | |||
type Timeslot { | |||
_id: UUID! | |||
time: Time! | |||
} | |||
"""A time string HH:MM:SS""" | |||
scalar Time | |||
type Event { | |||
_id: UUID! | |||
date: Date! | |||
timeslots: [Timeslot!]! | |||
_organizer: UUID! | |||
} | |||
"""A date string YYYY-MM-DD""" | |||
scalar Date | |||
type Query { | |||
Person(id: UUID!): Person | |||
PersonFind(offset: Int, limit: Int, email: String, familyName: String, givenName: String): [Person!] | |||
@@ -39,6 +57,8 @@ type Query { | |||
ApparatusFind(offset: Int, limit: Int, name: String): [Apparatus!] | |||
Organizer(id: UUID!): Organizer | |||
OrganizerFind(offset: Int, limit: Int, name: String): [Organizer!] | |||
Event(id: UUID!): Event | |||
EventFind(offset: Int, limit: Int, organizer: UUID, date: Date): [Event!] | |||
} | |||
type Mutation { |
@@ -10,6 +10,7 @@ import { GlobalModule } from './global/global.module'; | |||
import { PersonModule } from './person/person.module'; | |||
import { ApparatusModule } from './apparatus/apparatus.module'; | |||
import { OrganizerModule } from './organizer/organizer.module' | |||
import { EventModule } from './event/event.module' | |||
@Module({ | |||
imports: [ | |||
@@ -17,6 +18,7 @@ import { OrganizerModule } from './organizer/organizer.module' | |||
PersonModule, | |||
ApparatusModule, | |||
OrganizerModule, | |||
EventModule, | |||
GraphQLModule.forRoot({ | |||
installSubscriptionHandlers: true, | |||
autoSchemaFile: 'schema.gql', |
@@ -0,0 +1,14 @@ | |||
import { Module } from '@nestjs/common'; | |||
import {EventResolver} from './resolver/event' | |||
import {EventService} from './event.service' | |||
import {EventResolverQ} from './resolver/event.query' | |||
import {EventResolverM} from './resolver/event.mutation' | |||
@Module({ | |||
providers: [ | |||
EventResolverQ, EventResolverM, | |||
EventService, | |||
EventResolver, | |||
], | |||
}) | |||
export class EventModule {} |
@@ -0,0 +1,22 @@ | |||
import { Injectable } from '@nestjs/common'; | |||
import { db } from '../db'; | |||
import { Event } from './models/Event'; | |||
import { Client } from '../client'; | |||
import { UUID } from '../global/scalars/UUID'; | |||
@Injectable() | |||
export class EventService { | |||
async findOneById(id: UUID): Promise<Event> { | |||
const data = await db.fetch('event', { _id: id }); | |||
return data?.[0] || null as Event; | |||
} | |||
async find(filter: any, limit?: number, offset?: number): Promise<Event[]> { | |||
return db.fetch('event', filter, limit, offset); | |||
} | |||
async update(id: UUID, ops: any, filter: any, client: Client): Promise<Event> { | |||
return db.doOps('event', id, ops, filter, client) | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
import { Field, ObjectType } from '@nestjs/graphql'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
import { Date } from '../../global/scalars/Date' | |||
import { Timeslot } from './Timeslot' | |||
import { Organizer } from '../../organizer/models/Organizer' | |||
@ObjectType() | |||
export class Event { | |||
@Field(() => UUID,{ nullable: false }) | |||
_id: UUID | |||
@Field(() => Date, { nullable: false }) | |||
date: Date | |||
@Field(() => [Timeslot], { nullable: false }) | |||
timeslots: Timeslot[] | |||
@Field(() => UUID, { nullable: false }) | |||
_organizer: Organizer | |||
} |
@@ -0,0 +1,12 @@ | |||
import { Field, ObjectType } from '@nestjs/graphql'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
import { Time } from '../../global/scalars/Time' | |||
@ObjectType() | |||
export class Timeslot { | |||
@Field(() => UUID,{ nullable: false }) | |||
_id: UUID | |||
@Field(() => Time, { nullable: false }) | |||
time: Time | |||
} |
@@ -0,0 +1,12 @@ | |||
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql'; | |||
import { Event } from '../models/Event'; | |||
import { Client } from '../../client'; | |||
import { EventService } from '../event.service'; | |||
import { HttpException } from '@nestjs/common'; | |||
@Resolver(() => Event) | |||
export class EventResolverM { | |||
constructor( | |||
private readonly service: EventService, | |||
) {} | |||
} |
@@ -0,0 +1,41 @@ | |||
import { Args, Context, Int, Query, Resolver } from '@nestjs/graphql'; | |||
import { Event } from '../models/Event'; | |||
import { EventService } from '../event.service'; | |||
import { Client } from '../../client'; | |||
import { HttpException } from '@nestjs/common'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
import { Date } from '../../global/scalars/Date'; | |||
@Resolver(() => Event) | |||
export class EventResolverQ { | |||
constructor( | |||
private readonly service: EventService, | |||
) {} | |||
@Query(() => Event, { nullable: true }) | |||
async Event( | |||
@Context('client') client: Client, | |||
@Args('id') id: UUID, | |||
): Promise<Event> { | |||
return await this.service.findOneById(id) as Event; | |||
} | |||
@Query(() => [Event], { nullable: true }) | |||
async EventFind( | |||
@Context('client') client: Client, | |||
@Args('date', { nullable: true }) date?: Date, | |||
@Args('organizer', { nullable: true }) organizer?: UUID, | |||
@Args('limit', { type: () => Int, nullable: true }) limit?: number, | |||
@Args('offset', { type: () => Int, nullable: true }) offset?: number, | |||
): Promise<Event[]> { | |||
const filter: any = {} | |||
if (date) filter.date = date; | |||
if (organizer) filter._organizer = organizer; | |||
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,33 @@ | |||
import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql'; | |||
import { Event } from '../models/Event'; | |||
import { Client } from '../../client'; | |||
import { UUID } from '../../global/scalars/UUID'; | |||
import {Date} from '../../global/scalars/Date' | |||
import {Timeslot} from '../models/Timeslot' | |||
@Resolver(() => Event) | |||
export class EventResolver { | |||
@ResolveField(() => UUID, { nullable: false }) | |||
async _id( | |||
@Context('client') client: Client, | |||
@Parent() parent: Event | |||
): Promise<UUID> { | |||
return parent._id as UUID; | |||
} | |||
@ResolveField(() => Date, { nullable: false }) | |||
async date( | |||
@Context('client') client: Client, | |||
@Parent() parent: Event | |||
): Promise<Date> { | |||
return parent.date; | |||
} | |||
@ResolveField(() => [Timeslot], { nullable: true }) | |||
async timeslots( | |||
@Context('client') client: Client, | |||
@Parent() parent: Event | |||
): Promise<Timeslot[]> { | |||
return parent.timeslots; | |||
} | |||
} |
@@ -1,11 +1,17 @@ | |||
import { Module } from '@nestjs/common'; | |||
import { UUID } from './scalars/UUID'; | |||
import { EmailAddress } from './scalars/EmailAddress'; | |||
import { Date } from './scalars/Date' | |||
import { DateTime } from './scalars/DateTime' | |||
import { Time } from './scalars/Time' | |||
@Module({ | |||
providers: [ | |||
UUID, | |||
EmailAddress, | |||
Date, | |||
// DateTime, | |||
Time, | |||
] | |||
}) | |||
export class GlobalModule {} |
@@ -0,0 +1,47 @@ | |||
import { CustomScalar, Scalar } from '@nestjs/graphql'; | |||
import { GraphQLError, Kind, ValueNode } from 'graphql'; | |||
const validate = (value: string): string => { | |||
const DATE_REGEX = /^(18|19|20)\d{2}-(0?[1-9]|1[0-2])-(0?[1-9]|[12]\d|3[01])$/; | |||
if (typeof value !== 'string') { | |||
throw new TypeError(`Value is not string: ${value}`); | |||
} | |||
if (!DATE_REGEX.test(value)) { | |||
throw new TypeError(`Value is not a valid date: ${value}`); | |||
} | |||
const tmp = value.split('-'); | |||
if (tmp[1].length === 1) { | |||
tmp[1] = '0' + tmp[1]; | |||
} | |||
if (tmp[2].length === 1) { | |||
tmp[2] = '0' + tmp[2]; | |||
} | |||
value = tmp.join('-'); | |||
return value; | |||
}; | |||
@Scalar('Date', () => Date) | |||
export class Date implements CustomScalar<string, string> { | |||
description = 'A date string YYYY-MM-DD' | |||
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 date but got a: ${ast.kind}`, | |||
); | |||
} | |||
return validate(ast.value) | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
import { CustomScalar, Scalar } from '@nestjs/graphql'; | |||
import { GraphQLError, Kind, ValueNode } from 'graphql'; | |||
const validate = (value: string): string => { | |||
const DATE_REGEX = /^(18|19|20)\d{2}-(0?[1-9]|1[0-2])-(0?[1-9]|[12]\d|3[01])( (0?[0-9]|1[0-9]|2[0-3]):(0?[0-9]|[1-5][0-9]):(0?[0-9]|[1-5][0-9]))?$/; | |||
if (typeof value !== 'string') { | |||
throw new TypeError(`Value is not string: ${value}`); | |||
} | |||
if (!DATE_REGEX.test(value)) { | |||
throw new TypeError(`Value is not a valid datetime: ${value}`); | |||
} | |||
const split = value.split(' '); | |||
if (split.length === 1) split.push('00:00:00'); | |||
const tmp = split[0].split('-'); | |||
if (tmp[1].length === 1) tmp[1] = '0' + tmp[1]; | |||
if (tmp[2].length === 1) tmp[2] = '0' + tmp[2]; | |||
split[0] = tmp.join('-'); | |||
const time = split[1].split(':'); | |||
if(time[0].length === 1) time[0] = '0' + time[0]; | |||
if(time[1].length === 1) time[1] = '0' + time[1]; | |||
if(time[2].length === 1) time[2] = '0' + time[2]; | |||
split[1] = time.join(':'); | |||
return split.join(' '); | |||
}; | |||
@Scalar('DateTime', () => DateTime) | |||
export class DateTime implements CustomScalar<string, string> { | |||
description = 'A datetime string YYYY-MM-DD HH:MM:SS' | |||
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 date but got a: ${ast.kind}`, | |||
); | |||
} | |||
return validate(ast.value) | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
import { CustomScalar, Scalar } from '@nestjs/graphql'; | |||
import { GraphQLError, Kind, ValueNode } from 'graphql'; | |||
const validate = (value: string): string => { | |||
const DATE_REGEX = /^(0?[0-9]|1[0-9]|2[0-3]):(0?[0-9]|[1-5][0-9]):(0?[0-9]|[1-5][0-9])?$/; | |||
if (typeof value !== 'string') { | |||
throw new TypeError(`Value is not string: ${value}`); | |||
} | |||
if (!DATE_REGEX.test(value)) { | |||
throw new TypeError(`Value is not a valid time: ${value}`); | |||
} | |||
const time = value.split(':'); | |||
if(time[0].length === 1) time[0] = '0' + time[0]; | |||
if(time[1].length === 1) time[1] = '0' + time[1]; | |||
if(time[2].length === 1) time[2] = '0' + time[2]; | |||
return time.join(':'); | |||
}; | |||
@Scalar('Time', () => Time) | |||
export class Time implements CustomScalar<string, string> { | |||
description = 'A time string HH:MM:SS' | |||
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 time but got a: ${ast.kind}`, | |||
); | |||
} | |||
return validate(ast.value) | |||
} | |||
} |