Skip to content

[ PROD] - finance API release (Engagements and points supports)#139

Merged
jmgasper merged 48 commits into
masterfrom
dev
Feb 4, 2026
Merged

[ PROD] - finance API release (Engagements and points supports)#139
jmgasper merged 48 commits into
masterfrom
dev

Conversation

@kkartunov

Copy link
Copy Markdown
Contributor

No description provided.

jmgasper and others added 30 commits January 7, 2026 09:40
…heckpoints

PM-2688 payments for checkpoints
…-winnings

Pm 3242 support points winnings
Updates to allow Task managers to create payments -> dev
PM-3260 - add winnings category & type filters
export enum Role {
Administrator = 'Administrator',
PaymentAdmin = 'Payment Admin',
PaymentBaAdmin = 'Payment BA Admin',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 readability]
Consider using consistent naming conventions for enum values. The value PaymentBaAdmin uses an abbreviation (Ba) which might not be immediately clear to all developers. Consider using PaymentBusinessAdmin for clarity.

};

Object.keys(auth0User).forEach((key) => {
if (key.match(/\/roles$/gi) || key === 'roles' || key === 'role') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
Using key.match(/\/roles$/gi) may lead to unexpected matches if there are keys with similar suffixes. Consider using a more precise check or a stricter pattern to avoid false positives.


Object.keys(auth0User).forEach((key) => {
if (key.match(/\/roles$/gi) || key === 'roles' || key === 'role') {
appendRoles(auth0User[key]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
The appendRoles function is defined inside collectUserRoles and is called multiple times. Consider extracting it to a separate method to improve readability and maintainability.

}
if (typeof value === 'string' && value.trim()) {
value
.split(',')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The method collectUserRoles is introduced but not shown in the diff. Ensure that it correctly replicates the previous logic of extracting roles from auth0User keys ending with /roles. Any deviation might lead to incorrect role extraction.

return roles;
}, []);
const userRoles = this.collectUserRoles(auth0User);
const normalizedUserRoles = new Set(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
Using a Set for normalizedUserRoles is a good choice for performance when checking membership, but ensure that normalizeRole is deterministic and idempotent to avoid unexpected behavior.

const normalizedUserRoles = new Set(
userRoles.map((role) => this.normalizeRole(role)),
);
const normalizedRequiredRoles = requiredRoles.map((role) =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ performance]
Consider using a Set for normalizedRequiredRoles as well, to maintain consistency and potentially improve performance when checking for role inclusion.

id: userId,
handle: userHandle,
email: request.email,
roles: userRoles,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ security]
Ensure that userRoles is properly validated and sanitized before being assigned to roles. This is important to prevent potential security issues such as injection attacks if userRoles originates from an untrusted source.

Comment thread src/dto/challenge.dto.ts
Active: 'ACTIVE',
Draft: 'DRAFT',
Approved: 'APPROVED',
Canceled: 'CANCELLED',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The spelling of 'Canceled' has been changed to 'Cancelled'. Ensure that this change is intentional and consistent across the codebase, as it may affect any logic or database entries that rely on the previous spelling.

Comment thread src/dto/payment.dto.ts Outdated
@@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, Min, IsInt, IsString, IsNotEmpty } from 'class-validator';
import { IsNumber, Min, IsInt, IsString, IsNotEmpty, isEnum, IsEnum } from 'class-validator';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
The isEnum import is unused. Consider removing it to keep the imports clean and maintainable.

Comment thread src/dto/payment.dto.ts
example: 'USD',
})
@IsString()
@IsEnum(PrizeType)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The @IsEnum(PrizeType) decorator suggests that currency should be an enum type, but it is still declared as a string. This mismatch can lead to runtime errors or validation issues. Consider changing the type of currency to match the enum or adjust the validation decorator accordingly.

Comment thread src/dto/wallet.dto.ts
unit: 'currency',
},
{
type: 'POINTS',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 readability]
Consider using a consistent naming convention for type values. The existing currency is lowercase, while the new POINTS is uppercase. Consistency improves readability and reduces potential errors.

Comment thread src/dto/winning.dto.ts
@IsOptional()
@IsEnum(WinningsCategory)
type?: WinningsCategory;
category?: WinningsCategory;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
Renaming type to category may affect existing code that relies on the type property. Ensure that all references to type are updated accordingly throughout the codebase to prevent runtime errors.

Comment thread src/dto/winning.dto.ts
})
@IsOptional()
@IsEnum(WinningsType)
type?: WinningsType;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ design]
The introduction of the type property as WinningsType is a breaking change if this DTO is used in existing APIs. Ensure that consumers of this DTO are aware of this change and that it is documented appropriately.

Comment thread src/dto/winning.dto.ts
})
@IsOptional()
@IsArray()
@ArrayNotEmpty()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
The @ArrayNotEmpty() decorator ensures that the array is not empty, but the @IsOptional() decorator above allows billingAccounts to be undefined. Consider whether this is the intended behavior, as it might lead to confusion. If billingAccounts should be either undefined or a non-empty array, this is correct. Otherwise, you may want to adjust the decorators to reflect the intended validation logic.

if (!baPrismaClient) {
if (!ENV_CONFIG.BILLING_ACCOUNTS_DB_URL) {
throw new Error(
'BILLING_ACCOUNTS_DB_URL must be set for challenges Prisma client',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 readability]
The error message should specify that it is related to the billing accounts database URL to avoid confusion with other potential environment variables.

'BILLING_ACCOUNTS_DB_URL must be set for challenges Prisma client',
);
}
baPrismaClient = new BaPrismaClient({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Consider adding error handling for the BaPrismaClient instantiation to catch and log any potential errors during client creation. This will help in diagnosing issues if the client fails to initialize.

@@ -1 +1,2 @@
export * from './logger';
export * from './ba-prisma.client';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Exporting everything from ./ba-prisma.client can lead to potential namespace conflicts or unintended exports. Consider explicitly exporting only the necessary modules to improve maintainability and avoid accidental exposure of internal modules.

(sum, payment) => sum + payment.details[0].totalAmount,
// compute USD totals for BA validation/locking (POINT payments are persisted but not billed)
const totalUsdAmount = payments.reduce(
(sum, payment) =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
Consider adding a check to ensure payment.details is not empty before accessing payment.details[0]. This will prevent potential runtime errors if payment.details is unexpectedly empty.

error.message ?? error,
);
if (
error &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The check for error being truthy is removed. Ensure that error is always defined before accessing error.message to avoid potential runtime errors.


const payrollPayment = (body.attributes || {})['payroll'] === true;
const isPointsAward = body.category === WinningsCategory.POINTS_AWARD;
const requestedStatus = body.status as payment_status | undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
Casting body.status to payment_status without validation could lead to runtime errors if body.status is not a valid payment_status. Consider validating the value before casting to ensure type safety.

? PaymentStatus.OWED
: PaymentStatus.ON_HOLD;
let resolvedStatus: payment_status;
if (requestedStatus) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The variable resolvedStatus is declared with the type payment_status, which is not defined in the provided diff. Ensure that payment_status is a valid type or enum. If it's a typo, it should be corrected to PaymentStatus.

);
resolvedStatus = PaymentStatus.PAID;
} else if (body.category === WinningsCategory.POINTS_AWARD) {
resolvedStatus = payment_status.CREDITED;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The use of payment_status.CREDITED suggests that payment_status might be an incorrect reference. Ensure that payment_status is correctly defined or replace it with PaymentStatus if that was the intended enum.

Comment thread src/dto/payment.dto.ts
IsInt,
IsString,
IsNotEmpty,
isEnum,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 maintainability]
The isEnum import is not used anywhere in the current diff. Consider removing it to avoid unnecessary imports, which can improve maintainability.

Comment thread src/dto/winning.dto.ts
required: false,
})
@IsOptional()
@IsEnum(PaymentStatus)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ correctness]
Consider adding a default value for status to ensure consistent behavior when the property is not provided. This can prevent potential issues where the absence of a status might lead to unexpected behavior in the application logic.

setupComplete: boolean;
}[]
>`
WITH u(user_id) AS (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ security]
Using Prisma.join with Prisma.sql for constructing SQL queries can potentially lead to SQL injection vulnerabilities if not handled correctly. Ensure that ids are properly sanitized or validated before being used in this query. Consider using parameterized queries to mitigate this risk.

@jmgasper jmgasper changed the title [DO NOT MERGE - PROD] - finance API release [ PROD] - finance API release (Engagements and points supports) Feb 4, 2026
private readonly prisma: PrismaService,
private readonly paymentsService: PaymentsService,
private readonly tcChallengesService: TopcoderChallengesService,
private readonly baService: BillingAccountsService,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
The baService dependency is introduced but not used in the current diff. Ensure that this service is necessary for the class, or remove it to avoid unused dependencies.


return {
...filters,
billingAccounts: await this.baService.getBillingAccountsForUser(userId),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The change from this.getBillingAccountsForUser(userId) to this.baService.getBillingAccountsForUser(userId) introduces a dependency on baService. Ensure that baService is properly initialized and injected into the AdminService class to avoid potential runtime errors.

return;
}

const allowedBAs = await this.baService.getBillingAccountsForUser(userId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ correctness]
The method getBillingAccountsForUser has been moved from AdminService to baService. Ensure that baService is correctly initialized and available in this context to prevent runtime errors.

'BA admin user does not have access to the billing account for this winnings',
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 style]
There is an extra semicolon at the end of the line. While this does not affect functionality, it is unnecessary and could be removed for cleaner code.

`Failed to fetch billing accounts for user '${userId}'!`,
err,
);
throw new Error('Failed to fetch billing acccounts!');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[💡 correctness]
The error message contains a typo: 'acccounts' should be 'accounts'.

.m2mFetch<
{ tcBillingAccountId: string }[]
>(`${TOPCODER_API_V6_BASE_URL}/billing-accounts/users/${userId}`)
.then((r) => r.map((b) => `${b.tcBillingAccountId}`));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ readability]
Consider using await instead of .then() for consistency with the async/await pattern used in the rest of the method. This improves readability and makes error handling more straightforward.

.then((r) => r.map((b) => `${b.tcBillingAccountId}`));
} catch (err: any) {
this.logger.error(
err.response?.data?.result?.content ??

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
The error logging could be improved by providing more context about the error. Consider including the userId in the log message to aid in debugging.

@jmgasper
jmgasper merged commit e5322a5 into master Feb 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants