Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added campaign statistics endpoints #565

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { CacheModule } from '@nestjs/cache-manager'
import { CampaignNewsModule } from '../campaign-news/campaign-news.module'
import { CampaignNewsFileModule } from '../campaign-news-file/campaign-news-file.module'
import { MarketingNotificationsModule } from '../notifications/notifications.module'
import { StatisticsModule } from '../statistics/statistics.module'

@Module({
imports: [
Expand Down Expand Up @@ -107,6 +108,7 @@ import { MarketingNotificationsModule } from '../notifications/notifications.mod
JwtModule,
NotificationModule,
BankTransactionsModule,
StatisticsModule,
CacheModule.registerAsync({
imports: [ConfigModule],
useFactory: async (config: ConfigService) => ({
Expand Down
36 changes: 36 additions & 0 deletions apps/api/src/statistics/dto/donation-statistics.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ApiProperty } from '@nestjs/swagger'
import { Expose } from 'class-transformer'

export class GroupedDonationsDto {
@ApiProperty()
@Expose()
sum: number

@ApiProperty()
@Expose()
count: number

@ApiProperty()
@Expose()
date: Date
}

export class UniqueDonationsDto {
@ApiProperty()
@Expose()
amount: number

@ApiProperty()
@Expose()
count: number
}

export class HourlyDonationsDto {
@ApiProperty()
@Expose()
hour: number

@ApiProperty()
@Expose()
count: number
}
5 changes: 5 additions & 0 deletions apps/api/src/statistics/dto/group-by.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum GroupBy {
DAY = 'day',
WEEK = 'week',
MONTH = 'month',
}
38 changes: 38 additions & 0 deletions apps/api/src/statistics/statistics.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Public } from 'nest-keycloak-connect'
import { Controller, Get, Param, Query, UseInterceptors } from '@nestjs/common'
import { CacheInterceptor } from '@nestjs/cache-manager'

import { StatisticsService } from './statistics.service'
import { ApiQuery, ApiTags } from '@nestjs/swagger'
import { GroupBy } from './dto/group-by.dto'

@ApiTags('statistics')
@Controller('statistics')
export class StatisticsController {
constructor(private readonly statisticsService: StatisticsService) {}

@Get('donations/:campaignId')
@UseInterceptors(CacheInterceptor)
@Public()
@ApiQuery({ name: 'groupBy', required: false, enum: GroupBy })
async findGroupedDonations(
@Param('campaignId') campaignId: string,
@Query('groupBy') groupBy?: GroupBy,
) {
return await this.statisticsService.listGroupedDonations(campaignId, groupBy)
}

@Get('unique-donations/:campaignId')
@UseInterceptors(CacheInterceptor)
@Public()
async findUniqueDonations(@Param('campaignId') campaignId: string) {
return await this.statisticsService.listUniqueDonations(campaignId)
}

@Get('hourly-donations/:campaignId')
@UseInterceptors(CacheInterceptor)
@Public()
async findHourlyDonations(@Param('campaignId') campaignId: string) {
return await this.statisticsService.listHourlyDonations(campaignId)
}
}
12 changes: 12 additions & 0 deletions apps/api/src/statistics/statistics.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'
import { StatisticsController } from './statistics.controller'
import { StatisticsService } from './statistics.service'

@Module({
controllers: [StatisticsController],
providers: [StatisticsService, PrismaService],

exports: [StatisticsService],
})
export class StatisticsModule {}
59 changes: 59 additions & 0 deletions apps/api/src/statistics/statistics.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Prisma } from '@prisma/client'
import { Injectable } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'

import { GroupBy } from './dto/group-by.dto'
import {
GroupedDonationsDto,
HourlyDonationsDto,
UniqueDonationsDto,
} from './dto/donation-statistics.dto'

@Injectable()
export class StatisticsService {
constructor(private prisma: PrismaService) {}

async listGroupedDonations(
campaignId: string,
groupBy?: GroupBy,
): Promise<GroupedDonationsDto[]> {
const date =
groupBy === GroupBy.MONTH
? Prisma.sql`DATE_TRUNC('MONTH', created_at) date`
: groupBy === GroupBy.WEEK
? Prisma.sql`DATE_TRUNC('WEEK', created_at) date`
: Prisma.sql`DATE_TRUNC('DAY', created_at) date`

const group =
groupBy === GroupBy.MONTH
? Prisma.sql`GROUP BY DATE_TRUNC('MONTH', created_at)`
: groupBy === GroupBy.WEEK
? Prisma.sql`GROUP BY DATE_TRUNC('WEEK', created_at)`
: Prisma.sql`GROUP BY DATE_TRUNC('DAY', created_at)`

return this.prisma.$queryRaw`
SELECT SUM(amount)::INTEGER, COUNT(id)::INTEGER, ${date}
FROM api.donations WHERE status = 'succeeded'
${Prisma.sql`AND target_vault_id IN ( SELECT id from api.vaults WHERE campaign_id = ${campaignId}::uuid)`}
${group}
ORDER BY date ASC `
}

async listUniqueDonations(campaignId: string): Promise<UniqueDonationsDto[]> {
return this.prisma.$queryRaw`
SELECT amount::INTEGER, COUNT(id)::INTEGER AS count
FROM api.donations WHERE status = 'succeeded'
${Prisma.sql`AND target_vault_id IN ( SELECT id from api.vaults WHERE campaign_id = ${campaignId}::uuid)`}
GROUP BY amount
ORDER BY amount ASC`
}

async listHourlyDonations(campaignId: string): Promise<HourlyDonationsDto[]> {
return this.prisma.$queryRaw`
SELECT EXTRACT(HOUR from created_at)::INTEGER AS hour, COUNT(id)::INTEGER AS count
FROM api.donations where status = 'succeeded'
${Prisma.sql`AND target_vault_id IN ( SELECT id from api.vaults WHERE campaign_id = ${campaignId}::uuid)`}
GROUP BY hour
ORDER BY hour ASC`
}
igoychev marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
]
},
"resolutions": {
"undici": "^5.8.2",
"undici": "^5.26.2",
"semver": "^7.5.2"
},
"packageManager": "[email protected]",
Expand Down
19 changes: 13 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2415,6 +2415,13 @@ __metadata:
languageName: node
linkType: hard

"@fastify/busboy@npm:^2.0.0":
version: 2.0.0
resolution: "@fastify/busboy@npm:2.0.0"
checksum: 41879937ce1dee6421ef9cd4da53239830617e1f0bb7a0e843940772cd72827205d05e518af6adabe6e1ea19301285fff432b9d11bad01a531e698bea95c781b
languageName: node
linkType: hard

"@gar/promisify@npm:^1.1.3":
version: 1.1.3
resolution: "@gar/promisify@npm:1.1.3"
Expand Down Expand Up @@ -6767,7 +6774,7 @@ __metadata:
languageName: node
linkType: hard

"busboy@npm:^1.0.0, busboy@npm:^1.6.0":
"busboy@npm:^1.0.0":
version: 1.6.0
resolution: "busboy@npm:1.6.0"
dependencies:
Expand Down Expand Up @@ -16216,12 +16223,12 @@ __metadata:
languageName: node
linkType: hard

"undici@npm:^5.8.2":
version: 5.22.1
resolution: "undici@npm:5.22.1"
"undici@npm:^5.26.2":
version: 5.26.4
resolution: "undici@npm:5.26.4"
dependencies:
busboy: ^1.6.0
checksum: 048a3365f622be44fb319316cedfaa241c59cf7f3368ae7667a12323447e1822e8cc3d00f6956c852d1478a6fde1cbbe753f49e05f2fdaed229693e716ebaf35
"@fastify/busboy": ^2.0.0
checksum: 4d37f14ce56837d332ab1623be751f2a5b439069705671cc60b441133300b05bcb8803668132e7b3f98800db19cd6cb76b48831facbdbc2d73271b12383ab3cd
languageName: node
linkType: hard

Expand Down
Loading