Skip to content

Commit f66e52c

Browse files
committed
Selective migration to re-run pieces
1 parent 03f9bee commit f66e52c

File tree

3 files changed

+72
-25
lines changed

3 files changed

+72
-25
lines changed

data-migration/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ LOG_LEVEL=info
121121
SKIP_MISSING_REQUIRED=false
122122
USE_TRANSACTIONS=true
123123
CHALLENGE_COUNTERS_ONLY=false
124+
MIGRATORS_ONLY=
124125
125126
# Migration attribution
126127
CREATED_BY=migration
@@ -136,6 +137,15 @@ Further migration configuration can also be done in `src/config.js`
136137

137138
Set `CHALLENGE_COUNTERS_ONLY=true` to re-run the `Challenge` migrator without touching other fields. In this mode the tool will skip normal validations and only update `numOfRegistrants` and `numOfSubmissions` for challenges that already exist in the database. Make sure the JSON payload still includes the challenge `id` and the counter values you want to refresh.
138139

140+
### Selecting Specific Migrators
141+
142+
Use `MIGRATORS_ONLY` (comma-separated list) to limit which migrators run. The filter matches either the model name or the migrator class name without the `Migrator` suffix. Examples:
143+
144+
- `MIGRATORS_ONLY=Challenge` runs the challenge migrator only.
145+
- `MIGRATORS_ONLY=Challenge,ChallengeType,ChallengeTrack` runs those three migrators.
146+
147+
Combine with `CHALLENGE_COUNTERS_ONLY=true` to update just the challenge counters for existing rows.
148+
139149
## Testing
140150
The project includes comprehensive tests to validate that data has been migrated correctly:
141151
```

data-migration/src/config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const path = require('path');
22
require('dotenv').config();
33

4+
const parseListEnv = value => {
5+
if (!value) return null;
6+
const list = value.split(',').map(entry => entry.trim()).filter(Boolean);
7+
return list.length ? list : null;
8+
};
9+
410
// Default configuration with fallbacks
511
module.exports = {
612
// Database connection
@@ -25,6 +31,7 @@ module.exports = {
2531

2632
// Specialized challenge migration toggles
2733
CHALLENGE_COUNTERS_ONLY: process.env.CHALLENGE_COUNTERS_ONLY === 'true',
34+
MIGRATORS_ONLY: parseListEnv(process.env.MIGRATORS_ONLY || process.env.MIGRATE_ONLY),
2835

2936
migrator: {
3037
ChallengeType: {

data-migration/src/index.js

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,30 +51,60 @@ async function main() {
5151
const manager = new MigrationManager();
5252

5353
// Register migrators in any order (they'll be sorted by priority)
54-
manager
55-
.registerMigrator(new AuditLogMigrator())
56-
.registerMigrator(new ChallengeConstraintMigrator())
57-
.registerMigrator(new ChallengeDiscussionOptionMigrator())
58-
.registerMigrator(new ChallengeEventMigrator())
59-
.registerMigrator(new ChallengeSkillMigrator())
60-
.registerMigrator(new ChallengeTermMigrator())
61-
.registerMigrator(new ChallengeWinnerMigrator())
62-
.registerMigrator(new PrizeMigrator())
63-
.registerMigrator(new ChallengePrizeSetMigrator())
64-
.registerMigrator(new TimelineTemplatePhaseMigrator())
65-
.registerMigrator(new ChallengePhaseConstraintMigrator())
66-
.registerMigrator(new ChallengePhaseMigrator())
67-
.registerMigrator(new ChallengeMetadataMigrator())
68-
.registerMigrator(new ChallengeDiscussionMigrator())
69-
.registerMigrator(new ChallengeLegacyMigrator())
70-
.registerMigrator(new ChallengeBillingMigrator())
71-
.registerMigrator(new PhaseMigrator())
72-
.registerMigrator(new ChallengeTimelineTemplateMigrator())
73-
.registerMigrator(new TimelineTemplateMigrator())
74-
.registerMigrator(new ChallengeMigrator())
75-
.registerMigrator(new ChallengeTypeMigrator())
76-
.registerMigrator(new ChallengeTrackMigrator());
77-
54+
const migrators = [
55+
new AuditLogMigrator(),
56+
new ChallengeConstraintMigrator(),
57+
new ChallengeDiscussionOptionMigrator(),
58+
new ChallengeEventMigrator(),
59+
new ChallengeSkillMigrator(),
60+
new ChallengeTermMigrator(),
61+
new ChallengeWinnerMigrator(),
62+
new PrizeMigrator(),
63+
new ChallengePrizeSetMigrator(),
64+
new TimelineTemplatePhaseMigrator(),
65+
new ChallengePhaseConstraintMigrator(),
66+
new ChallengePhaseMigrator(),
67+
new ChallengeMetadataMigrator(),
68+
new ChallengeDiscussionMigrator(),
69+
new ChallengeLegacyMigrator(),
70+
new ChallengeBillingMigrator(),
71+
new PhaseMigrator(),
72+
new ChallengeTimelineTemplateMigrator(),
73+
new TimelineTemplateMigrator(),
74+
new ChallengeMigrator(),
75+
new ChallengeTypeMigrator(),
76+
new ChallengeTrackMigrator()
77+
];
78+
79+
const requestedOnly = manager.config.MIGRATORS_ONLY;
80+
const requestedSet = requestedOnly ? new Set(requestedOnly.map(name => name.toLowerCase())) : null;
81+
82+
if (requestedSet) {
83+
manager.logger.info(`MIGRATORS_ONLY set; limiting migration to: ${requestedOnly.join(', ')}`);
84+
}
85+
86+
for (const migrator of migrators) {
87+
const identifiers = [
88+
migrator.modelName,
89+
migrator.constructor?.name,
90+
migrator.constructor?.name?.replace(/Migrator$/i, '')
91+
].filter(Boolean).map(name => name.toLowerCase());
92+
93+
const shouldInclude = !requestedSet || identifiers.some(name => requestedSet.has(name));
94+
95+
if (!shouldInclude) {
96+
manager.logger.debug(`Skipping ${migrator.modelName} migrator due to MIGRATORS_ONLY filter`);
97+
continue;
98+
}
99+
100+
manager.registerMigrator(migrator);
101+
}
102+
103+
if (!manager.migrators.length) {
104+
manager.logger.warn('No migrators registered. Check MIGRATORS_ONLY configuration.');
105+
return;
106+
}
107+
78108
// Run migration
79109
await manager.migrate();
80110
} catch (error) {
@@ -83,4 +113,4 @@ async function main() {
83113
}
84114
}
85115

86-
main();
116+
main();

0 commit comments

Comments
 (0)