Skip to content

Commit f260de7

Browse files
committed
Optimizations for export (PS-530)
1 parent cfbfac7 commit f260de7

3 files changed

Lines changed: 78 additions & 28 deletions

File tree

src/api/admin/admin.controller.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
HttpCode,
1010
HttpStatus,
1111
BadRequestException,
12+
InternalServerErrorException,
1213
} from '@nestjs/common';
1314
import {
1415
ApiOperation,
@@ -38,6 +39,8 @@ import { WinningUpdateRequestDto } from './dto/winnings.dto';
3839
@Controller('/admin')
3940
@ApiBearerAuth()
4041
export class AdminController {
42+
private static readonly EXPORT_BATCH_SIZE = 1000;
43+
4144
constructor(
4245
private readonly adminService: AdminService,
4346
private readonly winningsRepo: WinningsRepository,
@@ -118,22 +121,52 @@ export class AdminController {
118121
@Header('Content-Type', 'text/csv')
119122
@Header('Content-Disposition', 'attachment; filename="winnings.csv"')
120123
async exportWinnings(@Body() body: WinningRequestDto, @User() user: any) {
121-
const result = await this.winningsRepo.searchWinnings(
122-
await this.adminService.applyBaAdminUserFilters(
123-
user.id,
124-
this.isBaAdmin(user),
124+
const baseFilters = await this.adminService.applyBaAdminUserFilters(
125+
user.id,
126+
this.isBaAdmin(user),
127+
{
128+
...body,
129+
limit: undefined,
130+
offset: undefined,
131+
},
132+
);
133+
134+
const winnings: SearchWinningResult['winnings'] = [];
135+
let offset = 0;
136+
137+
while (true) {
138+
const result = await this.winningsRepo.searchWinnings(
125139
{
126-
...body,
127-
limit: 999,
140+
...baseFilters,
141+
limit: AdminController.EXPORT_BATCH_SIZE,
142+
offset,
128143
},
129-
),
130-
);
144+
{
145+
includeCount: false,
146+
includePayoutStatus: false,
147+
},
148+
);
149+
150+
if (result.error || !result.data) {
151+
throw new InternalServerErrorException(
152+
result.error?.message ?? 'Export winnings failed',
153+
);
154+
}
155+
156+
winnings.push(...result.data.winnings);
157+
158+
if (result.data.winnings.length < AdminController.EXPORT_BATCH_SIZE) {
159+
break;
160+
}
161+
162+
offset += AdminController.EXPORT_BATCH_SIZE;
163+
}
131164

132165
const handles = await this.tcMembersService.getHandlesByUserIds(
133-
result.data.winnings.map((d) => d.winnerId),
166+
winnings.map((d) => d.winnerId),
134167
);
135168

136-
const csvRes = result.data.winnings.map((item) => {
169+
const csvRes = winnings.map((item) => {
137170
const payment =
138171
item.details && item.details.length > 0 ? item.details[0] : null;
139172

src/api/admin/admin.service.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ export class AdminService {
4949
const baRows = await baPrisma.billingAccountAccess.findMany({
5050
where: {
5151
userId,
52-
}
52+
},
5353
});
5454

55-
return baRows.map(r => `${r.billingAccountId}`);
55+
return baRows.map((r) => `${r.billingAccountId}`);
5656
}
5757

5858
async applyBaAdminUserFilters(
@@ -109,8 +109,8 @@ export class AdminService {
109109
},
110110
select: {
111111
billing_account: true,
112-
}
113-
});;
112+
},
113+
});
114114

115115
if (!payments || payments.length === 0) {
116116
// nothing to check
@@ -173,8 +173,6 @@ export class AdminService {
173173
throw new NotFoundException('failed to get current payments');
174174
}
175175

176-
177-
178176
let releaseDate;
179177
if (body.paymentStatus) {
180178
releaseDate = await this.getPaymentReleaseDateByWinningsId(winningsId);

src/api/repository/winnings.repo.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import { Logger } from 'src/shared/global';
2121

2222
const ONE_DAY = 24 * 60 * 60 * 1000;
2323

24+
interface SearchWinningsOptions {
25+
includeCount?: boolean;
26+
includePayoutStatus?: boolean;
27+
}
28+
2429
@Injectable()
2530
export class WinningsRepository {
2631
private readonly logger = new Logger(WinningsRepository.name);
@@ -162,8 +167,11 @@ export class WinningsRepository {
162167
*/
163168
async searchWinnings(
164169
searchProps: WinningRequestDto,
170+
options: SearchWinningsOptions = {},
165171
): Promise<ResponseDto<SearchWinningResult>> {
166172
const result = new ResponseDto<SearchWinningResult>();
173+
const includeCount = options.includeCount ?? true;
174+
const includePayoutStatus = options.includePayoutStatus ?? true;
167175

168176
try {
169177
let winnerIds: string[] | undefined;
@@ -202,7 +210,9 @@ export class WinningsRepository {
202210
!winnerIds && !!externalIds?.length,
203211
);
204212

205-
const winnings = await this.prisma.winnings.findMany({
213+
const limit = searchProps.limit ?? 10;
214+
const offset = searchProps.offset ?? 0;
215+
const winningsPromise = this.prisma.winnings.findMany({
206216
where: queryWhere,
207217
include: {
208218
payment: {
@@ -218,15 +228,22 @@ export class WinningsRepository {
218228
origin: true,
219229
},
220230
orderBy,
221-
skip: searchProps.offset,
222-
take: searchProps.limit,
231+
skip: offset,
232+
take: limit,
223233
});
224234

225-
const count = await this.prisma.winnings.count({ where: queryWhere });
235+
const [winnings, count] = includeCount
236+
? await Promise.all([
237+
winningsPromise,
238+
this.prisma.winnings.count({ where: queryWhere }),
239+
])
240+
: [await winningsPromise, 0];
226241

227-
const usersPayoutStatusMap = winnings?.length
228-
? await this.getUsersPayoutStatusForWinnings(winnings)
229-
: ({} as { [key: string]: payment_status });
242+
const usersPayoutStatusMap: Record<string, unknown> =
243+
includePayoutStatus && winnings?.length
244+
? await this.getUsersPayoutStatusForWinnings(winnings)
245+
: {};
246+
const totalItems = includeCount ? count : winnings.length;
230247

231248
result.data = {
232249
winnings: winnings.map((item) => ({
@@ -257,13 +274,15 @@ export class WinningsRepository {
257274
item.payment?.[0].updated_at ??
258275
undefined) as Date,
259276
releaseDate: item.payment?.[0]?.release_date as Date,
260-
paymentStatus: usersPayoutStatusMap[item.winner_id],
277+
paymentStatus: usersPayoutStatusMap[
278+
item.winner_id
279+
] as WinningDto['paymentStatus'],
261280
})),
262281
pagination: {
263-
totalItems: count,
264-
totalPages: Math.ceil(count / searchProps.limit),
265-
pageSize: searchProps.limit,
266-
currentPage: Math.ceil(searchProps.offset / searchProps.limit) + 1,
282+
totalItems,
283+
totalPages: Math.ceil(totalItems / limit),
284+
pageSize: limit,
285+
currentPage: Math.ceil(offset / limit) + 1,
267286
},
268287
};
269288
// response.data = winnings as any

0 commit comments

Comments
 (0)