Skip to content

Commit

Permalink
Merge branch 'master' into sort-bank-transactions-desc
Browse files Browse the repository at this point in the history
  • Loading branch information
dphilipov committed Sep 15, 2023
2 parents 94ff8e4 + df8b701 commit 3594185
Show file tree
Hide file tree
Showing 16 changed files with 258 additions and 70 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ PLATFORM_IBAN=
IMPORT_TRX_TASK_INTERVAL_MINUTES=60
CHECK_IRIS_CONSENT_TASK_HOUR=10
BILLING_ADMIN_MAIL=[email protected]
CAMPAIGN_ADMIN_MAIL=

## Cache ##
##############
Expand Down
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ IMPORT_TRX_TASK_INTERVAL_MINUTES=60
#which hour of the day to run the check for consent
CHECK_IRIS_CONSENT_TASK_HOUR=10
BILLING_ADMIN_MAIL=[email protected]
CAMPAIGN_ADMIN_MAIL=responsible for campaign management

## Cache ##
##############
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/assets/templates/campaign-news-draft.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"subject": "Новина по Кампания Е Качена за Одобрение"
}
69 changes: 69 additions & 0 deletions apps/api/src/assets/templates/campaign-news-draft.mjml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<mjml>
<mj-body background-color="#ffffff" font-size="13px" width="90%">
<mj-section
background-color="#009FE3"
vertical-align="top"
padding-bottom="0px"
padding-top="0">
<mj-column vertical-align="top" width="100%">
<mj-text
align="center"
color="#ffffff"
font-size="45px"
font-weight="bold"
font-family="open Sans Helvetica, Arial, sans-serif"
padding-left="25px"
padding-right="25px"
padding-top="50px">
{{campaignNewsTitle}}
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#009fe3" padding-bottom="20px">
<mj-column vertical-align="middle" width="100%">
<mj-text
align="left"
color="#ffffff"
font-size="22px"
font-family="open Sans Helvetica, Arial, sans-serif"
padding-left="25px"
padding-right="25px">
<br /><br />
</mj-text>
<mj-text
align="center"
color="#ffffff"
font-size="24px"
font-family="open Sans Helvetica, Arial, sans-serif"
padding-left="25px"
padding-right="25px">
Има качена новина по кампания <a href="{{campaignLink}}">{{campaignName}}</a>, която
изчаква одобрение от администратор!
</mj-text>

<mj-button
background-color="#feeb35"
font-family="Helvetica, Arial, sans-serif"
font-size="17px"
border-radius="30px"
color="#000000"
padding="15px 30px"
href="{{newsLink}}"
target="_blank">
Към Новината
</mj-button>

<mj-text
align="left"
color="#ffffff"
font-size="15px"
font-family="open Sans Helvetica, Arial, sans-serif"
padding-left="25px"
padding-right="25px">
Поздрави, <br />
Екипът на Подкрепи.бг
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
11 changes: 4 additions & 7 deletions apps/api/src/campaign-file/campaign-file.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('CampaignFileController', () => {
ConfigService,
{
provide: CampaignService,
useValue: { getCampaignByIdAndCoordinatorId: jest.fn(() => null) },
useValue: { verifyCampaignOwner: jest.fn(() => null) },
},
VaultService,
CampaignNewsService,
Expand Down Expand Up @@ -92,7 +92,7 @@ describe('CampaignFileController', () => {
).toEqual([fileId, fileId])

expect(personService.findOneByKeycloakId).toHaveBeenCalledWith(userMock.sub)
expect(campaignService.getCampaignByIdAndCoordinatorId).not.toHaveBeenCalled()
expect(campaignService.verifyCampaignOwner).not.toHaveBeenCalled()
expect(campaignFileService.create).toHaveBeenCalledTimes(2)
})

Expand All @@ -102,16 +102,13 @@ describe('CampaignFileController', () => {
await expect(controller.create(campaignId, { roles: [] }, [], userMock)).rejects.toThrowError()

expect(personService.findOneByKeycloakId).toHaveBeenCalledWith(userMock.sub)
expect(campaignService.getCampaignByIdAndCoordinatorId).not.toHaveBeenCalled()
expect(campaignService.verifyCampaignOwner).not.toHaveBeenCalled()
})

it('should throw an error for user not owning updated campaign', async () => {
await expect(controller.create(campaignId, { roles: [] }, [], userMock)).rejects.toThrowError()

expect(personService.findOneByKeycloakId).toHaveBeenCalledWith(userMock.sub)
expect(campaignService.getCampaignByIdAndCoordinatorId).toHaveBeenCalledWith(
campaignId,
personIdMock,
)
expect(campaignService.verifyCampaignOwner).toHaveBeenCalledWith(campaignId, personIdMock)
})
})
11 changes: 4 additions & 7 deletions apps/api/src/campaign-file/campaign-file.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { FilesRoleDto } from './dto/files-role.dto'
import { CampaignFileService } from './campaign-file.service'
import { CampaignService } from '../campaign/campaign.service'
import { KeycloakTokenParsed, isAdmin } from '../auth/keycloak'
import { ApiTags } from '@nestjs/swagger';
import { ApiTags } from '@nestjs/swagger'
import { CampaignFileRole } from '@prisma/client'

Check warning on line 27 in apps/api/src/campaign-file/campaign-file.controller.ts

View workflow job for this annotation

GitHub Actions / Run API tests

'CampaignFileRole' is defined but never used

@ApiTags('campaign-file')
Expand Down Expand Up @@ -51,10 +51,7 @@ export class CampaignFileController {
}

if (!isAdmin(user)) {
const campaign = await this.campaignService.getCampaignByIdAndCoordinatorId(
campaignId,
person.id,
)
const campaign = await this.campaignService.verifyCampaignOwner(campaignId, person.id)
if (!campaign) {
throw new NotFoundException(
'User ' + user.name + 'is not admin or coordinator of campaign with id: ' + campaignId,
Expand Down Expand Up @@ -88,8 +85,8 @@ export class CampaignFileController {
'Content-Type': file.mimetype,
'Content-Disposition': 'attachment; filename="' + file.filename + '"',
'Cache-Control': file.mimetype.startsWith('image/')
? 'public, s-maxage=15552000, stale-while-revalidate=15552000, immutable'
: 'no-store'
? 'public, s-maxage=15552000, stale-while-revalidate=15552000, immutable'
: 'no-store',
})

return new StreamableFile(file.stream)
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/campaign-news/campaign-news.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { PrismaService } from '../prisma/prisma.service'
import { PersonModule } from '../person/person.module'
import { MarketingNotificationsModule } from '../notifications/notifications.module'
import { ConfigService } from '@nestjs/config'
import { EmailService } from '../email/email.service'

@Module({
imports: [PersonModule, MarketingNotificationsModule],
controllers: [CampaignNewsController],
providers: [CampaignNewsService, PrismaService, ConfigService],
providers: [CampaignNewsService, PrismaService, ConfigService, EmailService],
exports: [CampaignNewsService],
})
export class CampaignNewsModule {}
44 changes: 43 additions & 1 deletion apps/api/src/campaign-news/campaign-news.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import { SendGridParams } from '../notifications/providers/notifications.sendgri
import { DateTime } from 'luxon'
import { ConfigService } from '@nestjs/config'
import { MarketingNotificationsService } from '../notifications/notifications.service'
import { CampaignNewsDraftEmailDto } from '../email/template.interface'
import { EmailService } from '../email/email.service'

@Injectable()
export class CampaignNewsService {
constructor(
private prisma: PrismaService,
private readonly config: ConfigService,
private sendEmail: EmailService,
private readonly marketingNotificationsService: MarketingNotificationsService,
) {}
private RECORDS_PER_PAGE = 4
Expand All @@ -24,8 +27,14 @@ export class CampaignNewsService {
try {
const campaignNews = await this.prisma.campaignNews.create({ data: campaignNewsDto })
if (campaignNews.state === 'published' && notify)
// USER Notification
//Don't await --> send to background
this.sendArticleNotification(campaignNews).catch((e) => console.log(e))
this.sendArticleNotification(campaignNews).catch((e) => Logger.warn(e))

// ADMIN Notification
//Don't await --> send to background
this.notifyAdminsForNewsUpload(campaignNews).catch((e) => Logger.warn(e))

return campaignNews
} catch (error) {
const message = 'Creating article about campaign failed'
Expand All @@ -34,6 +43,39 @@ export class CampaignNewsService {
}
}

async notifyAdminsForNewsUpload(news: CampaignNews) {
const campaign = await this.prisma.campaign.findFirst({
where: { id: news.campaignId },
})

if (!campaign) return

// Build the links
const stage = this.config.get<string>('APP_ENV') === 'development' ? 'APP_URL_LOCAL' : 'APP_URL'
const appUrl = this.config.get<string>(stage)
const newsLink = `${appUrl}/campaigns/${campaign.slug}/news/admin-panel`
const campaignLink = `${appUrl}/campaigns/${campaign.slug}`
const campaignAdminEmail = this.config.get<string>('mail.campaignAdminEmail', '')

if (!campaignAdminEmail) return

// Prepare Email data
const recepient = { to: [campaignAdminEmail] }

const mail = new CampaignNewsDraftEmailDto({
campaignName: campaign.title,
campaignNewsTitle: news.title,
campaignLink,
newsLink,
})

// Send Notification
await this.sendEmail.sendFromTemplate(mail, recepient, {
//Allow users to receive the mail, regardles of unsubscribes
bypassUnsubscribeManagement: { enable: true },
})
}

async sendArticleNotification(news: CampaignNews) {
const template = await this.prisma.marketingTemplates.findFirst({
where: {
Expand Down
48 changes: 35 additions & 13 deletions apps/api/src/campaign/campaign.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,23 @@ export class CampaignService {
return campaigns
}

async getCampaignByIdAndCoordinatorId(
campaignId: string,
coordinatorId: string,
): Promise<Campaign | null> {
const campaign = await this.prisma.campaign.findFirst({
where: { id: campaignId, coordinator: { personId: coordinatorId } },
include: { coordinator: true },
// Check if the campaign exists by coordinator or organizer
async verifyCampaignOwner(campaignId: string, personId: string): Promise<Campaign | null> {
const campaignByCoordinator = await this.prisma.campaign.findFirst({
where: { id: campaignId, coordinator: { personId } },
include: { coordinator: true, organizer: true },
})
return campaign

if (campaignByCoordinator !== null) {
return campaignByCoordinator
}

const campaignByOrganizer = await this.prisma.campaign.findFirst({
where: { id: campaignId, organizer: { personId } },
include: { coordinator: true, organizer: true },
})

return campaignByOrganizer
}

async getCampaignByIdWithPersonIds(id: string) {
Expand Down Expand Up @@ -630,8 +638,15 @@ export class CampaignService {
})

//if donation is switching to successful, increment the vault amount and send notification
if (newDonationStatus === DonationStatus.succeeded) {
await this.vaultService.incrementVaultAmount(donation.targetVaultId, donation.amount, tx)
if (
donation.status != DonationStatus.succeeded &&
newDonationStatus === DonationStatus.succeeded
) {
await this.vaultService.incrementVaultAmount(
donation.targetVaultId,
paymentData.netAmount,
tx,
)
this.notificationService.sendNotification('successfulDonation', {
...updatedDonation,
person: updatedDonation.person,
Expand Down Expand Up @@ -739,8 +754,15 @@ export class CampaignService {

async createDonationWish(wish: string, donationId: string, campaignId: string) {
const person = await this.prisma.donation.findUnique({ where: { id: donationId } }).person()
await this.prisma.donationWish.create({
data: {
await this.prisma.donationWish.upsert({
where: { donationId },
create: {
message: wish,
donationId,
campaignId,
personId: person?.id,
},
update: {
message: wish,
donationId,
campaignId,
Expand Down Expand Up @@ -1056,7 +1078,7 @@ export class CampaignService {
throw new UnauthorizedException()
}

const campaign = await this.getCampaignByIdAndCoordinatorId(campaignId, person.id)
const campaign = await this.verifyCampaignOwner(campaignId, person.id)
if (!campaign) {
throw new UnauthorizedException()
}
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ export default () => ({
banksEndPoint: process.env.IRIS_API_URL + '/banks?country=bulgaria',
ibansEndPoint: process.env.IRIS_API_URL + '/ibans',
transactionsEndPoint: process.env.IRIS_API_URL + '/transactions',
},
mail: {
billingAdminEmail: process.env.BILLING_ADMIN_MAIL,
campaignAdminEmail: process.env.CAMPAIGN_ADMIN_MAIL,
},
tasks: {
import_transactions: { interval: process.env.IMPORT_TRX_TASK_INTERVAL_MINUTES },
Expand Down
Loading

0 comments on commit 3594185

Please sign in to comment.