add first form endpoint
This commit is contained in:
parent
fdcc9fda3f
commit
93f63f7421
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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],
|
||||
|
||||
9
api/src/auth/auth.controllers.ts
Normal file
9
api/src/auth/auth.controllers.ts
Normal 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,
|
||||
]
|
||||
@ -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 {}
|
||||
|
||||
13
api/src/auth/auth.providers.ts
Normal file
13
api/src/auth/auth.providers.ts
Normal 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,
|
||||
]
|
||||
29
api/src/auth/controllers/recover.controller.ts
Normal file
29
api/src/auth/controllers/recover.controller.ts
Normal 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
|
||||
}
|
||||
}
|
||||
21
api/src/auth/controllers/register.controller.ts
Normal file
21
api/src/auth/controllers/register.controller.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
39
api/src/form/controllers/form.controller.ts
Normal file
39
api/src/form/controllers/form.controller.ts
Normal 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> {
|
||||
}
|
||||
}
|
||||
5
api/src/form/form.controllers.ts
Normal file
5
api/src/form/form.controllers.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { FormController } from "./controllers/form.controller"
|
||||
|
||||
export default [
|
||||
FormController,
|
||||
]
|
||||
15
api/src/form/form.module.ts
Normal file
15
api/src/form/form.module.ts
Normal 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 {}
|
||||
5
api/src/form/form.providers.ts
Normal file
5
api/src/form/form.providers.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { FormService } from "./services/form.service"
|
||||
|
||||
export default [
|
||||
FormService
|
||||
]
|
||||
5
api/src/form/interfaces/button.interface.ts
Normal file
5
api/src/form/interfaces/button.interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Document } from 'mongoose';
|
||||
|
||||
export interface Button extends Document{
|
||||
|
||||
}
|
||||
5
api/src/form/interfaces/field.interface.ts
Normal file
5
api/src/form/interfaces/field.interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Document } from 'mongoose';
|
||||
|
||||
export interface Field extends Document{
|
||||
|
||||
}
|
||||
51
api/src/form/interfaces/form.interface.ts
Normal file
51
api/src/form/interfaces/form.interface.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
14
api/src/form/models/embedded/analytics.ts
Normal file
14
api/src/form/models/embedded/analytics.ts
Normal 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[]
|
||||
}
|
||||
30
api/src/form/models/embedded/button.ts
Normal file
30
api/src/form/models/embedded/button.ts
Normal 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;
|
||||
}
|
||||
37
api/src/form/models/embedded/colors.ts
Normal file
37
api/src/form/models/embedded/colors.ts
Normal 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;
|
||||
}
|
||||
14
api/src/form/models/embedded/design.ts
Normal file
14
api/src/form/models/embedded/design.ts
Normal 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;
|
||||
}
|
||||
29
api/src/form/models/embedded/end.page.ts
Normal file
29
api/src/form/models/embedded/end.page.ts
Normal 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[];
|
||||
}
|
||||
19
api/src/form/models/embedded/field.option.ts
Normal file
19
api/src/form/models/embedded/field.option.ts
Normal 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;
|
||||
}
|
||||
83
api/src/form/models/embedded/field.ts
Normal file
83
api/src/form/models/embedded/field.ts
Normal 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;
|
||||
}
|
||||
45
api/src/form/models/embedded/logic.jump.ts
Normal file
45
api/src/form/models/embedded/logic.jump.ts
Normal 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;
|
||||
}
|
||||
38
api/src/form/models/embedded/rating.field.ts
Normal file
38
api/src/form/models/embedded/rating.field.ts
Normal 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];
|
||||
}
|
||||
30
api/src/form/models/embedded/respondent.notifications.ts
Normal file
30
api/src/form/models/embedded/respondent.notifications.ts
Normal 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> We’ve received your submission. <br><br> Thank you & have a nice day!',
|
||||
})
|
||||
readonly htmlTemplate: string;
|
||||
|
||||
@prop({
|
||||
default: false
|
||||
})
|
||||
readonly enabled: boolean;
|
||||
}
|
||||
24
api/src/form/models/embedded/self.notifications.ts
Normal file
24
api/src/form/models/embedded/self.notifications.ts
Normal 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;
|
||||
}
|
||||
29
api/src/form/models/embedded/start.page.ts
Normal file
29
api/src/form/models/embedded/start.page.ts
Normal 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[];
|
||||
}
|
||||
44
api/src/form/models/embedded/visitor.data.ts
Normal file
44
api/src/form/models/embedded/visitor.data.ts
Normal 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;
|
||||
}
|
||||
63
api/src/form/models/form.model.ts
Normal file
63
api/src/form/models/form.model.ts
Normal 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;
|
||||
}
|
||||
13
api/src/form/services/form.service.ts
Normal file
13
api/src/form/services/form.service.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
exports: [],
|
||||
})
|
||||
export class FormsModule {}
|
||||
@ -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'
|
||||
}
|
||||
});
|
||||
@ -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
|
||||
}
|
||||
});
|
||||
@ -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: ''
|
||||
}
|
||||
});
|
||||
@ -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> We’ve 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
|
||||
}
|
||||
});
|
||||
@ -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
|
||||
}
|
||||
});
|
||||
@ -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]
|
||||
}
|
||||
});
|
||||
@ -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
|
||||
}
|
||||
});
|
||||
@ -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);
|
||||
|
||||
0
api/src/user/controllers/users.controller.ts
Normal file
0
api/src/user/controllers/users.controller.ts
Normal file
93
api/src/user/models/user.model.ts
Normal file
93
api/src/user/models/user.model.ts
Normal 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;
|
||||
}
|
||||
@ -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', () => {
|
||||
@ -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([
|
||||
0
api/src/user/user.controllers.ts
Normal file
0
api/src/user/user.controllers.ts
Normal file
5
api/src/user/user.exports.ts
Normal file
5
api/src/user/user.exports.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import {UserService} from "./services/user.service"
|
||||
|
||||
export default [
|
||||
UserService
|
||||
]
|
||||
14
api/src/user/user.module.ts
Normal file
14
api/src/user/user.module.ts
Normal 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 {}
|
||||
5
api/src/user/user.providers.ts
Normal file
5
api/src/user/user.providers.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import {UserService} from "./services/user.service"
|
||||
|
||||
export default [
|
||||
UserService
|
||||
]
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
});
|
||||
@ -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 {}
|
||||
@ -49,6 +49,10 @@
|
||||
],
|
||||
"one-variable-per-declaration": [
|
||||
false
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-postbrace"
|
||||
]
|
||||
},
|
||||
"rulesDirectory": []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user