Skip to content

Commit bda7ea2

Browse files
authored
Merge pull request #144 from topcoder-platform/dev
Export optimization (PS-530)
2 parents cfc4e01 + f7f28a1 commit bda7ea2

3 files changed

Lines changed: 71 additions & 18 deletions

File tree

src/api/admin/admin.controller.ts

Lines changed: 39 additions & 5 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,
@@ -37,6 +38,8 @@ import { AccessControlService } from 'src/shared/access-control';
3738
@Controller('/admin')
3839
@ApiBearerAuth()
3940
export class AdminController {
41+
private static readonly EXPORT_BATCH_SIZE = 1000;
42+
4043
constructor(
4144
private readonly adminService: AdminService,
4245
private readonly winningsRepo: WinningsRepository,
@@ -113,22 +116,53 @@ export class AdminController {
113116
@Header('Content-Type', 'text/csv')
114117
@Header('Content-Disposition', 'attachment; filename="winnings.csv"')
115118
async exportWinnings(@Body() body: WinningRequestDto, @User() user: any) {
116-
const filters =
119+
const baseFilters =
117120
await this.accessControlService.applyFilters<WinningRequestDto>(
118121
user.id,
119122
user.roles,
120123
{
121124
...body,
122-
limit: 999,
125+
limit: undefined,
126+
offset: undefined,
123127
},
124128
);
125-
const result = await this.winningsRepo.searchWinnings(filters);
129+
130+
const winnings: SearchWinningResult['winnings'] = [];
131+
let offset = 0;
132+
133+
while (true) {
134+
const result = await this.winningsRepo.searchWinnings(
135+
{
136+
...baseFilters,
137+
limit: AdminController.EXPORT_BATCH_SIZE,
138+
offset,
139+
},
140+
{
141+
includeCount: false,
142+
includePayoutStatus: false,
143+
},
144+
);
145+
146+
if (result.error || !result.data) {
147+
throw new InternalServerErrorException(
148+
result.error?.message ?? 'Export winnings failed',
149+
);
150+
}
151+
152+
winnings.push(...result.data.winnings);
153+
154+
if (result.data.winnings.length < AdminController.EXPORT_BATCH_SIZE) {
155+
break;
156+
}
157+
158+
offset += AdminController.EXPORT_BATCH_SIZE;
159+
}
126160

127161
const handles = await this.tcMembersService.getHandlesByUserIds(
128-
result.data.winnings.map((d) => d.winnerId),
162+
winnings.map((d) => d.winnerId),
129163
);
130164

131-
const csvRes = result.data.winnings.map((item) => {
165+
const csvRes = winnings.map((item) => {
132166
const payment =
133167
item.details && item.details.length > 0 ? item.details[0] : null;
134168

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

src/shared/access-control/engagement-pa.provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class EngagementPaymentApproverProvider implements RoleAccessProvider {
3030
);
3131
if (unauthorized.length > 0) {
3232
throw new Error(
33-
`${Role.EngagementPaymentApprover} user is trying to access winning with category='${unauthorized.map(w => w.category).join(', ')}'`,
33+
`${Role.EngagementPaymentApprover} user is trying to access winning with category='${unauthorized.map((w) => w.category).join(', ')}'`,
3434
);
3535
}
3636
}

0 commit comments

Comments
 (0)