Skip to content
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
5 changes: 3 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"
DATABASE_URL="postgresql://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable"
29 changes: 1 addition & 28 deletions src/api/admin/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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) {
Expand Down
22 changes: 14 additions & 8 deletions src/api/challenges/challenges.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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 {
Expand All @@ -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<Challenge>(requestUrl);
Expand All @@ -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 =
Expand All @@ -71,10 +75,10 @@ export class ChallengesService {
async getChallengeResources(challengeId: string) {
try {
const resources = await this.m2MService.m2mFetch<ChallengeResource[]>(
`${TOPCODER_API_V6_BASE_URL}/resources?challengeId=${challengeId}`,
`${TC_API_BASE}/resources?challengeId=${challengeId}`,
);
const resourceRoles = await this.m2MService.m2mFetch<ResourceRole[]>(
`${TOPCODER_API_V6_BASE_URL}/resource-roles`,
`${TC_API_BASE}/resource-roles`,
);

const rolesMap = resourceRoles.reduce(
Expand Down Expand Up @@ -217,7 +221,7 @@ export class ChallengesService {
billingAccountId: challenge.billing.billingAccountId,
payroll: includes(
TGBillingAccounts,
+challenge.billing.billingAccountId,
parseInt(challenge.billing.billingAccountId),
),
},
}));
Expand All @@ -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(
Expand Down
12 changes: 6 additions & 6 deletions src/api/repository/winnings.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -176,7 +176,7 @@ export class WinningsRepository {

const orderBy = this.getOrderByWithWinnerId(
searchProps.sortBy,
searchProps.sortOrder!,
searchProps.sortOrder,
!winnerIds && !!externalIds?.length,
);

Expand Down Expand Up @@ -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
Expand Down
35 changes: 1 addition & 34 deletions src/api/withdrawal/withdrawal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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') {
Expand Down
4 changes: 2 additions & 2 deletions src/config/config.env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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];
}
4 changes: 2 additions & 2 deletions src/shared/topcoder/bus.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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({
Expand Down
52 changes: 0 additions & 52 deletions src/shared/topcoder/challenges.service.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
}
}
6 changes: 3 additions & 3 deletions src/shared/topcoder/members.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 },
Expand Down Expand Up @@ -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, {
Expand Down