updates
This commit is contained in:
parent
faa4aa48eb
commit
414bc04782
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
## License
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@ -6,6 +6,10 @@ services:
|
|||||||
- "27017:27017"
|
- "27017:27017"
|
||||||
volumes:
|
volumes:
|
||||||
- "./data/mongo:/data"
|
- "./data/mongo:/data"
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
# api:
|
# api:
|
||||||
# build: .
|
# build: .
|
||||||
# volumes:
|
# volumes:
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"license": "MIT",
|
||||||
"license": "UNLICENSED",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
@ -40,9 +39,12 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"graphql": "15.0.0",
|
"graphql": "15.0.0",
|
||||||
|
"graphql-redis-subscriptions": "^2.2.1",
|
||||||
|
"graphql-subscriptions": "^1.1.0",
|
||||||
"graphql-tools": "^5.0.0",
|
"graphql-tools": "^5.0.0",
|
||||||
"handlebars": "^4.7.6",
|
"handlebars": "^4.7.6",
|
||||||
"inquirer": "^7.1.0",
|
"inquirer": "^7.1.0",
|
||||||
|
"ioredis": "^4.17.1",
|
||||||
"migrate-mongoose": "^4.0.0",
|
"migrate-mongoose": "^4.0.0",
|
||||||
"mongoose": "^5.9.11",
|
"mongoose": "^5.9.11",
|
||||||
"nestjs-console": "^3.0.2",
|
"nestjs-console": "^3.0.2",
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { MailerModule } from '@nestjs-modules/mailer';
|
import { MailerModule } from '@nestjs-modules/mailer';
|
||||||
import { HttpModule, RequestMethod } from '@nestjs/common';
|
import { HttpModule, RequestMethod } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { GraphQLFederationModule } from '@nestjs/graphql';
|
import { GraphQLModule } from '@nestjs/graphql';
|
||||||
import { JwtModule, JwtModuleOptions } from '@nestjs/jwt';
|
import { JwtModule, JwtModuleOptions } from '@nestjs/jwt';
|
||||||
import { MongooseModule } from '@nestjs/mongoose';
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
import { MongooseModuleOptions } from '@nestjs/mongoose/dist/interfaces/mongoose-options.interface';
|
import { MongooseModuleOptions } from '@nestjs/mongoose/dist/interfaces/mongoose-options.interface';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
import { Request } from 'express-serve-static-core';
|
||||||
|
import { IncomingHttpHeaders } from 'http';
|
||||||
import { ConsoleModule } from 'nestjs-console';
|
import { ConsoleModule } from 'nestjs-console';
|
||||||
import { LoggerModule, Params as LoggerModuleParams } from 'nestjs-pino/dist';
|
import { LoggerModule, Params as LoggerModuleParams } from 'nestjs-pino/dist';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { ContextCache } from './resolver/context.cache';
|
|
||||||
import { schema } from './schema';
|
import { schema } from './schema';
|
||||||
|
|
||||||
export const LoggerConfig: LoggerModuleParams = {
|
export const LoggerConfig: LoggerModuleParams = {
|
||||||
@ -60,14 +61,14 @@ export const imports = [
|
|||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
LoggerModule.forRoot(LoggerConfig),
|
LoggerModule.forRoot(LoggerConfig),
|
||||||
GraphQLFederationModule.forRoot({
|
GraphQLModule.forRoot({
|
||||||
debug: process.env.NODE_ENV !== 'production',
|
debug: process.env.NODE_ENV !== 'production',
|
||||||
definitions: {
|
definitions: {
|
||||||
outputAs: 'class',
|
outputAs: 'class',
|
||||||
},
|
},
|
||||||
introspection: true,
|
introspection: true,
|
||||||
playground: true,
|
playground: true,
|
||||||
// installSubscriptionHandlers: true,
|
installSubscriptionHandlers: true,
|
||||||
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
|
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
|
||||||
// to allow guards on resolver props https://github.com/nestjs/graphql/issues/295
|
// to allow guards on resolver props https://github.com/nestjs/graphql/issues/295
|
||||||
fieldResolverEnhancers: [
|
fieldResolverEnhancers: [
|
||||||
@ -77,11 +78,22 @@ export const imports = [
|
|||||||
resolverValidationOptions: {
|
resolverValidationOptions: {
|
||||||
|
|
||||||
},
|
},
|
||||||
context: ({ req }) => {
|
context: ({ req, connection }) => {
|
||||||
return {
|
if (!req && connection) {
|
||||||
cache: new ContextCache(),
|
const headers: IncomingHttpHeaders = {}
|
||||||
req,
|
|
||||||
|
Object.keys(connection.context).forEach(key => {
|
||||||
|
headers[key.toLowerCase()] = connection.context[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
req: {
|
||||||
|
headers
|
||||||
|
} as Request
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { req }
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
MongooseModule.forRootAsync({
|
MongooseModule.forRootAsync({
|
||||||
|
|||||||
@ -3,10 +3,10 @@ export const fieldTypes = [
|
|||||||
'textfield',
|
'textfield',
|
||||||
'date',
|
'date',
|
||||||
'email',
|
'email',
|
||||||
'legal',
|
// 'legal',
|
||||||
'textarea',
|
'textarea',
|
||||||
'link',
|
'link',
|
||||||
'statement',
|
// 'statement',
|
||||||
'dropdown',
|
'dropdown',
|
||||||
'rating',
|
'rating',
|
||||||
'radio',
|
'radio',
|
||||||
|
|||||||
13
src/dto/form/abstract.notification.input.ts
Normal file
13
src/dto/form/abstract.notification.input.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@InputType('NotificationInput', { isAbstract: true })
|
||||||
|
export class AbstractNotificationInput {
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly subject?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly htmlTemplate?: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly enabled: boolean
|
||||||
|
}
|
||||||
19
src/dto/form/button.input.ts
Normal file
19
src/dto/form/button.input.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@InputType('ButtonInput')
|
||||||
|
export class ButtonInput {
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly url?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly action?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly text?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly bgColor?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly color?: string
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ export class ButtonModel {
|
|||||||
readonly action?: string
|
readonly action?: string
|
||||||
|
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
readonly text: string
|
readonly text?: string
|
||||||
|
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
readonly bgColor?: string
|
readonly bgColor?: string
|
||||||
|
|||||||
19
src/dto/form/colors.input.ts
Normal file
19
src/dto/form/colors.input.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@InputType('ColorsInput')
|
||||||
|
export class ColorsInput {
|
||||||
|
@Field()
|
||||||
|
readonly backgroundColor: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly questionColor: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly answerColor: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly buttonColor: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly buttonTextColor: string
|
||||||
|
}
|
||||||
11
src/dto/form/design.input.ts
Normal file
11
src/dto/form/design.input.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
import { ColorsInput } from './colors.input';
|
||||||
|
|
||||||
|
@InputType('DesignInput')
|
||||||
|
export class DesignInput {
|
||||||
|
@Field()
|
||||||
|
readonly colors: ColorsInput
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly font?: string
|
||||||
|
}
|
||||||
23
src/dto/form/form.create.input.ts
Normal file
23
src/dto/form/form.create.input.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Field, ID, InputType } from '@nestjs/graphql';
|
||||||
|
import { FormFieldInput } from './form.field.input';
|
||||||
|
|
||||||
|
@InputType('FormCreateInput')
|
||||||
|
export class FormCreateInput {
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
readonly id: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly title: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly language: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly showFooter: boolean
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly isLive: boolean
|
||||||
|
|
||||||
|
@Field(() => [FormFieldInput], { nullable: true })
|
||||||
|
readonly fields: FormFieldInput[]
|
||||||
|
}
|
||||||
22
src/dto/form/form.field.input.ts
Normal file
22
src/dto/form/form.field.input.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Field, ID, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@InputType('FormFieldInput')
|
||||||
|
export class FormFieldInput {
|
||||||
|
@Field(() => ID)
|
||||||
|
readonly id: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly title: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly type: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly description: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly required: boolean
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly value: string
|
||||||
|
}
|
||||||
32
src/dto/form/form.field.model.ts
Normal file
32
src/dto/form/form.field.model.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { FormFieldDocument } from '../../schema/form.field.schema';
|
||||||
|
|
||||||
|
@ObjectType('FormField')
|
||||||
|
export class FormFieldModel {
|
||||||
|
@Field(() => ID)
|
||||||
|
readonly id: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly title: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly type: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly description: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly required: boolean
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly value: string
|
||||||
|
|
||||||
|
constructor(document: FormFieldDocument) {
|
||||||
|
this.id = document.id
|
||||||
|
this.title = document.title
|
||||||
|
this.type = document.type
|
||||||
|
this.description = document.description
|
||||||
|
this.required = document.required
|
||||||
|
this.value = document.value
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,12 +21,12 @@ export class FormModel {
|
|||||||
@Field()
|
@Field()
|
||||||
readonly showFooter: boolean
|
readonly showFooter: boolean
|
||||||
|
|
||||||
constructor(partial: Partial<FormDocument>) {
|
constructor(form: FormDocument) {
|
||||||
this.id = partial.id
|
this.id = form.id
|
||||||
this.title = partial.title
|
this.title = form.title
|
||||||
this.created = partial.created
|
this.created = form.created
|
||||||
this.lastModified = partial.lastModified
|
this.lastModified = form.lastModified
|
||||||
this.language = partial.language
|
this.language = form.language
|
||||||
this.showFooter = partial.showFooter
|
this.showFooter = form.showFooter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/dto/form/form.update.input.ts
Normal file
42
src/dto/form/form.update.input.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Field, ID, InputType } from '@nestjs/graphql';
|
||||||
|
import { DesignInput } from './design.input';
|
||||||
|
import { FormFieldInput } from './form.field.input';
|
||||||
|
import { PageInput } from './page.input';
|
||||||
|
import { RespondentNotificationsInput } from './respondent.notifications.input';
|
||||||
|
import { SelfNotificationsInput } from './self.notifications.input';
|
||||||
|
|
||||||
|
@InputType('FormUpdateInput')
|
||||||
|
export class FormUpdateInput {
|
||||||
|
@Field(() => ID)
|
||||||
|
readonly id: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly title: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly language: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly showFooter: boolean
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly isLive: boolean
|
||||||
|
|
||||||
|
@Field(() => [FormFieldInput], { nullable: true })
|
||||||
|
readonly fields: FormFieldInput[]
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly design: DesignInput
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly startPage: PageInput
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly endPage: PageInput
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly selfNotifications: SelfNotificationsInput
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly respondentNotifications: RespondentNotificationsInput
|
||||||
|
}
|
||||||
20
src/dto/form/page.input.ts
Normal file
20
src/dto/form/page.input.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
import { ButtonInput } from './button.input';
|
||||||
|
|
||||||
|
@InputType('PageInput')
|
||||||
|
export class PageInput {
|
||||||
|
@Field()
|
||||||
|
readonly show: boolean
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly title?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly paragraph?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly buttonText?: string
|
||||||
|
|
||||||
|
@Field(() => [ButtonInput])
|
||||||
|
readonly buttons: ButtonInput[]
|
||||||
|
}
|
||||||
@ -2,8 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
|||||||
import { FormPage } from '../../schema/form.schema';
|
import { FormPage } from '../../schema/form.schema';
|
||||||
import { ButtonModel } from './button.model';
|
import { ButtonModel } from './button.model';
|
||||||
|
|
||||||
@ObjectType('FormPage')
|
@ObjectType('Page')
|
||||||
export class FormPageModel {
|
export class PageModel {
|
||||||
@Field()
|
@Field()
|
||||||
readonly show: boolean
|
readonly show: boolean
|
||||||
|
|
||||||
25
src/dto/form/pager.form.model.ts
Normal file
25
src/dto/form/pager.form.model.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { GraphQLInt } from 'graphql';
|
||||||
|
import { FormModel } from './form.model';
|
||||||
|
|
||||||
|
@ObjectType('PagerForm')
|
||||||
|
export class PagerFormModel {
|
||||||
|
@Field(() => [FormModel])
|
||||||
|
entries: FormModel[]
|
||||||
|
|
||||||
|
@Field(() => GraphQLInt)
|
||||||
|
total: number
|
||||||
|
|
||||||
|
@Field(() => GraphQLInt)
|
||||||
|
limit: number
|
||||||
|
|
||||||
|
@Field(() => GraphQLInt)
|
||||||
|
start: number
|
||||||
|
|
||||||
|
constructor(entries: FormModel[], total: number, limit: number, start: number) {
|
||||||
|
this.entries = entries
|
||||||
|
this.total = total
|
||||||
|
this.limit = limit
|
||||||
|
this.start = start
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/dto/form/respondent.notifications.input.ts
Normal file
11
src/dto/form/respondent.notifications.input.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
import { AbstractNotificationInput } from './abstract.notification.input';
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class RespondentNotificationsInput extends AbstractNotificationInput {
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly toField?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly fromEmail?: string
|
||||||
|
}
|
||||||
11
src/dto/form/self.notifications.input.ts
Normal file
11
src/dto/form/self.notifications.input.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
import { AbstractNotificationInput } from './abstract.notification.input';
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class SelfNotificationsInput extends AbstractNotificationInput {
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly fromField?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly toEmail?: string
|
||||||
|
}
|
||||||
@ -1,8 +1,15 @@
|
|||||||
import { Field, ObjectType } from '@nestjs/graphql';
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
import { UserModel } from './user.model';
|
import { UserModel } from './user.model';
|
||||||
|
|
||||||
@ObjectType('OwnUser')
|
@ObjectType('OwnUser')
|
||||||
export class OwnUserModel extends UserModel {
|
export class OwnUserModel extends UserModel {
|
||||||
@Field(() => [String])
|
@Field(() => [String])
|
||||||
readonly roles: string[]
|
readonly roles: string[]
|
||||||
|
|
||||||
|
constructor(user: UserDocument) {
|
||||||
|
super(user)
|
||||||
|
|
||||||
|
this.roles = user.roles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export class UserModel {
|
|||||||
@Field()
|
@Field()
|
||||||
readonly lastName?: string
|
readonly lastName?: string
|
||||||
|
|
||||||
constructor(user: Partial<UserDocument>) {
|
constructor(user: UserDocument) {
|
||||||
this.id = user.id
|
this.id = user.id
|
||||||
this.username = user.username
|
this.username = user.username
|
||||||
this.email = user.email
|
this.email = user.email
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { ContextCache } from '../resolver/context.cache';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GqlAuthGuard extends AuthGuard('jwt') {
|
export class GqlAuthGuard extends AuthGuard('jwt') {
|
||||||
getRequest(context: ExecutionContext) {
|
getRequest(context: ExecutionContext) {
|
||||||
const ctx = GqlExecutionContext.create(context);
|
const ctx = GqlExecutionContext.create(context);
|
||||||
if (!ctx.getContext().cache) {
|
if (!ctx.getContext().cache) {
|
||||||
ctx.getContext().cache = {
|
ctx.getContext().cache = new ContextCache()
|
||||||
// add(type, id, object) =>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ctx.getContext().req;
|
return ctx.getContext().req;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,5 +19,5 @@ import { AppModule } from './app.module';
|
|||||||
app.enableCors({origin: '*'})
|
app.enableCors({origin: '*'})
|
||||||
app.getHttpAdapter().options('*', cors())
|
app.getHttpAdapter().options('*', cors())
|
||||||
|
|
||||||
await app.listen(process.env.PORT || 3000);
|
await app.listen(process.env.PORT || 4100);
|
||||||
})()
|
})()
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Args, Mutation } from '@nestjs/graphql';
|
import { Args, Mutation } from '@nestjs/graphql';
|
||||||
|
import { PinoLogger } from 'nestjs-pino/dist';
|
||||||
import { AuthJwtModel } from '../../dto/auth/auth.jwt.model';
|
import { AuthJwtModel } from '../../dto/auth/auth.jwt.model';
|
||||||
import { UserCreateInput } from '../../dto/user/user.create.input';
|
import { UserCreateInput } from '../../dto/user/user.create.input';
|
||||||
import { AuthService } from '../../service/auth/auth.service';
|
import { AuthService } from '../../service/auth/auth.service';
|
||||||
@ -10,6 +11,7 @@ export class AuthRegisterResolver {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly createUser: UserCreateService,
|
private readonly createUser: UserCreateService,
|
||||||
private readonly auth: AuthService,
|
private readonly auth: AuthService,
|
||||||
|
private readonly logger: PinoLogger,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,6 +19,10 @@ export class AuthRegisterResolver {
|
|||||||
async authRegister(
|
async authRegister(
|
||||||
@Args({ name: 'user' }) data: UserCreateInput,
|
@Args({ name: 'user' }) data: UserCreateInput,
|
||||||
): Promise<AuthJwtModel> {
|
): Promise<AuthJwtModel> {
|
||||||
|
this.logger.info({
|
||||||
|
email: data.email,
|
||||||
|
username: data.username,
|
||||||
|
}, 'try to register new user')
|
||||||
const user = await this.createUser.create(data)
|
const user = await this.createUser.create(data)
|
||||||
|
|
||||||
return this.auth.login(user)
|
return this.auth.login(user)
|
||||||
|
|||||||
31
src/resolver/form/form.create.mutation.ts
Normal file
31
src/resolver/form/form.create.mutation.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Args, Context, Mutation } from '@nestjs/graphql';
|
||||||
|
import { Roles } from '../../decorator/roles.decorator';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { FormCreateInput } from '../../dto/form/form.create.input';
|
||||||
|
import { FormModel } from '../../dto/form/form.model';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { FormCreateService } from '../../service/form/form.create.service';
|
||||||
|
import { ContextCache } from '../context.cache';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FormCreateMutation {
|
||||||
|
constructor(
|
||||||
|
private readonly createService: FormCreateService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => FormModel)
|
||||||
|
@Roles('admin')
|
||||||
|
async createForm(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Args({ name: 'form', type: () => FormCreateInput }) input: FormCreateInput,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
): Promise<FormModel> {
|
||||||
|
const form = await this.createService.create(user, input)
|
||||||
|
|
||||||
|
cache.addForm(form)
|
||||||
|
|
||||||
|
return new FormModel(form)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/resolver/form/form.delete.mutation.ts
Normal file
34
src/resolver/form/form.delete.mutation.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Args, ID, Mutation } from '@nestjs/graphql';
|
||||||
|
import { Roles } from '../../decorator/roles.decorator';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { FormModel } from '../../dto/form/form.model';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { FormDeleteService } from '../../service/form/form.delete.service';
|
||||||
|
import { FormService } from '../../service/form/form.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FormDeleteMutation {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteService: FormDeleteService,
|
||||||
|
private readonly formService: FormService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => FormModel)
|
||||||
|
@Roles('admin')
|
||||||
|
async deleteForm(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Args({ name: 'id', type: () => ID}) id: string,
|
||||||
|
) {
|
||||||
|
const form = await this.formService.findById(id)
|
||||||
|
|
||||||
|
if (!form.isLive && !await this.formService.isAdmin(form, user)) {
|
||||||
|
throw new Error('invalid form')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.deleteService.delete(id)
|
||||||
|
|
||||||
|
return new FormModel(form)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,9 @@ import { Args, Context, ID, Parent, Query, ResolveField, Resolver } from '@nestj
|
|||||||
import { Roles } from '../../decorator/roles.decorator';
|
import { Roles } from '../../decorator/roles.decorator';
|
||||||
import { User } from '../../decorator/user.decorator';
|
import { User } from '../../decorator/user.decorator';
|
||||||
import { DesignModel } from '../../dto/form/design.model';
|
import { DesignModel } from '../../dto/form/design.model';
|
||||||
|
import { FormFieldModel } from '../../dto/form/form.field.model';
|
||||||
import { FormModel } from '../../dto/form/form.model';
|
import { FormModel } from '../../dto/form/form.model';
|
||||||
import { FormPageModel } from '../../dto/form/form.page.model';
|
import { PageModel } from '../../dto/form/page.model';
|
||||||
import { RespondentNotificationsModel } from '../../dto/form/respondent.notifications.model';
|
import { RespondentNotificationsModel } from '../../dto/form/respondent.notifications.model';
|
||||||
import { SelfNotificationsModel } from '../../dto/form/self.notifications.model';
|
import { SelfNotificationsModel } from '../../dto/form/self.notifications.model';
|
||||||
import { UserModel } from '../../dto/user/user.model';
|
import { UserModel } from '../../dto/user/user.model';
|
||||||
@ -35,6 +36,17 @@ export class FormResolver {
|
|||||||
return new FormModel(form)
|
return new FormModel(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResolveField('fields', () => [FormFieldModel])
|
||||||
|
async getFields(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Parent() parent: FormModel,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
): Promise<FormFieldModel[]> {
|
||||||
|
const form = await cache.getForm(parent.id)
|
||||||
|
|
||||||
|
return form.fields.map(field => new FormFieldModel(field))
|
||||||
|
}
|
||||||
|
|
||||||
@ResolveField('isLive', () => Boolean)
|
@ResolveField('isLive', () => Boolean)
|
||||||
@Roles('admin')
|
@Roles('admin')
|
||||||
async getRoles(
|
async getRoles(
|
||||||
@ -67,13 +79,13 @@ export class FormResolver {
|
|||||||
return new SelfNotificationsModel(form.selfNotifications)
|
return new SelfNotificationsModel(form.selfNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField('respondentNotifications', () => SelfNotificationsModel)
|
@ResolveField('respondentNotifications', () => RespondentNotificationsModel)
|
||||||
@Roles('admin')
|
@Roles('admin')
|
||||||
async getRespondentNotifications(
|
async getRespondentNotifications(
|
||||||
@User() user: UserDocument,
|
@User() user: UserDocument,
|
||||||
@Parent() parent: FormModel,
|
@Parent() parent: FormModel,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
): Promise<SelfNotificationsModel> {
|
): Promise<RespondentNotificationsModel> {
|
||||||
const form = await cache.getForm(parent.id)
|
const form = await cache.getForm(parent.id)
|
||||||
|
|
||||||
if (!await this.formService.isAdmin(form, user)) {
|
if (!await this.formService.isAdmin(form, user)) {
|
||||||
@ -94,24 +106,24 @@ export class FormResolver {
|
|||||||
return new DesignModel(form.design)
|
return new DesignModel(form.design)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField('startPage', () => FormPageModel)
|
@ResolveField('startPage', () => PageModel)
|
||||||
async getStartPage(
|
async getStartPage(
|
||||||
@Parent() parent: FormModel,
|
@Parent() parent: FormModel,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
): Promise<FormPageModel> {
|
): Promise<PageModel> {
|
||||||
const form = await cache.getForm(parent.id)
|
const form = await cache.getForm(parent.id)
|
||||||
|
|
||||||
return new FormPageModel(form.startPage)
|
return new PageModel(form.startPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField('endPage', () => FormPageModel)
|
@ResolveField('endPage', () => PageModel)
|
||||||
async getEndPage(
|
async getEndPage(
|
||||||
@Parent() parent: FormModel,
|
@Parent() parent: FormModel,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
): Promise<FormPageModel> {
|
): Promise<PageModel> {
|
||||||
const form = await cache.getForm(parent.id)
|
const form = await cache.getForm(parent.id)
|
||||||
|
|
||||||
return new FormPageModel(form.endPage)
|
return new PageModel(form.endPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField('admin', () => UserModel)
|
@ResolveField('admin', () => UserModel)
|
||||||
|
|||||||
35
src/resolver/form/form.search.resolver.ts
Normal file
35
src/resolver/form/form.search.resolver.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Args, Context, Query, Resolver } from '@nestjs/graphql';
|
||||||
|
import { GraphQLInt } from 'graphql';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { FormModel } from '../../dto/form/form.model';
|
||||||
|
import { PagerFormModel } from '../../dto/form/pager.form.model';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { FormService } from '../../service/form/form.service';
|
||||||
|
import { ContextCache } from '../context.cache';
|
||||||
|
|
||||||
|
@Resolver(() => PagerFormModel)
|
||||||
|
export class FormSearchResolver {
|
||||||
|
constructor(
|
||||||
|
private readonly formService: FormService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(() => PagerFormModel)
|
||||||
|
async listForms(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Args('start', {type: () => GraphQLInt, defaultValue: 0, nullable: true}) start,
|
||||||
|
@Args('limit', {type: () => GraphQLInt, defaultValue: 50, nullable: true}) limit,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
) {
|
||||||
|
const [forms, total] = await this.formService.find(user, start, limit)
|
||||||
|
|
||||||
|
forms.forEach(form => cache.addForm(form))
|
||||||
|
|
||||||
|
return new PagerFormModel(
|
||||||
|
forms.map(form => new FormModel(form)),
|
||||||
|
total,
|
||||||
|
limit,
|
||||||
|
start,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/resolver/form/form.update.mutation.ts
Normal file
39
src/resolver/form/form.update.mutation.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Args, Context, Mutation } from '@nestjs/graphql';
|
||||||
|
import { Roles } from '../../decorator/roles.decorator';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { FormModel } from '../../dto/form/form.model';
|
||||||
|
import { FormUpdateInput } from '../../dto/form/form.update.input';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { FormService } from '../../service/form/form.service';
|
||||||
|
import { FormUpdateService } from '../../service/form/form.update.service';
|
||||||
|
import { ContextCache } from '../context.cache';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FormUpdateMutation {
|
||||||
|
constructor(
|
||||||
|
private readonly updateService: FormUpdateService,
|
||||||
|
private readonly formService: FormService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => FormModel)
|
||||||
|
@Roles('admin')
|
||||||
|
async updateForm(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Args({ name: 'form', type: () => FormUpdateInput }) input: FormUpdateInput,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
): Promise<FormModel> {
|
||||||
|
let form = await this.formService.findById(input.id)
|
||||||
|
|
||||||
|
if (!form.isLive && !await this.formService.isAdmin(form, user)) {
|
||||||
|
throw new Error('invalid form')
|
||||||
|
}
|
||||||
|
|
||||||
|
form = await this.updateService.update(form, input)
|
||||||
|
|
||||||
|
cache.addForm(form)
|
||||||
|
|
||||||
|
return new FormModel(form)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,13 @@
|
|||||||
import { FieldResolver } from './field.resolver';
|
import { FormCreateMutation } from './form.create.mutation';
|
||||||
import { FormCreateResolver } from './form.create.resolver';
|
import { FormDeleteMutation } from './form.delete.mutation';
|
||||||
import { FormResolver } from './form.resolver';
|
import { FormResolver } from './form.resolver';
|
||||||
|
import { FormSearchResolver } from './form.search.resolver';
|
||||||
|
import { FormUpdateMutation } from './form.update.mutation';
|
||||||
|
|
||||||
export const formResolvers = [
|
export const formResolvers = [
|
||||||
FormResolver,
|
FormResolver,
|
||||||
FormCreateResolver,
|
FormSearchResolver,
|
||||||
FieldResolver,
|
FormCreateMutation,
|
||||||
|
FormDeleteMutation,
|
||||||
|
FormUpdateMutation,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Schema, SchemaDefinition } from 'mongoose';
|
import { Schema, SchemaDefinition } from 'mongoose';
|
||||||
import { FieldSchemaName } from '../field.schema';
|
import { FormFieldSchemaName } from '../form.field.schema';
|
||||||
|
|
||||||
export const LogicJump: SchemaDefinition = {
|
export const LogicJump: SchemaDefinition = {
|
||||||
expressionString: {
|
expressionString: {
|
||||||
@ -21,14 +21,14 @@ export const LogicJump: SchemaDefinition = {
|
|||||||
},
|
},
|
||||||
fieldA: {
|
fieldA: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: FieldSchemaName
|
ref: FormFieldSchemaName
|
||||||
},
|
},
|
||||||
valueB: {
|
valueB: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
jumpTo: {
|
jumpTo: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: FieldSchemaName
|
ref: FormFieldSchemaName
|
||||||
},
|
},
|
||||||
enabled: {
|
enabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@ -24,9 +24,4 @@ export const RatingField: SchemaDefinition = {
|
|||||||
'Trash',
|
'Trash',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
validShapes: {
|
|
||||||
type: [{
|
|
||||||
type: String,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,17 +4,21 @@ import { FieldOption } from './embedded/field.option';
|
|||||||
import { LogicJump } from './embedded/logic.jump';
|
import { LogicJump } from './embedded/logic.jump';
|
||||||
import { RatingField } from './embedded/rating.field';
|
import { RatingField } from './embedded/rating.field';
|
||||||
|
|
||||||
export const FieldSchemaName = 'FormField'
|
export const FormFieldSchemaName = 'FormField'
|
||||||
|
|
||||||
export interface FieldDocument extends Document {
|
export interface FormFieldDocument extends Document {
|
||||||
isSubmission: boolean
|
title: string
|
||||||
|
description: string
|
||||||
|
logicJump: any
|
||||||
|
rating: any
|
||||||
|
options: any
|
||||||
|
required: boolean
|
||||||
|
disabled: boolean
|
||||||
|
type: string
|
||||||
|
value: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldSchema = new Schema({
|
export const FormFieldSchema = new Schema({
|
||||||
isSubmission: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
trim: true,
|
trim: true,
|
||||||
@ -27,9 +31,11 @@ export const FieldSchema = new Schema({
|
|||||||
type: LogicJump,
|
type: LogicJump,
|
||||||
},
|
},
|
||||||
ratingOptions: {
|
ratingOptions: {
|
||||||
|
alias: 'rating',
|
||||||
type: RatingField,
|
type: RatingField,
|
||||||
},
|
},
|
||||||
fieldOptions: {
|
fieldOptions: {
|
||||||
|
alias: 'options',
|
||||||
type: [FieldOption],
|
type: [FieldOption],
|
||||||
},
|
},
|
||||||
required: {
|
required: {
|
||||||
@ -40,19 +46,20 @@ export const FieldSchema = new Schema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
deletePreserved: { // TODO remove
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
validFieldTypes: { // TODO remove
|
|
||||||
type: [String],
|
|
||||||
},
|
|
||||||
fieldType: {
|
fieldType: {
|
||||||
|
alias: 'type',
|
||||||
type: String,
|
type: String,
|
||||||
enum: fieldTypes,
|
enum: fieldTypes,
|
||||||
},
|
},
|
||||||
fieldValue: {
|
fieldValue: {
|
||||||
|
alias: 'value',
|
||||||
type: Schema.Types.Mixed,
|
type: Schema.Types.Mixed,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const FormFieldDefinition = {
|
||||||
|
name: FormFieldSchemaName,
|
||||||
|
schema: FormFieldSchema,
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,12 +1,10 @@
|
|||||||
import exp from 'constants';
|
|
||||||
import { Document, Schema } from 'mongoose';
|
import { Document, Schema } from 'mongoose';
|
||||||
import { matchType } from '../config/fields';
|
import { matchType } from '../config/fields';
|
||||||
import { defaultLanguage, languages } from '../config/languages';
|
import { defaultLanguage, languages } from '../config/languages';
|
||||||
import { rolesType } from '../config/roles';
|
|
||||||
import { ButtonDocument, ButtonSchema } from './button.schema';
|
import { ButtonDocument, ButtonSchema } from './button.schema';
|
||||||
import { FieldDocument, FieldSchema } from './field.schema';
|
import { FormFieldDocument, FormFieldSchema } from './form.field.schema';
|
||||||
import { VisitorDataDocument, VisitorDataSchema } from './visitor.data.schema';
|
|
||||||
import { UserDocument, UserSchemaName } from './user.schema';
|
import { UserDocument, UserSchemaName } from './user.schema';
|
||||||
|
import { VisitorDataDocument, VisitorDataSchema } from './visitor.data.schema';
|
||||||
|
|
||||||
export const FormSchemaName = 'Form'
|
export const FormSchemaName = 'Form'
|
||||||
|
|
||||||
@ -58,7 +56,7 @@ export interface FormDocument extends Document {
|
|||||||
readonly visitors: [VisitorDataDocument]
|
readonly visitors: [VisitorDataDocument]
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly fields: [FieldDocument]
|
readonly fields: [FormFieldDocument]
|
||||||
|
|
||||||
readonly admin: UserDocument
|
readonly admin: UserDocument
|
||||||
|
|
||||||
@ -103,9 +101,10 @@ export const FormSchema = new Schema({
|
|||||||
type: [VisitorDataSchema],
|
type: [VisitorDataSchema],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fields: {
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||||
alias: 'form_fields',
|
form_fields: {
|
||||||
type: [FieldSchema],
|
alias: 'fields',
|
||||||
|
type: [FormFieldSchema],
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
@ -113,18 +112,18 @@ export const FormSchema = new Schema({
|
|||||||
ref: UserSchemaName,
|
ref: UserSchemaName,
|
||||||
},
|
},
|
||||||
startPage: {
|
startPage: {
|
||||||
show: {
|
showStart: {
|
||||||
alias: 'showStart',
|
alias: 'startPage.show',
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
title: {
|
introTitle: {
|
||||||
alias: 'introTitle',
|
alias: 'startPage.title',
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Welcome to Form',
|
default: 'Welcome to Form',
|
||||||
},
|
},
|
||||||
paragraph: {
|
introParagraph: {
|
||||||
alias: 'introParagraph',
|
alias: 'startPage.paragraph',
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Start',
|
default: 'Start',
|
||||||
},
|
},
|
||||||
@ -133,8 +132,8 @@ export const FormSchema = new Schema({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
endPage: {
|
endPage: {
|
||||||
show: {
|
showEnd: {
|
||||||
alias: 'showEnd',
|
alias: 'endPage.show',
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
@ -157,8 +156,8 @@ export const FormSchema = new Schema({
|
|||||||
fromField: {
|
fromField: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
toEmail: {
|
toEmails: {
|
||||||
alias: 'toEmails',
|
alias: 'selfNotifications.toEmail',
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
subject: {
|
subject: {
|
||||||
@ -176,8 +175,8 @@ export const FormSchema = new Schema({
|
|||||||
toField: {
|
toField: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
fromEmail: {
|
fromEmails: {
|
||||||
alias: 'fromEmails',
|
alias: 'respondentNotifications.fromEmail',
|
||||||
type: String,
|
type: String,
|
||||||
match: matchType.email,
|
match: matchType.email,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { FormFieldDefinition } from './form.field.schema';
|
||||||
import { FormDefinition } from './form.schema';
|
import { FormDefinition } from './form.schema';
|
||||||
import { FormSubmissionDefinition, FormSubmissionSchema } from './form.submission.schema';
|
import { SubmissionDefinition } from './submission.schema';
|
||||||
import { UserDefinition } from './user.schema';
|
import { UserDefinition } from './user.schema';
|
||||||
|
|
||||||
export const schema = [
|
export const schema = [
|
||||||
FormDefinition,
|
FormDefinition,
|
||||||
FormSubmissionDefinition,
|
FormFieldDefinition,
|
||||||
|
SubmissionDefinition,
|
||||||
UserDefinition,
|
UserDefinition,
|
||||||
]
|
]
|
||||||
|
|||||||
26
src/schema/submission.field.schema.ts
Normal file
26
src/schema/submission.field.schema.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Document, Schema } from 'mongoose';
|
||||||
|
import { fieldTypes } from '../config/fields';
|
||||||
|
import { FormFieldDocument, FormFieldSchemaName } from './form.field.schema';
|
||||||
|
|
||||||
|
export const SubmissionFieldSchemaName = 'SubmissionField'
|
||||||
|
|
||||||
|
export interface SubmissionFieldDocument extends Document {
|
||||||
|
field: FormFieldDocument
|
||||||
|
fieldType: string
|
||||||
|
fieldValue: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SubmissionFormFieldSchema = new Schema({
|
||||||
|
field: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: FormFieldSchemaName
|
||||||
|
},
|
||||||
|
fieldType: {
|
||||||
|
type: String,
|
||||||
|
enum: fieldTypes,
|
||||||
|
},
|
||||||
|
fieldValue: {
|
||||||
|
type: Schema.Types.Mixed,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
@ -1,16 +1,17 @@
|
|||||||
import { Document, Schema } from 'mongoose';
|
import { Document, Schema } from 'mongoose';
|
||||||
import { FieldSchema } from './field.schema';
|
|
||||||
import { FormSchemaName } from './form.schema';
|
import { FormSchemaName } from './form.schema';
|
||||||
|
import { SubmissionFieldDocument, SubmissionFieldSchemaName } from './submission.field.schema';
|
||||||
|
|
||||||
export const FormSubmissionSchemaName = 'FormSubmission'
|
export const SubmissionSchemaName = 'FormSubmission'
|
||||||
|
|
||||||
export interface FormSubmissionDocument extends Document {
|
export interface SubmissionDocument extends Document {
|
||||||
|
fields: SubmissionFieldDocument[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FormSubmissionSchema = new Schema({
|
export const SubmissionSchema = new Schema({
|
||||||
fields: {
|
fields: {
|
||||||
alias: 'form_fields',
|
alias: 'form_fields',
|
||||||
type: [FieldSchema],
|
type: [SubmissionFieldSchemaName],
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
@ -50,8 +51,8 @@ export const FormSubmissionSchema = new Schema({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const FormSubmissionDefinition = {
|
export const SubmissionDefinition = {
|
||||||
name: FormSubmissionSchemaName,
|
name: SubmissionSchemaName,
|
||||||
schema: FormSubmissionSchema,
|
schema: SubmissionSchema,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { Document, Schema } from 'mongoose';
|
import { Document, Schema } from 'mongoose';
|
||||||
import { defaultLanguage, languages } from '../config/languages';
|
import { defaultLanguage, languages } from '../config/languages';
|
||||||
import { FieldDocument, FieldSchemaName } from './field.schema';
|
import { FormFieldDocument, FormFieldSchemaName } from './form.field.schema';
|
||||||
|
|
||||||
export interface VisitorDataDocument extends Document {
|
export interface VisitorDataDocument extends Document {
|
||||||
readonly introParagraph?: string
|
readonly introParagraph?: string
|
||||||
readonly referrer?: string
|
readonly referrer?: string
|
||||||
readonly filledOutFields: [FieldDocument]
|
readonly filledOutFields: [FormFieldDocument]
|
||||||
readonly timeElapsed: number
|
readonly timeElapsed: number
|
||||||
readonly isSubmitted: boolean
|
readonly isSubmitted: boolean
|
||||||
readonly language: string
|
readonly language: string
|
||||||
@ -24,7 +24,7 @@ export const VisitorDataSchema = new Schema({
|
|||||||
filledOutFields: {
|
filledOutFields: {
|
||||||
type: [{
|
type: [{
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: FieldSchemaName,
|
ref: FormFieldSchemaName,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
timeElapsed: {
|
timeElapsed: {
|
||||||
|
|||||||
@ -16,6 +16,8 @@ export class AuthService {
|
|||||||
async validateUser(username: string, password: string): Promise<UserDocument> {
|
async validateUser(username: string, password: string): Promise<UserDocument> {
|
||||||
console.log('check user??', username)
|
console.log('check user??', username)
|
||||||
|
|
||||||
|
// TODO only allow login for verified users!
|
||||||
|
|
||||||
const user = await this.userService.findByUsername(username);
|
const user = await this.userService.findByUsername(username);
|
||||||
if (user && await this.passwordService.verify(password, user.passwordHash, user.salt)) {
|
if (user && await this.passwordService.verify(password, user.passwordHash, user.salt)) {
|
||||||
return user;
|
return user;
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Model } from "mongoose";
|
import { Model } from 'mongoose';
|
||||||
|
import { FormCreateInput } from '../../dto/form/form.create.input';
|
||||||
import { FormDocument, FormSchemaName } from '../../schema/form.schema';
|
import { FormDocument, FormSchemaName } from '../../schema/form.schema';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FormCreateService {
|
export class FormCreateService {
|
||||||
@ -10,4 +12,7 @@ export class FormCreateService {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async create(admin: UserDocument, input: FormCreateInput): Promise<FormDocument> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/service/form/form.delete.service.ts
Normal file
17
src/service/form/form.delete.service.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
import { FormDocument, FormSchemaName } from '../../schema/form.schema';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FormDeleteService {
|
||||||
|
constructor(
|
||||||
|
@InjectModel(FormSchemaName) private formModel: Model<FormDocument>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string): Promise<void> {
|
||||||
|
// TODO
|
||||||
|
throw new Error('form.delete not yet implemented')
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { GraphQLError } from 'graphql';
|
|
||||||
import { Model, Types } from 'mongoose';
|
import { Model, Types } from 'mongoose';
|
||||||
import { FormDocument, FormSchemaName } from '../../schema/form.schema';
|
import { FormDocument, FormSchemaName } from '../../schema/form.schema';
|
||||||
import { UserDocument } from '../../schema/user.schema';
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
@ -20,6 +19,19 @@ export class FormService {
|
|||||||
return Types.ObjectId(form.admin.id).equals(Types.ObjectId(user.id))
|
return Types.ObjectId(form.admin.id).equals(Types.ObjectId(user.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async find(user: UserDocument, start: number, limit: number, sort: any = {}): Promise<[FormDocument[], number]> {
|
||||||
|
const qb = this.formModel.find()
|
||||||
|
|
||||||
|
// TODO apply restrictions based on user!
|
||||||
|
|
||||||
|
return [
|
||||||
|
await qb.sort(sort)
|
||||||
|
.skip(start)
|
||||||
|
.limit(limit),
|
||||||
|
await qb.count()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
async findById(id: string): Promise<FormDocument> {
|
async findById(id: string): Promise<FormDocument> {
|
||||||
const form = await this.formModel.findById(id);
|
const form = await this.formModel.findById(id);
|
||||||
|
|
||||||
|
|||||||
94
src/service/form/form.update.service.ts
Normal file
94
src/service/form/form.update.service.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
import { FormUpdateInput } from '../../dto/form/form.update.input';
|
||||||
|
import { FormFieldDocument, FormFieldSchemaName } from '../../schema/form.field.schema';
|
||||||
|
import { FormDocument, FormSchemaName } from '../../schema/form.schema';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FormUpdateService {
|
||||||
|
constructor(
|
||||||
|
@InjectModel(FormSchemaName) private formModel: Model<FormDocument>,
|
||||||
|
@InjectModel(FormFieldSchemaName) private formFieldModel: Model<FormFieldDocument>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(form: FormDocument, input: FormUpdateInput): Promise<FormDocument> {
|
||||||
|
if (input.language !== undefined) {
|
||||||
|
form.set('language', input.language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.title !== undefined) {
|
||||||
|
form.set('title', input.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.showFooter !== undefined) {
|
||||||
|
form.set('showFooter', input.showFooter)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldMapping = {}
|
||||||
|
|
||||||
|
if (input.fields !== undefined) {
|
||||||
|
const nextFields = await Promise.all(input.fields.map(async (nextField) => {
|
||||||
|
let field = form.fields.find(field => field.id.toString() === nextField.id)
|
||||||
|
|
||||||
|
if (!field) {
|
||||||
|
field = await this.formFieldModel.create({
|
||||||
|
type: nextField.type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ability for other fields to apply mapping
|
||||||
|
fieldMapping[nextField.id] = field.id.toString()
|
||||||
|
field.set('title', nextField.title)
|
||||||
|
field.set('description', nextField.description)
|
||||||
|
field.set('required', nextField.required)
|
||||||
|
field.set('value', nextField.value)
|
||||||
|
|
||||||
|
return field
|
||||||
|
}))
|
||||||
|
|
||||||
|
console.log('field mapping', fieldMapping)
|
||||||
|
|
||||||
|
form.set('fields', nextFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractField = (id) => {
|
||||||
|
if (id && fieldMapping[id]) {
|
||||||
|
return fieldMapping[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.design !== undefined) {
|
||||||
|
form.set('design', input.design)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.selfNotifications !== undefined) {
|
||||||
|
form.set('selfNotifications', {
|
||||||
|
...input.selfNotifications,
|
||||||
|
fromField: extractField(input.selfNotifications.fromField)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.respondentNotifications !== undefined) {
|
||||||
|
form.set('respondentNotifications', {
|
||||||
|
...input.respondentNotifications,
|
||||||
|
toField: extractField(input.respondentNotifications.toField)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.startPage !== undefined) {
|
||||||
|
form.set('startPage', input.startPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.endPage !== undefined) {
|
||||||
|
form.set('endPage', input.endPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
await form.save()
|
||||||
|
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,11 @@
|
|||||||
import { FormCreateService } from './form.create.service';
|
import { FormCreateService } from './form.create.service';
|
||||||
|
import { FormDeleteService } from './form.delete.service';
|
||||||
import { FormService } from './form.service';
|
import { FormService } from './form.service';
|
||||||
|
import { FormUpdateService } from './form.update.service';
|
||||||
|
|
||||||
export const formServices = [
|
export const formServices = [
|
||||||
FormService,
|
FormService,
|
||||||
FormCreateService,
|
FormCreateService,
|
||||||
|
FormUpdateService,
|
||||||
|
FormDeleteService,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
||||||
|
import { PubSub, PubSubEngine } from 'graphql-subscriptions';
|
||||||
|
import Redis from 'ioredis';
|
||||||
|
import { PinoLogger } from 'nestjs-pino/dist';
|
||||||
import { authServices } from './auth';
|
import { authServices } from './auth';
|
||||||
import { formServices } from './form';
|
import { formServices } from './form';
|
||||||
import { MailService } from './mail.service';
|
import { MailService } from './mail.service';
|
||||||
@ -8,4 +13,27 @@ export const services = [
|
|||||||
...formServices,
|
...formServices,
|
||||||
...authServices,
|
...authServices,
|
||||||
MailService,
|
MailService,
|
||||||
|
{
|
||||||
|
provide: 'PUB_SUB',
|
||||||
|
inject: [ConfigService, PinoLogger],
|
||||||
|
useFactory: (configService: ConfigService, logger: PinoLogger): PubSubEngine => {
|
||||||
|
const host = configService.get<string>('REDIS_HOST', null)
|
||||||
|
const port = configService.get<number>('REDIS_PORT', 6379)
|
||||||
|
|
||||||
|
if (host === null) {
|
||||||
|
logger.warn('without redis graphql subscriptions will be unreliable in load balanced environments')
|
||||||
|
return new PubSub()
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RedisPubSub({
|
||||||
|
publisher: new Redis(options),
|
||||||
|
subscriber: new Redis(options),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
59
yarn.lock
59
yarn.lock
@ -2979,6 +2979,11 @@ cls-hooked@^4.2.2:
|
|||||||
emitter-listener "^1.0.1"
|
emitter-listener "^1.0.1"
|
||||||
semver "^5.4.1"
|
semver "^5.4.1"
|
||||||
|
|
||||||
|
cluster-key-slot@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||||
|
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
||||||
|
|
||||||
co@^4.6.0:
|
co@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||||
@ -3458,7 +3463,7 @@ delegates@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||||
|
|
||||||
denque@^1.4.1:
|
denque@^1.1.0, denque@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
|
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
|
||||||
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
|
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
|
||||||
@ -4763,7 +4768,16 @@ graphql-extensions@^0.12.0:
|
|||||||
apollo-server-env "^2.4.3"
|
apollo-server-env "^2.4.3"
|
||||||
apollo-server-types "^0.4.0"
|
apollo-server-types "^0.4.0"
|
||||||
|
|
||||||
graphql-subscriptions@^1.0.0:
|
graphql-redis-subscriptions@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/graphql-redis-subscriptions/-/graphql-redis-subscriptions-2.2.1.tgz#377be5670ff344aa78cf147a9852e686a65e4b21"
|
||||||
|
integrity sha512-Rk0hapKUZuZpJIv3rG5rmd1SX3f+9k1k5AXoh8bxbM3Vkdzh28WM7kvJOqq1pJuO3gQ4OAoqzciNT0MMHRylXQ==
|
||||||
|
dependencies:
|
||||||
|
iterall "^1.3.0"
|
||||||
|
optionalDependencies:
|
||||||
|
ioredis "^4.6.3"
|
||||||
|
|
||||||
|
graphql-subscriptions@^1.0.0, graphql-subscriptions@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11"
|
resolved "https://registry.yarnpkg.com/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11"
|
||||||
integrity sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA==
|
integrity sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA==
|
||||||
@ -5240,6 +5254,21 @@ invert-kv@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
||||||
integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
|
integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
|
||||||
|
|
||||||
|
ioredis@^4.17.1, ioredis@^4.6.3:
|
||||||
|
version "4.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.17.1.tgz#06ef3d3b2cb96b7e6bc90a7b8839a33e743843ad"
|
||||||
|
integrity sha512-kfxkN/YO1dnyaoAGyNdH3my4A1eoGDy4QOfqn6o86fo4dTboxyxYVW0S0v/d3MkwCWlvSWhlwq6IJMY9BlWs6w==
|
||||||
|
dependencies:
|
||||||
|
cluster-key-slot "^1.1.0"
|
||||||
|
debug "^4.1.1"
|
||||||
|
denque "^1.1.0"
|
||||||
|
lodash.defaults "^4.2.0"
|
||||||
|
lodash.flatten "^4.4.0"
|
||||||
|
redis-commands "1.5.0"
|
||||||
|
redis-errors "^1.2.0"
|
||||||
|
redis-parser "^3.0.0"
|
||||||
|
standard-as-callback "^2.0.1"
|
||||||
|
|
||||||
ip-regex@^2.1.0:
|
ip-regex@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||||
@ -6339,7 +6368,7 @@ lodash.bind@^4.1.4:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35"
|
resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35"
|
||||||
integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=
|
integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=
|
||||||
|
|
||||||
lodash.defaults@^4.0.1:
|
lodash.defaults@^4.0.1, lodash.defaults@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||||
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
|
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
|
||||||
@ -6349,7 +6378,7 @@ lodash.filter@^4.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
|
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
|
||||||
integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=
|
integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=
|
||||||
|
|
||||||
lodash.flatten@^4.2.0:
|
lodash.flatten@^4.2.0, lodash.flatten@^4.4.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||||
@ -8410,6 +8439,23 @@ rechoir@^0.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
resolve "^1.1.6"
|
resolve "^1.1.6"
|
||||||
|
|
||||||
|
redis-commands@1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
|
||||||
|
integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
|
||||||
|
|
||||||
|
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
|
||||||
|
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
|
||||||
|
|
||||||
|
redis-parser@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
|
||||||
|
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
|
||||||
|
dependencies:
|
||||||
|
redis-errors "^1.0.0"
|
||||||
|
|
||||||
reflect-metadata@^0.1.13:
|
reflect-metadata@^0.1.13:
|
||||||
version "0.1.13"
|
version "0.1.13"
|
||||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
|
||||||
@ -9132,6 +9178,11 @@ stack-utils@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
|
||||||
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
|
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
|
||||||
|
|
||||||
|
standard-as-callback@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126"
|
||||||
|
integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==
|
||||||
|
|
||||||
static-extend@^0.1.1:
|
static-extend@^0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user