diff --git a/.eslintrc.json b/.eslintrc.json index 06cc47d9a..453c5bff7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,6 +26,19 @@ "extends": ["plugin:@nrwl/nx/typescript"], "rules": {} }, + { + "files": ["*.spec.ts"], + "extends": ["plugin:@nrwl/nx/typescript"], + "rules": { + "no-restricted-syntax": [ + "error", + { + "message": "Do you really need to write file to disk in a test?", + "selector": "[name=writeFileSync], [name=writeFile]" + } + ] + } + }, { "files": ["*.js", "*.jsx"], "extends": ["plugin:@nrwl/nx/javascript"], diff --git a/apps/api/src/assets/templates/create-campaign-application-organizer.json b/apps/api/src/assets/templates/create-campaign-application-organizer.json index bdccf1639..d0f8a40ba 100644 --- a/apps/api/src/assets/templates/create-campaign-application-organizer.json +++ b/apps/api/src/assets/templates/create-campaign-application-organizer.json @@ -1,3 +1,3 @@ { - "subject": "Създадена нова кампания" + "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 index 0c07ffb2b..a2b99589a 100644 --- a/apps/api/src/assets/templates/create-campaign-application-organizer.mjml +++ b/apps/api/src/assets/templates/create-campaign-application-organizer.mjml @@ -12,7 +12,7 @@ padding-right="25px" padding-bottom="30px" padding-top="50px"> - Успешно създадохте кампания в Подкрепи.бг + Потвърждение за получаване на заявка за дарителска кампания @@ -25,7 +25,7 @@ font-family="open Sans Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px"> - Здравейте {{firstName}}, + Здравейте, {{firstName}},

- Вашата кампания е създадена успешно! Вижте я + Благодарим Ви, че подадохте заявка за кампания на платформата Подкрепи.бг!
Можете да + я прегледате ТУК!

+ >.

- Пожелаваме успешно набиране на средствата! + Искаме да Ви уверим, че заявката Ви е успешно получена и ще бъде разгледана от екипа ни в + най-кратък срок. Ако има нужда от допълнителна информация или уточнения, член на екип + „Кампании“ ще се свърже с Вас.

+ + Междувременно, ако имате въпроси или желаете да предоставите допълнителни детайли за + кампанията, можете да се свържете с нас на следния имейл: + campaign_coordinators@podkrepi.bg. Благодарим Ви, че заедно правим добро!

- Поздрави,
+ С уважение,
Екипът на Подкрепи.бг
diff --git a/apps/api/src/assets/templates/forgot-password.mjml b/apps/api/src/assets/templates/forgot-password.mjml index e39d93fb0..3b1d41d8d 100644 --- a/apps/api/src/assets/templates/forgot-password.mjml +++ b/apps/api/src/assets/templates/forgot-password.mjml @@ -15,7 +15,7 @@ font-family="open Sans Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px"> - Здравейте {{firstName}} {{lastName}}, + Здравейте, {{firstName}} {{lastName}},

- някой (може би Вие) е пуснал заявка за смяна на парола за достъп до Подкрепи.бг.

+ Някой (може би Вие) е пуснал заявка за смяна на паролата Ви за достъп до Подкрепи.бг.

Ако не сте пускали подобна заявка, не е необходимо да правите нищо.

Ако Вие сте поискали възстановяване на паролата, натиснете бутона „Нова парола“.

diff --git a/apps/api/src/assets/templates/welcome.mjml b/apps/api/src/assets/templates/welcome.mjml index c77f6914e..8ee8c8743 100644 --- a/apps/api/src/assets/templates/welcome.mjml +++ b/apps/api/src/assets/templates/welcome.mjml @@ -29,7 +29,7 @@ font-family="open Sans Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px"> - Здравейте {{person.firstName}}, + Здравейте, {{person.firstName}},

Discord сървъра, където се помещава нашият виртуален офис. Съветът ни е да прочетете внимателно какво - пише на началната страница в канал #започни-от-тук.

- След това може да се включите в дискусиите и Ви чакаме на обща среща всяка сряда от 19:00 - ч. в гласовия канал #обща-среща-сряда-19ч на сървъра.

+ пише на началната страница в канал #започни-от-тук.

+ След това може да се включите в дискусиите и Ви чакаме на обща среща всяка сряда от 19:00 + ч. в гласовия канал #обща-среща-сряда-19ч на сървъра.

Още веднъж благодарим за готовността да помогнете за нашата кауза!
{ const emailAdminData = { campaignApplicationName: mockSingleCampaignApplication.campaignName, - campaignApplicationLink: `${process.env.APP_URL}/admin/campaigns/${mockSingleCampaignApplication.id}`, + campaignApplicationLink: `${process.env.APP_URL}/admin/campaigns/edit/${mockSingleCampaignApplication.id}`, email: mockPerson.email as string, firstName: mockPerson.firstName, } const emailOrganizerData = { campaignApplicationName: mockSingleCampaignApplication.campaignName, - campaignApplicationLink: `${process.env.APP_URL}/campaign/applications/${mockSingleCampaignApplication.id}`, + campaignApplicationLink: `${process.env.APP_URL}/campaigns/application/${mockSingleCampaignApplication.id}`, email: mockPerson.email as string, firstName: mockPerson.firstName, } diff --git a/apps/api/src/campaign-application/campaign-application.service.ts b/apps/api/src/campaign-application/campaign-application.service.ts index 777ce6900..1fcd566bc 100644 --- a/apps/api/src/campaign-application/campaign-application.service.ts +++ b/apps/api/src/campaign-application/campaign-application.service.ts @@ -110,7 +110,7 @@ export class CampaignApplicationService { campaignApplicationName, campaignApplicationLink: `${this.configService.get( 'APP_URL', - )}/admin/campaigns/${campaignApplicationId}`, + )}/admin/campaigns/edit/${campaignApplicationId}`, email: person.email as string, firstName: person.firstName, } @@ -119,7 +119,7 @@ export class CampaignApplicationService { campaignApplicationName, campaignApplicationLink: `${this.configService.get( 'APP_URL', - )}/campaign/applications/${campaignApplicationId}`, + )}/campaigns/application/${campaignApplicationId}`, email: person.email as string, firstName: person.firstName, } diff --git a/apps/api/src/donations/donations.service.ts b/apps/api/src/donations/donations.service.ts index 232006749..e334795ec 100644 --- a/apps/api/src/donations/donations.service.ts +++ b/apps/api/src/donations/donations.service.ts @@ -914,7 +914,7 @@ export class DonationsService { create: { amount: paymentData.netAmount, type: paymentData.type as DonationType, - person: paymentData.personId ? { connect: { email: paymentData.billingEmail } } : {}, + person: paymentData.personId ? { connect: { id: paymentData.personId } } : {}, targetVault: targetVaultData, }, }, @@ -928,7 +928,7 @@ export class DonationsService { donation.amount, tx, ) - this.notificationService.sendNotification('successfulDonation', donation) + this.notificationService.sendNotification('successfulDonation', donation.donations[0]) } return donation @@ -966,7 +966,9 @@ export class DonationsService { }, include: { donations: true }, }) - Logger.debug('Donation found by subscription: ', donation) + if (donation) { + Logger.debug('Donation found by subscription: ', donation) + } } return donation } diff --git a/apps/api/src/email/readme.email.md b/apps/api/src/email/readme.email.md new file mode 100644 index 000000000..8854de71f --- /dev/null +++ b/apps/api/src/email/readme.email.md @@ -0,0 +1,27 @@ +# Email Template Service + +It gets the templates based on the name and the templates in the src/assets/templates. + +## Testing with adapter + +Why do we need the `template.service.spec-adapter.ts` when we have the `template.service.ts`? +It's looking in a hardcoded path which is correct when the app is built and deployed but incorrect when running the tests. Hence we extend the base class with this for the tests. + +## Visualize the template before you ship the code + +- temporarily add a line like `writeFileSync('./rendered-template.html', rendered.html)` to your test (careful to not ) +- then open that with the browser i.e. `file:///C:/Users/gparl/Downloads/projects/podkrepi-bg-api/rendered-template.html` or on ubuntu under wsl `file://wsl.localhost/Ubuntu/home/gparlakov/projects/podkrepi-bg-api/rendered-template.html` +- remember to delete that `writeFileSync` line and the `rendered-template.html` before you ship + +```ts +const t = new CreateCampaignApplicationOrganizerEmailDto({ + firstName: 'test', + email: 'test@email', + campaignApplicationLink: 'link', + campaignApplicationName: 'campaignApplicationName', +}) + +const rendered = await s.getTemplate(t) + +writeFileSync('./rendered-template.html', rendered.html) +``` diff --git a/apps/api/src/email/template.service.spec-adapter.ts b/apps/api/src/email/template.service.spec-adapter.ts new file mode 100644 index 000000000..335c66037 --- /dev/null +++ b/apps/api/src/email/template.service.spec-adapter.ts @@ -0,0 +1,46 @@ +import { Logger } from '@nestjs/common' +import { readFile } from 'fs/promises' +import mjml from 'mjml' +import path from 'path' + +import { EmailMetadata, TemplateType } from './template.interface' +import { TemplateService } from './template.service' + +export class TemplateServiceSpecAdapter extends TemplateService { + /** + * Why do we need this when we have the template.service.ts? + * It's looking in a hardcoded path which is correct when the app is built and deployed but incorrect when running the tests. + * Hence we extend the base class with this for the tests. + * + * @param basePath where to look for the assets/templates/*.mjml files + */ + constructor(private basePath: string) { + super() + } + + protected async getEmailTemplate(templateName: TemplateType): Promise> { + try { + const file = await readFile( + path.resolve(this.basePath, `./assets/templates/${templateName}.mjml`), + { encoding: 'utf-8' }, + ) + return mjml(file) + } catch (error) { + Logger.error(`getEmailTemplate`, error) + throw error + } + } + + protected async getEmailData(templateName: string): Promise { + try { + const contents = await readFile( + path.resolve(this.basePath, `./assets/templates/${templateName}.json`), + { encoding: 'utf-8' }, + ) + return JSON.parse(contents) + } catch (error) { + Logger.error(`getEmailData`, error) + throw error + } + } +} diff --git a/apps/api/src/email/template.service.spec.ts b/apps/api/src/email/template.service.spec.ts new file mode 100644 index 000000000..aa7a93e91 --- /dev/null +++ b/apps/api/src/email/template.service.spec.ts @@ -0,0 +1,247 @@ +import { CreateCampaignApplicationOrganizerEmailDto } from './template.interface' +import { TemplateServiceSpecAdapter } from './template.service.spec-adapter' + +describe('Template service', () => { + let s: TemplateServiceSpecAdapter + + beforeEach(() => { + s = new TemplateServiceSpecAdapter('./apps/api/src') + }) + + it('should render the campaign application email template', async () => { + const t = new CreateCampaignApplicationOrganizerEmailDto({ + firstName: 'test', + email: 'test@email', + campaignApplicationLink: 'link', + campaignApplicationName: 'campaignApplicationName', + }) + + const rendered = await s.getTemplate(t) + // prettier-ignore-start + /* eslint-disable */ + expect(rendered).toMatchInlineSnapshot(` + { + "html": " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + +
+ +
Потвърждение за получаване на заявка за дарителска кампания
+ +
+ +
+ + +
+ +
+ + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + +
+ +
Здравейте, test, +

+ +
+ +
Благодарим Ви, че подадохте заявка за кампания на платформата Подкрепи.бг!
Можете да + я прегледате + ТУК.

+ + Искаме да Ви уверим, че заявката Ви е успешно получена и ще бъде разгледана от екипа ни в + най-кратък срок. Ако има нужда от допълнителна информация или уточнения, член на екип + „Кампании“ ще се свърже с Вас.

+ + Междувременно, ако имате въпроси или желаете да предоставите допълнителни детайли за + кампанията, можете да се свържете с нас на следния имейл: + campaign_coordinators@podkrepi.bg. Благодарим Ви, че заедно правим добро!

+ +
+ +
С уважение,
+ Екипът на Подкрепи.бг
+ +
+ +
+ + +
+ +
+ + + + + +
+ + + + ", + "metadata": { + "subject": "Потвърждение за получаване на заявка за дарителска кампания ", + }, + } + `) + /* eslint-enable */ + // prettier-ignore-start + }) +}) diff --git a/apps/api/src/email/template.service.ts b/apps/api/src/email/template.service.ts index efc1f8764..16cfcb007 100644 --- a/apps/api/src/email/template.service.ts +++ b/apps/api/src/email/template.service.ts @@ -26,7 +26,7 @@ export class TemplateService { } } - private async getEmailTemplate(templateName: TemplateType): Promise> { + protected async getEmailTemplate(templateName: TemplateType): Promise> { try { const file = await readFile( path.resolve(__dirname, `./assets/templates/${templateName}.mjml`), @@ -39,7 +39,7 @@ export class TemplateService { } } - private async getEmailData(templateName: string): Promise { + protected async getEmailData(templateName: string): Promise { try { const contents = await readFile( path.resolve(__dirname, `./assets/templates/${templateName}.json`), diff --git a/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts b/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts index bdfa78a4d..b06909992 100644 --- a/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts +++ b/apps/api/src/notifications/providers/notifications.sendgrid.provider.ts @@ -27,7 +27,6 @@ import { ContactsMap } from '../notifications.service' import { MailDataRequired } from '@sendgrid/mail' import { PersonalizationData } from '@sendgrid/helpers/classes/personalization' - @Injectable() export class SendGridNotificationsProvider implements NotificationsProviderInterface diff --git a/apps/api/src/sockets/notifications/notification.service.ts b/apps/api/src/sockets/notifications/notification.service.ts index ca02aea25..9d2f625df 100644 --- a/apps/api/src/sockets/notifications/notification.service.ts +++ b/apps/api/src/sockets/notifications/notification.service.ts @@ -9,11 +9,12 @@ export const donationNotificationSelect = Prisma.validator amount: true, extPaymentMethodId: true, createdAt: true, - donations: { select: { id: true, targetVaultId: true, + createdAt: true, + amount: true, person: { select: { firstName: true, diff --git a/apps/api/src/stripe/stripe.controller.spec.ts b/apps/api/src/stripe/stripe.controller.spec.ts index d6334f873..e66903243 100644 --- a/apps/api/src/stripe/stripe.controller.spec.ts +++ b/apps/api/src/stripe/stripe.controller.spec.ts @@ -34,7 +34,7 @@ describe('StripeController', () => { checkout: { sessions: { create: jest.fn() } }, paymentIntents: { retrieve: jest.fn() }, refunds: { create: jest.fn() }, - setupIntents: { retrieve: jest.fn() }, + setupIntents: { retrieve: jest.fn(), update: jest.fn() }, customers: { create: jest.fn(), list: jest.fn() }, paymentMethods: { attach: jest.fn() }, products: { search: jest.fn(), create: jest.fn() }, @@ -199,7 +199,7 @@ describe('StripeController', () => { reason: 'requested_by_customer', }) }) - it(`should not call setupintents.update if campaign can't accept donations`, async () => { + it(`should not call setupintents.update if no campaignId is provided`, async () => { prismaMock.campaign.findFirst.mockResolvedValue({ id: 'complete-campaign', allowDonationOnComplete: false, diff --git a/apps/api/src/stripe/stripe.service.ts b/apps/api/src/stripe/stripe.service.ts index da9441123..ee76a7078 100644 --- a/apps/api/src/stripe/stripe.service.ts +++ b/apps/api/src/stripe/stripe.service.ts @@ -37,7 +37,7 @@ export class StripeService { ): Promise> { if (!inputDto.metadata.campaignId) throw new BadRequestException('campaignId is missing from metadata') - const campaign = await this.campaignService.validateCampaignId( + await this.campaignService.validateCampaignId( inputDto.metadata.campaignId as string, ) return await this.stripeClient.setupIntents.update(id, inputDto, { idempotencyKey }) diff --git a/manifests/overlays/development/kustomization.yaml b/manifests/overlays/development/kustomization.yaml index 1e6ca717e..807f6c65d 100644 --- a/manifests/overlays/development/kustomization.yaml +++ b/manifests/overlays/development/kustomization.yaml @@ -9,6 +9,7 @@ bases: patches: - path: manual/keycloak-config.patch.yaml - path: manual/deployment.patch.yaml +- path: manual/sendgrid-config.patch.yaml images: - name: ghcr.io/podkrepi-bg/api diff --git a/manifests/overlays/development/manual/kustomization.yaml b/manifests/overlays/development/manual/kustomization.yaml index 05ada7178..327cef55b 100644 --- a/manifests/overlays/development/manual/kustomization.yaml +++ b/manifests/overlays/development/manual/kustomization.yaml @@ -9,6 +9,7 @@ bases: patches: - path: keycloak-config.patch.yaml - path: deployment.patch.yaml + - path: sendgrid-config.patch.yaml images: - name: ghcr.io/podkrepi-bg/api/migrations @@ -24,4 +25,4 @@ images: # - name: sendgrid-secret # envs: [sendgrid-secret.env] # - name: paypal-secret -# envs: [paypal.env] \ No newline at end of file +# envs: [paypal.env] diff --git a/manifests/overlays/development/manual/sendgrid-config.patch.yaml b/manifests/overlays/development/manual/sendgrid-config.patch.yaml new file mode 100644 index 000000000..17347752b --- /dev/null +++ b/manifests/overlays/development/manual/sendgrid-config.patch.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: sendgrid-config +data: + marketing-list-id: 96f5eb7b-15fc-4fa5-9e6f-48ddac3767e5