Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9279a19
Performance indices
jmgasper Oct 23, 2025
2449c2e
Expose phaseId for defaultReviewer
vas3a Oct 23, 2025
d269ee5
Merge pull request #24 from topcoder-platform/default-reviewers-phaseid
vas3a Oct 23, 2025
aa77c21
expose fixedAmount
vas3a Oct 23, 2025
a215324
Merge pull request #25 from topcoder-platform/expose-fixedamount
vas3a Oct 23, 2025
80170e9
Review guard when changing scorecards, and make sure we don't wipe te…
jmgasper Oct 24, 2025
3f96faa
Enrich skills data for PUT / PATCH response and add blocks on review …
jmgasper Oct 24, 2025
c003e03
Minor tweak for review status checking.
jmgasper Oct 24, 2025
41b0ca8
Add check before manually closing an Appeals Response phase to make s…
jmgasper Oct 24, 2025
362af3f
fix: added timeout for prisma client
hentrymartin Oct 27, 2025
21e760d
fix: added timeout for prisma client
hentrymartin Oct 27, 2025
6bd3690
Add Trivy scanner workflow for vulnerability scanning
kkartunov Oct 28, 2025
ec0656f
Sort prizes
rishabhtc Oct 28, 2025
76506ca
Merge pull request #27 from topcoder-platform/PM-2436-sort-prizes
rishabhtc Oct 28, 2025
fd82edf
fix: used CHALLENGE_SERVICE_PRISMA_TIMEOUT instead of PRISMA_TRANSACT…
hentrymartin Oct 28, 2025
4a20ab7
fix: lint
hentrymartin Oct 28, 2025
d34e20b
Allow for management of defaultchallengereviewers by admins
jmgasper Oct 29, 2025
6486a94
Merge pull request #26 from topcoder-platform/pm-2539
kkartunov Oct 29, 2025
1c69c38
Incremental update tweaks
jmgasper Oct 30, 2025
0b8dc13
Fix validation to not require GUIDs
jmgasper Oct 30, 2025
9fe2350
Additional indices for performance issues noted
jmgasper Oct 31, 2025
c80586d
Merge pull request #29 from topcoder-platform/performance
jmgasper Oct 31, 2025
0e0aa70
Fix up extension creation
jmgasper Oct 31, 2025
73a547b
Merge pull request #30 from topcoder-platform/performance
jmgasper Oct 31, 2025
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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ workflows:
- feature/top-262-projectid-non-mandatory
- TOP-2364
- PM-2097
- pm-2456
- pm-2539

- "build-qa":
context: org-global
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/trivy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Trivy Scanner

permissions:
contents: read
security-events: write
on:
push:
branches:
- main
- dev
pull_request:
jobs:
trivy-scan:
name: Use Trivy
runs-on: ubuntu-24.04

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Consider using ubuntu-latest instead of a specific version like ubuntu-24.04 to ensure the workflow uses the most up-to-date and supported version of the runner. This can help avoid issues with deprecated or unsupported versions in the future.

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run Trivy scanner in repo mode
uses: aquasecurity/trivy-action@0.33.1
with:
scan-type: "fs"
ignore-unfixed: true
format: "sarif"
output: "trivy-results.sarif"
severity: "CRITICAL,HIGH,UNKNOWN"
scanners: vuln,secret,misconfig,license
github-pat: ${{ secrets.GITHUB_TOKEN }}

Choose a reason for hiding this comment

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

[⚠️ security]
Ensure that the GITHUB_TOKEN secret has the necessary permissions for the actions being performed. While the default permissions should suffice, it's important to verify this to avoid unexpected permission issues.


- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: "trivy-results.sarif"
6 changes: 6 additions & 0 deletions app-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ const Topics = {
ChallengeTimelineTemplateDeleted: "challenge.action.challenge.timeline.deleted",
ChallengePhaseUpdated: "challenge.action.phase.updated",
ChallengePhaseDeleted: "challenge.action.phase.deleted",
DefaultChallengeReviewerCreated: "challenge.action.default.reviewer.created",
DefaultChallengeReviewerUpdated: "challenge.action.default.reviewer.updated",
DefaultChallengeReviewerDeleted: "challenge.action.default.reviewer.deleted",
// Self Service topics
Notifications: "notifications.action.create",
};
Expand Down Expand Up @@ -95,6 +98,9 @@ const DisabledTopics = [
Topics.ChallengeTimelineTemplateDeleted,
Topics.ChallengePhaseUpdated,
Topics.ChallengePhaseDeleted,
Topics.DefaultChallengeReviewerCreated,
Topics.DefaultChallengeReviewerUpdated,
Topics.DefaultChallengeReviewerDeleted,
];

const challengeTextSortField = {
Expand Down
1 change: 1 addition & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,5 @@ module.exports = {
// Database schemas for direct counts (shared DB)
RESOURCES_DB_SCHEMA: process.env.RESOURCES_DB_SCHEMA || "resources",
REVIEW_DB_SCHEMA: process.env.REVIEW_DB_SCHEMA || "reviews",
CHALLENGE_SERVICE_PRISMA_TIMEOUT: process.env.CHALLENGE_SERVICE_PRISMA_TIMEOUT ? parseInt(process.env.CHALLENGE_SERVICE_PRISMA_TIMEOUT, 10) : 10000,

Choose a reason for hiding this comment

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

[⚠️ correctness]
Consider using Number() instead of parseInt() for parsing CHALLENGE_SERVICE_PRISMA_TIMEOUT. Number() will convert the entire string to a number, while parseInt() stops at the first non-numeric character, which might lead to unexpected results if the environment variable contains non-numeric characters.

};
97 changes: 95 additions & 2 deletions data-migration/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,81 @@ const parseMigrationMode = value => {
return ALLOWED_MIGRATION_MODES.has(normalized) ? normalized : 'full';
};

// Default configuration with fallbacks
module.exports = {
const RECOMMENDED_INCREMENTAL_FIELDS = ['updatedAt', 'updatedBy'];

const collectKnownSchemaFields = migratorConfig => {
const knownFields = new Set();
if (!migratorConfig) {
return knownFields;
}

Object.values(migratorConfig).forEach(definition => {
if (!definition || typeof definition !== 'object') {
return;
}
(definition.requiredFields || []).forEach(field => knownFields.add(field));
(definition.optionalFields || []).forEach(field => knownFields.add(field));
const defaults = definition.hasDefaults || [];
defaults.forEach(field => knownFields.add(field));
});

return knownFields;
};

const validateIncrementalFieldConfiguration = (config) => {

Choose a reason for hiding this comment

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

[⚠️ design]
The function validateIncrementalFieldConfiguration directly logs warnings and errors to the console. Consider refactoring to allow the caller to handle logging, which would improve testability and flexibility in handling different logging mechanisms.

const incrementalFields = Array.isArray(config.INCREMENTAL_FIELDS) ? config.INCREMENTAL_FIELDS : [];
const isIncrementalMode = config.MIGRATION_MODE === 'incremental';
const knownFields = collectKnownSchemaFields(config.migrator);

const result = {
warnings: [],
errors: []
};

if (isIncrementalMode && incrementalFields.length === 0) {
result.errors.push('INCREMENTAL_FIELDS must be configured when running incremental migrations.');
}

incrementalFields.forEach(field => {
if (!knownFields.has(field)) {
result.warnings.push(`Incremental field "${field}" is not present in any migrator schema and will be ignored.`);
}
});

const recommendedMissing = RECOMMENDED_INCREMENTAL_FIELDS.filter(field => incrementalFields.length > 0 && !incrementalFields.includes(field));
if (recommendedMissing.length) {
result.warnings.push(`Consider including ${recommendedMissing.join(', ')} in INCREMENTAL_FIELDS to preserve audit columns during updates.`);
}

if (incrementalFields.length) {
console.info(`[config] Incremental fields configured: ${incrementalFields.join(', ')}`);
}

result.warnings.forEach(message => console.warn(`[config] ${message}`));
result.errors.forEach(message => console.error(`[config] ${message}`));

return result;
};

/**
* Migration configuration with environment-driven overrides.
*
* Environment variables:
* - DATABASE_URL: Postgres connection string.
* - DATA_DIRECTORY: Root directory where migration payloads are stored.
* - MIGRATION_MODE: 'full' or 'incremental' (defaults to 'full').
* - INCREMENTAL_SINCE_DATE: ISO-8601 date used to filter source records.
* - INCREMENTAL_FIELDS: Comma-separated list of fields updated during incremental runs.
* - INCREMENTAL_FIELDS examples:
* INCREMENTAL_FIELDS=updatedAt,updatedBy,status
* INCREMENTAL_FIELDS=updatedAt,updatedBy,status,currentPhaseNames
* Fields not listed remain unchanged during incremental updates.
* - INCREMENTAL_DATE_FIELDS: Ordered list of timestamp fields to evaluate for incremental filtering (default: updatedAt,updated).
* - MISSING_DATE_FIELD_BEHAVIOR: Behaviour when timestamps are missing (skip, include, warn-and-skip, warn-and-include).
* - INVALID_DATE_FIELD_BEHAVIOR: Behaviour when timestamps are invalid or suspicious (same options as above).
* - CREATED_BY / UPDATED_BY: Attribution columns written during migration.
*/
const config = {
// Database connection
DATABASE_URL: process.env.DATABASE_URL,

Expand All @@ -24,6 +97,9 @@ module.exports = {
BATCH_SIZE: parseInt(process.env.BATCH_SIZE || '100', 10),
CONCURRENCY_LIMIT: parseInt(process.env.CONCURRENCY_LIMIT || '10', 10),
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
LOG_VERBOSITY: (process.env.LOG_VERBOSITY || 'normal').toLowerCase(),
SUMMARY_LOG_LIMIT: parseInt(process.env.SUMMARY_LOG_LIMIT || '5', 10),
COLLECT_UPSERT_STATS: process.env.COLLECT_UPSERT_STATS === 'true',

// Migration behavior
SKIP_MISSING_REQUIRED: process.env.SKIP_MISSING_REQUIRED === 'true',
Expand All @@ -34,7 +110,15 @@ module.exports = {
// Incremental migration settings
MIGRATION_MODE: parseMigrationMode(process.env.MIGRATION_MODE),
INCREMENTAL_SINCE_DATE: process.env.INCREMENTAL_SINCE_DATE || null,
/**
* Fields that should be mutated when MIGRATION_MODE=incremental.
* Only columns listed here are included in UPDATE operations; omitted columns retain their existing values.
* Works in tandem with INCREMENTAL_SINCE_DATE to scope the incremental window.
*/
INCREMENTAL_FIELDS: parseListEnv(process.env.INCREMENTAL_FIELDS),
INCREMENTAL_DATE_FIELDS: parseListEnv(process.env.INCREMENTAL_DATE_FIELDS) || ['updatedAt', 'updated'],

Choose a reason for hiding this comment

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

[⚠️ correctness]
The default value for INCREMENTAL_DATE_FIELDS is set to ['updatedAt', 'updated'] if the environment variable is not provided. Ensure that these default fields are appropriate for all use cases, as hardcoding defaults can lead to unexpected behavior if the schema changes.

MISSING_DATE_FIELD_BEHAVIOR: (process.env.MISSING_DATE_FIELD_BEHAVIOR || 'warn-and-skip').toLowerCase(),
INVALID_DATE_FIELD_BEHAVIOR: (process.env.INVALID_DATE_FIELD_BEHAVIOR || 'warn-and-skip').toLowerCase(),

// Migration attribution
CREATED_BY: process.env.CREATED_BY || 'migration',
Expand Down Expand Up @@ -424,7 +508,16 @@ module.exports = {
},
};

config.incrementalFieldValidation = validateIncrementalFieldConfiguration(config);

Choose a reason for hiding this comment

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

[⚠️ maintainability]
Assigning the result of validateIncrementalFieldConfiguration to config.incrementalFieldValidation might lead to unexpected behavior if the configuration is modified after this assignment. Consider making this validation a function that can be called to get the latest validation results.


module.exports = config;

Object.defineProperty(module.exports, 'parseMigrationMode', {
value: parseMigrationMode,
enumerable: false
});

Object.defineProperty(module.exports, 'validateIncrementalFieldConfiguration', {
value: validateIncrementalFieldConfiguration,
enumerable: false
});
Loading
Loading