From 9dfe7b389dde81554f63b10caf9b30bdd5f282a8 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 29 Sep 2025 10:54:59 +0300 Subject: [PATCH 1/2] PM-2087 - use Topcoder v6 APIs --- src/api/admin/admin.service.ts | 29 +-------- src/api/withdrawal/withdrawal.service.ts | 35 +---------- src/config/config.env.ts | 3 + src/shared/topcoder/bus.service.ts | 4 +- src/shared/topcoder/challenges.service.ts | 72 ----------------------- src/shared/topcoder/members.service.ts | 6 +- 6 files changed, 10 insertions(+), 139 deletions(-) diff --git a/src/api/admin/admin.service.ts b/src/api/admin/admin.service.ts index 56b36af..76fd54c 100644 --- a/src/api/admin/admin.service.ts +++ b/src/api/admin/admin.service.ts @@ -13,10 +13,7 @@ import { ResponseDto } from 'src/dto/api-response.dto'; import { PaymentStatus } from 'src/dto/payment.dto'; import { WinningAuditDto, AuditPayoutDto } from './dto/audit.dto'; import { WinningUpdateRequestDto } from './dto/winnings.dto'; -import { - AdminPaymentUpdateData, - TopcoderChallengesService, -} from 'src/shared/topcoder/challenges.service'; +import { TopcoderChallengesService } from 'src/shared/topcoder/challenges.service'; import { Logger } from 'src/shared/global'; /** @@ -307,30 +304,6 @@ export class AdminService { } }); - transactions.push(async () => { - const winning = await this.getWinningById(winningsId); - if (!winning) { - this.logger.error( - `Error updating legacy system for winning ${winningsId}. Winning not found!`, - ); - throw new Error( - `Error updating legacy system for winning ${winningsId}. Winning not found!`, - ); - } - - const payoutData: AdminPaymentUpdateData = { - userId: +winning.winner_id, - status: body.paymentStatus, - amount: body.paymentAmount, - releaseDate: body.releaseDate, - }; - - await this.tcChallengesService.updateLegacyPayments( - winning.external_id as string, - payoutData, - ); - }); - // Run all transaction tasks in a single prisma transaction await this.prisma.$transaction(async (tx) => { for (const transaction of transactions) { diff --git a/src/api/withdrawal/withdrawal.service.ts b/src/api/withdrawal/withdrawal.service.ts index 10a9179..38b61f1 100644 --- a/src/api/withdrawal/withdrawal.service.ts +++ b/src/api/withdrawal/withdrawal.service.ts @@ -12,10 +12,7 @@ import { } from '@prisma/client'; import { TrolleyService } from 'src/shared/global/trolley.service'; import { PaymentsService } from 'src/shared/payments'; -import { - TopcoderChallengesService, - WithdrawUpdateData, -} from 'src/shared/topcoder/challenges.service'; +import { TopcoderChallengesService } from 'src/shared/topcoder/challenges.service'; import { TopcoderMembersService } from 'src/shared/topcoder/members.service'; import { BasicMemberInfo, BASIC_MEMBER_FIELDS } from 'src/shared/topcoder'; import { Logger } from 'src/shared/global'; @@ -35,16 +32,6 @@ interface ReleasableWinningRow { datePaid: Date; } -function formatDate(date = new Date()) { - const pad = (n, z = 2) => String(n).padStart(z, '0'); - - return ( - `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` + - `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.` + - `${pad(date.getMilliseconds(), 3)}` - ); -} - @Injectable() export class WithdrawalService { private readonly logger = new Logger(WithdrawalService.name); @@ -367,26 +354,6 @@ export class WithdrawalService { this.logger.error(errorMsg, error); throw new Error(errorMsg); } - - try { - for (const winning of winnings) { - const payoutData: WithdrawUpdateData = { - userId: +userId, - status: 'Paid', - datePaid: formatDate(new Date()), - }; - - await this.tcChallengesService.updateLegacyPayments( - winning.externalId as string, - payoutData, - ); - } - } catch (error) { - this.logger.error( - `Failed to update legacy payment while withdrawing for challenge ${error?.message ?? error}`, - error, - ); - } }); } catch (error) { if (error.code === 'P2010' && error.meta?.code === '55P03') { diff --git a/src/config/config.env.ts b/src/config/config.env.ts index fc47a24..b7e096a 100644 --- a/src/config/config.env.ts +++ b/src/config/config.env.ts @@ -22,6 +22,9 @@ export class ConfigEnv { @IsString() TOPCODER_API_BASE_URL!: string; + @IsString() + TOPCODER_API_V5_BASE_URL!: string; + @IsString() AUTH0_M2M_AUDIENCE!: string; diff --git a/src/shared/topcoder/bus.service.ts b/src/shared/topcoder/bus.service.ts index 8397eec..e24c105 100644 --- a/src/shared/topcoder/bus.service.ts +++ b/src/shared/topcoder/bus.service.ts @@ -3,7 +3,7 @@ import { ENV_CONFIG } from 'src/config'; import { TopcoderM2MService } from './topcoder-m2m.service'; import { Logger } from '../global'; -const { TOPCODER_API_BASE_URL } = ENV_CONFIG; +const { TOPCODER_API_V5_BASE_URL: TC_API_BASE } = ENV_CONFIG; @Injectable() export class TopcoderBusService { @@ -43,7 +43,7 @@ export class TopcoderBusService { try { const headers = await this.getHeaders(); - const response = await fetch(`${TOPCODER_API_BASE_URL}/bus/events`, { + const response = await fetch(`${TC_API_BASE}/bus/events`, { method: 'POST', headers, body: JSON.stringify({ diff --git a/src/shared/topcoder/challenges.service.ts b/src/shared/topcoder/challenges.service.ts index fb087df..063a2de 100644 --- a/src/shared/topcoder/challenges.service.ts +++ b/src/shared/topcoder/challenges.service.ts @@ -1,11 +1,7 @@ import { Injectable } from '@nestjs/common'; import { TopcoderM2MService } from './topcoder-m2m.service'; -import { ENV_CONFIG } from 'src/config'; -import { payment_status } from '@prisma/client'; import { Logger } from 'src/shared/global'; -const { TOPCODER_API_BASE_URL } = ENV_CONFIG; - export interface WithdrawUpdateData { userId: number; status: string; @@ -18,77 +14,9 @@ export interface AdminPaymentUpdateData { amount: number; releaseDate: string; } - -const mapStatus = (payoutData: WithdrawUpdateData | AdminPaymentUpdateData) => { - return { - ...payoutData, - status: { - [payment_status.CANCELLED]: 'Cancelled', - [payment_status.FAILED]: 'Failed', - [payment_status.ON_HOLD]: 'OnHold', - [payment_status.ON_HOLD_ADMIN]: 'OnHoldAdmin', - [payment_status.OWED]: 'Owed', - [payment_status.PAID]: 'Paid', - [payment_status.PROCESSING]: 'Processing', - [payment_status.RETURNED]: 'Returned', - }[payoutData.status], - }; -}; - @Injectable() export class TopcoderChallengesService { private readonly logger = new Logger(TopcoderChallengesService.name); constructor(private readonly m2MService: TopcoderM2MService) {} - - async updateLegacyPayments( - challengeId: string, - payoutData: WithdrawUpdateData | AdminPaymentUpdateData, - ) { - const requestData = mapStatus(payoutData); - - let m2mToken: string | undefined; - try { - m2mToken = await this.m2MService.getToken(); - } catch (e) { - this.logger.error( - 'Failed to fetch m2m token for fetching member details!', - e.message ?? e, - ); - } - const requestUrl = `${TOPCODER_API_BASE_URL}/challenges/${challengeId}/legacy-payment`; - - this.logger.debug( - `Updating legacy payment for challenge ${challengeId} with data: ${JSON.stringify(requestData, null, 2)}`, - ); - - try { - const response = await fetch(requestUrl, { - method: 'PATCH', - body: JSON.stringify(requestData), - headers: { - Authorization: `Bearer ${m2mToken}`, - 'Content-Type': 'application/json', - }, - }); - - const jsonResponse: { [key: string]: string } = await response.json(); - - if (response.status > 299) { - throw new Error(jsonResponse.message ?? JSON.stringify(jsonResponse)); - } - - this.logger.debug( - `Response from updating legacy payment for challenge ${challengeId}: ${JSON.stringify(jsonResponse, null, 2)}`, - ); - - return jsonResponse; - } catch (e) { - this.logger.error( - `Failed to update legacy payment for challenge ${challengeId}! Error: ${e?.message ?? e}`, - e, - ); - throw e; - } - } } diff --git a/src/shared/topcoder/members.service.ts b/src/shared/topcoder/members.service.ts index c0bc57f..f8cad54 100644 --- a/src/shared/topcoder/members.service.ts +++ b/src/shared/topcoder/members.service.ts @@ -5,7 +5,7 @@ import { TopcoderM2MService } from './topcoder-m2m.service'; import { ENV_CONFIG } from 'src/config'; import { Logger } from 'src/shared/global'; -const { TOPCODER_API_BASE_URL } = ENV_CONFIG; +const { TOPCODER_API_BASE_URL: TC_API_BASE } = ENV_CONFIG; @Injectable() export class TopcoderMembersService { @@ -27,7 +27,7 @@ export class TopcoderMembersService { // Split the unique user IDs into chunks of 100 to comply with API request limits const requests = chunk(uniqUserIds, 30).map((chunk) => { - const requestUrl = `${TOPCODER_API_BASE_URL}/members?${chunk.map((id) => `userIds[]=${id}`).join('&')}&fields=handle,userId`; + const requestUrl = `${TC_API_BASE}/members?${chunk.map((id) => `userIds[]=${id}`).join('&')}&fields=handle,userId`; return fetch(requestUrl).then( async (response) => (await response.json()) as { handle: string; userId: string }, @@ -77,7 +77,7 @@ export class TopcoderMembersService { e.message ?? e, ); } - const requestUrl = `${TOPCODER_API_BASE_URL}/members/${handle}${fields ? `?fields=${fields.join(',')}` : ''}`; + const requestUrl = `${TC_API_BASE}/members/${handle}${fields ? `?fields=${fields.join(',')}` : ''}`; try { const response = await fetch(requestUrl, { From 41d75cc4a366496ebe3eb2c44df6b1a44f2e1469 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 8 Oct 2025 09:42:49 +0300 Subject: [PATCH 2/2] update sample env --- .env.sample | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.sample b/.env.sample index 370bca4..69d0c09 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,5 @@ -TOPCODER_API_BASE_URL="https://api.topcoder-dev.com/v5" +TOPCODER_API_V5_BASE_URL="https://api.topcoder-dev.com/v5" +TOPCODER_API_V6_BASE_URL="https://api.topcoder-dev.com/v6" AUTH0_CERT="-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEArAV0dmDkedFdlaQ6KQiqUv+UGshfMXx/4jJCLZ9802ynJqAvIt+Z V7EiPqjc2J1xVfJJEvQ9ZS5A2TFWAk16NUTU4LN+TkjEnqeg+LlUPWY3Y4RXa2OU @@ -15,4 +16,4 @@ DB_PASSWORD=randompassword DB_HOST=127.0.0.1 DB_PORT=5434 DB_NAME=walletdb -DATABASE_URL="postgresql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" \ No newline at end of file +DATABASE_URL="postgresql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable"