add logic to create forms, to add submissions, to read submissions, etc
This commit is contained in:
parent
eb5bc26e5c
commit
eda8a3920c
@ -38,6 +38,7 @@
|
|||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
|
"dayjs": "^1.8.28",
|
||||||
"graphql": "15.0.0",
|
"graphql": "15.0.0",
|
||||||
"graphql-redis-subscriptions": "^2.2.1",
|
"graphql-redis-subscriptions": "^2.2.1",
|
||||||
"graphql-subscriptions": "^1.1.0",
|
"graphql-subscriptions": "^1.1.0",
|
||||||
|
|||||||
@ -16,8 +16,8 @@ export const fieldTypes = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export const matchType = {
|
export const matchType = {
|
||||||
color: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
|
color: /^#([A-F0-9]{6}|[A-F0-9]{3})$/i,
|
||||||
url: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/,
|
url: /((([A-Z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/i,
|
||||||
email: /.+@.+\..+/,
|
email: /.+@.+\..+/,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
@InputType('ButtonInput')
|
@InputType()
|
||||||
export class ButtonInput {
|
export class ButtonInput {
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
readonly url?: string
|
readonly url?: string
|
||||||
@ -14,6 +14,9 @@ export class ButtonInput {
|
|||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
readonly bgColor?: string
|
readonly bgColor?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly activeColor?: string
|
||||||
|
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
readonly color?: string
|
readonly color?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,9 @@ export class ButtonModel {
|
|||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
readonly bgColor?: string
|
readonly bgColor?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly activeColor?: string
|
||||||
|
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
readonly color?: string
|
readonly color?: string
|
||||||
|
|
||||||
@ -22,6 +25,7 @@ export class ButtonModel {
|
|||||||
this.action = button.action
|
this.action = button.action
|
||||||
this.text = button.text
|
this.text = button.text
|
||||||
this.bgColor = button.bgColor
|
this.bgColor = button.bgColor
|
||||||
|
this.activeColor = button.activeColor
|
||||||
this.color = button.color
|
this.color = button.color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
@InputType('ColorsInput')
|
@InputType()
|
||||||
export class ColorsInput {
|
export class ColorsInput {
|
||||||
@Field()
|
@Field()
|
||||||
readonly backgroundColor: string
|
readonly backgroundColor: string
|
||||||
@ -14,6 +14,9 @@ export class ColorsInput {
|
|||||||
@Field()
|
@Field()
|
||||||
readonly buttonColor: string
|
readonly buttonColor: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly buttonActiveColor: string
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
readonly buttonTextColor: string
|
readonly buttonTextColor: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,9 @@ export class ColorsModel {
|
|||||||
@Field()
|
@Field()
|
||||||
readonly buttonColor: string
|
readonly buttonColor: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly buttonActiveColor: string
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
readonly buttonTextColor: string
|
readonly buttonTextColor: string
|
||||||
|
|
||||||
@ -23,6 +26,7 @@ export class ColorsModel {
|
|||||||
this.questionColor = partial.questionColor
|
this.questionColor = partial.questionColor
|
||||||
this.answerColor = partial.answerColor
|
this.answerColor = partial.answerColor
|
||||||
this.buttonColor = partial.buttonColor
|
this.buttonColor = partial.buttonColor
|
||||||
|
this.buttonActiveColor = partial.buttonActiveColor
|
||||||
this.buttonTextColor = partial.buttonTextColor
|
this.buttonTextColor = partial.buttonTextColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
import { ColorsInput } from './colors.input';
|
import { ColorsInput } from './colors.input';
|
||||||
|
|
||||||
@InputType('DesignInput')
|
@InputType()
|
||||||
export class DesignInput {
|
export class DesignInput {
|
||||||
@Field()
|
@Field()
|
||||||
readonly colors: ColorsInput
|
readonly colors: ColorsInput
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
import { Field, ID, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
import { FormUpdateInput } from './form.update.input';
|
|
||||||
|
|
||||||
@InputType('FormCreateInput')
|
@InputType('FormCreateInput')
|
||||||
export class FormCreateInput extends FormUpdateInput {
|
export class FormCreateInput {
|
||||||
@Field(() => ID, { nullable: true })
|
@Field()
|
||||||
readonly id: string
|
readonly title: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly language: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly showFooter: boolean
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly isLive: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Field, ID, InputType } from '@nestjs/graphql';
|
import { Field, ID, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
@InputType('FormFieldInput')
|
@InputType()
|
||||||
export class FormFieldInput {
|
export class FormFieldInput {
|
||||||
@Field(() => ID)
|
@Field(() => ID)
|
||||||
readonly id: string
|
readonly id: string
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { PageInput } from './page.input';
|
|||||||
import { RespondentNotificationsInput } from './respondent.notifications.input';
|
import { RespondentNotificationsInput } from './respondent.notifications.input';
|
||||||
import { SelfNotificationsInput } from './self.notifications.input';
|
import { SelfNotificationsInput } from './self.notifications.input';
|
||||||
|
|
||||||
@InputType('FormUpdateInput')
|
@InputType()
|
||||||
export class FormUpdateInput {
|
export class FormUpdateInput {
|
||||||
@Field(() => ID)
|
@Field(() => ID)
|
||||||
readonly id: string
|
readonly id: string
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
import { ButtonInput } from './button.input';
|
import { ButtonInput } from './button.input';
|
||||||
|
|
||||||
@InputType('PageInput')
|
@InputType()
|
||||||
export class PageInput {
|
export class PageInput {
|
||||||
@Field()
|
@Field()
|
||||||
readonly show: boolean
|
readonly show: boolean
|
||||||
|
|||||||
10
src/dto/submission/device.input.ts
Normal file
10
src/dto/submission/device.input.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class DeviceInput {
|
||||||
|
@Field()
|
||||||
|
readonly type: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly name: string
|
||||||
|
}
|
||||||
16
src/dto/submission/device.model.ts
Normal file
16
src/dto/submission/device.model.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { Device } from '../../schema/submission.schema';
|
||||||
|
|
||||||
|
@ObjectType('Device')
|
||||||
|
export class DeviceModel {
|
||||||
|
@Field()
|
||||||
|
readonly type: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly name: string
|
||||||
|
|
||||||
|
constructor(device: Device) {
|
||||||
|
this.type = device.type
|
||||||
|
this.name = device.name
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/dto/submission/geo.location.model.ts
Normal file
16
src/dto/submission/geo.location.model.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { GeoLocation } from '../../schema/submission.schema';
|
||||||
|
|
||||||
|
@ObjectType('GeoLocation')
|
||||||
|
export class GeoLocationModel {
|
||||||
|
@Field({ nullable: true })
|
||||||
|
country?: string
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
city?: string
|
||||||
|
|
||||||
|
constructor(geo: GeoLocation) {
|
||||||
|
this.country = geo.country
|
||||||
|
this.city = geo.city
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/dto/submission/pager.submission.model.ts
Normal file
25
src/dto/submission/pager.submission.model.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { GraphQLInt } from 'graphql';
|
||||||
|
import { SubmissionModel } from './submission.model';
|
||||||
|
|
||||||
|
@ObjectType('PagerSubmission')
|
||||||
|
export class PagerSubmissionModel {
|
||||||
|
@Field(() => [SubmissionModel])
|
||||||
|
entries: SubmissionModel[]
|
||||||
|
|
||||||
|
@Field(() => GraphQLInt)
|
||||||
|
total: number
|
||||||
|
|
||||||
|
@Field(() => GraphQLInt)
|
||||||
|
limit: number
|
||||||
|
|
||||||
|
@Field(() => GraphQLInt)
|
||||||
|
start: number
|
||||||
|
|
||||||
|
constructor(entries: SubmissionModel[], total: number, limit: number, start: number) {
|
||||||
|
this.entries = entries
|
||||||
|
this.total = total
|
||||||
|
this.limit = limit
|
||||||
|
this.start = start
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/dto/submission/submission.field.model.ts
Normal file
20
src/dto/submission/submission.field.model.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { SubmissionFieldDocument } from '../../schema/submission.field.schema';
|
||||||
|
|
||||||
|
@ObjectType('SubmissionField')
|
||||||
|
export class SubmissionFieldModel {
|
||||||
|
@Field(() => ID)
|
||||||
|
readonly id: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly value: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly type: string
|
||||||
|
|
||||||
|
constructor(field: SubmissionFieldDocument) {
|
||||||
|
this.id = field.id
|
||||||
|
this.value = JSON.stringify(field.fieldValue)
|
||||||
|
this.type = field.fieldType
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/dto/submission/submission.model.ts
Normal file
45
src/dto/submission/submission.model.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { SubmissionDocument } from '../../schema/submission.schema';
|
||||||
|
import { DeviceModel } from './device.model';
|
||||||
|
import { GeoLocationModel } from './geo.location.model';
|
||||||
|
|
||||||
|
@ObjectType('Submission')
|
||||||
|
export class SubmissionModel {
|
||||||
|
@Field(() => ID)
|
||||||
|
readonly id: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly ipAddr: string
|
||||||
|
|
||||||
|
@Field(() => GeoLocationModel)
|
||||||
|
readonly geoLocation: GeoLocationModel
|
||||||
|
|
||||||
|
@Field(() => DeviceModel)
|
||||||
|
readonly device: DeviceModel
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly timeElapsed: number
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly percentageComplete: number
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly created: Date
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly lastModified?: Date
|
||||||
|
|
||||||
|
constructor(submission: SubmissionDocument) {
|
||||||
|
this.id = submission.id
|
||||||
|
|
||||||
|
this.ipAddr = submission.ipAddr
|
||||||
|
this.geoLocation = new GeoLocationModel(submission.geoLocation)
|
||||||
|
this.device = new DeviceModel(submission.device)
|
||||||
|
|
||||||
|
this.timeElapsed = submission.timeElapsed
|
||||||
|
this.percentageComplete = submission.percentageComplete
|
||||||
|
|
||||||
|
this.created = submission.created
|
||||||
|
this.lastModified = submission.lastModified
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/dto/submission/submission.progress.model.ts
Normal file
30
src/dto/submission/submission.progress.model.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||||
|
import { SubmissionDocument } from '../../schema/submission.schema';
|
||||||
|
|
||||||
|
@ObjectType('SubmissionProgress')
|
||||||
|
export class SubmissionProgressModel {
|
||||||
|
@Field(() => ID)
|
||||||
|
readonly id: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly timeElapsed: number
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly percentageComplete: number
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly created: Date
|
||||||
|
|
||||||
|
@Field({ nullable: true })
|
||||||
|
readonly lastModified?: Date
|
||||||
|
|
||||||
|
constructor(submission: Partial<SubmissionDocument>) {
|
||||||
|
this.id = submission.id
|
||||||
|
|
||||||
|
this.timeElapsed = submission.timeElapsed
|
||||||
|
this.percentageComplete = submission.percentageComplete
|
||||||
|
|
||||||
|
this.created = submission.created
|
||||||
|
this.lastModified = submission.lastModified
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/dto/submission/submission.set.field.input.ts
Normal file
13
src/dto/submission/submission.set.field.input.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Field, ID, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class SubmissionSetFieldInput {
|
||||||
|
@Field()
|
||||||
|
readonly token: string
|
||||||
|
|
||||||
|
@Field(() => ID)
|
||||||
|
readonly field: string
|
||||||
|
|
||||||
|
@Field()
|
||||||
|
readonly data: string
|
||||||
|
}
|
||||||
11
src/dto/submission/submission.start.input.ts
Normal file
11
src/dto/submission/submission.start.input.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
import { DeviceInput } from './device.input';
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class SubmissionStartInput {
|
||||||
|
@Field()
|
||||||
|
readonly token: string
|
||||||
|
|
||||||
|
@Field(() => DeviceInput)
|
||||||
|
readonly device: DeviceInput
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
|
import { FormFieldDocument } from '../schema/form.field.schema';
|
||||||
import { FormDocument } from '../schema/form.schema';
|
import { FormDocument } from '../schema/form.schema';
|
||||||
|
import { SubmissionDocument } from '../schema/submission.schema';
|
||||||
import { UserDocument } from '../schema/user.schema';
|
import { UserDocument } from '../schema/user.schema';
|
||||||
|
|
||||||
export class ContextCache {
|
export class ContextCache {
|
||||||
@ -10,6 +12,14 @@ export class ContextCache {
|
|||||||
[id: string]: FormDocument,
|
[id: string]: FormDocument,
|
||||||
} = {}
|
} = {}
|
||||||
|
|
||||||
|
private submissions: {
|
||||||
|
[id: string]: SubmissionDocument,
|
||||||
|
} = {}
|
||||||
|
|
||||||
|
private formField: {
|
||||||
|
[id: string]: FormFieldDocument,
|
||||||
|
} = {}
|
||||||
|
|
||||||
public addUser(user: UserDocument) {
|
public addUser(user: UserDocument) {
|
||||||
this.users[user.id] = user;
|
this.users[user.id] = user;
|
||||||
}
|
}
|
||||||
@ -25,4 +35,20 @@ export class ContextCache {
|
|||||||
public async getForm(id: any): Promise<FormDocument> {
|
public async getForm(id: any): Promise<FormDocument> {
|
||||||
return this.forms[id]
|
return this.forms[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addSubmission(submission: SubmissionDocument) {
|
||||||
|
this.submissions[submission.id] = submission
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSubmission(id: any): Promise<SubmissionDocument> {
|
||||||
|
return this.submissions[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
public addFormField(formField: FormFieldDocument) {
|
||||||
|
this.formField[formField.id] = formField
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getFormField(id: any): Promise<FormFieldDocument> {
|
||||||
|
return this.formField[id]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,12 @@ export class FormSearchResolver {
|
|||||||
@Args('limit', {type: () => GraphQLInt, defaultValue: 50, nullable: true}) limit,
|
@Args('limit', {type: () => GraphQLInt, defaultValue: 50, nullable: true}) limit,
|
||||||
@Context('cache') cache: ContextCache,
|
@Context('cache') cache: ContextCache,
|
||||||
) {
|
) {
|
||||||
const [forms, total] = await this.formService.find(user, start, limit)
|
const [forms, total] = await this.formService.find(
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
{},
|
||||||
|
user.roles.includes('superuser') ? null : user,
|
||||||
|
)
|
||||||
|
|
||||||
forms.forEach(form => cache.addForm(form))
|
forms.forEach(form => cache.addForm(form))
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { authServices } from './auth';
|
|||||||
import { formResolvers } from './form';
|
import { formResolvers } from './form';
|
||||||
import { myResolvers } from './me';
|
import { myResolvers } from './me';
|
||||||
import { StatusResolver } from './status.resolver';
|
import { StatusResolver } from './status.resolver';
|
||||||
|
import { submissionResolvers } from './submission';
|
||||||
import { userResolvers } from './user';
|
import { userResolvers } from './user';
|
||||||
|
|
||||||
export const resolvers = [
|
export const resolvers = [
|
||||||
@ -10,4 +11,5 @@ export const resolvers = [
|
|||||||
...authServices,
|
...authServices,
|
||||||
...myResolvers,
|
...myResolvers,
|
||||||
...formResolvers,
|
...formResolvers,
|
||||||
|
...submissionResolvers,
|
||||||
]
|
]
|
||||||
|
|||||||
13
src/resolver/submission/index.ts
Normal file
13
src/resolver/submission/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { SubmissionProgressResolver } from './submission.progress.resolver';
|
||||||
|
import { SubmissionResolver } from './submission.resolver';
|
||||||
|
import { SubmissionSearchResolver } from './submission.search.resolver';
|
||||||
|
import { SubmissionSetFieldMutation } from './submission.set.field.mutation';
|
||||||
|
import { SubmissionStartMutation } from './submission.start.mutation';
|
||||||
|
|
||||||
|
export const submissionResolvers = [
|
||||||
|
SubmissionProgressResolver,
|
||||||
|
SubmissionResolver,
|
||||||
|
SubmissionSetFieldMutation,
|
||||||
|
SubmissionStartMutation,
|
||||||
|
SubmissionSearchResolver,
|
||||||
|
]
|
||||||
7
src/resolver/submission/submission.progress.resolver.ts
Normal file
7
src/resolver/submission/submission.progress.resolver.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Resolver } from '@nestjs/graphql';
|
||||||
|
import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model';
|
||||||
|
|
||||||
|
@Resolver(() => SubmissionProgressModel)
|
||||||
|
export class SubmissionProgressResolver {
|
||||||
|
|
||||||
|
}
|
||||||
30
src/resolver/submission/submission.resolver.ts
Normal file
30
src/resolver/submission/submission.resolver.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { SubmissionFieldModel } from '../../dto/submission/submission.field.model';
|
||||||
|
import { SubmissionModel } from '../../dto/submission/submission.model';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { ContextCache } from '../context.cache';
|
||||||
|
|
||||||
|
@Resolver(() => SubmissionModel)
|
||||||
|
export class SubmissionResolver {
|
||||||
|
@ResolveField('fields', () => [SubmissionFieldModel])
|
||||||
|
async getFields(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Parent() parent: SubmissionModel,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
): Promise<SubmissionFieldModel[]> {
|
||||||
|
const submission = await cache.getSubmission(parent.id)
|
||||||
|
|
||||||
|
if (!submission.populated('form')) {
|
||||||
|
submission.populate('form')
|
||||||
|
await submission.execPopulate()
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.addForm(submission.form)
|
||||||
|
submission.form.fields.forEach(field => {
|
||||||
|
cache.addFormField(field)
|
||||||
|
})
|
||||||
|
|
||||||
|
return submission.fields.map(field => new SubmissionFieldModel(field))
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/resolver/submission/submission.search.resolver.ts
Normal file
45
src/resolver/submission/submission.search.resolver.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Args, Context, ID, Query, Resolver } from '@nestjs/graphql';
|
||||||
|
import { GraphQLInt } from 'graphql';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { PagerSubmissionModel } from '../../dto/submission/pager.submission.model';
|
||||||
|
import { SubmissionModel } from '../../dto/submission/submission.model';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { FormService } from '../../service/form/form.service';
|
||||||
|
import { SubmissionService } from '../../service/submission/submission.service';
|
||||||
|
import { ContextCache } from '../context.cache';
|
||||||
|
|
||||||
|
@Resolver(() => PagerSubmissionModel)
|
||||||
|
export class SubmissionSearchResolver {
|
||||||
|
constructor(
|
||||||
|
private readonly formService: FormService,
|
||||||
|
private readonly submissionService: SubmissionService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(() => PagerSubmissionModel)
|
||||||
|
async listSubmissions(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Args('form', {type: () => ID}) id: string,
|
||||||
|
@Args('start', {type: () => GraphQLInt, defaultValue: 0, nullable: true}) start,
|
||||||
|
@Args('limit', {type: () => GraphQLInt, defaultValue: 50, nullable: true}) limit,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
): Promise<PagerSubmissionModel> {
|
||||||
|
const form = await this.formService.findById(id)
|
||||||
|
|
||||||
|
const [submissions, total] = await this.submissionService.find(
|
||||||
|
form,
|
||||||
|
start,
|
||||||
|
limit,
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
submissions.forEach(submission => cache.addSubmission(submission))
|
||||||
|
|
||||||
|
return new PagerSubmissionModel(
|
||||||
|
submissions.map(submission => new SubmissionModel(submission)),
|
||||||
|
total,
|
||||||
|
limit,
|
||||||
|
start,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/resolver/submission/submission.set.field.mutation.ts
Normal file
38
src/resolver/submission/submission.set.field.mutation.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Args, Context, ID, Mutation } from '@nestjs/graphql';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model';
|
||||||
|
import { SubmissionSetFieldInput } from '../../dto/submission/submission.set.field.input';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { SubmissionService } from '../../service/submission/submission.service';
|
||||||
|
import { SubmissionSetFieldService } from '../../service/submission/submission.set.field.service';
|
||||||
|
import { ContextCache } from '../context.cache';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SubmissionSetFieldMutation {
|
||||||
|
constructor(
|
||||||
|
private readonly submissionService: SubmissionService,
|
||||||
|
private readonly setFieldService: SubmissionSetFieldService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => SubmissionProgressModel)
|
||||||
|
async submissionSetField(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Args({ name: 'submission', type: () => ID }) id: string,
|
||||||
|
@Args({ name: 'field', type: () => SubmissionSetFieldInput }) input: SubmissionSetFieldInput,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
): Promise<SubmissionProgressModel> {
|
||||||
|
const submission = await this.submissionService.findById(id)
|
||||||
|
|
||||||
|
if (!await this.submissionService.isOwner(submission, input.token)) {
|
||||||
|
throw new Error('no access to submission')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.setFieldService.saveField(submission, input)
|
||||||
|
|
||||||
|
cache.addSubmission(submission)
|
||||||
|
|
||||||
|
return new SubmissionProgressModel(submission)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/resolver/submission/submission.start.mutation.ts
Normal file
34
src/resolver/submission/submission.start.mutation.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Args, Context, ID, Mutation } from '@nestjs/graphql';
|
||||||
|
import { User } from '../../decorator/user.decorator';
|
||||||
|
import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model';
|
||||||
|
import { SubmissionStartInput } from '../../dto/submission/submission.start.input';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { FormService } from '../../service/form/form.service';
|
||||||
|
import { SubmissionStartService } from '../../service/submission/submission.start.service';
|
||||||
|
import { ContextCache } from '../context.cache';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SubmissionStartMutation {
|
||||||
|
constructor(
|
||||||
|
private readonly startService: SubmissionStartService,
|
||||||
|
private readonly formService: FormService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => SubmissionProgressModel)
|
||||||
|
async submissionStart(
|
||||||
|
@User() user: UserDocument,
|
||||||
|
@Args({ name: 'form', type: () => ID }) id: string,
|
||||||
|
@Args({ name: 'submission', type: () => SubmissionStartInput }) input: SubmissionStartInput,
|
||||||
|
@Context('cache') cache: ContextCache,
|
||||||
|
): Promise<SubmissionProgressModel> {
|
||||||
|
const form = await this.formService.findById(id)
|
||||||
|
|
||||||
|
const submission = await this.startService.start(form, input, user)
|
||||||
|
|
||||||
|
cache.addSubmission(submission)
|
||||||
|
|
||||||
|
return new SubmissionProgressModel(submission)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Schema, Document } from 'mongoose';
|
import { Document, Schema } from 'mongoose';
|
||||||
import { matchType } from '../config/fields';
|
import { matchType } from '../config/fields';
|
||||||
|
|
||||||
export interface ButtonDocument extends Document{
|
export interface ButtonDocument extends Document{
|
||||||
@ -6,6 +6,7 @@ export interface ButtonDocument extends Document{
|
|||||||
readonly action?: string
|
readonly action?: string
|
||||||
readonly text?: string
|
readonly text?: string
|
||||||
readonly bgColor?: string
|
readonly bgColor?: string
|
||||||
|
readonly activeColor?: string
|
||||||
readonly color?: string
|
readonly color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,11 +24,16 @@ export const ButtonSchema = new Schema({
|
|||||||
bgColor: {
|
bgColor: {
|
||||||
type: String,
|
type: String,
|
||||||
match: matchType.color,
|
match: matchType.color,
|
||||||
default: '#5bc0de',
|
default: '#fff',
|
||||||
|
},
|
||||||
|
activeColor: {
|
||||||
|
type: String,
|
||||||
|
match: matchType.color,
|
||||||
|
default: '#40a9ff',
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
match: matchType.color,
|
match: matchType.color,
|
||||||
default: '#ffffff'
|
default: '#666'
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export interface Colors {
|
|||||||
readonly questionColor: string
|
readonly questionColor: string
|
||||||
readonly answerColor: string
|
readonly answerColor: string
|
||||||
readonly buttonColor: string
|
readonly buttonColor: string
|
||||||
|
readonly buttonActiveColor: string
|
||||||
readonly buttonTextColor: string
|
readonly buttonTextColor: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +94,14 @@ export const FormSchema = new Schema({
|
|||||||
default: defaultLanguage,
|
default: defaultLanguage,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
showFooter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
isLive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
analytics: {
|
analytics: {
|
||||||
gaCode: {
|
gaCode: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -193,14 +202,6 @@ export const FormSchema = new Schema({
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
showFooter: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
isLive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
design: {
|
design: {
|
||||||
colors: {
|
colors: {
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
@ -223,10 +224,15 @@ export const FormSchema = new Schema({
|
|||||||
match: matchType.color,
|
match: matchType.color,
|
||||||
default: '#fff'
|
default: '#fff'
|
||||||
},
|
},
|
||||||
|
buttonActiveColor: {
|
||||||
|
type: String,
|
||||||
|
match: matchType.color,
|
||||||
|
default: '#40a9ff'
|
||||||
|
},
|
||||||
buttonTextColor: {
|
buttonTextColor: {
|
||||||
type: String,
|
type: String,
|
||||||
match: matchType.color,
|
match: matchType.color,
|
||||||
default: '#333'
|
default: '#666'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { FormFieldDefinition } from './form.field.schema';
|
import { FormFieldDefinition } from './form.field.schema';
|
||||||
import { FormDefinition } from './form.schema';
|
import { FormDefinition } from './form.schema';
|
||||||
|
import { SubmissionFieldDefinition } from './submission.field.schema';
|
||||||
import { SubmissionDefinition } from './submission.schema';
|
import { SubmissionDefinition } from './submission.schema';
|
||||||
import { UserDefinition } from './user.schema';
|
import { UserDefinition } from './user.schema';
|
||||||
|
|
||||||
@ -7,5 +8,6 @@ export const schema = [
|
|||||||
FormDefinition,
|
FormDefinition,
|
||||||
FormFieldDefinition,
|
FormFieldDefinition,
|
||||||
SubmissionDefinition,
|
SubmissionDefinition,
|
||||||
|
SubmissionFieldDefinition,
|
||||||
UserDefinition,
|
UserDefinition,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -5,12 +5,12 @@ import { FormFieldDocument, FormFieldSchemaName } from './form.field.schema';
|
|||||||
export const SubmissionFieldSchemaName = 'SubmissionField'
|
export const SubmissionFieldSchemaName = 'SubmissionField'
|
||||||
|
|
||||||
export interface SubmissionFieldDocument extends Document {
|
export interface SubmissionFieldDocument extends Document {
|
||||||
field: FormFieldDocument
|
readonly field: FormFieldDocument
|
||||||
fieldType: string
|
readonly fieldType: string
|
||||||
fieldValue: any
|
readonly fieldValue: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubmissionFormFieldSchema = new Schema({
|
export const SubmissionFieldSchema = new Schema({
|
||||||
field: {
|
field: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: FormFieldSchemaName
|
ref: FormFieldSchemaName
|
||||||
@ -24,3 +24,9 @@ export const SubmissionFormFieldSchema = new Schema({
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const SubmissionFieldDefinition = {
|
||||||
|
name: SubmissionFieldSchemaName,
|
||||||
|
schema: SubmissionFieldSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,38 @@
|
|||||||
import { Document, Schema } from 'mongoose';
|
import { Document, Schema } from 'mongoose';
|
||||||
import { FormSchemaName } from './form.schema';
|
import { FormDocument, FormSchemaName } from './form.schema';
|
||||||
import { SubmissionFieldDocument, SubmissionFieldSchemaName } from './submission.field.schema';
|
import { SubmissionFieldDocument, SubmissionFieldSchema } from './submission.field.schema';
|
||||||
|
import { UserDocument, UserSchemaName } from './user.schema';
|
||||||
|
|
||||||
export const SubmissionSchemaName = 'FormSubmission'
|
export const SubmissionSchemaName = 'Submission'
|
||||||
|
|
||||||
|
export interface GeoLocation {
|
||||||
|
readonly country?: string
|
||||||
|
readonly city?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Device {
|
||||||
|
readonly type?: string
|
||||||
|
readonly name?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface SubmissionDocument extends Document {
|
export interface SubmissionDocument extends Document {
|
||||||
fields: SubmissionFieldDocument[]
|
readonly fields: SubmissionFieldDocument[]
|
||||||
|
readonly form: FormDocument
|
||||||
|
readonly ipAddr: string
|
||||||
|
readonly tokenHash: string
|
||||||
|
readonly geoLocation: GeoLocation
|
||||||
|
readonly device: Device
|
||||||
|
readonly timeElapsed: number
|
||||||
|
readonly percentageComplete: number
|
||||||
|
|
||||||
|
readonly user?: UserDocument
|
||||||
|
readonly created: Date
|
||||||
|
readonly lastModified: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubmissionSchema = new Schema({
|
export const SubmissionSchema = new Schema({
|
||||||
fields: {
|
fields: {
|
||||||
alias: 'form_fields',
|
type: [SubmissionFieldSchema],
|
||||||
type: [SubmissionFieldSchemaName],
|
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
@ -19,14 +40,21 @@ export const SubmissionSchema = new Schema({
|
|||||||
ref: FormSchemaName,
|
ref: FormSchemaName,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: UserSchemaName,
|
||||||
|
},
|
||||||
ipAddr: {
|
ipAddr: {
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
geoLocation: {
|
tokenHash: {
|
||||||
Country: {
|
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
City: {
|
geoLocation: {
|
||||||
|
country: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
city: {
|
||||||
type: String
|
type: String
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -39,10 +67,12 @@ export const SubmissionSchema = new Schema({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
timeElapsed: {
|
timeElapsed: {
|
||||||
type: Number
|
type: Number,
|
||||||
|
default: 0,
|
||||||
},
|
},
|
||||||
percentageComplete: {
|
percentageComplete: {
|
||||||
type: Number
|
type: Number,
|
||||||
|
default: 0,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
timestamps: {
|
timestamps: {
|
||||||
|
|||||||
@ -4,21 +4,19 @@ import { Model } from 'mongoose';
|
|||||||
import { FormCreateInput } from '../../dto/form/form.create.input';
|
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';
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
import { FormUpdateService } from './form.update.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FormCreateService {
|
export class FormCreateService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectModel(FormSchemaName) private readonly formModel: Model<FormDocument>,
|
@InjectModel(FormSchemaName) private readonly formModel: Model<FormDocument>,
|
||||||
private readonly updateService: FormUpdateService,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(admin: UserDocument, input: FormCreateInput): Promise<FormDocument> {
|
async create(admin: UserDocument, input: FormCreateInput): Promise<FormDocument> {
|
||||||
const form = await this.formModel.create({
|
return await this.formModel.create({
|
||||||
admin
|
admin,
|
||||||
|
...input,
|
||||||
})
|
})
|
||||||
|
|
||||||
return await this.updateService.update(form, input)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Model, Types } from 'mongoose';
|
import { FilterQuery, 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';
|
||||||
|
|
||||||
@ -19,8 +19,16 @@ 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]> {
|
async find(start: number, limit: number, sort: any = {}, user?: UserDocument): Promise<[FormDocument[], number]> {
|
||||||
const qb = this.formModel.find()
|
let conditions: FilterQuery<FormDocument>
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
conditions = {
|
||||||
|
admin: user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const qb = this.formModel.find(conditions)
|
||||||
|
|
||||||
// TODO apply restrictions based on user!
|
// TODO apply restrictions based on user!
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import { FormDocument, FormSchemaName } from '../../schema/form.schema';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class FormUpdateService {
|
export class FormUpdateService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectModel(FormSchemaName) private formModel: Model<FormDocument>,
|
@InjectModel(FormSchemaName) private readonly formModel: Model<FormDocument>,
|
||||||
@InjectModel(FormFieldSchemaName) private formFieldModel: Model<FormFieldDocument>,
|
@InjectModel(FormFieldSchemaName) private readonly formFieldModel: Model<FormFieldDocument>,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ export class FormUpdateService {
|
|||||||
let field = form.fields.find(field => field.id.toString() === nextField.id)
|
let field = form.fields.find(field => field.id.toString() === nextField.id)
|
||||||
|
|
||||||
if (!field) {
|
if (!field) {
|
||||||
field = await this.formFieldModel.create({
|
field = new this.formFieldModel({
|
||||||
type: nextField.type,
|
type: nextField.type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,12 +6,14 @@ 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';
|
||||||
|
import { submissionServices } from './submission';
|
||||||
import { userServices } from './user';
|
import { userServices } from './user';
|
||||||
|
|
||||||
export const services = [
|
export const services = [
|
||||||
...userServices,
|
...userServices,
|
||||||
...formServices,
|
...formServices,
|
||||||
...authServices,
|
...authServices,
|
||||||
|
...submissionServices,
|
||||||
MailService,
|
MailService,
|
||||||
{
|
{
|
||||||
provide: 'PUB_SUB',
|
provide: 'PUB_SUB',
|
||||||
|
|||||||
11
src/service/submission/index.ts
Normal file
11
src/service/submission/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { SubmissionService } from './submission.service';
|
||||||
|
import { SubmissionSetFieldService } from './submission.set.field.service';
|
||||||
|
import { SubmissionStartService } from './submission.start.service';
|
||||||
|
import { SubmissionTokenService } from './submission.token.service';
|
||||||
|
|
||||||
|
export const submissionServices = [
|
||||||
|
SubmissionSetFieldService,
|
||||||
|
SubmissionStartService,
|
||||||
|
SubmissionService,
|
||||||
|
SubmissionTokenService,
|
||||||
|
]
|
||||||
40
src/service/submission/submission.service.ts
Normal file
40
src/service/submission/submission.service.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
import { FormDocument } from '../../schema/form.schema';
|
||||||
|
import { SubmissionDocument, SubmissionSchemaName } from '../../schema/submission.schema';
|
||||||
|
import { SubmissionTokenService } from './submission.token.service';
|
||||||
|
|
||||||
|
export class SubmissionService {
|
||||||
|
constructor(
|
||||||
|
@InjectModel(SubmissionSchemaName) private readonly submissionModel: Model<SubmissionDocument>,
|
||||||
|
private readonly tokenService: SubmissionTokenService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async isOwner(submission: SubmissionDocument, token: string): Promise<boolean> {
|
||||||
|
return await this.tokenService.verify(token, submission.tokenHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
async find(form: FormDocument, start: number, limit: number, sort: any = {}): Promise<[SubmissionDocument[], number]> {
|
||||||
|
const qb = this.submissionModel.find({
|
||||||
|
form
|
||||||
|
})
|
||||||
|
|
||||||
|
return [
|
||||||
|
await qb.sort(sort)
|
||||||
|
.skip(start)
|
||||||
|
.limit(limit),
|
||||||
|
await qb.count()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<SubmissionDocument> {
|
||||||
|
const submission = await this.submissionModel.findById(id);
|
||||||
|
|
||||||
|
if (!submission) {
|
||||||
|
throw new Error('no form found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return submission
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/service/submission/submission.set.field.service.ts
Normal file
49
src/service/submission/submission.set.field.service.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
import { SubmissionSetFieldInput } from '../../dto/submission/submission.set.field.input';
|
||||||
|
import { SubmissionFieldDocument, SubmissionFieldSchemaName } from '../../schema/submission.field.schema';
|
||||||
|
import { SubmissionDocument, SubmissionSchemaName } from '../../schema/submission.schema';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SubmissionSetFieldService {
|
||||||
|
constructor(
|
||||||
|
@InjectModel(SubmissionSchemaName) private readonly submissionModel: Model<SubmissionDocument>,
|
||||||
|
@InjectModel(SubmissionFieldSchemaName) private readonly submissionFieldModel: Model<SubmissionFieldDocument>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveField(submission: SubmissionDocument, input: SubmissionSetFieldInput) {
|
||||||
|
const existing = submission.fields.find(field => field.field.toString() === input.field)
|
||||||
|
|
||||||
|
const data = JSON.parse(input.data)
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
existing.set('fieldValue', data)
|
||||||
|
} else {
|
||||||
|
if (!submission.populated('form')) {
|
||||||
|
submission.populate('form')
|
||||||
|
await submission.execPopulate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const field = submission.form.fields.find(field => field.id.toString() === input.field)
|
||||||
|
|
||||||
|
const newField = new this.submissionFieldModel({
|
||||||
|
field,
|
||||||
|
fieldType: field.type,
|
||||||
|
fieldValue: data
|
||||||
|
})
|
||||||
|
|
||||||
|
submission.set('percentageComplete', (1 + submission.fields.length) / submission.form.fields.length)
|
||||||
|
submission.set('timeElapsed', dayjs().diff(dayjs(submission.created), 'second'))
|
||||||
|
|
||||||
|
submission.set('fields', [
|
||||||
|
...submission.fields,
|
||||||
|
newField,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
await submission.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/service/submission/submission.start.service.ts
Normal file
30
src/service/submission/submission.start.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
|
import { Model } from 'mongoose';
|
||||||
|
import { SubmissionStartInput } from '../../dto/submission/submission.start.input';
|
||||||
|
import { FormDocument } from '../../schema/form.schema';
|
||||||
|
import { SubmissionDocument, SubmissionSchemaName } from '../../schema/submission.schema';
|
||||||
|
import { UserDocument } from '../../schema/user.schema';
|
||||||
|
import { SubmissionTokenService } from './submission.token.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SubmissionStartService {
|
||||||
|
constructor(
|
||||||
|
@InjectModel(SubmissionSchemaName) private submissionModel: Model<SubmissionDocument>,
|
||||||
|
private readonly tokenService: SubmissionTokenService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(
|
||||||
|
form: FormDocument,
|
||||||
|
input: SubmissionStartInput,
|
||||||
|
user?: UserDocument,
|
||||||
|
): Promise<SubmissionDocument> {
|
||||||
|
return await this.submissionModel.create({
|
||||||
|
form,
|
||||||
|
device: input.device,
|
||||||
|
user,
|
||||||
|
tokenHash: await this.tokenService.hash(input.token)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/service/submission/submission.token.service.ts
Normal file
12
src/service/submission/submission.token.service.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SubmissionTokenService {
|
||||||
|
async hash(token: string): Promise<string> {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
async verify(token: string, hash: string): Promise<boolean> {
|
||||||
|
return token == hash
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3355,6 +3355,11 @@ dayjs@^1.8.16:
|
|||||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.26.tgz#c6d62ccdf058ca72a8d14bb93a23501058db9f1e"
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.26.tgz#c6d62ccdf058ca72a8d14bb93a23501058db9f1e"
|
||||||
integrity sha512-KqtAuIfdNfZR5sJY1Dixr2Is4ZvcCqhb0dZpCOt5dGEFiMzoIbjkTSzUb4QKTCsP+WNpGwUjAFIZrnZvUxxkhw==
|
integrity sha512-KqtAuIfdNfZR5sJY1Dixr2Is4ZvcCqhb0dZpCOt5dGEFiMzoIbjkTSzUb4QKTCsP+WNpGwUjAFIZrnZvUxxkhw==
|
||||||
|
|
||||||
|
dayjs@^1.8.28:
|
||||||
|
version "1.8.28"
|
||||||
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.28.tgz#37aa6201df483d089645cb6c8f6cef6f0c4dbc07"
|
||||||
|
integrity sha512-ccnYgKC0/hPSGXxj7Ju6AV/BP4HUkXC2u15mikXT5mX9YorEaoi1bEKOmAqdkJHN4EEkmAf97SpH66Try5Mbeg==
|
||||||
|
|
||||||
debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
|
debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user