diff --git a/api/src/forms/schemas/button.schema.ts b/api/src/forms/schemas/button.schema.ts
new file mode 100644
index 00000000..5b4d1230
--- /dev/null
+++ b/api/src/forms/schemas/button.schema.ts
@@ -0,0 +1,20 @@
+import * as mongoose from 'mongoose';
+
+export const ButtonSchema = new mongoose.Schema({
+ url: {
+ type: String,
+ match: [/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/],
+ },
+ action: String,
+ text: String,
+ bgColor: {
+ type: String,
+ match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
+ default: '#5bc0de'
+ },
+ color: {
+ type: String,
+ match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
+ default: '#ffffff'
+ }
+});
diff --git a/api/src/forms/schemas/field.option.schema.ts b/api/src/forms/schemas/field.option.schema.ts
new file mode 100644
index 00000000..aedb04d5
--- /dev/null
+++ b/api/src/forms/schemas/field.option.schema.ts
@@ -0,0 +1,16 @@
+import * as mongoose from 'mongoose';
+
+export const FieldOptionSchema = new mongoose.Schema({
+ option_id: {
+ type: Number
+ },
+
+ option_title: {
+ type: String
+ },
+
+ option_value: {
+ type: String,
+ trim: true
+ }
+});
diff --git a/api/src/forms/schemas/field.schema.ts b/api/src/forms/schemas/field.schema.ts
new file mode 100644
index 00000000..22a08212
--- /dev/null
+++ b/api/src/forms/schemas/field.schema.ts
@@ -0,0 +1,66 @@
+import * as mongoose from 'mongoose';
+import {RatingFieldSchema} from "./rating.field.schema"
+import {FieldOptionSchema} from "./field.option.schema"
+import {LogicJumpSchema} from "./logic.jump.schema"
+
+export const FieldSchema = new mongoose.Schema({
+ isSubmission: {
+ type: Boolean,
+ default: false
+ },
+ submissionId: {
+ type: mongoose.Schema.Types.ObjectId
+ },
+ title: {
+ type: String,
+ trim: true
+ },
+ description: {
+ type: String,
+ default: ''
+ },
+
+ logicJump: LogicJumpSchema,
+
+ ratingOptions: RatingFieldSchema,
+ fieldOptions: [FieldOptionSchema],
+
+ required: {
+ type: Boolean,
+ default: true
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+
+ deletePreserved: {
+ type: Boolean,
+ default: false
+ },
+ validFieldTypes: {
+ type: [String]
+ },
+ fieldType: {
+ type: String,
+ enum: [
+ 'textfield',
+ 'date',
+ 'email',
+ 'legal',
+ 'textarea',
+ 'link',
+ 'statement',
+ 'dropdown',
+ 'rating',
+ 'radio',
+ 'hidden',
+ 'yes_no',
+ 'number'
+ ]
+ },
+ fieldValue: {
+ type: mongoose.Schema.Types.Mixed,
+ default: ''
+ }
+});
diff --git a/api/src/forms/schemas/form.schema.ts b/api/src/forms/schemas/form.schema.ts
new file mode 100644
index 00000000..18ab73a8
--- /dev/null
+++ b/api/src/forms/schemas/form.schema.ts
@@ -0,0 +1,151 @@
+import * as mongoose from 'mongoose';
+import {VisitorDataSchema} from "./visitor.data.schema"
+import {ButtonSchema} from "./button.schema"
+import {FieldSchema} from "./field.schema"
+
+export const FormSchema = new mongoose.Schema({
+ title: {
+ type: String,
+ trim: true,
+ required: 'Form Title cannot be blank'
+ },
+ language: {
+ type: String,
+ enum: ['en', 'fr', 'es', 'it', 'de'],
+ default: 'en',
+ required: 'Form must have a language'
+ },
+ analytics:{
+ gaCode: {
+ type: String
+ },
+ visitors: [VisitorDataSchema]
+ },
+ form_fields: {
+ type: [FieldSchema],
+ default: []
+ },
+ admin: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'User',
+ required: 'Form must have an Admin'
+ },
+ startPage: {
+ showStart:{
+ type: Boolean,
+ default: false
+ },
+ introTitle:{
+ type: String,
+ default: 'Welcome to Form'
+ },
+ introParagraph:{
+ type: String
+ },
+ introButtonText:{
+ type: String,
+ default: 'Start'
+ },
+ buttons:[ButtonSchema]
+ },
+ endPage: {
+ showEnd:{
+ type: Boolean,
+ default: false
+ },
+ title:{
+ type: String,
+ default: 'Thank you for filling out the form'
+ },
+ paragraph:{
+ type: String
+ },
+ buttonText:{
+ type: String,
+ default: 'Go back to Form'
+ },
+ buttons:[ButtonSchema]
+ },
+
+ selfNotifications: {
+ fromField: {
+ type: String
+ },
+ toEmails: {
+ type: String
+ },
+ subject: {
+ type: String
+ },
+ htmlTemplate: {
+ type: String
+ },
+ enabled: {
+ type: Boolean,
+ default: false
+ }
+ },
+
+ respondentNotifications: {
+ toField: {
+ type: String
+ },
+ fromEmails: {
+ type: String,
+ match: [/.+\@.+\..+/, 'Please fill a valid email address']
+ },
+ subject: {
+ type: String,
+ default: 'OhMyForm: Thank you for filling out this OhMyForm'
+ },
+ htmlTemplate: {
+ type: String,
+ default: 'Hello,
We’ve received your submission.
Thank you & have a nice day!',
+ },
+ enabled: {
+ type: Boolean,
+ default: false
+ }
+ },
+
+ showFooter: {
+ type: Boolean,
+ default: true
+ },
+
+ isLive: {
+ type: Boolean,
+ default: true
+ },
+
+ design: {
+ colors: {
+ backgroundColor: {
+ type: String,
+ match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
+ default: '#fff'
+ },
+ questionColor: {
+ type: String,
+ match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
+ default: '#333'
+ },
+ answerColor: {
+ type: String,
+ match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
+ default: '#333'
+ },
+ buttonColor: {
+ type: String,
+ match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
+ default: '#fff'
+ },
+ buttonTextColor: {
+ type: String,
+ match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
+ default: '#333'
+ }
+ },
+ font: String
+ }
+});
diff --git a/api/src/forms/schemas/logic.jump.schema.ts b/api/src/forms/schemas/logic.jump.schema.ts
new file mode 100644
index 00000000..9f3b7a83
--- /dev/null
+++ b/api/src/forms/schemas/logic.jump.schema.ts
@@ -0,0 +1,36 @@
+import * as mongoose from 'mongoose';
+
+export const LogicJumpSchema = new mongoose.Schema({
+ expressionString: {
+ type: String,
+ enum: [
+ 'field == static',
+ 'field != static',
+ 'field > static',
+ 'field >= static',
+ 'field <= static',
+ 'field < static',
+ 'field contains static',
+ 'field !contains static',
+ 'field begins static',
+ 'field !begins static',
+ 'field ends static',
+ 'field !ends static'
+ ]
+ },
+ fieldA: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'FormField'
+ },
+ valueB: {
+ type: mongoose.Schema.Types.String
+ },
+ jumpTo: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'FormField'
+ },
+ enabled: {
+ type: mongoose.Schema.Types.Boolean,
+ default: false
+ }
+});
diff --git a/api/src/forms/schemas/rating.field.schema.ts b/api/src/forms/schemas/rating.field.schema.ts
new file mode 100644
index 00000000..e6b63b50
--- /dev/null
+++ b/api/src/forms/schemas/rating.field.schema.ts
@@ -0,0 +1,30 @@
+import * as mongoose from 'mongoose';
+
+export const RatingFieldSchema = new mongoose.Schema({
+ steps: {
+ type: Number,
+ min: 1,
+ max: 10
+ },
+ shape: {
+ type: String,
+ enum: [
+ 'Heart',
+ 'Star',
+ 'thumbs-up',
+ 'thumbs-down',
+ 'Circle',
+ 'Square',
+ 'Check Circle',
+ 'Smile Outlined',
+ 'Hourglass',
+ 'bell',
+ 'Paper Plane',
+ 'Comment',
+ 'Trash'
+ ]
+ },
+ validShapes: {
+ type: [String]
+ }
+});
diff --git a/api/src/forms/schemas/visitor.data.schema.ts b/api/src/forms/schemas/visitor.data.schema.ts
new file mode 100644
index 00000000..ee2e83eb
--- /dev/null
+++ b/api/src/forms/schemas/visitor.data.schema.ts
@@ -0,0 +1,35 @@
+import * as mongoose from 'mongoose';
+
+export const VisitorDataSchema = new mongoose.Schema({
+ socketId: {
+ type: String
+ },
+ referrer: {
+ type: String
+ },
+ filledOutFields: {
+ type: [mongoose.Schema.Types.ObjectId]
+ },
+ timeElapsed: {
+ type: Number
+ },
+ isSubmitted: {
+ type: Boolean
+ },
+ language: {
+ type: String,
+ enum: ['en', 'fr', 'es', 'it', 'de'],
+ default: 'en',
+ },
+ ipAddr: {
+ type: String
+ },
+ deviceType: {
+ type: String,
+ enum: ['desktop', 'phone', 'tablet', 'other'],
+ default: 'other'
+ },
+ userAgent: {
+ type: String
+ }
+});
diff --git a/api/src/users/schemas/user.schema.ts b/api/src/users/schemas/user.schema.ts
new file mode 100644
index 00000000..7cd7cd47
--- /dev/null
+++ b/api/src/users/schemas/user.schema.ts
@@ -0,0 +1,80 @@
+import * as mongoose from 'mongoose';
+
+export const UserSchema = new mongoose.Schema({
+ firstName: {
+ type: String,
+ trim: true,
+ default: ''
+ },
+ lastName: {
+ type: String,
+ trim: true,
+ default: ''
+ },
+ email: {
+ type: String,
+ trim: true,
+ lowercase: true,
+ unique: 'Account already exists with this email',
+ match: [
+ /^(([^<>()\[\]\\.,;:\s@']+(\.[^<>()\[\]\\.,;:\s@']+)*)|('.+'))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
+ 'Please fill a valid email address'
+ ],
+ required: [true, 'Email is required']
+ },
+ username: {
+ type: String,
+ unique: true,
+ lowercase: true,
+ match: [
+ /^[a-zA-Z0-9\-]+$/,
+ 'Username can only contain alphanumeric characters and \'-\''
+ ],
+ required: [true, 'Username is required']
+ },
+ passwordHash: {
+ type: String,
+ default: ''
+ },
+ salt: {
+ type: String
+ },
+ provider: {
+ type: String,
+ default: 'local'
+ },
+ roles: {
+ type: [{
+ type: String,
+ enum: ['user', 'admin', 'superuser']
+ }],
+ default: ['user']
+ },
+ language: {
+ type: String,
+ enum: ['en', 'fr', 'es', 'it', 'de'],
+ default: 'en',
+ },
+ lastModified: {
+ type: Date
+ },
+ created: {
+ type: Date,
+ default: Date.now
+ },
+
+ /* For reset password */
+ resetPasswordToken: {
+ type: String
+ },
+ resetPasswordExpires: {
+ type: Date
+ },
+ token: String,
+ apiKey: {
+ type: String,
+ unique: true,
+ index: true,
+ sparse: true
+ }
+});