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
34 changes: 34 additions & 0 deletions database/migrate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
9 changes: 9 additions & 0 deletions database/migrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Migrate

Migrates schemas in the `freecodecamp` database.

```bash
bun install
cp sample.env .env
bun run index.ts
```
173 changes: 173 additions & 0 deletions database/migrate/bun.lock

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions database/migrate/collections/exam-creator-exam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Db, ObjectId } from 'mongodb';
import { log } from '../logger';

interface ExamCreatorExam {
_id: ObjectId;
config: {
totalTimeInMS: number;
totalTimeInS?: number | null;
retakeTimeInMS: number;
retakeTimeInS?: number | null;
};
version: number;
}

export async function migrate(db: Db) {
const child = log.child({ collection: 'ExamCreatorExam' });
child.info('Starting migration for ExamCreatorExam collection');
const collection = db.collection<ExamCreatorExam>('ExamCreatorExam');

const query = {
$or: [
{ 'config.totalTimeInS': { $exists: false } },
{ 'config.retakeTimeInS': { $exists: false } }
],
version: 1
};

const cursor = collection.find(query, {
projection: { _id: 1, config: 1 }
});

const updates: {
_id: ExamCreatorExam['_id'];
totalTimeInS: number;
retakeTimeInS: number;
}[] = [];

while (await cursor.hasNext()) {
const doc = await cursor.next();
if (!doc) break;

const totalTimeInMS = doc.config.totalTimeInMS;
const totalTimeInS = Math.round(totalTimeInMS / 1000);

const retakeTimeInMS = doc.config.retakeTimeInMS;
const retakeTimeInS = Math.round(retakeTimeInMS / 1000);

updates.push({ _id: doc._id, totalTimeInS, retakeTimeInS });
}

child.info(`Found ${updates.length} docs to migrate.`);

if (!updates.length) {
return;
}

// Perform updates in bulk for efficiency
const bulk = collection.initializeUnorderedBulkOp();
for (const u of updates) {
bulk.find({ _id: u._id, ...query }).updateOne({
$set: {
'config.totalTimeInS': u.totalTimeInS,
'config.retakeTimeInS': u.retakeTimeInS,
version: 2
}
});
}

const result = await bulk.execute();
child.info(
`Bulk update complete. Matched: ${result.matchedCount}, Modified: ${result.modifiedCount}`
);
}
64 changes: 64 additions & 0 deletions database/migrate/collections/exam-creator-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Db, ObjectId } from "mongodb";
import { log } from "../logger";

interface ExamCreatorUser {
_id: ObjectId;
settings?: ExamCreatorUserSettings;
version: number;
}

interface ExamCreatorUserSettings {
databaseEnvironment: "Production" | "Staging";
}

export async function migrate(db: Db) {
const child = log.child({ collection: "ExamCreatorUser" });
child.info("Starting migration for ExamCreatorUser collection");
const collection = db.collection<ExamCreatorUser>("ExamCreatorUser");

const query = {
$or: [{ settings: { $exists: false } }, { version: { $exists: false } }],
};

const cursor = collection.find(query, {
projection: { _id: 1, settings: 1 },
});

const updates: {
_id: ExamCreatorUser["_id"];
settings: ExamCreatorUserSettings;
}[] = [];

while (await cursor.hasNext()) {
const doc = await cursor.next();
if (!doc) break;

const settings: ExamCreatorUserSettings = {
databaseEnvironment: doc.settings?.databaseEnvironment || "Production",
};

updates.push({ _id: doc._id, settings });
}

child.info(`Found ${updates.length} docs to migrate.`);

if (!updates.length) {
return;
}

// Perform updates in bulk for efficiency
const bulk = collection.initializeUnorderedBulkOp();
for (const u of updates) {
bulk.find({ _id: u._id, ...query }).updateOne({
$set: {
settings: u.settings,
version: 1,
},
});
}

const result = await bulk.execute();
child.info(
`Bulk update complete. Matched: ${result.matchedCount}, Modified: ${result.modifiedCount}`
);
}
56 changes: 56 additions & 0 deletions database/migrate/collections/exam-environment-challenge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Db, ObjectId } from 'mongodb';
import { log } from '../logger';

interface ExamEnvironmentChallenge {
_id: ObjectId;
version?: number;
}

export async function migrate(db: Db) {
const child = log.child({ collection: 'ExamEnvironmentChallenge' });
child.info('Starting migration for ExamEnvironmentChallenge collection');
const collection = db.collection<ExamEnvironmentChallenge>(
'ExamEnvironmentChallenge'
);

const query = {
version: { $exists: false }
};

const cursor = collection.find(query, {
projection: { _id: 1 }
});

const updates: {
_id: ExamEnvironmentChallenge['_id'];
version: number;
}[] = [];

while (await cursor.hasNext()) {
const doc = await cursor.next();
if (!doc) break;

updates.push({ _id: doc._id, version: 1 });
}

child.info(`Found ${updates.length} docs to migrate.`);

if (!updates.length) {
return;
}

// Perform updates in bulk for efficiency
const bulk = collection.initializeUnorderedBulkOp();
for (const u of updates) {
bulk.find({ _id: u._id, ...query }).updateOne({
$set: {
version: u.version
}
});
}

const result = await bulk.execute();
child.info(
`Bulk update complete. Matched: ${result.matchedCount}, Modified: ${result.modifiedCount}`
);
}
91 changes: 91 additions & 0 deletions database/migrate/collections/exam-environment-exam-attempt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Db, ObjectId } from 'mongodb';
import { log } from '../logger';

interface ExamEnvironmentExamAttempt {
_id: ObjectId;
questionSets: ExamEnvironmentQuestionSetAttempt[];
startTimeInMS: number;
startTime?: Date;
version: number;
}

interface ExamEnvironmentQuestionSetAttempt {
id: ObjectId;
questions: ExamEnvironmentMultipleChoiceQuestionAttempt[];
}

interface ExamEnvironmentMultipleChoiceQuestionAttempt {
id: ObjectId;
submissionTimeInMS: number;
submissionTime?: Date;
}

export async function migrate(db: Db) {
const child = log.child({ collection: 'ExamEnvironmentExamAttempt' });
child.info('Starting migration for ExamEnvironmentExamAttempt collection');
const collection = db.collection<ExamEnvironmentExamAttempt>(
'ExamEnvironmentExamAttempt'
);

const query = {
$and: [
{ startTime: { $exists: false } },
{ 'questionSets.questions.submissionTime': { $exists: false } }
],
version: 1
};

const cursor = collection.find(query, {
projection: { _id: 1, questionSets: 1, startTimeInMS: 1 }
});

const updates: {
_id: ExamEnvironmentExamAttempt['_id'];
questionSets: ExamEnvironmentQuestionSetAttempt[];
startTime: Date;
}[] = [];

while (await cursor.hasNext()) {
const doc = await cursor.next();
if (!doc) break;

// Add startTime
const startTime = new Date(doc.startTimeInMS);

for (const qs of doc.questionSets) {
for (const q of qs.questions) {
// Add submissionTime
q.submissionTime = new Date(q.submissionTimeInMS);
}
}

updates.push({
_id: doc._id,
startTime,
questionSets: doc.questionSets
});
}

child.info(`Found ${updates.length} docs to migrate.`);

if (!updates.length) {
return;
}

// Perform updates in bulk for efficiency
const bulk = collection.initializeUnorderedBulkOp();
for (const u of updates) {
bulk.find({ _id: u._id, ...query }).updateOne({
$set: {
questionSets: u.questionSets,
startTime: u.startTime,
version: 2
}
});
}

const result = await bulk.execute();
child.info(
`Bulk update complete. Matched: ${result.matchedCount}, Modified: ${result.modifiedCount}`
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Db, ObjectId } from 'mongodb';
import { log } from '../logger';

interface ExamEnvironmentExamModeration {
_id: ObjectId;
challengesAwarded?: boolean;
}

export async function migrate(db: Db) {
const child = log.child({ collection: 'ExamEnvironmentExamModeration' });
child.info('Starting migration for ExamEnvironmentExamModeration collection');
const collection = db.collection<ExamEnvironmentExamModeration>(
'ExamEnvironmentExamModeration'
);

const query = {
challengesAwarded: { $exists: false },
version: 1
};

const cursor = collection.find(query, {
projection: { _id: 1 }
});

const updates: {
_id: ExamEnvironmentExamModeration['_id'];
challengesAwarded: boolean;
}[] = [];

while (await cursor.hasNext()) {
const doc = await cursor.next();
if (!doc) break;

updates.push({ _id: doc._id, challengesAwarded: false });
}

child.info(`Found ${updates.length} docs to migrate.`);

if (!updates.length) {
return;
}

// Perform updates in bulk for efficiency
const bulk = collection.initializeUnorderedBulkOp();
for (const u of updates) {
bulk.find({ _id: u._id, ...query }).updateOne({
$set: {
challengesAwarded: u.challengesAwarded,
version: 2
}
});
}

const result = await bulk.execute();
child.info(
`Bulk update complete. Matched: ${result.matchedCount}, Modified: ${result.modifiedCount}`
);
}
Loading