Skip to content

Conversation

billsedison
Copy link
Collaborator

No description provided.

@billsedison billsedison requested review from jmgasper and kkartunov June 3, 2025 13:55
.env.sample Outdated
M2M_AUTH_CLIENT_SECRET="ldzqVaVEbqhwjM5KtZ79sG8djZpAVK8Z7qieVcC3vRjI4NirgcinKSBpPwk6mYYP"
M2M_AUTH_DOMAIN="topcoder-dev.auth0.com"
M2M_AUTH_AUDIENCE="https://m2m.topcoder-dev.com/"
M2M_AUTH_PROXY_SEREVR_URL=
Copy link

Choose a reason for hiding this comment

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

The variable M2M_AUTH_PROXY_SEREVR_URL seems to have a typo in its name. It should likely be M2M_AUTH_PROXY_SERVER_URL.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Let's fix this thanks.

mock/mock-api.js Outdated
res.json({})
})

const m2mToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoiakdJZjJwZDNmNDRCMWpxdk9haTMwQklLVFphbllCZlVAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNzQ4MDk5NDk4LCJleHAiOjE4NDgxODU4OTgsInNjb3BlIjoid3JpdGU6YnVzX2FwaSxhbGw6Y2hhbGxlbmdlcyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyIsImF6cCI6ImpHSWYycGQzZjQ0QjFqcXZPYWkzMEJJS1RaYW5ZQmZVIn0.h3ksdsdJm5USGF1VgROrpkTtStmCzv5ZA6y8bd8AnGY';
Copy link

Choose a reason for hiding this comment

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

Storing sensitive information such as tokens directly in the code is not secure. Consider using environment variables or a secure vault to manage sensitive data.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - We can't put these into any code, even if they are long since expired, due to security headaches they cause us in Wipro scans. Let's remove this and make it an env variable.

// Event bus
app.post('/eventBus', (req, res) => {
logger.info(`Event Bus received message: ${JSON.stringify(req.body)}`);
res.statusCode = 200;
Copy link

Choose a reason for hiding this comment

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

Instead of setting res.statusCode = 200;, use res.status(200); for consistency and readability.

app.get('/members', (req, res) => {
logger.info(`Member API receives params: ${JSON.stringify(req.query)}`)
let userIdStr = req.query.userIds
userIdStr = userIdStr.replaceAll('[', '').replaceAll(']', '')
Copy link

Choose a reason for hiding this comment

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

Using replaceAll might not be supported in all environments. Consider using a more compatible method such as replace with a regular expression.

// directly challenge details
const id = req.params.id
logger.info(`Getting challenge with id ${id}`)
if (id === '11111111-2222-3333-9999-444444444444') {
Copy link

Choose a reason for hiding this comment

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

Consider using res.status(404).json({}); for setting the status and sending the response in a single chain for better readability.


'@types/[email protected]':
resolution: {integrity: sha512-iJbM7nsyBgnxCrCe7VjWIi4nyyhlaKUl7jxeHDpK+KXk3sYrUZViMkgFv9qSZmxDleB8dfpQR9gK5MGNyM/M6w==}
deprecated: This is a stub types definition. express-unless provides its own type definitions, so you do not need this installed.
Copy link

Choose a reason for hiding this comment

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

The entry for @types/[email protected] indicates that it is deprecated and not needed because express-unless provides its own type definitions. Consider removing this dependency to avoid unnecessary installations and potential conflicts.


[email protected]:
resolution: {integrity: sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==}
engines: {'0': node >=0.10.0}
Copy link

Choose a reason for hiding this comment

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

The engines field uses an unconventional format with a numeric key '0'. Typically, this field should directly specify the engine requirements without a numeric key. Consider using engines: {node: '>=0.10.0'} for consistency and clarity.

engines: {node: 20 || >=22}
hasBin: true

[email protected]:
Copy link

Choose a reason for hiding this comment

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

The addition of [email protected] is using a deprecated version. Consider updating to a supported version of the glob package to avoid potential issues with unsupported versions.

[email protected]:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}

[email protected]:
Copy link

Choose a reason for hiding this comment

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

The addition of [email protected] alongside [email protected] could lead to potential conflicts or inconsistencies. Consider verifying if both versions are necessary and if not, consolidate to a single version to avoid dependency issues.

[email protected]:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}

[email protected]:
Copy link

Choose a reason for hiding this comment

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

The package [email protected] seems to be added, but there is already a similar package [email protected] present. Please verify if both packages are necessary or if this is a duplication error.


[email protected]:
resolution: {integrity: sha512-xx0kgFxSHWY9aG1109uv4w2b+JLwHseSowOWo1bzCTDBpUk3er2rZdtQ90mAjUYbkh6Hus9DAwWvmHsX5pHaIQ==}
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
Copy link

Choose a reason for hiding this comment

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

The engines field specifies compatibility with Node.js versions. Ensure that the specified versions align with the project's requirements and do not introduce compatibility issues with other dependencies.


[email protected]:
resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==}
deprecated: Rimraf versions prior to v4 are no longer supported
Copy link

Choose a reason for hiding this comment

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

The deprecated field indicates that versions prior to v4 of Rimraf are no longer supported. Consider updating to a supported version to avoid potential issues.


'@types/[email protected]':
dependencies:
'@types/express': 5.0.0
Copy link

Choose a reason for hiding this comment

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

The version specified for @types/express is 5.0.0, which is a major version update. Ensure that this version is compatible with the rest of the codebase and other dependencies that rely on @types/express.

optionalDependencies:
graceful-fs: 4.2.11

[email protected]:
Copy link

Choose a reason for hiding this comment

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

Consider updating jsonwebtoken to the latest version if possible, as version 9.0.2 is already listed in the dependencies. This could help avoid potential security vulnerabilities or bugs present in older versions.

[email protected]:
dependencies:
'@types/express-jwt': 0.0.42
axios: 0.21.4([email protected])
Copy link

Choose a reason for hiding this comment

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

The dependency 'axios' is listed with a version and a nested dependency '[email protected]' in parentheses. This format is unusual and may cause confusion. Consider separating 'axios' and 'debug' into distinct entries if they are intended to be separate dependencies.

dependencies:
wrappy: 1.0.2

[email protected]:
Copy link

Choose a reason for hiding this comment

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

The package [email protected] seems to be added here. Please verify if this is the correct version and if it is necessary to include both [email protected] and [email protected] as they might be different packages or versions of the same package. Ensure there is no conflict or redundancy.

dependencies:
semver: 7.7.1

[email protected]: {}
Copy link

Choose a reason for hiding this comment

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

The addition of [email protected] seems unnecessary if it is not being used anywhere in the project. Consider removing it to keep the dependencies clean.

[email protected]:
dependencies:
debug: 4.3.6
debug: 4.4.0
Copy link

Choose a reason for hiding this comment

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

The update of debug from version 4.3.6 to 4.4.0 should be verified for compatibility with the rest of the codebase. Ensure that there are no breaking changes that could affect the functionality.

"id" VARCHAR(14) NOT NULL DEFAULT nanoid(),
"userId" TEXT NOT NULL,
"handle" TEXT NOT NULL,
"opportunityId" TEXT NOT NULL,
Copy link

Choose a reason for hiding this comment

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

The column opportunityId is defined as TEXT NOT NULL, but it references the id column in the reviewOpportunity table, which is defined as VARCHAR(14). Consider using the same data type (VARCHAR(14)) for consistency and to avoid potential issues with data integrity.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Should we fix this?

"incrementalPayment" DOUBLE PRECISION NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdBy" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
Copy link

Choose a reason for hiding this comment

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

Consider adding a default value for the updatedAt column to ensure it is automatically set when a new record is created.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Should we set a default for this?

"status" "ReviewApplicationStatus" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdBy" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
Copy link

Choose a reason for hiding this comment

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

Consider adding a default value for the updatedAt column to ensure it is automatically set when a new record is created.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Should we set a default for this?

@@unique([challengeId, type])
@@index([id]) // Index for direct ID lookups
Copy link

Choose a reason for hiding this comment

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

The index on id is redundant because id is already a primary key, which is indexed by default. Consider removing this index.

@@unique([opportunityId, userId, role])
@@index([id]) // Index for direct ID lookups
Copy link

Choose a reason for hiding this comment

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

The index on id is redundant because id is already a primary key, which is indexed by default. Consider removing this index.

@@ -1,22 +1,32 @@
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
Copy link

Choose a reason for hiding this comment

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

Consider checking if HttpModule is actually used within this module. If it's not being utilized, importing it might be unnecessary and could be removed to keep the module clean and efficient.


@Module({
imports: [GlobalProvidersModule],
imports: [HttpModule, GlobalProvidersModule],
Copy link

Choose a reason for hiding this comment

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

Suggestion:

Consider verifying if the addition of HttpModule to the imports is necessary for the new controllers or services. If it is not required, it might be better to avoid adding it to keep the module dependencies minimal.

ProjectResultController,
ReviewOpportunityController,
ReviewApplicationController,
ReviewHistoryController
Copy link

Choose a reason for hiding this comment

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

Consider verifying if ReviewHistoryController is correctly implemented and necessary for the current module. Ensure it aligns with the module's responsibilities.

ReviewHistoryController
],
providers: [],
providers: [ReviewOpportunityService, ReviewApplicationService, ChallengeApiService],
Copy link

Choose a reason for hiding this comment

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

Ensure that ReviewOpportunityService, ReviewApplicationService, and ChallengeApiService are properly implemented and necessary for this module. Verify that they are being used effectively within the module's context.

async getByUserId(@Req() req: Request, @Param('userId') userId: string) {
// Check user permission. Only admin and user himself can access
const authUser: JwtUser = req['user'] as JwtUser;
if (authUser.userId !== userId && !isAdmin(authUser)) {
Copy link

Choose a reason for hiding this comment

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

Consider using a constant or enum for the error message to maintain consistency and ease of maintenance.

@ApiOperation({
summary: 'Get applications by opportunity ID',
description:
'All users should be able to see full list. Including anonymous.',
Copy link

Choose a reason for hiding this comment

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

The description mentions that all users, including anonymous, should be able to see the full list. Consider adding appropriate authentication or authorization checks if needed to ensure this behavior aligns with security requirements.

const handle = authUser.handle as string;
// make sure review opportunity exists
const opportunity = await this.prisma.reviewOpportunity.findUnique({
where: { id: dto.opportunityId}
Copy link

Choose a reason for hiding this comment

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

Consider adding a semicolon at the end of the object in the findUnique method for consistency with the rest of the code.

* @param entityList review application entity list
* @param status application status
*/
private async sendEmails(entityList, status: ReviewApplicationStatus) {
Copy link

Choose a reason for hiding this comment

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

Specify the type for the entityList parameter to improve type safety and readability.

* @param entity prisma entity
* @returns response dto
*/
private buildResponse(entity): ReviewApplicationResponseDto {
Copy link

Choose a reason for hiding this comment

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

Specify the type for the entity parameter to improve type safety and readability.

})
@ApiResponse({ status: 400, description: 'Bad Request' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiResponse({ status: 403, description: 'Unauthorized' })
Copy link

Choose a reason for hiding this comment

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

The description for the 403 status code should be 'Forbidden' instead of 'Unauthorized' to accurately reflect the nature of the error.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Let's fix this, thanks.

@ApiBearerAuth()
@Roles(UserRole.Reviewer, UserRole.Admin)
@Get('/:userId')
async getHistory(@Req() req: Request, @Param('userId') userId: string, @Query('range') range: number): Promise<ResponseDto<ReviewApplicationResponseDto[]>> {
Copy link

Choose a reason for hiding this comment

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

The range parameter should be validated to ensure it is a positive number. Consider adding validation logic to handle cases where range might be negative or not a number.

@Get('/:userId')
async getHistory(@Req() req: Request, @Param('userId') userId: string, @Query('range') range: number): Promise<ResponseDto<ReviewApplicationResponseDto[]>> {
// Check user permission
const authUser: JwtUser = req['user'] as JwtUser;
Copy link

Choose a reason for hiding this comment

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

The type assertion as JwtUser could potentially lead to runtime errors if req['user'] is not of type JwtUser. Consider adding a type guard or validation to ensure req['user'] is indeed a JwtUser.

required: false
})
@ApiQuery({
name: 'limit',
Copy link

Choose a reason for hiding this comment

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

The query parameter name 'limit' is duplicated. Consider renaming the second 'limit' parameter to 'offset' to avoid confusion and ensure correct functionality.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - This should be fixed, thanks.

type: UpdateReviewOpportunityDto,
})
@ApiResponse({
status: 201,
Copy link

Choose a reason for hiding this comment

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

The HTTP status code for a successful update operation is typically 200, not 201. Consider changing the status code to 200 to align with RESTful conventions.

}
// sort list
responseList = [...responseList].sort((a, b) => {
return dto.sortOrder === 'asc' ? (a[dto.sortBy] - b[dto.sortBy]) : (a[dto.sortBy] - b[dto.sortBy]);
Copy link

Choose a reason for hiding this comment

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

The sorting logic is incorrect. The comparison function should return a positive number, zero, or a negative number, but currently, it always returns zero because the same expression is used for both 'asc' and 'desc'. Consider using (a[dto.sortBy] - b[dto.sortBy]) for 'asc' and (b[dto.sortBy] - a[dto.sortBy]) for 'desc'.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Let's double check this thanks.

* @returns response list
*/
private async assembleList(
entityList,
Copy link

Choose a reason for hiding this comment

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

The entityList parameter should have a type annotation for better type safety and readability. Consider specifying the type, such as entityList: ReviewOpportunityEntity[].

* @param entity prisma entity
* @returns response dto
*/
private async assembleResult(entity): Promise<ReviewOpportunityResponseDto> {
Copy link

Choose a reason for hiding this comment

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

The entity parameter should have a type annotation for better type safety and readability. Consider specifying the type, such as entity: ReviewOpportunityEntity.

* @returns response dto
*/
private buildResponse(
entity,
Copy link

Choose a reason for hiding this comment

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

The entity parameter should have a type annotation for better type safety and readability. Consider specifying the type, such as entity: ReviewOpportunityEntity.

): ResponseDto<string> {
const ret = new ResponseDto<string>();
const result = new ResultDto<string>();
result.success = true;
Copy link

Choose a reason for hiding this comment

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

The success property is set to true in FailResponse, which seems incorrect for an error response. Consider setting it to false to accurately represent a failure.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Let's fix this thanks.

@ApiProperty({
description: 'Review Application Role',
})
role: string;
Copy link

Choose a reason for hiding this comment

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

Consider using the ReviewApplicationRole type for the role property instead of string to ensure type safety and consistency with the CreateReviewApplicationDto class.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Seems like a good suggestion here thanks.

example: '50.0',
})
@IsNumber()
incrementalPayment: number;
Copy link

Choose a reason for hiding this comment

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

Consider adding @IsPositive() to ensure incrementalPayment is a positive number, similar to basePayment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Let's fix this thanks.

@ApiProperty({
description: 'Review opportunity id',
})
id: string;
Copy link

Choose a reason for hiding this comment

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

Consider adding @IsUUID() to validate that id is a valid UUID, similar to challengeId.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Let's fix this thanks.

description: 'Review application role id',
example: 8,
})
roleId: number;
Copy link

Choose a reason for hiding this comment

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

Consider adding validation decorators such as @IsNumber() and @IsPositive() to ensure roleId is a positive number.

'Review payment. Should be base payment if there is 1 submission.',
example: 180.0,
})
payment: number;
Copy link

Choose a reason for hiding this comment

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

Consider adding validation decorators such as @IsNumber() and @IsPositive() to ensure payment is a positive number.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - Seems like a reasonable suggestion thanks.

class EventBusMessage<T> {
topic: string;
originator: string;
'mime-type': string = 'application/json';
Copy link

Choose a reason for hiding this comment

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

Consider using camelCase for the 'mime-type' property to maintain consistency with JavaScript naming conventions.

// build event bus message
const msg = new EventBusMessage<EventBusSendEmailPayload>();
msg.topic = 'external.action.email';
// TODO: Maybe we should update this value.
Copy link

Choose a reason for hiding this comment

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

The 'originator' value is marked with a TODO comment. Ensure that this value is updated if necessary before merging.

throw new Error(`Event bus status code: ${response.status}`);
}
} catch (e) {
this.logger.error(`Event bus failed with error: ${e.message}`);
Copy link

Choose a reason for hiding this comment

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

Consider logging the entire error object instead of just 'e.message' to capture more detailed error information for debugging purposes.

@@ -1,15 +1,21 @@
import { Global, Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { HttpModule } from '@nestjs/axios';
Copy link

Choose a reason for hiding this comment

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

Consider checking if HttpModule is necessary for all consumers of this global module. If not, it might be better to import it only in the specific modules that require it to avoid unnecessary dependencies.

import { JwtService } from './jwt.service';
import { LoggerService } from './logger.service';
import { PrismaErrorService } from './prisma-error.service';
import { M2MService } from './m2m.service';
Copy link

Choose a reason for hiding this comment

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

Ensure that M2MService is intended to be a global provider. If it is only used in specific contexts, consider importing it in those specific modules instead.

import { LoggerService } from './logger.service';
import { PrismaErrorService } from './prisma-error.service';
import { M2MService } from './m2m.service';
import { ChallengeApiService } from './challenge.service';
Copy link

Choose a reason for hiding this comment

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

Verify if ChallengeApiService needs to be globally available. If its usage is limited to certain modules, it might be more efficient to import it only where needed.

import { PrismaErrorService } from './prisma-error.service';
import { M2MService } from './m2m.service';
import { ChallengeApiService } from './challenge.service';
import { EventBusService } from './eventBus.service';
Copy link

Choose a reason for hiding this comment

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

Check if EventBusService is required globally. If not, it could be imported in the specific modules that utilize it to reduce the global scope.

import { M2MService } from './m2m.service';
import { ChallengeApiService } from './challenge.service';
import { EventBusService } from './eventBus.service';
import { MemberService } from './member.service';
Copy link

Choose a reason for hiding this comment

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

Consider whether MemberService should be a global provider. If its application is limited to certain areas, importing it in those specific modules might be more appropriate.

handle?: string;
roles?: UserRole[];
scopes?: string[];
isMachine: boolean;
Copy link

Choose a reason for hiding this comment

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

The isMachine property is added to the JwtUser interface but is not marked as optional. Consider whether this property should be optional like userId, handle, roles, and scopes.

}

export const isAdmin = (user: JwtUser): boolean => {
return user.isMachine || (user.roles ?? []).includes(UserRole.Admin);
Copy link

Choose a reason for hiding this comment

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

The isAdmin function uses the isMachine property to determine if a user is an admin. Ensure that all instances of JwtUser are updated to include this new property, as it is currently not optional.

const rawScopes = TEST_M2M_TOKENS[token];
const scopes = this.expandScopes(rawScopes);
return { scopes };
return { scopes, isMachine: false };
Copy link

Choose a reason for hiding this comment

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

The isMachine property is set to false for test M2M tokens. Consider whether this should be true to accurately reflect that these are machine-to-machine tokens.

}

const user: JwtUser = {};
const user: JwtUser = {isMachine: false};
Copy link

Choose a reason for hiding this comment

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

The initialization of user with isMachine: false is a good default, but it might be clearer to explicitly set user.isMachine = false in the else block to ensure clarity in the logic flow.

if (decodedToken.roles) {
user.roles = decodedToken.roles as UserRole[];
user.userId = decodedToken.sub;
user.isMachine = true;
Copy link

Choose a reason for hiding this comment

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

Consider adding a check to ensure decodedToken.sub is defined before assigning it to user.userId to prevent potential runtime errors.

user.userId = decodedToken[key] as string;
}
if (key.endsWith('roles')) {
user.roles = decodedToken[key] as UserRole[]
Copy link

Choose a reason for hiding this comment

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

Ensure that decodedToken[key] is of type UserRole[] before assigning it to user.roles to avoid potential type errors.

*/
@Injectable()
export class M2MService {
private readonly m2m;
Copy link

Choose a reason for hiding this comment

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

Consider specifying the type for the m2m property for better type safety and clarity.

});
}

async getM2MToken() {
Copy link

Choose a reason for hiding this comment

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

Consider adding error handling for the getM2MToken method to manage potential exceptions from the getMachineToken call.

// construct URL of member API. Eg, https://api.topcoder-dev.com/v5/members?fields=email,userId&userIds=[123456]
const url =
CommonConfig.apis.memberApiUrl +
`?fields=email,userId&userIds=[${userIds.join(',')}]`;
Copy link

Choose a reason for hiding this comment

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

The URL construction assumes that userIds is always a valid array of strings. Consider adding validation to ensure userIds is not empty or null before constructing the URL.

}),
);
const infoList = plainToInstance(MemberInfo, response.data);
await Promise.all(infoList.map((e) => validateOrReject(e)));
Copy link

Choose a reason for hiding this comment

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

The validateOrReject function can throw an error, which is caught in the catch block. However, it might be useful to log which specific MemberInfo instance failed validation for better debugging.

if (e instanceof AxiosError) {
this.logger.error(
`Can't get member info: ${e.message}`,
e.response?.data,
Copy link

Choose a reason for hiding this comment

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

Consider logging more details from e.response?.data to provide more context in the error logs, which can be helpful for debugging issues related to the API response.

@@ -1 +1,18 @@
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb"

Choose a reason for hiding this comment

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

Consider removing the hardcoded credentials in the DATABASE_URL for security reasons. It's better to use placeholders like <username> and <password>.

M2M_AUTH_CLIENT_ID="jGIf2pd3f44B1jqvOai30BIKTZanYBfU"
M2M_AUTH_CLIENT_SECRET="ldzqVaVEbqhwjM5KtZ79sG8djZpAVK8Z7qieVcC3vRjI4NirgcinKSBpPwk6mYYP"
M2M_AUTH_DOMAIN="topcoder-dev.auth0.com"
M2M_AUTH_AUDIENCE="https://m2m.topcoder-dev.com/"

Choose a reason for hiding this comment

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

Typo correction: The variable name M2M_AUTH_PROXY_SEREVR_URL was corrected to M2M_AUTH_PROXY_SERVER_URL. Ensure that this change is reflected in all parts of the codebase where this variable is used.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - We shouldn't have any mock client ids, tokens, or secrets in the codebase, even though they aren't valid. We get pinged by Wipro security on those and they don't understand that these aren't usable.

.env.sample Outdated
M2M_AUTH_AUDIENCE="https://m2m.topcoder-dev.com/"
M2M_AUTH_PROXY_SERVER_URL=
# Mock API configs
M2M_MOCK_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoiakdJZjJwZDNmNDRCMWpxdk9haTMwQklLVFphbllCZlVAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNzQ4MDk5NDk4LCJleHAiOjE4NDgxODU4OTgsInNjb3BlIjoid3JpdGU6YnVzX2FwaSxhbGw6Y2hhbGxlbmdlcyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyIsImF6cCI6ImpHSWYycGQzZjQ0QjFqcXZPYWkzMEJJS1RaYW5ZQmZVIn0.h3ksdsdJm5USGF1VgROrpkTtStmCzv5ZA6y8bd8AnGY"

Choose a reason for hiding this comment

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

Security concern: The M2M_MOCK_TOKEN contains a JWT token in the sample environment file. Consider whether this token should be included here, as it might expose sensitive information. If it's necessary for testing, ensure it's a non-sensitive mock token.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - We shouldn't have any mock client ids, tokens, or secrets in the codebase, even though they aren't valid. We get pinged by Wipro security on those and they don't understand that these aren't usable.

#!/bin/bash
set -eo pipefail

export DATABASE_URL=$(echo -e ${DATABASE_URL})

Choose a reason for hiding this comment

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

Using echo -e with variable substitution can lead to unexpected behavior if the variable contains escape sequences. Consider using printf for safer handling of variables.

fi

echo "Database - running migrations."
if $RESET_DB; then

Choose a reason for hiding this comment

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

The condition $RESET_DB assumes that RESET_DB is a command or a boolean. Ensure that RESET_DB is properly initialized and is a boolean value to avoid unexpected behavior.

})

// Use environment variable for M2M token instead of hardcoding
const m2mToken = process.env.M2M_MOCK_TOKEN || 'dummy-token';

Choose a reason for hiding this comment

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

Using a hardcoded fallback token ('dummy-token') can be a security risk if it is accidentally used in production. Consider removing the fallback or ensuring it is only used in a safe development environment.


// Get the schema name from environment variable or use 'public' as default
const schema = process.env.POSTGRES_SCHEMA || 'public';
console.log(`Using PostgreSQL schema: ${schema}`);

Choose a reason for hiding this comment

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

Using console.log for logging may not be suitable for production environments. Consider using a logging library that can handle different log levels and outputs.

"incrementalPayment" DOUBLE PRECISION NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdBy" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

Choose a reason for hiding this comment

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

The addition of DEFAULT CURRENT_TIMESTAMP to the updatedAt column may not be appropriate if the intention is to track the last update time. This change will set the updatedAt field to the current timestamp upon record creation, which might not reflect the actual update time of the record. Consider whether this behavior aligns with the intended use of the updatedAt field.

"id" VARCHAR(14) NOT NULL DEFAULT nanoid(),
"userId" TEXT NOT NULL,
"handle" TEXT NOT NULL,
"opportunityId" VARCHAR(14) NOT NULL,

Choose a reason for hiding this comment

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

Consider ensuring that the change from TEXT to VARCHAR(14) for opportunityId aligns with the expected data length and constraints for this field. If opportunityId values can exceed 14 characters, this change might lead to data truncation issues.

"status" "ReviewApplicationStatus" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdBy" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

Choose a reason for hiding this comment

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

Adding a default value of CURRENT_TIMESTAMP to updatedAt might not be appropriate if the intention is to track the actual update time. Consider whether this default value should be removed or if an update trigger should be used to set updatedAt to the current timestamp upon record modification.

required: false
})
@ApiQuery({
name: 'offset',

Choose a reason for hiding this comment

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

The parameter name has been changed from limit to offset. Ensure that this change is intentional and that the rest of the codebase, including any documentation or dependent services, is updated to reflect this change. If the parameter is meant to control pagination, verify that the logic handling this parameter is correctly adjusted to use offset instead of limit.

'Review payment. Should be base payment if there is 1 submission.',
example: 180.0,
})
@IsNumber()

Choose a reason for hiding this comment

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

Consider specifying the @IsNumber() decorator with { allowNaN: false } to ensure that the payment is not NaN, which is a valid number in JavaScript but may not be desirable in this context.

});

this.logger = LoggerService.forRoot('PrismaService');
this.logger.log(`Using PostgreSQL schema: ${schema}`);

Choose a reason for hiding this comment

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

Consider using a more secure method to log the schema name, especially if it contains sensitive information. Logging sensitive information can lead to security vulnerabilities.

})
@IsString()
@IsNotEmpty()
@IsUUID()

Choose a reason for hiding this comment

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

The @IsUUID() decorator already ensures that the value is a non-empty string that conforms to the UUID format. Therefore, the @IsString() and @IsNotEmpty() decorators are redundant and can be removed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@billsedison - This one can be fixed

@ApiProperty({
description: 'Challenge id',
})
@IsUUID()

Choose a reason for hiding this comment

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

Consider re-adding the @IsString() and @IsNotEmpty() decorators if they are necessary for validation. The @IsUUID() decorator ensures the format is a valid UUID, but it does not check for non-empty strings or enforce that the input is a string type. If challengeId should not be empty and must be a string, these decorators are important for validation.

@ApiProperty({
description: 'Review opportunity id',
})
@IsUUID()

Choose a reason for hiding this comment

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

Consider re-adding the @IsString() and @IsNotEmpty() decorators if the id field is expected to be a non-empty string. While @IsUUID() ensures the format, it does not enforce non-emptiness or string type explicitly.

@ApiProperty({
description: 'Challenge id',
})
@IsString()

Choose a reason for hiding this comment

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

The @IsString() decorator is unnecessary here because @IsUUID() already implies that the value should be a string. Consider removing @IsString() to avoid redundancy.

@ApiProperty({
description: 'Review opportunity id',
})
@IsString()

Choose a reason for hiding this comment

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

The @IsString() decorator is redundant here since @IsUUID() already implies that the value is a string. Consider removing @IsString().

@billsedison billsedison merged commit 42a278d into develop Jun 23, 2025
1 check passed
@kkartunov kkartunov deleted the feat/review-opportunities branch August 6, 2025 05:14
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.

2 participants