diff --git a/ormconfig_pg.json b/ormconfig_pg.json index 622cda3..4bb2b40 100644 --- a/ormconfig_pg.json +++ b/ormconfig_pg.json @@ -4,7 +4,7 @@ "username": "root", "password": "root", "database": "ohmyform", - "synchronize": true, + "synchronize": false, "logging": false, "entities": [ "src/entity/**/*.ts" diff --git a/ormconfig_sqlite.json b/ormconfig_sqlite.json index 034d514..c1e0095 100644 --- a/ormconfig_sqlite.json +++ b/ormconfig_sqlite.json @@ -1,7 +1,7 @@ { "type": "sqlite", "database": "data.sqlite", - "synchronize": true, + "synchronize": false, "logging": false, "entities": [ "src/entity/**/*.ts" diff --git a/package.json b/package.json index 6c9572c..eb9f35b 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "pg": "^8.6.0", "pino-pretty": "^4.7.1", "reflect-metadata": "^0.1.13", + "request-ip": "^2.1.3", "rimraf": "^3.0.2", "rxjs": "^6.6.7", "sqlite3": "^5.0.2", @@ -81,6 +82,7 @@ "@types/node": "^15.0.1", "@types/passport-jwt": "^3.0.5", "@types/passport-local": "^1.0.33", + "@types/request-ip": "^0.0.35", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/parser": "^4.22.0", diff --git a/src/app.imports.ts b/src/app.imports.ts index 6d1b781..df01f00 100644 --- a/src/app.imports.ts +++ b/src/app.imports.ts @@ -107,6 +107,7 @@ export const imports = [ inject: [ConfigService], useFactory: (configService: ConfigService): TypeOrmModuleOptions => ({ name: 'ohmyform', + synchronize: false, type: configService.get('DB_TYPE', 'sqlite') as any, url: configService.get('DB_URI', 'sqlite://data.sqlite'), entityPrefix: configService.get('DB_TABLE_PREFIX', ''), diff --git a/src/decorator/ip.address.decorator.ts b/src/decorator/ip.address.decorator.ts new file mode 100644 index 0000000..91d0ac5 --- /dev/null +++ b/src/decorator/ip.address.decorator.ts @@ -0,0 +1,19 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common' +import { GqlExecutionContext } from '@nestjs/graphql' +import { getClientIp } from 'request-ip' + +export const IpAddress = createParamDecorator((data: string, ctx: ExecutionContext) => { + let req + + if (ctx.getType() === 'graphql') { + req = GqlExecutionContext.create(ctx).getContext().req + } else { + req = ctx.switchToHttp().getRequest() + } + + if (req.clientIp) { + return req.clientIp + } + + return getClientIp(req) +}) diff --git a/src/dto/submission/submission.field.model.ts b/src/dto/submission/submission.field.model.ts index 7659797..5d44958 100644 --- a/src/dto/submission/submission.field.model.ts +++ b/src/dto/submission/submission.field.model.ts @@ -14,7 +14,7 @@ export class SubmissionFieldModel { constructor(field: SubmissionFieldEntity) { this.id = field.id.toString() - this.value = JSON.stringify(field.fieldValue) + this.value = field.fieldValue this.type = field.fieldType } } diff --git a/src/dto/submission/submission.model.ts b/src/dto/submission/submission.model.ts index 21b045e..6df98d6 100644 --- a/src/dto/submission/submission.model.ts +++ b/src/dto/submission/submission.model.ts @@ -1,4 +1,4 @@ -import { Field, ID, ObjectType } from '@nestjs/graphql' +import { Field, Float, ID, ObjectType } from '@nestjs/graphql' import { SubmissionEntity } from '../../entity/submission.entity' import { DeviceModel } from './device.model' import { GeoLocationModel } from './geo.location.model' diff --git a/src/entity/submission.entity.ts b/src/entity/submission.entity.ts index 0a6ba86..7573dd4 100644 --- a/src/entity/submission.entity.ts +++ b/src/entity/submission.entity.ts @@ -4,7 +4,7 @@ import { Entity, ManyToOne, OneToMany, - PrimaryGeneratedColumn, + PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm' import { DeviceEmbedded } from './embedded/device.embedded' @@ -25,7 +25,10 @@ export class SubmissionEntity { @ManyToOne(() => FormEntity, form => form.submissions, { eager: true }) public form: FormEntity - @ManyToOne(() => VisitorEntity, visitor => visitor.submissions) + @RelationId('form') + readonly formId: number + + @ManyToOne(() => VisitorEntity, visitor => visitor.submissions, { eager: true }) public visitor: VisitorEntity @Column() @@ -40,10 +43,10 @@ export class SubmissionEntity { @Column(() => DeviceEmbedded) public device: DeviceEmbedded = new DeviceEmbedded() - @Column() + @Column({ type: 'numeric' }) public timeElapsed: number - @Column() + @Column({ type: 'numeric' }) public percentageComplete: number @ManyToOne(() => UserEntity, { eager: true }) diff --git a/src/entity/submission.field.entity.ts b/src/entity/submission.field.entity.ts index e05303b..48b69ee 100644 --- a/src/entity/submission.field.entity.ts +++ b/src/entity/submission.field.entity.ts @@ -1,4 +1,5 @@ -import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm' +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, RelationId } from 'typeorm' +import { FormEntity } from './form.entity' import { FormFieldEntity } from './form.field.entity' import { SubmissionEntity } from './submission.entity' @@ -13,6 +14,9 @@ export class SubmissionFieldEntity { @ManyToOne(() => FormFieldEntity, { eager: true }) public field: FormFieldEntity + @RelationId('field') + readonly fieldId: number + @Column() public fieldType: string diff --git a/src/migrations/1619723437787-initial.ts b/src/migrations/1619723437787-initial.ts index ee8ba66..35b9524 100644 --- a/src/migrations/1619723437787-initial.ts +++ b/src/migrations/1619723437787-initial.ts @@ -5,17 +5,17 @@ export class initial1619723437787 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { if (queryRunner.connection.driver.options.type === 'sqlite') { - await queryRunner.query(`CREATE TABLE "user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "firstName" varchar, "lastName" varchar, "email" varchar(255) NOT NULL, "username" varchar(255) NOT NULL, "passwordHash" varchar NOT NULL, "salt" varchar, "provider" varchar NOT NULL, "roles" text NOT NULL, "language" varchar NOT NULL, "resetPasswordToken" varchar, "resetPasswordExpires" datetime, "token" varchar, "apiKey" varchar, "created" datetime NOT NULL DEFAULT (datetime('now')), "lastModified" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))`); await queryRunner.query(`CREATE TABLE "page" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "show" boolean NOT NULL, "title" varchar, "paragraph" text, "buttonText" varchar)`); + await queryRunner.query(`CREATE TABLE "user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "firstName" varchar, "lastName" varchar, "email" varchar(255) NOT NULL, "username" varchar(255) NOT NULL, "passwordHash" varchar NOT NULL, "salt" varchar, "provider" varchar NOT NULL, "roles" text NOT NULL, "language" varchar NOT NULL, "resetPasswordToken" varchar, "resetPasswordExpires" datetime, "token" varchar, "apiKey" varchar, "created" datetime NOT NULL DEFAULT (datetime('now')), "lastModified" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"))`); await queryRunner.query(`CREATE TABLE "form_field_logic" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "formula" varchar NOT NULL, "action" varchar(10) NOT NULL, "visible" boolean, "require" boolean, "disable" boolean, "enabled" boolean NOT NULL, "fieldId" integer, "jumpToId" integer, CONSTRAINT "FK_6098b83f6759445d8cfdd03d545" FOREIGN KEY ("fieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_4a8019f2b753cfb3216dc3001a6" FOREIGN KEY ("jumpToId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); await queryRunner.query(`CREATE TABLE "form_field_option" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "key" varchar, "title" varchar, "value" varchar NOT NULL, "fieldId" integer, CONSTRAINT "FK_c4484ad12c2c56db31dffdbfe97" FOREIGN KEY ("fieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); await queryRunner.query(`CREATE TABLE "form_field" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar NOT NULL, "description" text NOT NULL, "slug" varchar, "required" boolean NOT NULL, "disabled" boolean NOT NULL, "type" varchar NOT NULL, "value" varchar NOT NULL, "formId" integer, "ratingSteps" integer, "ratingShape" varchar, CONSTRAINT "FK_2d83d8a334dd66445db13f92b77" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); await queryRunner.query(`CREATE TABLE "form_hook" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "enabled" boolean NOT NULL, "url" varchar NOT NULL, "format" varchar, "formId" integer, CONSTRAINT "FK_bbeb4d224d8857fd5a458538a30" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); await queryRunner.query(`CREATE TABLE "form_notification" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "subject" varchar, "htmlTemplate" varchar, "enabled" boolean NOT NULL, "toEmail" varchar, "fromEmail" varchar, "formId" integer, "fromFieldId" integer, "toFieldId" integer, CONSTRAINT "FK_a9ed55144108ded893b502d6321" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_0876741ce2acdaee4553d7a3bbd" FOREIGN KEY ("fromFieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_4915ebae53e09b732322d0ff6ed" FOREIGN KEY ("toFieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`CREATE TABLE "page_button" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "url" varchar, "action" varchar, "text" varchar NOT NULL, "bgColor" varchar, "activeColor" varchar, "color" varchar, "pageId" integer, CONSTRAINT "FK_d9f099286b75fa0034dcd8cf7c2" FOREIGN KEY ("pageId") REFERENCES "page" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); await queryRunner.query(`CREATE TABLE "submission_field" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "fieldType" varchar NOT NULL, "fieldValue" varchar NOT NULL, "submissionId" integer, "fieldId" integer, CONSTRAINT "FK_16fae661ce5b10f27abe2e524a0" FOREIGN KEY ("submissionId") REFERENCES "submission" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_5befa92da2370b7eb1cab6ae30a" FOREIGN KEY ("fieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); await queryRunner.query(`CREATE TABLE "form_visitor" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "referrer" varchar, "ipAddr" varchar NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "updated" datetime NOT NULL DEFAULT (datetime('now')), "formId" integer, "geoLocationCountry" varchar, "geoLocationCity" varchar, "deviceLanguage" varchar, "deviceType" varchar, "deviceName" varchar, CONSTRAINT "FK_72ade6c3a3e55d1fce94300f8b6" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); - await queryRunner.query(`CREATE TABLE "submission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "ipAddr" varchar NOT NULL, "tokenHash" varchar NOT NULL, "timeElapsed" integer NOT NULL, "percentageComplete" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "lastModified" datetime NOT NULL DEFAULT (datetime('now')), "formId" integer, "visitorId" integer, "userId" integer, "geoLocationCountry" varchar, "geoLocationCity" varchar, "deviceLanguage" varchar, "deviceType" varchar, "deviceName" varchar, CONSTRAINT "FK_7bd626272858ef6464aa2579094" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_95b73c7faf2c199f005fda5e8c8" FOREIGN KEY ("visitorId") REFERENCES "form_visitor" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_6090e1d5cbf3433ffd14e3b53e7" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); - await queryRunner.query(`CREATE TABLE "page_button" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "url" varchar, "action" varchar, "text" varchar NOT NULL, "bgColor" varchar, "activeColor" varchar, "color" varchar, "pageId" integer, CONSTRAINT "FK_d9f099286b75fa0034dcd8cf7c2" FOREIGN KEY ("pageId") REFERENCES "page" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`CREATE TABLE "submission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "ipAddr" varchar NOT NULL, "tokenHash" varchar NOT NULL, "timeElapsed" numeric NOT NULL, "percentageComplete" numeric NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "lastModified" datetime NOT NULL DEFAULT (datetime('now')), "formId" integer, "visitorId" integer, "userId" integer, "geoLocationCountry" varchar, "geoLocationCity" varchar, "deviceLanguage" varchar, "deviceType" varchar, "deviceName" varchar, CONSTRAINT "FK_6090e1d5cbf3433ffd14e3b53e7" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_95b73c7faf2c199f005fda5e8c8" FOREIGN KEY ("visitorId") REFERENCES "form_visitor" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_7bd626272858ef6464aa2579094" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); await queryRunner.query(`CREATE TABLE "form" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar NOT NULL, "language" varchar(10) NOT NULL, "showFooter" boolean NOT NULL, "isLive" boolean NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "lastModified" datetime NOT NULL DEFAULT (datetime('now')), "adminId" integer, "startPageId" integer, "endPageId" integer, "analyticsGacode" varchar, "designFont" varchar, "designColorsBackground" varchar, "designColorsQuestion" varchar, "designColorsAnswer" varchar, "designColorsButton" varchar, "designColorsButtonactive" varchar, "designColorsButtontext" varchar, CONSTRAINT "FK_a7cb33580bca2b362e5e34fdfcd" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_023d9cf1d97e93facc96c86ca70" FOREIGN KEY ("startPageId") REFERENCES "page" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_e5d158932e43cfbf9958931ee01" FOREIGN KEY ("endPageId") REFERENCES "page" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); } else { await queryRunner.query(`CREATE TABLE "form_field_logic" ("id" SERIAL NOT NULL, "formula" character varying NOT NULL, "action" character varying(10) NOT NULL, "visible" boolean, "require" boolean, "disable" boolean, "enabled" boolean NOT NULL, "fieldId" integer, "jumpToId" integer, CONSTRAINT "PK_c40e7f583854ff1b60900d8cf1b" PRIMARY KEY ("id"))`); @@ -23,12 +23,12 @@ export class initial1619723437787 implements MigrationInterface { await queryRunner.query(`CREATE TABLE "form_field" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "description" text NOT NULL, "slug" character varying, "required" boolean NOT NULL, "disabled" boolean NOT NULL, "type" character varying NOT NULL, "value" character varying NOT NULL, "formId" integer, "ratingSteps" integer, "ratingShape" character varying, CONSTRAINT "PK_135904ddb60085b07254ea4f485" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "form_hook" ("id" SERIAL NOT NULL, "enabled" boolean NOT NULL, "url" character varying NOT NULL, "format" character varying, "formId" integer, CONSTRAINT "PK_4b63bd9ff09f7b3e5c4a41fcbec" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "form_notification" ("id" SERIAL NOT NULL, "subject" character varying, "htmlTemplate" character varying, "enabled" boolean NOT NULL, "toEmail" character varying, "fromEmail" character varying, "formId" integer, "fromFieldId" integer, "toFieldId" integer, CONSTRAINT "PK_935306529aed07c9f6628f6e24f" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "submission_field" ("id" SERIAL NOT NULL, "fieldType" character varying NOT NULL, "fieldValue" character varying NOT NULL, "submissionId" integer, "fieldId" integer, CONSTRAINT "PK_5443f5f769fce3107982c16e0b5" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "user" ("id" SERIAL NOT NULL, "firstName" character varying, "lastName" character varying, "email" character varying(255) NOT NULL, "username" character varying(255) NOT NULL, "passwordHash" character varying NOT NULL, "salt" character varying, "provider" character varying NOT NULL, "roles" character varying(10) NOT NULL, "language" character varying NOT NULL, "resetPasswordToken" character varying, "resetPasswordExpires" TIMESTAMP, "token" character varying, "apiKey" character varying, "created" TIMESTAMP NOT NULL DEFAULT now(), "lastModified" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "form_visitor" ("id" SERIAL NOT NULL, "referrer" character varying, "ipAddr" character varying NOT NULL, "created" TIMESTAMP NOT NULL DEFAULT now(), "updated" TIMESTAMP NOT NULL DEFAULT now(), "formId" integer, "geoLocationCountry" character varying, "geoLocationCity" character varying, CONSTRAINT "PK_74224dc63e13cf5cb5f0420e65b" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "submission" ("id" SERIAL NOT NULL, "ipAddr" character varying NOT NULL, "tokenHash" character varying NOT NULL, "timeElapsed" integer NOT NULL, "percentageComplete" integer NOT NULL, "created" TIMESTAMP NOT NULL DEFAULT now(), "lastModified" TIMESTAMP NOT NULL DEFAULT now(), "formId" integer, "visitorId" integer, "userId" integer, "geoLocationCountry" character varying, "geoLocationCity" character varying, CONSTRAINT "PK_7faa571d0e4a7076e85890c9bd0" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "page_button" ("id" SERIAL NOT NULL, "url" character varying, "action" character varying, "text" character varying NOT NULL, "bgColor" character varying, "activeColor" character varying, "color" character varying, "pageId" integer, CONSTRAINT "PK_6609a75a7d82775aac8af1a591c" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "page" ("id" SERIAL NOT NULL, "show" boolean NOT NULL, "title" character varying, "paragraph" text, "buttonText" character varying, CONSTRAINT "PK_742f4117e065c5b6ad21b37ba1f" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "submission_field" ("id" SERIAL NOT NULL, "fieldType" character varying NOT NULL, "fieldValue" character varying NOT NULL, "submissionId" integer, "fieldId" integer, CONSTRAINT "PK_5443f5f769fce3107982c16e0b5" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "user" ("id" SERIAL NOT NULL, "firstName" character varying, "lastName" character varying, "email" character varying(255) NOT NULL, "username" character varying(255) NOT NULL, "passwordHash" character varying NOT NULL, "salt" character varying, "provider" character varying NOT NULL, "roles" text NOT NULL, "language" character varying NOT NULL, "resetPasswordToken" character varying, "resetPasswordExpires" TIMESTAMP, "token" character varying, "apiKey" character varying, "created" TIMESTAMP NOT NULL DEFAULT now(), "lastModified" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "form_visitor" ("id" SERIAL NOT NULL, "referrer" character varying, "ipAddr" character varying NOT NULL, "created" TIMESTAMP NOT NULL DEFAULT now(), "updated" TIMESTAMP NOT NULL DEFAULT now(), "formId" integer, "geoLocationCountry" character varying, "geoLocationCity" character varying, "deviceLanguage" character varying, "deviceType" character varying, "deviceName" character varying, CONSTRAINT "PK_74224dc63e13cf5cb5f0420e65b" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "submission" ("id" SERIAL NOT NULL, "ipAddr" character varying NOT NULL, "tokenHash" character varying NOT NULL, "timeElapsed" numeric NOT NULL, "percentageComplete" numeric NOT NULL, "created" TIMESTAMP NOT NULL DEFAULT now(), "lastModified" TIMESTAMP NOT NULL DEFAULT now(), "formId" integer, "visitorId" integer, "userId" integer, "geoLocationCountry" character varying, "geoLocationCity" character varying, "deviceLanguage" character varying, "deviceType" character varying, "deviceName" character varying, CONSTRAINT "PK_7faa571d0e4a7076e85890c9bd0" PRIMARY KEY ("id"))`); await queryRunner.query(`CREATE TABLE "form" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "language" character varying(10) NOT NULL, "showFooter" boolean NOT NULL, "isLive" boolean NOT NULL, "created" TIMESTAMP NOT NULL DEFAULT now(), "lastModified" TIMESTAMP NOT NULL DEFAULT now(), "adminId" integer, "startPageId" integer, "endPageId" integer, "analyticsGacode" character varying, "designFont" character varying, "designColorsBackground" character varying, "designColorsQuestion" character varying, "designColorsAnswer" character varying, "designColorsButton" character varying, "designColorsButtonactive" character varying, "designColorsButtontext" character varying, CONSTRAINT "PK_8f72b95aa2f8ba82cf95dc7579e" PRIMARY KEY ("id"))`); await queryRunner.query(`ALTER TABLE "form_field_logic" ADD CONSTRAINT "FK_6098b83f6759445d8cfdd03d545" FOREIGN KEY ("fieldId") REFERENCES "form_field"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "form_field_logic" ADD CONSTRAINT "FK_4a8019f2b753cfb3216dc3001a6" FOREIGN KEY ("jumpToId") REFERENCES "form_field"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); @@ -38,27 +38,16 @@ export class initial1619723437787 implements MigrationInterface { await queryRunner.query(`ALTER TABLE "form_notification" ADD CONSTRAINT "FK_a9ed55144108ded893b502d6321" FOREIGN KEY ("formId") REFERENCES "form"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "form_notification" ADD CONSTRAINT "FK_0876741ce2acdaee4553d7a3bbd" FOREIGN KEY ("fromFieldId") REFERENCES "form_field"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "form_notification" ADD CONSTRAINT "FK_4915ebae53e09b732322d0ff6ed" FOREIGN KEY ("toFieldId") REFERENCES "form_field"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "page_button" ADD CONSTRAINT "FK_d9f099286b75fa0034dcd8cf7c2" FOREIGN KEY ("pageId") REFERENCES "page"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "submission_field" ADD CONSTRAINT "FK_16fae661ce5b10f27abe2e524a0" FOREIGN KEY ("submissionId") REFERENCES "submission"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "submission_field" ADD CONSTRAINT "FK_5befa92da2370b7eb1cab6ae30a" FOREIGN KEY ("fieldId") REFERENCES "form_field"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "form_visitor" ADD CONSTRAINT "FK_72ade6c3a3e55d1fce94300f8b6" FOREIGN KEY ("formId") REFERENCES "form"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "submission" ADD CONSTRAINT "FK_6090e1d5cbf3433ffd14e3b53e7" FOREIGN KEY ("formId") REFERENCES "form"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "submission" ADD CONSTRAINT "FK_95b73c7faf2c199f005fda5e8c8" FOREIGN KEY ("visitorId") REFERENCES "form_visitor"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "submission" ADD CONSTRAINT "FK_7bd626272858ef6464aa2579094" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "page_button" ADD CONSTRAINT "FK_d9f099286b75fa0034dcd8cf7c2" FOREIGN KEY ("pageId") REFERENCES "page"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "form" ADD CONSTRAINT "FK_a7cb33580bca2b362e5e34fdfcd" FOREIGN KEY ("adminId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "form" ADD CONSTRAINT "FK_023d9cf1d97e93facc96c86ca70" FOREIGN KEY ("startPageId") REFERENCES "page"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); await queryRunner.query(`ALTER TABLE "form" ADD CONSTRAINT "FK_e5d158932e43cfbf9958931ee01" FOREIGN KEY ("endPageId") REFERENCES "page"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - - - await queryRunner.query(`ALTER TABLE "form_visitor" ADD "deviceLanguage" character varying`); - await queryRunner.query(`ALTER TABLE "form_visitor" ADD "deviceType" character varying`); - await queryRunner.query(`ALTER TABLE "form_visitor" ADD "deviceName" character varying`); - await queryRunner.query(`ALTER TABLE "submission" ADD "deviceLanguage" character varying`); - await queryRunner.query(`ALTER TABLE "submission" ADD "deviceType" character varying`); - await queryRunner.query(`ALTER TABLE "submission" ADD "deviceName" character varying`); - - await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "roles"`); - await queryRunner.query(`ALTER TABLE "user" ADD "roles" text NOT NULL`); } } @@ -67,13 +56,13 @@ export class initial1619723437787 implements MigrationInterface { await queryRunner.query(`ALTER TABLE "form" DROP CONSTRAINT "FK_e5d158932e43cfbf9958931ee01"`); await queryRunner.query(`ALTER TABLE "form" DROP CONSTRAINT "FK_023d9cf1d97e93facc96c86ca70"`); await queryRunner.query(`ALTER TABLE "form" DROP CONSTRAINT "FK_a7cb33580bca2b362e5e34fdfcd"`); - await queryRunner.query(`ALTER TABLE "page_button" DROP CONSTRAINT "FK_d9f099286b75fa0034dcd8cf7c2"`); await queryRunner.query(`ALTER TABLE "submission" DROP CONSTRAINT "FK_7bd626272858ef6464aa2579094"`); await queryRunner.query(`ALTER TABLE "submission" DROP CONSTRAINT "FK_95b73c7faf2c199f005fda5e8c8"`); await queryRunner.query(`ALTER TABLE "submission" DROP CONSTRAINT "FK_6090e1d5cbf3433ffd14e3b53e7"`); await queryRunner.query(`ALTER TABLE "form_visitor" DROP CONSTRAINT "FK_72ade6c3a3e55d1fce94300f8b6"`); await queryRunner.query(`ALTER TABLE "submission_field" DROP CONSTRAINT "FK_5befa92da2370b7eb1cab6ae30a"`); await queryRunner.query(`ALTER TABLE "submission_field" DROP CONSTRAINT "FK_16fae661ce5b10f27abe2e524a0"`); + await queryRunner.query(`ALTER TABLE "page_button" DROP CONSTRAINT "FK_d9f099286b75fa0034dcd8cf7c2"`); await queryRunner.query(`ALTER TABLE "form_notification" DROP CONSTRAINT "FK_4915ebae53e09b732322d0ff6ed"`); await queryRunner.query(`ALTER TABLE "form_notification" DROP CONSTRAINT "FK_0876741ce2acdaee4553d7a3bbd"`); await queryRunner.query(`ALTER TABLE "form_notification" DROP CONSTRAINT "FK_a9ed55144108ded893b502d6321"`); @@ -85,12 +74,12 @@ export class initial1619723437787 implements MigrationInterface { } await queryRunner.query(`DROP TABLE "form"`); - await queryRunner.query(`DROP TABLE "page"`); - await queryRunner.query(`DROP TABLE "page_button"`); await queryRunner.query(`DROP TABLE "submission"`); await queryRunner.query(`DROP TABLE "form_visitor"`); await queryRunner.query(`DROP TABLE "user"`); await queryRunner.query(`DROP TABLE "submission_field"`); + await queryRunner.query(`DROP TABLE "page"`); + await queryRunner.query(`DROP TABLE "page_button"`); await queryRunner.query(`DROP TABLE "form_notification"`); await queryRunner.query(`DROP TABLE "form_hook"`); await queryRunner.query(`DROP TABLE "form_field"`); diff --git a/src/resolver/form/form.resolver.ts b/src/resolver/form/form.resolver.ts index 56ccb73..be68690 100644 --- a/src/resolver/form/form.resolver.ts +++ b/src/resolver/form/form.resolver.ts @@ -26,9 +26,9 @@ export class FormResolver { @Parent() parent: FormModel, @Context('cache') cache: ContextCache, ): Promise { - const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) + const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) - return form.fields.map(field => new FormFieldModel(field)) + return form.fields?.map(field => new FormFieldModel(field)) || [] } @ResolveField('hooks', () => [FormHookModel]) @@ -37,9 +37,9 @@ export class FormResolver { @Parent() parent: FormModel, @Context('cache') cache: ContextCache, ): Promise { - const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) + const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) - return form.hooks.map(hook => new FormHookModel(hook)) + return form.hooks?.map(hook => new FormHookModel(hook)) || [] } @ResolveField('isLive', () => Boolean) @@ -49,7 +49,7 @@ export class FormResolver { @Parent() parent: FormModel, @Context('cache') cache: ContextCache, ): Promise { - const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) + const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) if (!await this.formService.isAdmin(form, user)) { throw new Error('no access to field') @@ -71,7 +71,7 @@ export class FormResolver { throw new Error('no access to field') } - return form.notifications.map(notification => new FormNotificationModel(notification)) + return form.notifications?.map(notification => new FormNotificationModel(notification)) || [] } @ResolveField('design', () => DesignModel) @@ -80,7 +80,7 @@ export class FormResolver { @Parent() parent: FormModel, @Context('cache') cache: ContextCache, ): Promise { - const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) + const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) return new DesignModel(form.design) } @@ -90,7 +90,7 @@ export class FormResolver { @Parent() parent: FormModel, @Context('cache') cache: ContextCache, ): Promise { - const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) + const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) return new PageModel(form.startPage) } @@ -100,7 +100,7 @@ export class FormResolver { @Parent() parent: FormModel, @Context('cache') cache: ContextCache, ): Promise { - const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) + const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) return new PageModel(form.endPage) } @@ -111,7 +111,7 @@ export class FormResolver { @Parent() parent: FormModel, @Context('cache') cache: ContextCache, ): Promise { - const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) + const form = await cache.get(cache.getCacheKey(FormEntity.name, parent.id)) if (!form.admin) { return null diff --git a/src/resolver/submission/submission.field.resolver.ts b/src/resolver/submission/submission.field.resolver.ts index 95ffcc0..a52bc0f 100644 --- a/src/resolver/submission/submission.field.resolver.ts +++ b/src/resolver/submission/submission.field.resolver.ts @@ -1,11 +1,18 @@ import { Context, Parent, ResolveField, Resolver } from '@nestjs/graphql' import { FormFieldModel } from '../../dto/form/form.field.model' import { SubmissionFieldModel } from '../../dto/submission/submission.field.model' +import { FormFieldEntity } from '../../entity/form.field.entity' import { SubmissionFieldEntity } from '../../entity/submission.field.entity' +import { FormFieldService } from '../../service/form/form.field.service' import { ContextCache } from '../context.cache' @Resolver(() => SubmissionFieldModel) export class SubmissionFieldResolver { + constructor( + private readonly formFieldService: FormFieldService, + ) { + } + @ResolveField('field', () => FormFieldModel, { nullable: true }) async getFields( @Parent() parent: SubmissionFieldModel, @@ -13,10 +20,12 @@ export class SubmissionFieldResolver { ): Promise { const submissionField = await cache.get(cache.getCacheKey(SubmissionFieldEntity.name, parent.id)) - if (!submissionField.field) { + const field = await cache.get(cache.getCacheKey(FormFieldEntity.name, submissionField.fieldId), () => this.formFieldService.findById(submissionField.fieldId, submissionField.field)) + + if (!field) { return null } - return new FormFieldModel(submissionField.field) + return new FormFieldModel(field) } } diff --git a/src/resolver/submission/submission.resolver.ts b/src/resolver/submission/submission.resolver.ts index 979fc81..a0766e7 100644 --- a/src/resolver/submission/submission.resolver.ts +++ b/src/resolver/submission/submission.resolver.ts @@ -2,8 +2,6 @@ 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 { FormEntity } from '../../entity/form.entity' -import { FormFieldEntity } from '../../entity/form.field.entity' import { SubmissionEntity } from '../../entity/submission.entity' import { SubmissionFieldEntity } from '../../entity/submission.field.entity' import { UserEntity } from '../../entity/user.entity' @@ -19,12 +17,6 @@ export class SubmissionResolver { ): Promise { const submission = await cache.get(cache.getCacheKey(SubmissionEntity.name, parent.id)) - cache.add(cache.getCacheKey(FormEntity.name, submission.form.id), submission.form) - - submission.form.fields.forEach(field => { - cache.add(cache.getCacheKey(FormFieldEntity.name, field.id), field) - }) - return submission.fields.map(field => { cache.add(cache.getCacheKey(SubmissionFieldEntity.name, field.id), field) return new SubmissionFieldModel(field) diff --git a/src/resolver/submission/submission.start.mutation.ts b/src/resolver/submission/submission.start.mutation.ts index af03ba0..d4bd6dd 100644 --- a/src/resolver/submission/submission.start.mutation.ts +++ b/src/resolver/submission/submission.start.mutation.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common' import { Args, Context, ID, Mutation } from '@nestjs/graphql' +import { IpAddress } from '../../decorator/ip.address.decorator' import { User } from '../../decorator/user.decorator' import { SubmissionProgressModel } from '../../dto/submission/submission.progress.model' import { SubmissionStartInput } from '../../dto/submission/submission.start.input' @@ -22,11 +23,12 @@ export class SubmissionStartMutation { @User() user: UserEntity, @Args({ name: 'form', type: () => ID }) id: string, @Args({ name: 'submission', type: () => SubmissionStartInput }) input: SubmissionStartInput, + @IpAddress() ipAddr: string, @Context('cache') cache: ContextCache, ): Promise { const form = await this.formService.findById(id) - const submission = await this.startService.start(form, input, user) + const submission = await this.startService.start(form, input, user, ipAddr) cache.add(cache.getCacheKey(SubmissionEntity.name, submission.id), submission) diff --git a/src/service/form/form.field.service.ts b/src/service/form/form.field.service.ts new file mode 100644 index 0000000..cf4ba4a --- /dev/null +++ b/src/service/form/form.field.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Repository } from 'typeorm' +import { FormEntity } from '../../entity/form.entity' +import { FormFieldEntity } from '../../entity/form.field.entity' +import { UserEntity } from '../../entity/user.entity' + +@Injectable() +export class FormFieldService { + constructor( + @InjectRepository(FormFieldEntity) + private readonly formFieldRepository: Repository, + ) { + } + + async findById(id: number | string, existing?: FormFieldEntity): Promise { + if (existing) return existing + + const entity = await this.formFieldRepository.findOne(id); + + if (!entity) { + throw new Error('no form field found') + } + + return entity + } +} diff --git a/src/service/form/form.service.ts b/src/service/form/form.service.ts index 5eef386..25e1e30 100644 --- a/src/service/form/form.service.ts +++ b/src/service/form/form.service.ts @@ -41,7 +41,9 @@ export class FormService { return await qb.getManyAndCount() } - async findById(id: string): Promise { + async findById(id: number | string, existing?: FormEntity): Promise { + if (existing) return existing + const form = await this.formRepository.findOne(id); if (!form) { diff --git a/src/service/form/index.ts b/src/service/form/index.ts index bbbe668..2617cad 100644 --- a/src/service/form/index.ts +++ b/src/service/form/index.ts @@ -1,5 +1,6 @@ import { FormCreateService } from './form.create.service' import { FormDeleteService } from './form.delete.service' +import { FormFieldService } from './form.field.service' import { FormService } from './form.service' import { FormStatisticService } from './form.statistic.service' import { FormUpdateService } from './form.update.service' @@ -7,6 +8,7 @@ import { FormUpdateService } from './form.update.service' export const formServices = [ FormCreateService, FormDeleteService, + FormFieldService, FormService, FormStatisticService, FormUpdateService, diff --git a/src/service/submission/index.ts b/src/service/submission/index.ts index 75e7668..ca46021 100644 --- a/src/service/submission/index.ts +++ b/src/service/submission/index.ts @@ -1,4 +1,5 @@ import { SubmissionHookService } from './submission.hook.service' +import { SubmissionNotificationService } from './submission.notification.service' import { SubmissionService } from './submission.service' import { SubmissionSetFieldService } from './submission.set.field.service' import { SubmissionStartService } from './submission.start.service' @@ -6,10 +7,11 @@ import { SubmissionStatisticService } from './submission.statistic.service' import { SubmissionTokenService } from './submission.token.service' export const submissionServices = [ + SubmissionHookService, + SubmissionNotificationService, SubmissionService, SubmissionSetFieldService, SubmissionStartService, SubmissionStatisticService, SubmissionTokenService, - SubmissionHookService, ] diff --git a/src/service/submission/submission.hook.service.ts b/src/service/submission/submission.hook.service.ts index 2ba5cb5..407ef91 100644 --- a/src/service/submission/submission.hook.service.ts +++ b/src/service/submission/submission.hook.service.ts @@ -23,7 +23,7 @@ export class SubmissionHookService { await this.format(submission, hook.format) ).toPromise() - console.log('sent', response.data) + console.log('sent hook', response.data) } catch (e) { this.logger.error(`failed to post to "${hook.url}: ${e.message}`) this.logger.error(e.stack) diff --git a/src/service/submission/submission.notification.service.ts b/src/service/submission/submission.notification.service.ts new file mode 100644 index 0000000..c31c85e --- /dev/null +++ b/src/service/submission/submission.notification.service.ts @@ -0,0 +1,70 @@ +import { MailerService } from '@nestjs-modules/mailer' +import { Injectable } from '@nestjs/common' +import handlebars from 'handlebars' +import htmlToText from 'html-to-text' +import { PinoLogger } from 'nestjs-pino/dist' +import { SubmissionEntity } from '../../entity/submission.entity' +import mjml2html from 'mjml' + +@Injectable() +export class SubmissionNotificationService { + constructor( + private readonly nestMailer: MailerService, + private readonly logger: PinoLogger, + ) { + } + + public async process(submission: SubmissionEntity): Promise { + await Promise.all(submission.form.notifications.map(async (notification) => { + if (!notification.enabled) { + return + } + + try { + const to = this.getEmail(submission.fields.find(field => field.fieldId === notification.toField.id )?.fieldValue, notification.toEmail) + const from = this.getEmail(submission.fields.find(field => field.fieldId === notification.fromField.id )?.fieldValue, notification.fromEmail) + + const html = mjml2html( + handlebars.compile( + notification.htmlTemplate + )({ + // TODO add varialbes + }), + { + minify: true + } + ).html + + await this.nestMailer.sendMail({ + to, + replyTo: from, + subject: notification.subject, + html, + text: htmlToText.fromString(html) + }) + console.log('sent notification to', to) + } catch (e) { + this.logger.error(e.stack) + throw e + } + })) + } + + private getEmail(raw: string, fallback: string): string { + if (!raw) { + return fallback + } + + try { + const data = JSON.parse(raw) + + if (data.value) { + return data.value + } + } catch (e) { + this.logger.error('could not decode field value', raw) + } + + return fallback + } +} diff --git a/src/service/submission/submission.service.ts b/src/service/submission/submission.service.ts index e4ea96d..ce57afa 100644 --- a/src/service/submission/submission.service.ts +++ b/src/service/submission/submission.service.ts @@ -19,6 +19,8 @@ export class SubmissionService { async find(form: FormEntity, start: number, limit: number, sort: any = {}): Promise<[SubmissionEntity[], number]> { const qb = this.submissionRepository.createQueryBuilder('s') + qb.leftJoinAndSelect('s.fields', 'fields') + qb.where('s.form = :form', { form: form.id }) // TODO readd sort diff --git a/src/service/submission/submission.set.field.service.ts b/src/service/submission/submission.set.field.service.ts index 89c5ef9..71ce7ac 100644 --- a/src/service/submission/submission.set.field.service.ts +++ b/src/service/submission/submission.set.field.service.ts @@ -7,6 +7,7 @@ import { SubmissionSetFieldInput } from '../../dto/submission/submission.set.fie import { SubmissionEntity } from '../../entity/submission.entity' import { SubmissionFieldEntity } from '../../entity/submission.field.entity' import { SubmissionHookService } from './submission.hook.service' +import { SubmissionNotificationService } from './submission.notification.service' @Injectable() export class SubmissionSetFieldService { @@ -16,6 +17,7 @@ export class SubmissionSetFieldService { @InjectRepository(SubmissionFieldEntity) private readonly submissionFieldRepository: Repository, private readonly webHook: SubmissionHookService, + private readonly notifications: SubmissionNotificationService, private readonly logger: PinoLogger, ) { } @@ -24,7 +26,7 @@ export class SubmissionSetFieldService { let field = submission.fields.find(field => field.field.id.toString() === input.field) if (field) { - field.fieldValue = input.data + field.fieldValue = JSON.parse(input.data) await this.submissionFieldRepository.save(field) } else { @@ -33,7 +35,7 @@ export class SubmissionSetFieldService { field.submission = submission field.field = submission.form.fields.find(field => field.id.toString() === input.field) field.fieldType = field.field.type - field.fieldValue = input.data + field.fieldValue = JSON.parse(input.data) field = await this.submissionFieldRepository.save(field) @@ -50,6 +52,9 @@ export class SubmissionSetFieldService { this.webHook.process(submission).catch(e => { this.logger.error(`failed to send webhooks: ${e.message}`) }) + this.notifications.process(submission).catch(e => { + this.logger.error(`failed to send notifications: ${e.message}`) + }) } } } diff --git a/src/service/submission/submission.start.service.ts b/src/service/submission/submission.start.service.ts index 149c28d..408adee 100644 --- a/src/service/submission/submission.start.service.ts +++ b/src/service/submission/submission.start.service.ts @@ -20,11 +20,15 @@ export class SubmissionStartService { form: FormEntity, input: SubmissionStartInput, user?: UserEntity, + ipAddr?: string, ): Promise { const submission = new SubmissionEntity() submission.form = form submission.user = user + submission.ipAddr = ipAddr || '?' + submission.timeElapsed = 0 + submission.percentageComplete = 0 submission.device.language = input.device.language submission.device.name = input.device.name diff --git a/yarn.lock b/yarn.lock index a6157ad..df640eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2280,6 +2280,13 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/request-ip@^0.0.35": + version "0.0.35" + resolved "https://registry.yarnpkg.com/@types/request-ip/-/request-ip-0.0.35.tgz#c30e832533296151c221532c48c10591b7edd468" + integrity sha512-FtI7lv1EDaZnWmaCU7ZTwfOpW76EioocaWyiSeWdfW1cDPZYzzij781A5O/UeHQUN9yjtjEcD3StTzZjKG0XEA== + dependencies: + "@types/node" "*" + "@types/semver@^6.0.0": version "6.2.2" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.2.2.tgz#5c27df09ca39e3c9beb4fae6b95f4d71426df0a9" @@ -6847,6 +6854,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is_js@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/is_js/-/is_js-0.9.0.tgz#0ab94540502ba7afa24c856aa985561669e9c52d" + integrity sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0= + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -10235,6 +10247,13 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +request-ip@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-2.1.3.tgz#99ab2bafdeaf2002626e28083cb10597511d9e14" + integrity sha512-J3qdE/IhVM3BXkwMIVO4yFrvhJlU3H7JH16+6yHucadT4fePnR8dyh+vEs6FIx0S2x5TCt2ptiPfHcn0sqhbYQ== + dependencies: + is_js "^0.9.0" + request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"