add first form endpoint

This commit is contained in:
wodka 2019-08-04 00:48:39 +02:00
parent fdcc9fda3f
commit 93f63f7421
52 changed files with 867 additions and 511 deletions

View File

@ -34,13 +34,15 @@
"class-transformer": "^0.2.3",
"class-validator": "^0.9.1",
"mongoose": "^5.6.7",
"nestjs-typegoose": "^5.2.1",
"passport": "^0.4.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.12",
"rimraf": "^2.6.2",
"rxjs": "^6.3.3",
"swagger-ui-express": "^4.0.7"
"swagger-ui-express": "^4.0.7",
"typegoose": "^5.9.0"
},
"devDependencies": {
"@nestjs/testing": "6.1.1",

View File

@ -1,10 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import {ApiExcludeEndpoint} from "@nestjs/swagger"
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@ApiExcludeEndpoint()
@Get()
getIndex(): object {
return {

View File

@ -1,21 +1,23 @@
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { MongooseModule } from '@nestjs/mongoose';
import { TypegooseModule } from 'nestjs-typegoose';
import { TerminusOptionsService } from './terminus-options.service';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {UsersModule} from "./users/users.module"
import {FormsModule} from "./forms/forms.module"
import { UserModule } from "./user/user.module"
import { FormModule } from "./form/form.module"
import { AuthModule } from './auth/auth.module';
@Module({
imports: [
TypegooseModule.forRoot('mongodb://localhost/ohmyform', { useNewUrlParser: true }),
MongooseModule.forRoot('mongodb://localhost/ohmyform'),
TerminusModule.forRootAsync({
useClass: TerminusOptionsService,
}),
UsersModule,
FormsModule,
UserModule,
FormModule,
AuthModule,
],
controllers: [AppController],

View File

@ -0,0 +1,9 @@
import {AuthController} from "./controllers/auth.controller"
import {RecoverController} from "./controllers/recover.controller"
import {RegisterController} from "./controllers/register.controller"
export default [
AuthController,
RecoverController,
RegisterController,
]

View File

@ -1,31 +1,21 @@
import { Module } from '@nestjs/common';
import { AuthService } from './services/auth.service';
import { UsersModule } from '../users/users.module';
import { UserModule } from '../user/user.module';
import { PassportModule } from '@nestjs/passport';
import { PasswordStrategy } from "./strategies/password.strategy"
import { PasswordService } from "./services/password.service"
import { AuthController } from "./controllers/auth.controller"
import { jwtConstants } from "./constants"
import { JwtModule } from "@nestjs/jwt"
import { JwtStrategy } from "./strategies/jwt.strategy"
import { JwtRefreshStrategy } from "./strategies/jwt.refresh.strategy"
import controllers from './auth.controllers'
import providers from './auth.providers'
@Module({
imports: [
UsersModule,
UserModule,
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '12h' },
}),
],
controllers: [AuthController],
providers: [
AuthService,
PasswordService,
PasswordStrategy,
JwtStrategy,
JwtRefreshStrategy,
]
controllers,
providers
})
export class AuthModule {}

View File

@ -0,0 +1,13 @@
import { AuthService } from "./services/auth.service"
import { PasswordService } from "./services/password.service"
import { PasswordStrategy } from "./strategies/password.strategy"
import { JwtStrategy } from "./strategies/jwt.strategy"
import { JwtRefreshStrategy } from "./strategies/jwt.refresh.strategy"
export default [
AuthService,
PasswordService,
PasswordStrategy,
JwtStrategy,
JwtRefreshStrategy,
]

View File

@ -0,0 +1,29 @@
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from "../services/auth.service"
import { AuthJwtDto } from "../dto/auth.jwt.dto"
import {ApiBearerAuth, ApiImplicitBody, ApiImplicitQuery, ApiResponse, ApiUseTags} from "@nestjs/swagger"
@ApiUseTags('authentication')
@Controller('auth/recover')
export class RecoverController {
constructor(private readonly authService: AuthService) {}
@ApiResponse({ status: 201, description: 'Successful registration.', type: AuthJwtDto})
@ApiImplicitQuery({name: 'email', type: String})
@ApiImplicitQuery({name: 'username', type: String})
@Post('request')
async request(@Request() req): Promise<AuthJwtDto> {
// TODO
return null
}
@ApiResponse({ status: 201, description: 'Successful registration.', type: AuthJwtDto})
@ApiImplicitQuery({name: 'token', type: String})
@ApiImplicitQuery({name: 'password', type: String})
@Post('finish')
async finish(@Request() req): Promise<AuthJwtDto> {
// TODO
return null
}
}

View File

@ -0,0 +1,21 @@
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from "../services/auth.service"
import { AuthJwtDto } from "../dto/auth.jwt.dto"
import {ApiBearerAuth, ApiImplicitBody, ApiImplicitQuery, ApiResponse, ApiUseTags} from "@nestjs/swagger"
@ApiUseTags('authentication')
@Controller('auth')
export class RegisterController {
constructor(private readonly authService: AuthService) {}
@ApiResponse({ status: 201, description: 'Successful registration.', type: AuthJwtDto})
@ApiImplicitQuery({name: 'email', type: String})
@ApiImplicitQuery({name: 'username', type: String})
@ApiImplicitQuery({name: 'password', type: String})
@Post('register')
async register(@Request() req): Promise<AuthJwtDto> {
// TODO
return null
}
}

View File

@ -1,15 +1,15 @@
import { Injectable } from '@nestjs/common';
import { UsersService } from '../../users/users.service';
import { UserService } from '../../user/services/user.service';
import { PasswordService } from "./password.service"
import { JwtService } from '@nestjs/jwt';
import { AuthUser } from "../interfaces/auth.user.interface"
import { User } from "../../users/interfaces/user.interface"
import {AuthJwtDto} from "../dto/auth.jwt.dto"
import { User } from "../../user/models/user.model"
import { AuthJwtDto } from "../dto/auth.jwt.dto"
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly usersService: UserService,
private readonly passwordService: PasswordService,
private readonly jwtService: JwtService
) {}
@ -38,12 +38,12 @@ export class AuthService {
private static setupAuthUser(user:User):AuthUser {
return {
id: user.id,
id: user._id,
username: user.username,
email: user.email,
roles: user.roles,
created: user.created,
lastUpdated: user.lastUpdated
lastUpdated: user.lastModified
};
}

View File

@ -0,0 +1,39 @@
import {Controller, Request, Get, Post, Put, Delete, UseGuards, Param} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiUseTags } from "@nestjs/swagger"
import { FormService } from "../services/form.service"
import {Form} from "../models/form.model"
@ApiUseTags('forms')
@Controller('forms')
export class FormController {
constructor(private readonly formService: FormService) {}
@Get()
@UseGuards(AuthGuard('jwt'))
async list(@Request() req): Promise<any> {
return true;
}
@Post()
@UseGuards(AuthGuard('jwt'))
async create(@Request() req): Promise<Form> {
return null;
}
@Get(':id')
@UseGuards(AuthGuard('jwt'))
async read(@Param('id') id): Promise<Form> {
return this.formService.findById(id);
}
@Put(':id')
@UseGuards(AuthGuard('jwt'))
async update(@Param('id') id, @Request() req): Promise<Form> {
return this.formService.findById(id);
}
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
async delete(@Param('id') id): Promise<void> {
}
}

View File

@ -0,0 +1,5 @@
import { FormController } from "./controllers/form.controller"
export default [
FormController,
]

View File

@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import controllers from './form.controllers';
import providers from './form.providers'
import {TypegooseModule} from "nestjs-typegoose"
import {Form} from "./models/form.model"
@Module({
imports: [
TypegooseModule.forFeature([Form]),
],
exports: [],
controllers,
providers,
})
export class FormModule {}

View File

@ -0,0 +1,5 @@
import { FormService } from "./services/form.service"
export default [
FormService
]

View File

@ -0,0 +1,5 @@
import { Document } from 'mongoose';
export interface Button extends Document{
}

View File

@ -0,0 +1,5 @@
import { Document } from 'mongoose';
export interface Field extends Document{
}

View File

@ -0,0 +1,51 @@
import { Document } from 'mongoose';
import { Field } from "./field.interface"
import { Button } from "./button.interface"
export class Form extends Document{
readonly firstName: string;
readonly language: string;
readonly analytics: string;
readonly form_fields: Field[];
readonly admin: any;
readonly startPage: {
readonly showStart: boolean;
readonly introTitle: string;
readonly introParagraph: string;
readonly introButtonText: string;
readonly buttons: Button[];
};
readonly endPage: {
readonly showEnd: boolean;
readonly title: string;
readonly paragraph: string;
readonly buttonText: string;
readonly buttons: Button[];
};
readonly selfNotifications: {
readonly fromField: string;
readonly toEmails: string;
readonly subject: string;
readonly htmlTemplate: string;
readonly enabled: boolean;
};
readonly respondentNotifications: {
readonly toField: string;
readonly fromEmails: string;
readonly subject: string;
readonly htmlTemplate: string;
readonly enabled: boolean;
};
readonly showFooter: boolean;
readonly isLive: boolean;
readonly design: {
readonly colors: {
readonly backgroundColor: string;
readonly questionColor: string;
readonly answerColor: string;
readonly buttonColor: string;
readonly buttonTextColor: string;
};
readonly font: string;
};
}

View File

@ -0,0 +1,14 @@
import {prop} from "typegoose"
import {VisitorData} from "./visitor.data"
import {Exclude} from "class-transformer"
export class Analytics {
@Exclude()
readonly _id: string;
@prop()
gaCode: string;
@prop()
visitors: VisitorData[]
}

View File

@ -0,0 +1,30 @@
import {Exclude} from "class-transformer"
import {prop} from "typegoose"
export class Button {
@Exclude()
readonly _id: string;
@prop({
match: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/
})
url: string;
@prop()
action: string;
@prop()
text: string;
@prop({
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#5bc0de'
})
bgColor: string;
@prop({
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#ffffff'
})
color: string;
}

View File

@ -0,0 +1,37 @@
import {prop, Ref, Typegoose} from "typegoose"
import {Exclude} from "class-transformer"
export class Colors {
@Exclude()
readonly _id: string;
@prop({
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#fff'
})
readonly backgroundColor: string;
@prop({
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#333'
})
readonly questionColor: string;
@prop({
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#333'
})
readonly answerColor: string;
@prop({
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#fff'
})
readonly buttonColor: string;
@prop({
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#333'
})
readonly buttonTextColor: string;
}

View File

@ -0,0 +1,14 @@
import {prop} from "typegoose"
import {Colors} from "./colors"
import {Exclude} from "class-transformer"
export class Design {
@Exclude()
readonly _id: string;
@prop()
readonly colors: Colors;
@prop()
readonly font: string;
}

View File

@ -0,0 +1,29 @@
import {prop} from "typegoose"
import {Button} from "./button"
import {Exclude} from "class-transformer"
export class EndPage {
@Exclude()
readonly _id: string;
@prop({
default: false
})
readonly showEnd: boolean;
@prop({
default: 'Thank you for filling out the form'
})
readonly title: string;
@prop()
readonly paragraph: string;
@prop({
default: 'Go back to Form'
})
readonly buttonText: string;
@prop()
readonly buttons: Button[];
}

View File

@ -0,0 +1,19 @@
import {prop} from "typegoose"
import {VisitorData} from "./visitor.data"
import {Exclude} from "class-transformer"
export class FieldOption {
@Exclude()
readonly _id: string;
@prop()
option_id: number;
@prop()
option_title: string;
@prop({
trim: true
})
option_value: string;
}

View File

@ -0,0 +1,83 @@
import {arrayProp, prop} from "typegoose"
import {LogicJump} from "./logic.jump"
import {RatingField} from "./rating.field"
import {FieldOption} from "./field.option"
import {Exclude} from "class-transformer"
export class Field {
@Exclude()
readonly _id: string;
@prop({
default: false
})
isSubmission: boolean;
@prop()
submissionId: string;
@prop({
trim: true
})
title: string;
@prop({
default: ''
})
description: string;
@prop()
logicJump: LogicJump;
@prop()
ratingOptions: RatingField;
@arrayProp({
items: FieldOption
})
fieldOptions: FieldOption[];
@prop({
default: true
})
required: boolean;
@prop({
default: false
})
disabled: boolean;
@prop({
default: false
})
deletePreserved: boolean;
@arrayProp({
items: String
})
validFieldTypes: boolean;
@prop({
enum: [
'textfield',
'date',
'email',
'legal',
'textarea',
'link',
'statement',
'dropdown',
'rating',
'radio',
'hidden',
'yes_no',
'number'
]
})
fieldType: string;
@prop({
default: ''
})
fieldValue: any;
}

View File

@ -0,0 +1,45 @@
import {prop, Ref} from "typegoose"
import {VisitorData} from "./visitor.data"
import {Exclude} from "class-transformer"
import {Field} from "./field"
export class LogicJump {
@Exclude()
readonly _id: string;
@prop({
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'
]
})
expressionString: string;
@prop({
ref: Field
})
fieldA: Ref<Field>;
@prop()
valueB: string;
@prop({
ref: Field
})
jumpTo: Ref<Field>;
@prop({
default: false
})
enabled: boolean;
}

View File

@ -0,0 +1,38 @@
import {arrayProp, prop} from "typegoose"
import {VisitorData} from "./visitor.data"
import {Exclude} from "class-transformer"
export class RatingField {
@Exclude()
readonly _id: string;
@prop({
min: 1,
max: 10
})
steps: number;
@prop({
enum: [
'Heart',
'Star',
'thumbs-up',
'thumbs-down',
'Circle',
'Square',
'Check Circle',
'Smile Outlined',
'Hourglass',
'bell',
'Paper Plane',
'Comment',
'Trash'
]
})
shape: string;
@arrayProp({
items: String
})
validShapes: [String];
}

View File

@ -0,0 +1,30 @@
import {prop} from "typegoose"
import {Exclude} from "class-transformer"
export class RespondentNotifications {
@Exclude()
readonly _id: string;
@prop()
readonly toField: string;
@prop({
match: [/.+\@.+\..+/, 'Please fill a valid email address']
})
readonly fromEmails: string;
@prop({
default: 'OhMyForm: Thank you for filling out this OhMyForm'
})
readonly subject: string;
@prop({
default: 'Hello, <br><br> Weve received your submission. <br><br> Thank you & have a nice day!',
})
readonly htmlTemplate: string;
@prop({
default: false
})
readonly enabled: boolean;
}

View File

@ -0,0 +1,24 @@
import {prop} from "typegoose"
import {Exclude} from "class-transformer"
export class SelfNotifications {
@Exclude()
readonly _id: string;
@prop()
readonly fromField: string;
@prop()
readonly toEmails: string;
@prop()
readonly subject: string;
@prop()
readonly htmlTemplate: string;
@prop({
default: false
})
readonly enabled: boolean;
}

View File

@ -0,0 +1,29 @@
import {prop} from "typegoose"
import {Button} from "./button"
import {Exclude} from "class-transformer"
export class StartPage {
@Exclude()
readonly _id: string;
@prop({
default: false
})
readonly showStart: boolean;
@prop({
default: 'Welcome to Form'
})
readonly introTitle: string;
@prop()
readonly introParagraph: string;
@prop({
default: 'Start'
})
readonly introButtonText: string;
@prop()
readonly buttons: Button[];
}

View File

@ -0,0 +1,44 @@
import {Exclude} from "class-transformer"
import {arrayProp, prop} from "typegoose"
import {Field} from "./field"
import {Ref} from "typegoose/lib/prop"
export class VisitorData {
@Exclude()
readonly _id: string;
@prop()
readonly introParagraph: string;
@prop()
readonly referrer: string;
@arrayProp({
itemsRef: Field
})
readonly filledOutFields: Ref<Field>[];
@prop()
readonly timeElapsed: number;
@prop()
readonly isSubmitted: boolean;
@prop({
enum: ['en', 'fr', 'es', 'it', 'de'],
default: 'en',
})
readonly language: string;
@prop()
readonly ipAddr: string;
@prop({
enum: ['desktop', 'phone', 'tablet', 'other'],
default: 'other'
})
readonly deviceType: string;
@prop()
readonly userAgent: string;
}

View File

@ -0,0 +1,63 @@
import {prop, Ref, Typegoose} from "typegoose"
import {Analytics} from "./embedded/analytics"
import {Field} from "./embedded/field"
import {StartPage} from "./embedded/start.page"
import {EndPage} from "./embedded/end.page"
import {SelfNotifications} from "./embedded/self.notifications"
import {RespondentNotifications} from "./embedded/respondent.notifications"
import {Design} from "./embedded/design"
import {User} from "../../user/models/user.model"
export class Form extends Typegoose{
@prop({
trim: true,
required: 'Form Title cannot be blank'
})
readonly firstName: string;
@prop({
enum: ['en', 'fr', 'es', 'it', 'de'],
default: 'en',
required: 'Form must have a language'
})
readonly language: string;
@prop()
readonly analytics: Analytics;
@prop({
default: []
})
readonly form_fields: Field[];
@prop({
ref: User,
required: 'Form must have an Admin'
})
readonly admin: Ref<User>;
@prop()
readonly startPage: StartPage;
@prop()
readonly endPage: EndPage;
@prop()
readonly selfNotifications: SelfNotifications;
@prop()
readonly respondentNotifications: RespondentNotifications;
@prop({
default: true
})
readonly showFooter: boolean;
@prop({
default: true
})
readonly isLive: boolean;
@prop()
readonly design: Design;
}

View File

@ -0,0 +1,13 @@
import {Injectable} from '@nestjs/common';
import { InjectModel } from 'nestjs-typegoose';
import { ModelType } from 'typegoose';
import {Form} from "../models/form.model"
@Injectable()
export class FormService {
constructor(@InjectModel(Form) private readonly formModel: ModelType<Form>) {}
async findById(id: string): Promise<Form> {
return await this.formModel.findById(id).exec()
}
}

View File

@ -1,7 +0,0 @@
import { Module } from '@nestjs/common';
@Module({
providers: [],
exports: [],
})
export class FormsModule {}

View File

@ -1,20 +0,0 @@
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'
}
});

View File

@ -1,16 +0,0 @@
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
}
});

View File

@ -1,66 +0,0 @@
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: ''
}
});

View File

@ -1,151 +0,0 @@
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, <br><br> Weve received your submission. <br><br> 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
}
});

View File

@ -1,36 +0,0 @@
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
}
});

View File

@ -1,30 +0,0 @@
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]
}
});

View File

@ -1,35 +0,0 @@
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
}
});

View File

@ -1,7 +1,7 @@
import { NestFactory } from '@nestjs/core';
import {NestFactory, Reflector} from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import {ClassSerializerInterceptor, ValidationPipe} from '@nestjs/common';
const pkg = require('../package.json')
async function bootstrap() {
@ -15,10 +15,13 @@ async function bootstrap() {
transform: true,
}));
// app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)))
const options = new DocumentBuilder()
.setTitle('OhMyForm')
.setDescription('API documentation')
.setVersion(pkg.version)
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, options);

View File

@ -0,0 +1,93 @@
import {arrayProp, prop, Typegoose} from 'typegoose';
import { IsString } from 'class-validator';
import {Exclude} from "class-transformer"
export class User extends Typegoose {
@Exclude()
readonly _id: string;
@prop({
trim: true,
default: ''
})
readonly firstName: string;
@prop({
trim: true,
default: ''
})
readonly lastName: string;
@prop({
trim: true,
lowercase: true,
unique: true, // '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']
})
readonly email: string;
@prop({
unique: true,
lowercase: true,
match: [
/^[a-zA-Z0-9\-]+$/,
'Username can only contain alphanumeric characters and \'-\''
],
required: [true, 'Username is required']
})
readonly username: string;
@prop({
default: ''
})
readonly passwordHash: string;
@prop()
readonly salt: string;
@prop({
default: 'local'
})
readonly provider: string;
@arrayProp({
items: String,
enum: ['user', 'admin', 'superuser'],
default: ['user']
})
readonly roles: [string];
@prop({
enum: ['en', 'fr', 'es', 'it', 'de'],
default: 'en',
})
readonly language: string;
@prop({
default: Date.now
})
readonly created: Date;
@prop()
readonly lastModified: Date;
@prop()
readonly resetPasswordToken: string;
@prop()
readonly resetPasswordExpires: Date;
@prop()
readonly token: string;
@prop({
unique: true,
index: true,
sparse: true
})
readonly apiKey: string;
}

View File

@ -1,15 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { UserService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
let service: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
providers: [UserService],
}).compile();
service = module.get<UsersService>(UsersService);
service = module.get<UserService>(UserService);
});
it('should be defined', () => {

View File

@ -1,13 +1,11 @@
import { Model } from 'mongoose';
import {Injectable, NotFoundException} from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import {User} from "./interfaces/user.interface"
export type User = any;
import { InjectModel } from 'nestjs-typegoose';
import { ModelType } from 'typegoose';
import { User } from "../models/user.model"
@Injectable()
export class UsersService {
constructor(@InjectModel('User') private readonly userModel: Model<User>) {}
export class UserService {
constructor(@InjectModel(User) private readonly userModel: ModelType<User>) {}
async findOneByIdentifier(identifier: string): Promise<User> {
const results = await this.userModel.find().or([

View File

View File

@ -0,0 +1,5 @@
import {UserService} from "./services/user.service"
export default [
UserService
]

View File

@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypegooseModule } from 'nestjs-typegoose';
import providers from './user.providers'
import exportList from './user.exports'
import { User } from "./models/user.model"
@Module({
imports: [
TypegooseModule.forFeature([User]),
],
providers,
exports: exportList,
})
export class UserModule {}

View File

@ -0,0 +1,5 @@
import {UserService} from "./services/user.service"
export default [
UserService
]

View File

@ -1,19 +0,0 @@
import { Document } from 'mongoose';
export interface User extends Document{
readonly firstName: string;
readonly lastName: string;
readonly email: string;
readonly username: string;
readonly passwordHash: string;
readonly salt: string;
readonly provider: string;
readonly roles: [string];
readonly language: string;
readonly created: Date;
readonly lastUpdated: Date;
readonly resetPasswordToken: string;
readonly resetPasswordExpires: Date;
readonly token: string;
readonly apiKey: string;
}

View File

@ -1,80 +0,0 @@
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
}
});

View File

@ -1,11 +0,0 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersService } from './users.service';
import {UserSchema} from "./schemas/user.schema"
@Module({
imports: [MongooseModule.forFeature([{ name: 'User', schema: UserSchema }])],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}

View File

@ -49,6 +49,10 @@
],
"one-variable-per-declaration": [
false
],
"whitespace": [
true,
"check-postbrace"
]
},
"rulesDirectory": []