From c507f53646c9f10d40ebe1d6ea518b7aeea27876 Mon Sep 17 00:00:00 2001 From: Antonin COQUET <ext.sopra.acoquet@grandlyon.com> Date: Thu, 8 Apr 2021 11:17:15 +0200 Subject: [PATCH] feat: add newsletter subscription --- src/admin/admin.controller.spec.ts | 9 +++- src/admin/admin.controller.ts | 24 ++++++++- src/admin/admin.module.ts | 3 +- src/app.module.ts | 2 + .../newsletter-subscription.interface.ts | 5 ++ .../newsletter-subscription.schema.ts | 12 +++++ src/newsletter/newsletter.controller.spec.ts | 44 ++++++++++++++++ src/newsletter/newsletter.controller.ts | 17 +++++++ src/newsletter/newsletter.module.ts | 15 ++++++ src/newsletter/newsletter.service.ts | 50 +++++++++++++++++++ 10 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 src/newsletter/interface/newsletter-subscription.interface.ts create mode 100644 src/newsletter/newsletter-subscription.schema.ts create mode 100644 src/newsletter/newsletter.controller.spec.ts create mode 100644 src/newsletter/newsletter.controller.ts create mode 100644 src/newsletter/newsletter.module.ts create mode 100644 src/newsletter/newsletter.service.ts diff --git a/src/admin/admin.controller.spec.ts b/src/admin/admin.controller.spec.ts index a93e257c8..84dbf424a 100644 --- a/src/admin/admin.controller.spec.ts +++ b/src/admin/admin.controller.spec.ts @@ -3,8 +3,8 @@ import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; import { ConfigurationModule } from '../configuration/configuration.module'; import { MailerService } from '../mailer/mailer.service'; -import { CreateStructureDto } from '../structures/dto/create-structure.dto'; -import { structureDto } from '../structures/dto/structure.dto'; +import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema'; +import { NewsletterService } from '../newsletter/newsletter.service'; import { Structure } from '../structures/schemas/structure.schema'; import { StructuresService } from '../structures/services/structures.service'; import { User } from '../users/schemas/user.schema'; @@ -21,11 +21,16 @@ describe('AdminController', () => { providers: [ UsersService, StructuresService, + NewsletterService, MailerService, { provide: getModelToken('User'), useValue: User, }, + { + provide: getModelToken('NewsletterSubscription'), + useValue: NewsletterSubscription, + }, { provide: getModelToken('Structure'), useValue: Structure, diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index 3947dd029..03442f8e5 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -2,6 +2,7 @@ import { Body, Delete, Param } from '@nestjs/common'; import { Controller, Get, Post, UseGuards } from '@nestjs/common'; import { ApiOperation, ApiParam } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { NewsletterService } from '../newsletter/newsletter.service'; import { StructuresService } from '../structures/services/structures.service'; import { Roles } from '../users/decorators/roles.decorator'; import { RolesGuard } from '../users/guards/roles.guard'; @@ -10,7 +11,11 @@ import { PendingStructureDto } from './dto/pending-structure.dto'; @Controller('admin') export class AdminController { - constructor(private usersService: UsersService, private structuresService: StructuresService) {} + constructor( + private usersService: UsersService, + private structuresService: StructuresService, + private newsletterService: NewsletterService + ) {} @UseGuards(JwtAuthGuard, RolesGuard) @Roles('admin') @@ -96,4 +101,21 @@ export class AdminController { return this.usersService.searchUsers(searchString.searchString); else return this.usersService.findAll(); } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Post('searchNewsletterSubscriptions') + public async getNewsletterSubscriptions(@Body() searchString: { searchString: string }) { + if (searchString && searchString.searchString.length > 0) + return this.newsletterService.searchNewsletterSubscription(searchString.searchString); + else return this.newsletterService.findAll(); + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Delete('newsletterSubscription/:email') + @ApiParam({ name: 'email', type: String, required: true }) + public async unsubscribeUserFromNewsletter(@Param() params) { + return await this.newsletterService.deleteOneEmail(params.email); + } } diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts index a13616a18..7854c19b2 100644 --- a/src/admin/admin.module.ts +++ b/src/admin/admin.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; +import { NewsletterModule } from '../newsletter/newsletter.module'; import { StructuresModule } from '../structures/structures.module'; import { UsersModule } from '../users/users.module'; import { AdminController } from './admin.controller'; import { AdminService } from './admin.service'; @Module({ - imports: [UsersModule, StructuresModule], + imports: [UsersModule, StructuresModule, NewsletterModule], controllers: [AdminController], providers: [AdminService], }) diff --git a/src/app.module.ts b/src/app.module.ts index 467fe404c..ed6a03004 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { TclModule } from './tcl/tcl.module'; import { AdminModule } from './admin/admin.module'; import { PostsModule } from './posts/posts.module'; import { TempUserModule } from './temp-user/temp-user.module'; +import { NewsletterModule } from './newsletter/newsletter.module'; @Module({ imports: [ ConfigurationModule, @@ -28,6 +29,7 @@ import { TempUserModule } from './temp-user/temp-user.module'; AdminModule, PostsModule, TempUserModule, + NewsletterModule ], controllers: [AppController], }) diff --git a/src/newsletter/interface/newsletter-subscription.interface.ts b/src/newsletter/interface/newsletter-subscription.interface.ts new file mode 100644 index 000000000..382e0911c --- /dev/null +++ b/src/newsletter/interface/newsletter-subscription.interface.ts @@ -0,0 +1,5 @@ +import { Document } from 'mongoose'; + +export interface INewsletterSubscription extends Document { + email: string; +} diff --git a/src/newsletter/newsletter-subscription.schema.ts b/src/newsletter/newsletter-subscription.schema.ts new file mode 100644 index 000000000..9ff18ab01 --- /dev/null +++ b/src/newsletter/newsletter-subscription.schema.ts @@ -0,0 +1,12 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +export type NewsletterSubscriptionDocument = NewsletterSubscription & Document; + +@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } }) +export class NewsletterSubscription { + @Prop({ required: true }) + email: string; +} + +export const NewsletterSubscriptionSchema = SchemaFactory.createForClass(NewsletterSubscription); diff --git a/src/newsletter/newsletter.controller.spec.ts b/src/newsletter/newsletter.controller.spec.ts new file mode 100644 index 000000000..2e5adbb61 --- /dev/null +++ b/src/newsletter/newsletter.controller.spec.ts @@ -0,0 +1,44 @@ +import { HttpModule } from '@nestjs/common'; +import { getModelToken } from '@nestjs/mongoose'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigurationModule } from '../configuration/configuration.module'; +import { NewsletterSubscription } from './newsletter-subscription.schema'; +import { NewsletterController } from './newsletter.controller'; +import { NewsletterService } from './newsletter.service'; +describe('NewsletterController', () => { + let controller: NewsletterController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ConfigurationModule, HttpModule], + providers: [ + NewsletterService, + { + provide: getModelToken('NewsletterSubscription'), + useValue: NewsletterSubscription, + }, + ], + controllers: [NewsletterController], + }).compile(); + + controller = module.get<NewsletterController>(NewsletterController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should subscribe user', async () => { + const result = { email: 'email@test.com' }; + jest.spyOn(controller, 'newsletterSubscribe').mockImplementation(async (): Promise<{ email }> => result); + const email = { email: 'email@test.com' }; + expect(await controller.newsletterSubscribe(email)).toBe(result); + }); + + it('should unsubscribe user', async () => { + const result = { email: 'email@test.com' }; + jest.spyOn(controller, 'newsletterUnsubscribe').mockImplementation(async (): Promise<{ email }> => result); + const email = { email: 'email@test.com' }; + expect(await controller.newsletterUnsubscribe(email)).toBe(result); + }); +}); diff --git a/src/newsletter/newsletter.controller.ts b/src/newsletter/newsletter.controller.ts new file mode 100644 index 000000000..8a7c77d35 --- /dev/null +++ b/src/newsletter/newsletter.controller.ts @@ -0,0 +1,17 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { NewsletterService } from './newsletter.service'; + +@Controller('newsletter') +export class NewsletterController { + constructor(private newsletterService: NewsletterService) {} + + @Post('subscribe') + public async newsletterSubscribe(@Body() email: { email: string }) { + return this.newsletterService.newsletterSubscribe(email.email); + } + + @Post('unsubscribe') + public async newsletterUnsubscribe(@Body() email: { email: string }) { + return this.newsletterService.newsletterUnsubscribe(email.email); + } +} diff --git a/src/newsletter/newsletter.module.ts b/src/newsletter/newsletter.module.ts new file mode 100644 index 000000000..2f949c385 --- /dev/null +++ b/src/newsletter/newsletter.module.ts @@ -0,0 +1,15 @@ +import { HttpModule, Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { NewsletterService } from './newsletter.service'; +import { NewsletterController } from './newsletter.controller'; +import { NewsletterSubscription, NewsletterSubscriptionSchema } from './newsletter-subscription.schema'; +@Module({ + imports: [ + MongooseModule.forFeature([{ name: NewsletterSubscription.name, schema: NewsletterSubscriptionSchema }]), + HttpModule, + ], + providers: [NewsletterService], + exports: [NewsletterService], + controllers: [NewsletterController], +}) +export class NewsletterModule {} diff --git a/src/newsletter/newsletter.service.ts b/src/newsletter/newsletter.service.ts new file mode 100644 index 000000000..c1ca5efc0 --- /dev/null +++ b/src/newsletter/newsletter.service.ts @@ -0,0 +1,50 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model, Types } from 'mongoose'; +import { INewsletterSubscription } from './interface/newsletter-subscription.interface'; +import { NewsletterSubscription } from './newsletter-subscription.schema'; + +@Injectable() +export class NewsletterService { + constructor( + @InjectModel(NewsletterSubscription.name) private newsletterSubscriptionModel: Model<INewsletterSubscription> + ) {} + + public async newsletterSubscribe(email: string): Promise<NewsletterSubscription> { + const existingEmail = await this.findOne(email); + if (existingEmail) { + throw new HttpException('Email already exists', HttpStatus.BAD_REQUEST); + } + const createSubscription = new this.newsletterSubscriptionModel({ email: email }); + createSubscription.save(); + return await this.findOne(email); + } + + public async newsletterUnsubscribe(email: string): Promise<NewsletterSubscription> { + const subscription = await this.newsletterSubscriptionModel.findOne({ email: email }).exec(); + if (!subscription) { + throw new HttpException('Invalid email', HttpStatus.BAD_REQUEST); + } + return subscription.deleteOne(); + } + + public async findOne(mail: string): Promise<NewsletterSubscription | undefined> { + return this.newsletterSubscriptionModel.findOne({ email: mail }).exec(); + } + + public async searchNewsletterSubscription(searchString: string) { + return this.newsletterSubscriptionModel.find({ email: new RegExp(searchString, 'i') }).exec(); + } + + public async deleteOneEmail(mail: string): Promise<NewsletterSubscription | undefined> { + const subscription = await this.newsletterSubscriptionModel.findOne({ email: mail }).exec(); + if (!subscription) { + throw new HttpException('Invalid email', HttpStatus.BAD_REQUEST); + } + return subscription.deleteOne(); + } + + public async findAll(): Promise<NewsletterSubscription[]> { + return await this.newsletterSubscriptionModel.find().exec(); + } +} -- GitLab