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" 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/challenges/challenges.service.ts b/src/api/challenges/challenges.service.ts index 71e8d88..b4aaf2a 100644 --- a/src/api/challenges/challenges.service.ts +++ b/src/api/challenges/challenges.service.ts @@ -12,7 +12,11 @@ import { BillingAccountsService } from 'src/shared/topcoder/billing-accounts.ser import { TopcoderM2MService } from 'src/shared/topcoder/topcoder-m2m.service'; import { ChallengeStatuses } from 'src/dto/challenge.dto'; import { WinningsService } from '../winnings/winnings.service'; -import { WinningsCategory, WinningsType } from 'src/dto/winning.dto'; +import { + WinningRequestDto, + WinningsCategory, + WinningsType, +} from 'src/dto/winning.dto'; import { WinningsRepository } from '../repository/winnings.repo'; const placeToOrdinal = (place: number) => { @@ -23,7 +27,7 @@ const placeToOrdinal = (place: number) => { return `${place}th`; }; -const { TOPCODER_API_V6_BASE_URL, TGBillingAccounts } = ENV_CONFIG; +const { TOPCODER_API_V6_BASE_URL: TC_API_BASE, TGBillingAccounts } = ENV_CONFIG; @Injectable() export class ChallengesService { @@ -37,7 +41,7 @@ export class ChallengesService { ) {} async getChallenge(challengeId: string) { - const requestUrl = `${TOPCODER_API_V6_BASE_URL}/challenges/${challengeId}`; + const requestUrl = `${TC_API_BASE}/challenges/${challengeId}`; try { const challenge = await this.m2MService.m2mFetch(requestUrl); @@ -51,7 +55,7 @@ export class ChallengesService { } async getChallengeSubmissionsCount(challengeId: string) { - const requestUrl = `${TOPCODER_API_V6_BASE_URL}/submissions?challengeId=${challengeId}&perPage=9999`; + const requestUrl = `${TC_API_BASE}/submissions?challengeId=${challengeId}&perPage=9999`; try { const submissions = @@ -71,10 +75,10 @@ export class ChallengesService { async getChallengeResources(challengeId: string) { try { const resources = await this.m2MService.m2mFetch( - `${TOPCODER_API_V6_BASE_URL}/resources?challengeId=${challengeId}`, + `${TC_API_BASE}/resources?challengeId=${challengeId}`, ); const resourceRoles = await this.m2MService.m2mFetch( - `${TOPCODER_API_V6_BASE_URL}/resource-roles`, + `${TC_API_BASE}/resource-roles`, ); const rolesMap = resourceRoles.reduce( @@ -217,7 +221,7 @@ export class ChallengesService { billingAccountId: challenge.billing.billingAccountId, payroll: includes( TGBillingAccounts, - +challenge.billing.billingAccountId, + parseInt(challenge.billing.billingAccountId), ), }, })); @@ -240,7 +244,9 @@ export class ChallengesService { } const existingPayments = ( - await this.winningsRepo.searchWinnings({ externalIds: [challengeId] }) + await this.winningsRepo.searchWinnings({ + externalIds: [challengeId], + } as WinningRequestDto) )?.data?.winnings; if (existingPayments?.length > 0) { this.logger.log( diff --git a/src/api/repository/winnings.repo.ts b/src/api/repository/winnings.repo.ts index 798e50b..c6d932b 100644 --- a/src/api/repository/winnings.repo.ts +++ b/src/api/repository/winnings.repo.ts @@ -56,8 +56,8 @@ export class WinningsRepository { private getWinningsQueryFilters( type?: string, status?: string, - winnerIds?: string[] | undefined, - externalIds?: string[] | undefined, + winnerIds?: string[], + externalIds?: string[], date?: DateFilterType, ): Prisma.winningsFindManyArgs['where'] { return { @@ -176,7 +176,7 @@ export class WinningsRepository { const orderBy = this.getOrderByWithWinnerId( searchProps.sortBy, - searchProps.sortOrder!, + searchProps.sortOrder, !winnerIds && !!externalIds?.length, ); @@ -240,9 +240,9 @@ export class WinningsRepository { })), pagination: { totalItems: count, - totalPages: Math.ceil(count / searchProps.limit!), - pageSize: searchProps.limit!, - currentPage: Math.ceil(searchProps.offset! / searchProps.limit!) + 1, + totalPages: Math.ceil(count / searchProps.limit), + pageSize: searchProps.limit, + currentPage: Math.ceil(searchProps.offset / searchProps.limit) + 1, }, }; // response.data = winnings as any 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 62e7ad4..529eaf4 100644 --- a/src/config/config.env.ts +++ b/src/config/config.env.ts @@ -20,7 +20,7 @@ export class ConfigEnv { PORT = 3000; @IsString() - TOPCODER_API_BASE_URL!: string; + TOPCODER_API_V5_BASE_URL!: string; @IsString() TOPCODER_API_V6_BASE_URL!: string; @@ -114,6 +114,6 @@ export class ConfigEnv { @IsString() SENDGRID_TEMPLATE_ID_OTP_CODE: string = 'd-2d0ab9f6c9cc4efba50080668a9c35c1'; - @IsInt({each: true}) + @IsInt({ each: true }) TGBillingAccounts = [80000062, 80002800]; } 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 308ed48..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,57 +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); - 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 this.m2MService.m2mFetch(requestUrl, { - method: 'PATCH', - body: JSON.stringify(requestData), - }); - - this.logger.debug( - `Response from updating legacy payment for challenge ${challengeId}: ${JSON.stringify(response, null, 2)}`, - ); - - return response; - } 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..a7ad113 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_V6_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, {