From 70859f6871272c09e5b842c45b4f39c580ede2bd Mon Sep 17 00:00:00 2001 From: wodka Date: Thu, 29 Aug 2019 19:07:09 +0200 Subject: [PATCH] start mailing service, and public form output --- api/package.json | 1 + api/src/app.module.ts | 2 + api/src/auth/auth.module.ts | 2 + api/src/auth/auth.providers.ts | 2 + .../auth/controllers/register.controller.ts | 21 ++++---- api/src/auth/dto/register.dto.ts | 12 +++++ api/src/auth/services/register.service.ts | 21 ++++++++ .../auth/strategies/jwt.refresh.strategy.ts | 2 +- api/src/form/controllers/form.controller.ts | 24 +++++---- api/src/form/controllers/public.controller.ts | 24 +++++++++ api/src/form/dto/form.dto.ts | 17 +++++++ api/src/form/dto/public.form.dto.ts | 17 +++++++ api/src/form/form.controllers.ts | 2 + api/src/form/models/form.model.ts | 15 ++++-- api/src/mail/dto/options.dto.ts | 12 +++++ api/src/mail/mail.exports.ts | 5 ++ api/src/mail/mail.module.ts | 10 ++++ api/src/mail/mail.providers.ts | 5 ++ api/src/mail/services/mail.service.ts | 10 ++++ api/src/mail/views/en/auth/recover.hbs | 6 +++ api/src/mail/views/en/auth/register.hbs | 5 ++ api/src/main.ts | 2 +- api/src/user/controllers/user.controller.ts | 50 +++++++++++++++++++ api/src/user/controllers/users.controller.ts | 0 api/src/user/dto/user.dto.ts | 15 ++++++ api/src/user/services/user.service.ts | 5 ++ api/src/user/user.controllers.ts | 5 ++ api/src/user/user.module.ts | 2 + 28 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 api/src/auth/dto/register.dto.ts create mode 100644 api/src/auth/services/register.service.ts create mode 100644 api/src/form/controllers/public.controller.ts create mode 100644 api/src/form/dto/form.dto.ts create mode 100644 api/src/form/dto/public.form.dto.ts create mode 100644 api/src/mail/dto/options.dto.ts create mode 100644 api/src/mail/mail.exports.ts create mode 100644 api/src/mail/mail.module.ts create mode 100644 api/src/mail/mail.providers.ts create mode 100644 api/src/mail/services/mail.service.ts create mode 100644 api/src/mail/views/en/auth/recover.hbs create mode 100644 api/src/mail/views/en/auth/register.hbs create mode 100644 api/src/user/controllers/user.controller.ts delete mode 100644 api/src/user/controllers/users.controller.ts create mode 100644 api/src/user/dto/user.dto.ts diff --git a/api/package.json b/api/package.json index 899c4cce..5a9d7612 100644 --- a/api/package.json +++ b/api/package.json @@ -33,6 +33,7 @@ "bcrypt": "^3.0.6", "class-transformer": "^0.2.3", "class-validator": "^0.9.1", + "handlebars": "^4.1.2", "mongoose": "^5.6.7", "nestjs-typegoose": "^5.2.1", "passport": "^0.4.0", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 0e65607a..5305c02c 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -8,6 +8,7 @@ import { AppService } from './app.service'; import { UserModule } from "./user/user.module" import { FormModule } from "./form/form.module" import { AuthModule } from './auth/auth.module'; +import { MailModule } from "./mail/mail.module" @Module({ imports: [ @@ -19,6 +20,7 @@ import { AuthModule } from './auth/auth.module'; UserModule, FormModule, AuthModule, + MailModule, ], controllers: [AppController], providers: [AppService], diff --git a/api/src/auth/auth.module.ts b/api/src/auth/auth.module.ts index 381b32f5..8ced3bce 100644 --- a/api/src/auth/auth.module.ts +++ b/api/src/auth/auth.module.ts @@ -5,11 +5,13 @@ import { jwtConstants } from "./constants" import { JwtModule } from "@nestjs/jwt" import controllers from './auth.controllers' import providers from './auth.providers' +import { MailModule } from "../mail/mail.module" @Module({ imports: [ UserModule, PassportModule, + MailModule, JwtModule.register({ secret: jwtConstants.secret, signOptions: { expiresIn: '12h' }, diff --git a/api/src/auth/auth.providers.ts b/api/src/auth/auth.providers.ts index bff9888e..7f7a338d 100644 --- a/api/src/auth/auth.providers.ts +++ b/api/src/auth/auth.providers.ts @@ -3,6 +3,7 @@ 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" +import { RegisterService } from "./services/register.service" export default [ AuthService, @@ -10,4 +11,5 @@ export default [ PasswordStrategy, JwtStrategy, JwtRefreshStrategy, + RegisterService, ] diff --git a/api/src/auth/controllers/register.controller.ts b/api/src/auth/controllers/register.controller.ts index b2254549..5aa4cbb7 100644 --- a/api/src/auth/controllers/register.controller.ts +++ b/api/src/auth/controllers/register.controller.ts @@ -1,21 +1,18 @@ -import { Controller, Request, Post, UseGuards } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; +import { Controller, Post, Body } from '@nestjs/common'; import { AuthService } from "../services/auth.service" -import { AuthJwtDto } from "../dto/auth.jwt.dto" -import {ApiBearerAuth, ApiImplicitBody, ApiImplicitQuery, ApiResponse, ApiUseTags} from "@nestjs/swagger" +import { ApiBadRequestResponse, ApiCreatedResponse, ApiUseTags } from "@nestjs/swagger" +import { RegisterDto } from "../dto/register.dto" +import {RegisterService} from "../services/register.service" @ApiUseTags('authentication') @Controller('auth') export class RegisterController { - constructor(private readonly authService: AuthService) {} + constructor(private readonly registerService: RegisterService) {} - @ApiResponse({ status: 201, description: 'Successful registration.', type: AuthJwtDto}) - @ApiImplicitQuery({name: 'email', type: String}) - @ApiImplicitQuery({name: 'username', type: String}) - @ApiImplicitQuery({name: 'password', type: String}) + @ApiCreatedResponse({ description: 'Successful registration.'}) + @ApiBadRequestResponse({}) @Post('register') - async register(@Request() req): Promise { - // TODO - return null + async register(@Body() params: RegisterDto): Promise { + await this.registerService.register(params.username, params.email, params.password) } } diff --git a/api/src/auth/dto/register.dto.ts b/api/src/auth/dto/register.dto.ts new file mode 100644 index 00000000..1670d2fd --- /dev/null +++ b/api/src/auth/dto/register.dto.ts @@ -0,0 +1,12 @@ +import { ApiModelProperty } from "@nestjs/swagger" + +export class RegisterDto { + @ApiModelProperty() + readonly username: string; + + @ApiModelProperty() + readonly password: string; + + @ApiModelProperty() + readonly email: string; +} diff --git a/api/src/auth/services/register.service.ts b/api/src/auth/services/register.service.ts new file mode 100644 index 00000000..507a92f9 --- /dev/null +++ b/api/src/auth/services/register.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from "@nestjs/common" +import { MailService } from "../../mail/services/mail.service" + +@Injectable() +export class RegisterService { + constructor(private readonly mailService: MailService) {} + + async register (username: string, email: string, password: string): Promise { + // TODO actually create user + + await this.mailService.sendEmail( + { + template: 'auth/register.hbs', + to: email + }, + { + confirm: 'some url' + } + ) + } +} diff --git a/api/src/auth/strategies/jwt.refresh.strategy.ts b/api/src/auth/strategies/jwt.refresh.strategy.ts index 2b8c20e5..f505f331 100644 --- a/api/src/auth/strategies/jwt.refresh.strategy.ts +++ b/api/src/auth/strategies/jwt.refresh.strategy.ts @@ -3,7 +3,7 @@ import { PassportStrategy } from '@nestjs/passport'; import { Injectable } from '@nestjs/common'; import { jwtConstants } from '../constants'; import { AuthService } from "../services/auth.service" -import {AuthUser} from "../interfaces/auth.user.interface" +import { AuthUser } from "../interfaces/auth.user.interface" @Injectable() export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'jwt.refresh') { diff --git a/api/src/form/controllers/form.controller.ts b/api/src/form/controllers/form.controller.ts index 150e56d2..9a445818 100644 --- a/api/src/form/controllers/form.controller.ts +++ b/api/src/form/controllers/form.controller.ts @@ -1,10 +1,11 @@ -import {Controller, Request, Get, Post, Put, Delete, UseGuards, Param} from '@nestjs/common'; +import {Controller, Request, Get, Post, Put, Delete, UseGuards, Param, NotImplementedException} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; -import { ApiUseTags } from "@nestjs/swagger" +import { ApiBearerAuth, ApiImplicitQuery, ApiResponse, ApiUseTags } from "@nestjs/swagger" import { FormService } from "../services/form.service" -import {Form} from "../models/form.model" +import { FormDto } from "../dto/form.dto" @ApiUseTags('forms') +@ApiBearerAuth() @Controller('forms') export class FormController { constructor(private readonly formService: FormService) {} @@ -12,28 +13,31 @@ export class FormController { @Get() @UseGuards(AuthGuard('jwt')) async list(@Request() req): Promise { - return true; + throw new NotImplementedException() } @Post() @UseGuards(AuthGuard('jwt')) - async create(@Request() req): Promise
{ - return null; + async create(@Request() req): Promise { + throw new NotImplementedException() } + @ApiResponse({ status: 200, description: 'Form Object', type: FormDto}) + @ApiImplicitQuery({name: 'id', type: String}) @Get(':id') @UseGuards(AuthGuard('jwt')) - async read(@Param('id') id): Promise { - return this.formService.findById(id); + async read(@Param('id') id): Promise { + return new FormDto(await this.formService.findById(id)); } @Put(':id') @UseGuards(AuthGuard('jwt')) - async update(@Param('id') id, @Request() req): Promise { - return this.formService.findById(id); + async update(@Param('id') id, @Request() req): Promise { + throw new NotImplementedException() } @Delete(':id') @UseGuards(AuthGuard('jwt')) async delete(@Param('id') id): Promise { + throw new NotImplementedException() } } diff --git a/api/src/form/controllers/public.controller.ts b/api/src/form/controllers/public.controller.ts new file mode 100644 index 00000000..c93e2dbb --- /dev/null +++ b/api/src/form/controllers/public.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Param, NotFoundException } from '@nestjs/common'; +import { ApiImplicitQuery, ApiResponse, ApiUseTags } from "@nestjs/swagger" +import { FormService } from "../services/form.service" +import { Form } from "../models/form.model" +import { PublicFormDto } from "../dto/public.form.dto" + +@ApiUseTags('forms') +@Controller('public') +export class PublicController { + constructor(private readonly formService: FormService) {} + + @ApiResponse({ status: 200, description: 'Form Object', type: PublicFormDto}) + @ApiImplicitQuery({name: 'id', type: String}) + @Get(':id') + async read(@Param('id') id): Promise { + const form:Form = await this.formService.findById(id) + + if (!form.isLive) { + throw new NotFoundException(); + } + + return new PublicFormDto(form); + } +} diff --git a/api/src/form/dto/form.dto.ts b/api/src/form/dto/form.dto.ts new file mode 100644 index 00000000..34f027f0 --- /dev/null +++ b/api/src/form/dto/form.dto.ts @@ -0,0 +1,17 @@ +import { ApiModelProperty } from '@nestjs/swagger'; +import { Form } from "../models/form.model" + +export class FormDto { + @ApiModelProperty() + id: string; + + @ApiModelProperty() + title: string; + + fields: []; + + constructor(partial: Partial) { + this.id = partial._id.toString(); + this.title = partial.title + } +} diff --git a/api/src/form/dto/public.form.dto.ts b/api/src/form/dto/public.form.dto.ts new file mode 100644 index 00000000..55ea3cc3 --- /dev/null +++ b/api/src/form/dto/public.form.dto.ts @@ -0,0 +1,17 @@ +import { ApiModelProperty } from '@nestjs/swagger'; +import { Form } from "../models/form.model" + +export class PublicFormDto { + @ApiModelProperty() + id: string; + + @ApiModelProperty() + title: string; + + fields: []; + + constructor(partial: Partial) { + this.id = partial._id.toString(); + this.title = partial.title + } +} diff --git a/api/src/form/form.controllers.ts b/api/src/form/form.controllers.ts index 3dafd609..43e7cfea 100644 --- a/api/src/form/form.controllers.ts +++ b/api/src/form/form.controllers.ts @@ -1,5 +1,7 @@ import { FormController } from "./controllers/form.controller" +import { PublicController } from "./controllers/public.controller" export default [ FormController, + PublicController, ] diff --git a/api/src/form/models/form.model.ts b/api/src/form/models/form.model.ts index 50c1b4ac..6b9dc9bd 100644 --- a/api/src/form/models/form.model.ts +++ b/api/src/form/models/form.model.ts @@ -1,4 +1,4 @@ -import {prop, Ref, Typegoose} from "typegoose" +import {arrayProp, prop, Ref, Typegoose} from "typegoose" import {Analytics} from "./embedded/analytics" import {Field} from "./embedded/field" import {StartPage} from "./embedded/start.page" @@ -8,12 +8,18 @@ import {RespondentNotifications} from "./embedded/respondent.notifications" import {Design} from "./embedded/design" import {User} from "../../user/models/user.model" -export class Form extends Typegoose{ +export class Form extends Typegoose { + readonly _id: any; + @prop({ trim: true, required: 'Form Title cannot be blank' }) - readonly firstName: string; + readonly title: string; + + readonly created: any; + + readonly lastModified: any; @prop({ enum: ['en', 'fr', 'es', 'it', 'de'], @@ -25,7 +31,8 @@ export class Form extends Typegoose{ @prop() readonly analytics: Analytics; - @prop({ + @arrayProp({ + items: Field, default: [] }) readonly form_fields: Field[]; diff --git a/api/src/mail/dto/options.dto.ts b/api/src/mail/dto/options.dto.ts new file mode 100644 index 00000000..63edb8f5 --- /dev/null +++ b/api/src/mail/dto/options.dto.ts @@ -0,0 +1,12 @@ + +export class OptionsDto { + template: string; + + language?: string; + + to: string; + + cc?: string[]; + + bcc?: string[]; +} diff --git a/api/src/mail/mail.exports.ts b/api/src/mail/mail.exports.ts new file mode 100644 index 00000000..86866edd --- /dev/null +++ b/api/src/mail/mail.exports.ts @@ -0,0 +1,5 @@ +import { MailService } from "./services/mail.service" + +export default [ + MailService +] diff --git a/api/src/mail/mail.module.ts b/api/src/mail/mail.module.ts new file mode 100644 index 00000000..d9d7c3db --- /dev/null +++ b/api/src/mail/mail.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import providers from './mail.providers' +import exportList from './mail.exports' + +@Module({ + imports: [], + providers, + exports: exportList, +}) +export class MailModule {} diff --git a/api/src/mail/mail.providers.ts b/api/src/mail/mail.providers.ts new file mode 100644 index 00000000..86866edd --- /dev/null +++ b/api/src/mail/mail.providers.ts @@ -0,0 +1,5 @@ +import { MailService } from "./services/mail.service" + +export default [ + MailService +] diff --git a/api/src/mail/services/mail.service.ts b/api/src/mail/services/mail.service.ts new file mode 100644 index 00000000..875373f5 --- /dev/null +++ b/api/src/mail/services/mail.service.ts @@ -0,0 +1,10 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { OptionsDto } from "../dto/options.dto" + +@Injectable() +export class MailService { + // TODO + async sendEmail(options:OptionsDto, placeholders:any): Promise { + return false + } +} diff --git a/api/src/mail/views/en/auth/recover.hbs b/api/src/mail/views/en/auth/recover.hbs new file mode 100644 index 00000000..bd821196 --- /dev/null +++ b/api/src/mail/views/en/auth/recover.hbs @@ -0,0 +1,6 @@ +Hi, + +if you have not requested a new password you can ignore this email. To set a new password for your account +just follow this link: {{ recover }} + +See you soon diff --git a/api/src/mail/views/en/auth/register.hbs b/api/src/mail/views/en/auth/register.hbs new file mode 100644 index 00000000..759399f2 --- /dev/null +++ b/api/src/mail/views/en/auth/register.hbs @@ -0,0 +1,5 @@ +Welcome to OhMyForm! + +please confirm your account by following the following link: {{ confirm }} + +enjoy! diff --git a/api/src/main.ts b/api/src/main.ts index 4c0a7a2e..7c882026 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -15,7 +15,7 @@ async function bootstrap() { transform: true, })); - // app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))) + app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))) const options = new DocumentBuilder() .setTitle('OhMyForm') diff --git a/api/src/user/controllers/user.controller.ts b/api/src/user/controllers/user.controller.ts new file mode 100644 index 00000000..fac9543e --- /dev/null +++ b/api/src/user/controllers/user.controller.ts @@ -0,0 +1,50 @@ +import {Controller, Request, Get, Post, Put, Delete, UseGuards, Param, NotImplementedException} from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { ApiBearerAuth, ApiImplicitQuery, ApiResponse, ApiUseTags } from "@nestjs/swagger" +import { UserService } from "../services/user.service" +import { UserDto } from "../dto/user.dto" + +@ApiUseTags('users') +@ApiBearerAuth() +@Controller('users') +export class UserController { + constructor(private readonly userService: UserService) {} + + @Get() + @UseGuards(AuthGuard('jwt')) + async list(@Request() req): Promise { + throw new NotImplementedException() + } + + @ApiResponse({ status: 200, description: 'User Object', type: UserDto}) + @Post() + @UseGuards(AuthGuard('jwt')) + async create(@Request() req): Promise { + throw new NotImplementedException() + } + + @ApiResponse({ status: 200, description: 'User Object', type: UserDto}) + @ApiImplicitQuery({name: 'id', type: String}) + @Get(':id') + @UseGuards(AuthGuard('jwt')) + async read(@Param('id') id): Promise { + return new UserDto(await this.userService.findById(id)); + } + + @ApiResponse({ status: 200, description: 'User Object', type: UserDto}) + @ApiImplicitQuery({name: 'id', type: String}) + @Put(':id') + @UseGuards(AuthGuard('jwt')) + async update(@Param('id') id, @Request() req): Promise { + throw new NotImplementedException() + } + + + @ApiResponse({ status: 200, description: 'User Object', type: UserDto}) + @ApiImplicitQuery({name: 'id', type: String}) + @Delete(':id') + @UseGuards(AuthGuard('jwt')) + async delete(@Param('id') id): Promise { + throw new NotImplementedException() + } +} diff --git a/api/src/user/controllers/users.controller.ts b/api/src/user/controllers/users.controller.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/api/src/user/dto/user.dto.ts b/api/src/user/dto/user.dto.ts new file mode 100644 index 00000000..967fde05 --- /dev/null +++ b/api/src/user/dto/user.dto.ts @@ -0,0 +1,15 @@ +import { ApiModelProperty } from '@nestjs/swagger'; +import {User} from "../models/user.model" + +export class UserDto { + @ApiModelProperty() + id: string; + + @ApiModelProperty() + username: string; + + constructor(partial: Partial) { + this.id = partial._id.toString(); + this.username = partial.username + } +} diff --git a/api/src/user/services/user.service.ts b/api/src/user/services/user.service.ts index afcff462..fe22c3b3 100644 --- a/api/src/user/services/user.service.ts +++ b/api/src/user/services/user.service.ts @@ -2,6 +2,7 @@ import {Injectable, NotFoundException} from '@nestjs/common'; import { InjectModel } from 'nestjs-typegoose'; import { ModelType } from 'typegoose'; import { User } from "../models/user.model" +import {Form} from "../../form/models/form.model" @Injectable() export class UserService { @@ -23,4 +24,8 @@ export class UserService { return results[0] } + + async findById(id: string): Promise { + return await this.userModel.findById(id).exec() + } } diff --git a/api/src/user/user.controllers.ts b/api/src/user/user.controllers.ts index e69de29b..86403ef6 100644 --- a/api/src/user/user.controllers.ts +++ b/api/src/user/user.controllers.ts @@ -0,0 +1,5 @@ +import { UserController } from "./controllers/user.controller" + +export default [ + UserController, +] diff --git a/api/src/user/user.module.ts b/api/src/user/user.module.ts index 12481031..838c1188 100644 --- a/api/src/user/user.module.ts +++ b/api/src/user/user.module.ts @@ -3,11 +3,13 @@ import { TypegooseModule } from 'nestjs-typegoose'; import providers from './user.providers' import exportList from './user.exports' import { User } from "./models/user.model" +import controllers from './user.controllers' @Module({ imports: [ TypegooseModule.forFeature([User]), ], + controllers, providers, exports: exportList, })