diff --git a/.env b/.env
index 5382ecde7..8568884c4 100644
--- a/.env
+++ b/.env
@@ -107,3 +107,7 @@ CAMPAIGN_ADMIN_MAIL=responsible for campaign management
## Cache ##
##############
CACHE_TTL=30000
+
+## AdminEmail ##
+##############
+CAMPAIGN_COORDINATOR_EMAIL=campaign_coordinators@podkrepi.bg
diff --git a/.env.example b/.env.example
index 5382ecde7..8568884c4 100644
--- a/.env.example
+++ b/.env.example
@@ -107,3 +107,7 @@ CAMPAIGN_ADMIN_MAIL=responsible for campaign management
## Cache ##
##############
CACHE_TTL=30000
+
+## AdminEmail ##
+##############
+CAMPAIGN_COORDINATOR_EMAIL=campaign_coordinators@podkrepi.bg
diff --git a/apps/api/src/assets/templates/create-campaign-application-admin.json b/apps/api/src/assets/templates/create-campaign-application-admin.json
new file mode 100644
index 000000000..bdccf1639
--- /dev/null
+++ b/apps/api/src/assets/templates/create-campaign-application-admin.json
@@ -0,0 +1,3 @@
+{
+ "subject": "Създадена нова кампания"
+}
diff --git a/apps/api/src/assets/templates/create-campaign-application-admin.mjml b/apps/api/src/assets/templates/create-campaign-application-admin.mjml
new file mode 100644
index 000000000..f03c84614
--- /dev/null
+++ b/apps/api/src/assets/templates/create-campaign-application-admin.mjml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Успешно създадена кампания от {{firstName}}
+
+
+
+
+
+
+ Организатор {{firstName}} с имейл {{email}} създаде нова кампания
+ {{campaignApplicationName}}!
+
+
+ Поздрави,
+ Екипът на Подкрепи.бг
+
+
+
+
+
diff --git a/apps/api/src/assets/templates/create-campaign-application-organizer.json b/apps/api/src/assets/templates/create-campaign-application-organizer.json
new file mode 100644
index 000000000..bdccf1639
--- /dev/null
+++ b/apps/api/src/assets/templates/create-campaign-application-organizer.json
@@ -0,0 +1,3 @@
+{
+ "subject": "Създадена нова кампания"
+}
diff --git a/apps/api/src/assets/templates/create-campaign-application-organizer.mjml b/apps/api/src/assets/templates/create-campaign-application-organizer.mjml
new file mode 100644
index 000000000..0c07ffb2b
--- /dev/null
+++ b/apps/api/src/assets/templates/create-campaign-application-organizer.mjml
@@ -0,0 +1,57 @@
+
+
+
+
+
+ Успешно създадохте кампания в Подкрепи.бг
+
+
+
+
+
+
+ Здравейте {{firstName}},
+
+
+
+ Вашата кампания е създадена успешно! Вижте я
+ ТУК!
+
+ Пожелаваме успешно набиране на средствата!
+
+
+ Поздрави,
+ Екипът на Подкрепи.бг
+
+
+
+
+
diff --git a/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts b/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts
index d5a6387e2..b9b2ed45f 100644
--- a/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts
+++ b/apps/api/src/campaign-application/__mocks__/campaign-application-mocks.ts
@@ -1,4 +1,4 @@
-import { CampaignApplicationState } from '@prisma/client'
+import { CampaignApplicationState, CampaignTypeCategory } from '@prisma/client'
export const mockNewCampaignApplication = {
campaignName: 'Test Campaign',
@@ -38,12 +38,10 @@ export const mockSingleCampaignApplication = {
otherFinanceSources: 'test otherFinanceSources1',
otherNotes: 'test otherNotes1',
state: CampaignApplicationState.review,
- campaignTypeId: 'ffdbcc41-85ec-0000-9e59-0662f3b433af',
+ category: CampaignTypeCategory.medical,
ticketURL: 'testsodifhso1',
archived: false,
documents: [{ id: 'fileId' }],
- campaignEnd: 'funds',
- campaignEndDate: undefined,
}
export const mockCampaigns = [
diff --git a/apps/api/src/campaign-application/campaign-application.module.ts b/apps/api/src/campaign-application/campaign-application.module.ts
index 5b0047d23..06c89bf48 100644
--- a/apps/api/src/campaign-application/campaign-application.module.ts
+++ b/apps/api/src/campaign-application/campaign-application.module.ts
@@ -1,3 +1,4 @@
+import { EmailService } from './../email/email.service'
import { Module } from '@nestjs/common'
import { CampaignApplicationService } from './campaign-application.service'
import { CampaignApplicationController } from './campaign-application.controller'
@@ -5,9 +6,10 @@ import { PrismaModule } from '../prisma/prisma.module'
import { PersonModule } from '../person/person.module'
import { OrganizerModule } from '../organizer/organizer.module'
import { S3Service } from '../s3/s3.service'
+import { TemplateService } from '../email/template.service'
@Module({
imports: [PrismaModule, PersonModule, OrganizerModule],
controllers: [CampaignApplicationController],
- providers: [CampaignApplicationService, S3Service],
+ providers: [CampaignApplicationService, S3Service, EmailService, TemplateService],
})
export class CampaignApplicationModule {}
diff --git a/apps/api/src/campaign-application/campaign-application.service.spec.ts b/apps/api/src/campaign-application/campaign-application.service.spec.ts
index d9c35fd89..a157126ac 100644
--- a/apps/api/src/campaign-application/campaign-application.service.spec.ts
+++ b/apps/api/src/campaign-application/campaign-application.service.spec.ts
@@ -16,10 +16,18 @@ import {
mockCampaignApplicationFilesFn,
} from './__mocks__/campaing-application-file-mocks'
import { CampaignApplicationService } from './campaign-application.service'
+import {
+ CreateCampaignApplicationAdminEmailDto,
+ CreateCampaignApplicationOrganizerEmailDto,
+} from '../email/template.interface'
import { CreateCampaignApplicationDto } from './dto/create-campaign-application.dto'
+import { EmailService } from '../email/email.service'
+import { ConfigService } from 'aws-sdk'
+import { ConfigModule } from '@nestjs/config'
describe('CampaignApplicationService', () => {
let service: CampaignApplicationService
+ let configService: ConfigService
const mockPerson = {
...personMock,
@@ -40,13 +48,23 @@ describe('CampaignApplicationService', () => {
deleteObject: jest.fn(),
}
+ const mockEmailService = {
+ sendFromTemplate: jest.fn(),
+ }
+
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
+ imports: [
+ ConfigModule.forFeature(async () => ({
+ APP_URL: process.env.APP_URL,
+ })),
+ ],
providers: [
CampaignApplicationService,
MockPrismaService,
{ provide: OrganizerService, useValue: mockOrganizerService },
{ provide: S3Service, useValue: mockS3Service },
+ { provide: EmailService, useValue: mockEmailService },
],
}).compile()
@@ -99,14 +117,14 @@ describe('CampaignApplicationService', () => {
)
})
- it('should add a new campaign-application to db if all agreements are true', async () => {
+ it('should add a new campaign-application to db a if all agreements are true', async () => {
const dto: CreateCampaignApplicationDto = {
...mockNewCampaignApplication,
acceptTermsAndConditions: true,
transparencyTermsAccepted: true,
personalInformationProcessingAccepted: true,
toEntity: new CreateCampaignApplicationDto().toEntity,
- campaignEndDate: '2024-01-01'
+ campaignEndDate: '2024-01-01',
}
const mockOrganizerId = 'mockOrganizerId'
@@ -119,6 +137,10 @@ describe('CampaignApplicationService', () => {
.spyOn(prismaMock.campaignApplication, 'create')
.mockResolvedValue(mockCreatedCampaignApplication)
+ const sendEmailsOnCreatedCampaignApplicationSpy = jest
+ .spyOn(service, 'sendEmailsOnCreatedCampaignApplication')
+ .mockResolvedValue(undefined)
+
const result = await service.create(dto, mockPerson)
expect(result).toEqual(mockCreatedCampaignApplication)
@@ -149,8 +171,63 @@ describe('CampaignApplicationService', () => {
},
})
+ expect(sendEmailsOnCreatedCampaignApplicationSpy).toHaveBeenCalledWith(
+ mockCreatedCampaignApplication.campaignName,
+ mockCreatedCampaignApplication.id,
+ mockPerson,
+ )
+
expect(mockOrganizerService.create).toHaveBeenCalledTimes(1)
expect(prismaMock.campaignApplication.create).toHaveBeenCalledTimes(1)
+ expect(sendEmailsOnCreatedCampaignApplicationSpy).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('sendEmailsOnCreatedCampaignApplication', () => {
+ it('should send emails to both the organizer and the admin', async () => {
+ const mockAdminEmail = 'campaign_coordinators@podkrepi.bg'
+ const userEmail = { to: [mockPerson.email] }
+ const adminEmail = { to: [mockAdminEmail] }
+
+ const emailAdminData = {
+ campaignApplicationName: mockSingleCampaignApplication.campaignName,
+ campaignApplicationLink: `${process.env.APP_URL}/admin/campaigns/${mockSingleCampaignApplication.id}`,
+ email: mockPerson.email as string,
+ firstName: mockPerson.firstName,
+ }
+
+ const emailOrganizerData = {
+ campaignApplicationName: mockSingleCampaignApplication.campaignName,
+ campaignApplicationLink: `${process.env.APP_URL}/campaign/applications/${mockSingleCampaignApplication.id}`,
+ email: mockPerson.email as string,
+ firstName: mockPerson.firstName,
+ }
+
+ const mailAdmin = new CreateCampaignApplicationAdminEmailDto(emailAdminData)
+ const mailOrganizer = new CreateCampaignApplicationOrganizerEmailDto(emailOrganizerData)
+
+ mockEmailService.sendFromTemplate.mockResolvedValueOnce(undefined)
+
+ await service.sendEmailsOnCreatedCampaignApplication(
+ mockSingleCampaignApplication.campaignName,
+ mockSingleCampaignApplication.id,
+ mockPerson,
+ )
+
+ expect(mockEmailService.sendFromTemplate).toHaveBeenNthCalledWith(
+ 1,
+ mailOrganizer,
+ userEmail,
+ {
+ bypassUnsubscribeManagement: { enable: true },
+ },
+ )
+
+ expect(mockEmailService.sendFromTemplate).toHaveBeenNthCalledWith(2, mailAdmin, adminEmail, {
+ bypassUnsubscribeManagement: { enable: true },
+ })
+
+ expect(mockEmailService.sendFromTemplate).toHaveBeenCalledTimes(2)
})
})
@@ -284,7 +361,7 @@ describe('CampaignApplicationService', () => {
where: { id: '1' },
data: {
...mockUpdateCampaignApplication,
- campaignEndDate: new Date('2024-09-09T00:00:00.000Z')
+ campaignEndDate: new Date('2024-09-09T00:00:00.000Z'),
},
})
})
@@ -340,7 +417,7 @@ describe('CampaignApplicationService', () => {
where: { id: '1' },
data: {
...mockUpdateCampaignApplication,
- campaignEndDate: new Date('2024-09-09T00:00:00.000Z')
+ campaignEndDate: new Date('2024-09-09T00:00:00.000Z'),
},
})
})
diff --git a/apps/api/src/campaign-application/campaign-application.service.ts b/apps/api/src/campaign-application/campaign-application.service.ts
index d8b29d81c..b9fcda03d 100644
--- a/apps/api/src/campaign-application/campaign-application.service.ts
+++ b/apps/api/src/campaign-application/campaign-application.service.ts
@@ -12,8 +12,15 @@ import { OrganizerService } from '../organizer/organizer.service'
import { CampaignApplicationFileRole, Person, Prisma } from '@prisma/client'
import { S3Service } from './../s3/s3.service'
import { CreateCampaignApplicationFileDto } from './dto/create-campaignApplication-file.dto'
+import { EmailService } from '../email/email.service'
+import { EmailData } from '../email/email.interface'
+import {
+ CreateCampaignApplicationAdminEmailDto,
+ CreateCampaignApplicationOrganizerEmailDto,
+} from '../email/template.interface'
+import { ConfigService } from '@nestjs/config'
-function dateMaybe (d?: string) {
+function dateMaybe(d?: string) {
return d != null &&
typeof d === 'string' &&
new Date(d).toString() != new Date('----invalid date ---').toString()
@@ -28,6 +35,8 @@ export class CampaignApplicationService {
private prisma: PrismaService,
private organizerService: OrganizerService,
private s3: S3Service,
+ private emailService: EmailService,
+ private readonly configService: ConfigService,
) {}
async create(createCampaignApplicationDto: CreateCampaignApplicationDto, person: Person) {
@@ -67,13 +76,19 @@ export class CampaignApplicationService {
campaignTypeId: createCampaignApplicationDto.campaignTypeId,
organizerId: organizer.id,
campaignEnd: createCampaignApplicationDto.campaignEnd,
- campaignEndDate: dateMaybe(createCampaignApplicationDto.campaignEndDate)
+ campaignEndDate: dateMaybe(createCampaignApplicationDto.campaignEndDate),
}
const newCampaignApplication = await this.prisma.campaignApplication.create({
data: campaingApplicationData,
})
+ await this.sendEmailsOnCreatedCampaignApplication(
+ newCampaignApplication.campaignName,
+ newCampaignApplication.id,
+ person,
+ )
+
return newCampaignApplication
} catch (error) {
Logger.error('Error in create():', error)
@@ -81,6 +96,53 @@ export class CampaignApplicationService {
}
}
+ async sendEmailsOnCreatedCampaignApplication(
+ campaignApplicationName: string,
+ campaignApplicationId: string,
+ person: Person,
+ ) {
+ const adminMail = this.configService.get('CAMPAIGN_COORDINATOR_EMAIL', '')
+ const userEmail = { to: [person.email] as EmailData[] }
+ const adminEmail = { to: [adminMail] as EmailData[] }
+ // const adminEmail = { to: ['martbul01@gmail.com'] as EmailData[] }
+
+ const emailAdminData = {
+ campaignApplicationName,
+ campaignApplicationLink: `${this.configService.get(
+ 'APP_URL',
+ )}/admin/campaigns/${campaignApplicationId}`,
+ email: person.email as string,
+ firstName: person.firstName,
+ }
+
+ const emailOrganizerData = {
+ campaignApplicationName,
+ campaignApplicationLink: `${this.configService.get(
+ 'APP_URL',
+ )}/campaign/applications/${campaignApplicationId}`,
+ email: person.email as string,
+ firstName: person.firstName,
+ }
+
+ const mailAdmin = new CreateCampaignApplicationAdminEmailDto(emailAdminData)
+ const mailOrganizer = new CreateCampaignApplicationOrganizerEmailDto(emailOrganizerData)
+
+ try {
+ const userEmailPromise = this.emailService.sendFromTemplate(mailOrganizer, userEmail, {
+ bypassUnsubscribeManagement: { enable: true },
+ })
+
+ const adminEmailPromise = this.emailService.sendFromTemplate(mailAdmin, adminEmail, {
+ bypassUnsubscribeManagement: { enable: true },
+ })
+
+ await Promise.allSettled([userEmailPromise, adminEmailPromise])
+ } catch (error) {
+ Logger.error('Error in sendEmailsOnCreatedCampaignApplication():', error)
+ throw error
+ }
+ }
+
async uploadFiles(id: string, person: Person, files: Express.Multer.File[]) {
try {
const createdFiles = await Promise.all(
@@ -111,14 +173,6 @@ export class CampaignApplicationService {
try {
const singleCampaignApplication = await this.prisma.campaignApplication.findUnique({
where: { id },
- include: {
- documents: {
- select: {
- id: true,
- filename: true,
- },
- },
- },
})
if (!singleCampaignApplication) {
throw new NotFoundException('Campaign application doesnt exist')
diff --git a/apps/api/src/email/template.interface.ts b/apps/api/src/email/template.interface.ts
index bcc0fd937..12059d83d 100644
--- a/apps/api/src/email/template.interface.ts
+++ b/apps/api/src/email/template.interface.ts
@@ -14,6 +14,8 @@ export enum TemplateType {
confirmConsent = 'confirm-notifications-consent',
campaignNewsDraft = 'campaign-news-draft',
refundDonation = 'refund-donation',
+ createCampaignApplicationAdmin = 'create-campaign-application-admin',
+ createCampaignApplicationOrganizer = 'create-campaign-application-organizer',
}
export type TemplateTypeKeys = keyof typeof TemplateType
export type TemplateTypeValues = typeof TemplateType[TemplateTypeKeys]
@@ -100,3 +102,24 @@ export class RefundDonationEmailDto extends EmailTemplate<{
}> {
name = TemplateType.refundDonation
}
+
+export class CreateCampaignApplicationAdminEmailDto extends EmailTemplate<{
+ campaignApplicationName: string
+ adminEditLink?: string
+ campaignApplicationLink: string
+ email: string
+ firstName: string
+}> {
+ name = TemplateType.createCampaignApplicationAdmin
+}
+
+
+export class CreateCampaignApplicationOrganizerEmailDto extends EmailTemplate<{
+ campaignApplicationName: string
+ editLink?: string
+ campaignApplicationLink: string
+ email: string
+ firstName: string
+}> {
+ name = TemplateType.createCampaignApplicationOrganizer
+}
\ No newline at end of file