upgrade packages, improve field logic, fix slider, hide empty submissions, fix urls for buttons, improve data handling for fields, improve sqlite migration handling

This commit is contained in:
Michael Schramm 2022-02-27 12:58:52 +01:00
parent 25d314a183
commit b440c97661
27 changed files with 1603 additions and 1286 deletions

View File

@ -1,7 +1,7 @@
language: node_js
node_js:
- 12
- 16
cache:
directories:
- node_modules

View File

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- idx for fields and logic to have stable order
- ability to load submission by id if token is present
- anonymous form submissions (fixes https://github.com/ohmyform/ohmyform/issues/108)
- ability to filter for partial / completed or empty submissions
### Changed
@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- improved eslint checks
- validate submission field data and store it json encoded
- forms are no longer finished on 100% but instead on finish mutation
- field default value renamed from value to defaultValue
### Fixed
@ -34,6 +36,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- version env variable for yarn
- path argument error (https://github.com/ohmyform/ohmyform/issues/149)
- webhook and form submission (https://github.com/ohmyform/api/pull/37)
- sqlite migration fixes to allow changes to tables
### Security

View File

@ -9,7 +9,7 @@
"migrations": [
"src/migrations/sqlite/**/*.ts"
],
"migrationsTransactionMode": "each",
"migrationsTransactionMode": "none",
"cli": {
"migrationsDir": "src/migrations/sqlite"
}

View File

@ -25,22 +25,23 @@
"typeorm:mariadb": "cross-env TS_NODE_TRANSPILE_ONLY=true ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js -f ormconfig_mariadb.json"
},
"dependencies": {
"@nestjs-modules/mailer": "^1.6.0",
"@nestjs/axios": "^0.0.3",
"@nestjs/common": "^8.2.4",
"@nestjs/config": "^1.1.5",
"@nestjs/core": "^8.2.4",
"@nestjs/graphql": "^9.1.2",
"@ardatan/aggregate-error": "^0.0.6",
"@nestjs-modules/mailer": "^1.6.1",
"@nestjs/apollo": "^10.0.5",
"@nestjs/axios": "^0.0.6",
"@nestjs/common": "^8.3.1",
"@nestjs/config": "^1.2.0",
"@nestjs/core": "^8.3.1",
"@nestjs/graphql": "^10.0.5",
"@nestjs/jwt": "^8.0.0",
"@nestjs/passport": "^8.0.1",
"@nestjs/platform-express": "^8.2.4",
"@nestjs/passport": "^8.2.1",
"@nestjs/platform-express": "^8.3.1",
"@nestjs/serve-static": "^2.2.2",
"@nestjs/typeorm": "^8.0.2",
"apollo-server-express": "^3.6.1",
"@nestjs/typeorm": "^8.0.3",
"apollo-server-express": "^3.6.3",
"bcrypt": "^5.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"commander": "^8.3.0",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"dayjs": "^1.10.7",
@ -51,59 +52,59 @@
"handlebars": "^4.7.7",
"html-to-text": "^8.1.0",
"inquirer": "^8.2.0",
"ioredis": "^4.28.2",
"ioredis": "^4.28.5",
"ip-anonymize": "^0.1.0",
"matomo-tracker": "^2.2.4",
"mjml": "^4.11.0",
"mjml": "^4.12.0",
"mysql2": "^2.3.3",
"nestjs-console": "^7.0.1",
"nestjs-pino": "^2.4.0",
"nestjs-pino": "^2.5.0",
"nodemailer": "^6.7.2",
"passport": "^0.5.2",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"pg": "^8.7.1",
"pino-http": "^6.5.0",
"pino-pretty": "^7.3.0",
"pg": "^8.7.3",
"pino-http": "^6.6.0",
"pino-pretty": "^7.5.1",
"reflect-metadata": "^0.1.13",
"request-ip": "^2.1.3",
"rimraf": "^3.0.2",
"rxjs": "^7.5.1",
"rxjs": "^7.5.4",
"serialize-error": "^8.1.0",
"sqlite3": "^5.0.2",
"typeorm": "^0.2.41"
"typeorm": "^0.2.44"
},
"devDependencies": {
"@nestjs/cli": "^8.1.6",
"@nestjs/schematics": "^8.0.5",
"@nestjs/testing": "^8.2.4",
"@nestjs/cli": "^8.2.1",
"@nestjs/schematics": "^8.0.7",
"@nestjs/testing": "^8.3.1",
"@types/bcrypt": "^5.0.0",
"@types/express-serve-static-core": "^4.17.27",
"@types/express-serve-static-core": "^4.17.28",
"@types/handlebars": "^4.1.0",
"@types/html-to-text": "^8.0.1",
"@types/inquirer": "^8.1.3",
"@types/jest": "26.0.23",
"@types/inquirer": "^8.2.0",
"@types/jest": "27.4.1",
"@types/mjml": "^4.7.0",
"@types/node": "^16.11.17",
"@types/node": "^17.0.21",
"@types/passport-jwt": "^3.0.6",
"@types/passport-local": "^1.0.34",
"@types/request-ip": "^0.0.37",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.4.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-nestjs": "^1.2.3",
"eslint-plugin-unused-imports": "^2.0.0",
"jest": "^27.4.5",
"jest": "^27.5.1",
"prettier": "^2.5.1",
"supertest": "^6.1.6",
"ts-jest": "27.1.2",
"supertest": "^6.2.2",
"ts-jest": "27.1.3",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"ts-node": "^10.5.0",
"tsconfig-paths": "^3.12.0",
"typescript": "^4.5.4"
"typescript": "^4.5.5"
},
"jest": {
"moduleFileExtensions": [

View File

@ -1,4 +1,5 @@
import { MailerModule } from '@nestjs-modules/mailer'
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'
import { HttpModule } from '@nestjs/axios'
import { RequestMethod } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
@ -72,11 +73,12 @@ export const imports = [
}),
}),
LoggerModule.forRoot(LoggerConfig),
GraphQLModule.forRoot({
GraphQLModule.forRoot<ApolloDriverConfig>({
debug: process.env.NODE_ENV !== 'production',
definitions: {
outputAs: 'class',
},
driver: ApolloDriver,
sortSchema: true,
introspection: process.env.NODE_ENV !== 'production',
playground: process.env.NODE_ENV !== 'production',
@ -114,6 +116,7 @@ export const imports = [
useFactory: (configService: ConfigService): TypeOrmModuleOptions => {
const type: any = configService.get<string>('DATABASE_DRIVER', 'sqlite')
let migrationFolder: string
let migrationsTransactionMode: 'each' | 'none' | 'all' = 'each'
switch (type) {
case 'cockroachdb':
@ -128,6 +131,7 @@ export const imports = [
case 'sqlite':
migrationFolder = 'sqlite'
migrationsTransactionMode = 'none'
break
default:
@ -146,7 +150,7 @@ export const imports = [
entities,
migrations: [`${__dirname}/**/migrations/${migrationFolder}/**/*{.ts,.js}`],
migrationsRun: configService.get<boolean>('DATABASE_MIGRATE', true),
migrationsTransactionMode: 'each',
migrationsTransactionMode,
})
},
}),

View File

@ -26,8 +26,8 @@ export class FormFieldInput {
@Field()
readonly required: boolean
@Field()
readonly value: string
@Field({ nullable: true })
readonly defaultValue: string
@Field({ nullable: true })
readonly disabled?: boolean

View File

@ -27,8 +27,8 @@ export class FormFieldModel {
@Field()
readonly required: boolean
@Field()
readonly value: string
@Field({ nullable: true })
readonly defaultValue: string
@Field(() => [FormFieldOptionModel])
readonly options: FormFieldOptionModel[]
@ -47,7 +47,7 @@ export class FormFieldModel {
this.type = document.type
this.description = document.description
this.required = document.required
this.value = document.value
this.defaultValue = document.defaultValue
this.options = document.options?.map(option => new FormFieldOptionModel(option)) || []
this.logic = document.logic?.map(logic => new FormFieldLogicModel(logic)) || []
this.rating = document.rating ? new FormFieldRatingModel(document.rating) : null

View File

@ -0,0 +1,10 @@
import { Field, InputType } from '@nestjs/graphql'
@InputType('SubmissionPagerFilterInput')
export class SubmissionPagerFilterInput {
@Field({ nullable: true } )
finished?: boolean
@Field({ nullable: true } )
excludeEmpty?: boolean
}

View File

@ -42,6 +42,6 @@ export class FormFieldEntity {
@Column()
public type: string
@Column()
public value: string
@Column({ nullable: true })
public defaultValue: string
}

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class defaultValue1645952169100 implements MigrationInterface {
name = 'defaultValue1645952169100'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `form_field` RENAME COLUMN `value` TO `defaultValue`');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `form_field` RENAME COLUMN `defaultValue` TO `value`');
}
}

View File

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class defaultValue1645956388810 implements MigrationInterface {
name = 'defaultValue1645956388810'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `form_field` MODIFY COLUMN `defaultValue` varchar(255)');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('UPDATE `form_field` SET `defaultValue` = "" WHERE `defaultValue` IS NULL');
await queryRunner.query('ALTER TABLE `form_field` MODIFY COLUMN `defaultValue` varchar(255) NOT NULL');
}
}

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class defaultValue1645952169100 implements MigrationInterface {
name = 'defaultValue1645952169100'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form_field" RENAME COLUMN "value" TO "defaultValue"');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form_field" RENAME COLUMN "defaultValue" TO "value"');
}
}

View File

@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class defaultValue1645956388810 implements MigrationInterface {
name = 'defaultValue1645956388810'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form_field" ALTER COLUMN "defaultValue" DROP NOT NULL');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('UPDATE "form_field" SET "defaultValue" = \'\' WHERE "defaultValue" IS NULL');
await queryRunner.query('ALTER TABLE "form_field" ALTER COLUMN "defaultValue" SET NOT NULL');
}
}

View File

@ -0,0 +1,47 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
function final(target: object, key: string | symbol, descriptor: PropertyDescriptor) {
descriptor.writable = false;
}
export abstract class SqliteMigration implements MigrationInterface {
abstract realUp(queryRunner: QueryRunner): Promise<any>
abstract realDown(queryRunner: QueryRunner): Promise<any>
@final
async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query('PRAGMA foreign_keys=off')
await queryRunner.query('BEGIN TRANSACTION')
try {
await this.realUp(queryRunner)
await queryRunner.query('COMMIT')
} catch (e) {
await queryRunner.query('ROLLBACK')
throw e
} finally {
await queryRunner.query('PRAGMA foreign_keys=on')
}
}
@final
async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query('PRAGMA foreign_keys=off')
await queryRunner.query('BEGIN TRANSACTION')
try {
await this.realDown(queryRunner)
await queryRunner.query('COMMIT')
} catch (e) {
await queryRunner.query('ROLLBACK')
throw e
} finally {
await queryRunner.query('PRAGMA foreign_keys=on')
}
}
}

View File

@ -1,20 +1,20 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { QueryRunner } from 'typeorm'
import { SqliteMigration } from '../sqlite.migration'
export class layout1621078163528 implements MigrationInterface {
export class layout1621078163528 extends SqliteMigration {
name = 'layout1621078163528'
public async up(queryRunner: QueryRunner): Promise<void> {
public async realUp(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE TABLE "temporary_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, "designLayout" varchar, CONSTRAINT "FK_e5d158932e43cfbf9958931ee01" FOREIGN KEY ("endPageId") REFERENCES "page" ("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_a7cb33580bca2b362e5e34fdfcd" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "temporary_form"("id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext") SELECT "id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext" FROM "form"');
await queryRunner.query('DROP TABLE "form"');
await queryRunner.query('ALTER TABLE "temporary_form" RENAME TO "form"');
}
public async down(queryRunner: QueryRunner): Promise<void> {
public async realDown(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form" RENAME TO "temporary_form"');
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_e5d158932e43cfbf9958931ee01" FOREIGN KEY ("endPageId") REFERENCES "page" ("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_a7cb33580bca2b362e5e34fdfcd" FOREIGN KEY ("adminId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "form"("id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext") SELECT "id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext" FROM "temporary_form"');
await queryRunner.query('DROP TABLE "temporary_form"');
}
}

View File

@ -1,20 +1,20 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { QueryRunner } from 'typeorm'
import { SqliteMigration } from '../sqlite.migration'
export class submission1641124349039 implements MigrationInterface {
export class submission1641124349039 extends SqliteMigration {
name = 'submission1641124349039'
public async up(queryRunner: QueryRunner): Promise<void> {
public async realUp(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE TABLE "temporary_submission_field" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "submissionId" integer, "fieldId" integer, "type" varchar NOT NULL, "content" text NOT NULL, CONSTRAINT "FK_5befa92da2370b7eb1cab6ae30a" FOREIGN KEY ("fieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_16fae661ce5b10f27abe2e524a0" FOREIGN KEY ("submissionId") REFERENCES "submission" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "temporary_submission_field"("id", "submissionId", "type", "content", "fieldId") SELECT "id", "submissionId", "fieldType", "fieldValue", "fieldId" FROM "submission_field"');
await queryRunner.query('DROP TABLE "submission_field"');
await queryRunner.query('ALTER TABLE "temporary_submission_field" RENAME TO "submission_field"');
}
public async down(queryRunner: QueryRunner): Promise<void> {
public async realDown(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "submission_field" RENAME TO "temporary_submission_field"');
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_5befa92da2370b7eb1cab6ae30a" FOREIGN KEY ("fieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_16fae661ce5b10f27abe2e524a0" FOREIGN KEY ("submissionId") REFERENCES "submission" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "submission_field"("id", "submissionId", "fieldType", "fieldValue", "fieldId") SELECT "id", "submissionId", "type", "content", "fieldId" FROM "temporary_submission_field"');
await queryRunner.query('DROP TABLE "temporary_submission_field"');
}
}

View File

@ -1,16 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { QueryRunner } from 'typeorm'
import { SqliteMigration } from '../sqlite.migration'
export class confirm1641132645227 implements MigrationInterface {
export class confirm1641132645227 extends SqliteMigration {
name = 'confirm1641132645227'
public async up(queryRunner: QueryRunner): Promise<void> {
public async realUp(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE TABLE "temporary_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\')), "emailVerified" boolean NOT NULL DEFAULT (0), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"))');
await queryRunner.query('INSERT INTO "temporary_user"("id", "firstName", "lastName", "email", "username", "passwordHash", "salt", "provider", "roles", "language", "resetPasswordToken", "resetPasswordExpires", "token", "apiKey", "created", "lastModified") SELECT "id", "firstName", "lastName", "email", "username", "passwordHash", "salt", "provider", "roles", "language", "resetPasswordToken", "resetPasswordExpires", "token", "apiKey", "created", "lastModified" FROM "user"');
await queryRunner.query('DROP TABLE "user"');
await queryRunner.query('ALTER TABLE "temporary_user" RENAME TO "user"');
}
public async down(queryRunner: QueryRunner): Promise<void> {
public async realDown(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "user" RENAME TO "temporary_user"');
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('INSERT INTO "user"("id", "firstName", "lastName", "email", "username", "passwordHash", "salt", "provider", "roles", "language", "resetPasswordToken", "resetPasswordExpires", "token", "apiKey", "created", "lastModified") SELECT "id", "firstName", "lastName", "email", "username", "passwordHash", "salt", "provider", "roles", "language", "resetPasswordToken", "resetPasswordExpires", "token", "apiKey", "created", "lastModified" FROM "temporary_user"');

View File

@ -1,9 +1,10 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { QueryRunner } from 'typeorm'
import { SqliteMigration } from '../sqlite.migration'
export class idx1641151802308 implements MigrationInterface {
export class idx1641151802308 extends SqliteMigration {
name = 'idx1641151802308'
public async up(queryRunner: QueryRunner): Promise<void> {
public async realUp(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE TABLE "temporary_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, "idx" integer, CONSTRAINT "FK_4a8019f2b753cfb3216dc3001a6" FOREIGN KEY ("jumpToId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_6098b83f6759445d8cfdd03d545" FOREIGN KEY ("fieldId") REFERENCES "form_field" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "temporary_form_field_logic"("id", "formula", "action", "visible", "require", "disable", "enabled", "fieldId", "jumpToId") SELECT "id", "formula", "action", "visible", "require", "disable", "enabled", "fieldId", "jumpToId" FROM "form_field_logic"');
await queryRunner.query('DROP TABLE "form_field_logic"');
@ -14,7 +15,7 @@ export class idx1641151802308 implements MigrationInterface {
await queryRunner.query('ALTER TABLE "temporary_form_field" RENAME TO "form_field"');
}
public async down(queryRunner: QueryRunner): Promise<void> {
public async realDown(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form_field" RENAME TO "temporary_form_field"');
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('INSERT INTO "form_field"("id", "title", "description", "slug", "required", "disabled", "type", "value", "formId", "ratingSteps", "ratingShape") SELECT "id", "title", "description", "slug", "required", "disabled", "type", "value", "formId", "ratingSteps", "ratingShape" FROM "temporary_form_field"');

View File

@ -1,16 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
import { QueryRunner } from 'typeorm'
import { SqliteMigration } from '../sqlite.migration'
export class anonymous1641193946192 implements MigrationInterface {
export class anonymous1641193946192 extends SqliteMigration {
name = 'anonymous1641193946192'
public async up(queryRunner: QueryRunner): Promise<void> {
public async realUp(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE TABLE "temporary_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, "designLayout" varchar, "anonymousSubmission" boolean NOT NULL DEFAULT (0), 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)');
await queryRunner.query('INSERT INTO "temporary_form"("id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext", "designLayout") SELECT "id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext", "designLayout" FROM "form"');
await queryRunner.query('DROP TABLE "form"');
await queryRunner.query('ALTER TABLE "temporary_form" RENAME TO "form"');
}
public async down(queryRunner: QueryRunner): Promise<void> {
public async realDown(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form" RENAME TO "temporary_form"');
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, "designLayout" 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)');
await queryRunner.query('INSERT INTO "form"("id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext", "designLayout") SELECT "id", "title", "language", "showFooter", "isLive", "created", "lastModified", "adminId", "startPageId", "endPageId", "analyticsGacode", "designFont", "designColorsBackground", "designColorsQuestion", "designColorsAnswer", "designColorsButton", "designColorsButtonactive", "designColorsButtontext", "designLayout" FROM "temporary_form"');

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class defaultValue1645952169100 implements MigrationInterface {
name = 'defaultValue1645952169100'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form_field" RENAME COLUMN "value" TO "defaultValue"');
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE "form_field" RENAME COLUMN "defaultValue" TO "value"');
}
}

View File

@ -0,0 +1,30 @@
import { QueryRunner } from 'typeorm'
import { SqliteMigration } from '../sqlite.migration'
export class defaultValue1645956388810 extends SqliteMigration {
name = 'defaultValue1645956388810'
public async realUp(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE TABLE "temporary_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, "defaultValue" varchar NOT NULL, "formId" integer, "ratingSteps" integer, "ratingShape" varchar, "idx" integer, CONSTRAINT "FK_2d83d8a334dd66445db13f92b77" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "temporary_form_field"("id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx") SELECT "id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx" FROM "form_field"');
await queryRunner.query('DROP TABLE "form_field"');
await queryRunner.query('ALTER TABLE "temporary_form_field" RENAME TO "form_field"');
await queryRunner.query('CREATE TABLE "temporary_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, "defaultValue" varchar, "formId" integer, "ratingSteps" integer, "ratingShape" varchar, "idx" integer, CONSTRAINT "FK_2d83d8a334dd66445db13f92b77" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "temporary_form_field"("id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx") SELECT "id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx" FROM "form_field"');
await queryRunner.query('DROP TABLE "form_field"');
await queryRunner.query('ALTER TABLE "temporary_form_field" RENAME TO "form_field"');
}
public async realDown(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('UPDATE "form_field" SET "defaultValue" = \'\' WHERE "defaultValue" IS NULL');
await queryRunner.query('ALTER TABLE "form_field" RENAME TO "temporary_form_field"');
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, "defaultValue" varchar NOT NULL, "formId" integer, "ratingSteps" integer, "ratingShape" varchar, "idx" integer, CONSTRAINT "FK_2d83d8a334dd66445db13f92b77" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "form_field"("id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx") SELECT "id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx" FROM "temporary_form_field"');
await queryRunner.query('DROP TABLE "temporary_form_field"');
await queryRunner.query('ALTER TABLE "form_field" RENAME TO "temporary_form_field"');
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, "defaultValue" varchar NOT NULL, "formId" integer, "ratingSteps" integer, "ratingShape" varchar, "idx" integer, CONSTRAINT "FK_2d83d8a334dd66445db13f92b77" FOREIGN KEY ("formId") REFERENCES "form" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)');
await queryRunner.query('INSERT INTO "form_field"("id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx") SELECT "id", "title", "description", "slug", "required", "disabled", "type", "defaultValue", "formId", "ratingSteps", "ratingShape", "idx" FROM "temporary_form_field"');
await queryRunner.query('DROP TABLE "temporary_form_field"');
}
}

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'
import { Args, Context, ID, Int, Query } from '@nestjs/graphql'
import { User } from '../../decorator/user.decorator'
import { SubmissionModel } from '../../dto/submission/submission.model'
import { SubmissionPagerFilterInput } from '../../dto/submission/submission.pager.filter.input'
import { SubmissionPagerModel } from '../../dto/submission/submission.pager.model'
import { SubmissionEntity } from '../../entity/submission.entity'
import { UserEntity } from '../../entity/user.entity'
@ -23,6 +24,7 @@ export class SubmissionListQuery {
@Args('form', {type: () => ID}) id: string,
@Args('start', {type: () => Int, defaultValue: 0, nullable: true}) start: number,
@Args('limit', {type: () => Int, defaultValue: 50, nullable: true}) limit: number,
@Args('filter', {type: () => SubmissionPagerFilterInput, defaultValue: new SubmissionPagerFilterInput()}) filter: SubmissionPagerFilterInput,
@Context('cache') cache: ContextCache,
): Promise<SubmissionPagerModel> {
const form = await this.formService.findById(id)
@ -32,6 +34,7 @@ export class SubmissionListQuery {
start,
limit,
{},
filter,
)
submissions.forEach(submission => {

View File

@ -77,8 +77,8 @@ export class FormUpdateService {
field.required = nextField.required
}
if (nextField.value !== undefined) {
field.value = nextField.value
if (nextField.defaultValue !== undefined) {
field.defaultValue = nextField.defaultValue
}
if (nextField.slug !== undefined) {

View File

@ -59,9 +59,9 @@ export class SubmissionHookService {
return {
field: submissionField.field.id,
slug: submissionField.field.slug || null,
default_value: submissionField.field.value,
default_value: submissionField.field.defaultValue,
content: submissionField.content,
}
}
}),
}

View File

@ -23,7 +23,11 @@ export class SubmissionService {
form: FormEntity,
start: number,
limit: number,
sort: any = {}
sort: any = {},
filter: {
finished?: boolean,
excludeEmpty?: boolean
} = {}
): Promise<[SubmissionEntity[], number]> {
const qb = this.submissionRepository.createQueryBuilder('s')
@ -31,6 +35,18 @@ export class SubmissionService {
qb.where('s.form = :form', { form: form.id })
if (filter.finished === true) {
qb.andWhere('s.percentageComplete = 1')
}
if (filter.finished === false) {
qb.andWhere('s.percentageComplete < 1')
}
if (filter.excludeEmpty === true) {
qb.andWhere('s.percentageComplete > 0')
}
// TODO readd sort
this.logger.debug({
sort,

View File

@ -51,12 +51,18 @@ export class SubmissionSetFieldService {
}
if (submission.percentageComplete === 1) {
this.finishSubmission(submission)
await this.finishSubmission(submission)
}
}
async finishSubmission(submission: SubmissionEntity) {
async finishSubmission(submission: SubmissionEntity): Promise<void> {
submission.percentageComplete = 1
await this.submissionRepository.update({
id: submission.id,
}, {
percentageComplete: 1,
})
this.webHook.process(submission).catch(e => {
this.logger.error({
submission: submission.id,
@ -64,6 +70,7 @@ export class SubmissionSetFieldService {
error: serializeError(e),
}, 'failed to send webhooks')
})
this.notifications.process(submission).catch(e => {
this.logger.error({
submission: submission.id,

2546
yarn.lock

File diff suppressed because it is too large Load Diff