Skip to content

Commit

Permalink
Merge branch 'monkeytypegame:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
CedricBaaklini authored May 15, 2024
2 parents c143259 + 4d76537 commit 31d3bd0
Show file tree
Hide file tree
Showing 193 changed files with 13,078 additions and 9,145 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
backend/build
docker
backend/__migration__
docker
18 changes: 12 additions & 6 deletions .github/workflows/publish-docker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ jobs:
env:
BE_REPO: monkeytype/monkeytype-backend
FE_REPO: monkeytype/monkeytype-frontend
PLATFORMS: linux/amd64,linux/arm64

name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb
- name: Log in to Docker Hub
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
Expand All @@ -32,9 +36,10 @@ jobs:
type=semver,pattern={{version}}
- name: Backend build and push Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0
with:
context: .
platforms: ${{ env.PLATFORMS }}
file: ./docker/backend/Dockerfile
push: true
tags: ${{ env.BE_REPO }}:latest,${{ steps.bemeta.outputs.tags }}
Expand All @@ -46,7 +51,7 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
repository: ${{ env.BE_REPO }}
short-description: Backend server for monkeytype.com
short-description: Official backend server for monkeytype.com
readme-filepath: ./SELF_HOSTING.md

- name: Frontend extract metadata (tags, labels)
Expand All @@ -58,9 +63,10 @@ jobs:
type=semver,pattern={{version}}
- name: Frontend build and push Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0
with:
context: .
platforms: ${{ env.PLATFORMS }}
file: ./docker/frontend/Dockerfile
push: true
tags: ${{ env.FE_REPO }}:latest,${{ steps.femeta.outputs.tags }}
Expand All @@ -72,5 +78,5 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
repository: ${{ env.FE_REPO }}
short-description: Frontend server for monkeytype.com
short-description: Official frontend server for monkeytype.com
readme-filepath: ./SELF_HOSTING.md
2 changes: 0 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"Orta.vscode-jest",
"vitest.explorer",
"ryanluker.vscode-coverage-gutters",
"huntertran.auto-markdown-toc"
]
}
12 changes: 6 additions & 6 deletions SELF_HOSTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ Stop the running docker containers using `docker compose down` before making any
```
- update the `.env` file with the values above:
```
FIREBASE_APIKEY="AAAAAAAA"
FIREBASE_AUTHDOMAIN="monkeytype-00000.firebaseapp.com"
FIREBASE_PROJECTID="monkeytype-00000"
FIREBASE_STORAGEBUCKET="monkeytype-00000.appspot.com"
FIREBASE_MESSAGINGSENDERID="90000000000"
FIREBASE_APPID="1:90000000000:web:000000000000"
FIREBASE_APIKEY=AAAAAAAA
FIREBASE_AUTHDOMAIN=monkeytype-00000.firebaseapp.com
FIREBASE_PROJECTID=monkeytype-00000
FIREBASE_STORAGEBUCKET=monkeytype-00000.appspot.com
FIREBASE_MESSAGINGSENDERID=90000000000
FIREBASE_APPID=1:90000000000:web:000000000000
```

### Update backend configuration
Expand Down
237 changes: 237 additions & 0 deletions backend/__migration__/testActivity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import "dotenv/config";
import * as DB from "../src/init/db";
import { Collection, Db } from "mongodb";

import readlineSync from "readline-sync";

let appRunning = true;
let db: Db | undefined;
let userCollection: Collection<MonkeyTypes.DBUser>;
let resultCollection: Collection<MonkeyTypes.DBResult>;

const filter = { testActivity: { $exists: false } };

process.on("SIGINT", () => {
console.log("\nshutting down...");
appRunning = false;
});

if (require.main === module) {
void main();
}

async function main(): Promise<void> {
try {
console.log(
`Connecting to database ${process.env["DB_NAME"]} on ${process.env["DB_URI"]}...`
);

//@ts-ignore
if (!readlineSync.keyInYN("Ready to start migration?")) {
appRunning = false;
}

if (appRunning) {
await DB.connect();
console.log("Connected to database");
db = DB.getDb();
if (db === undefined) {
throw Error("db connection failed");
}

await migrate();
}

console.log(`\nMigration ${appRunning ? "done" : "aborted"}.`);
} catch (e) {
console.log("error occured:", { e });
} finally {
await DB.close();
}
}

export async function migrate(): Promise<void> {
userCollection = DB.collection("users");
resultCollection = DB.collection("results");

console.log("Creating index on users collection...");
await userCollection.createIndex({ uid: 1 }, { unique: true });
await migrateResults();
}

async function migrateResults(batchSize = 50): Promise<void> {
const allUsersCount = await userCollection.countDocuments(filter);
if (allUsersCount === 0) {
console.log("No users to migrate.");
return;
} else {
console.log("Users to migrate:", allUsersCount);
}

console.log(`Migrating ~${allUsersCount} users using batchSize=${batchSize}`);

let count = 0;
const start = new Date().valueOf();
let uids: string[] = [];
do {
uids = await getUsersToMigrate(batchSize);

//migrate
await migrateUsers(uids);
await handleUsersWithNoResults(uids);

//progress tracker
count += uids.length;
updateProgress(allUsersCount, count, start);
} while (uids.length > 0 && appRunning);

if (appRunning) updateProgress(100, 100, start);
}

async function getUsersToMigrate(limit: number): Promise<string[]> {
return (
await userCollection
.find(filter, { limit })
.project({ uid: 1, _id: 0 })
.toArray()
).map((it) => it["uid"]);
}

async function migrateUsers(uids: string[]): Promise<void> {
console.log("migrateUsers:", uids.join(","));
await resultCollection
.aggregate(
[
{
$match: {
uid: { $in: uids },
},
},
{
$project: {
_id: 0,
timestamp: -1,
uid: 1,
},
},
{
$addFields: {
date: {
$toDate: "$timestamp",
},
},
},
{
$replaceWith: {
uid: "$uid",
year: {
$year: "$date",
},
day: {
$dayOfYear: "$date",
},
},
},
{
$group: {
_id: {
uid: "$uid",
year: "$year",
day: "$day",
},
count: {
$sum: 1,
},
},
},
{
$group: {
_id: {
uid: "$_id.uid",
year: "$_id.year",
},
days: {
$addToSet: {
day: "$_id.day",
tests: "$count",
},
},
},
},
{
$replaceWith: {
uid: "$_id.uid",
days: {
$function: {
lang: "js",
args: ["$days", "$_id.year"],
body: `function (days, year) {
var max = Math.max(
...days.map((it) => it.day)
)-1;
var arr = new Array(max).fill(null);
for (day of days) {
arr[day.day-1] = day.tests;
}
let result = {};
result[year] = arr;
return result;
}`,
},
},
},
},
{
$group: {
_id: "$uid",
testActivity: {
$mergeObjects: "$days",
},
},
},
{
$addFields: {
uid: "$_id",
},
},
{
$project: {
_id: 0,
},
},
{
$merge: {
into: "users",
on: "uid",
whenMatched: "merge",
whenNotMatched: "discard",
},
},
],
{ allowDiskUse: true }
)
.toArray();
}

async function handleUsersWithNoResults(uids: string[]): Promise<void> {
console.log("handleUsersWithNoResults:", uids.join(","));
await userCollection.updateMany(
{
$and: [{ uid: { $in: uids } }, filter],
},
{ $set: { testActivity: {} } }
);
}

function updateProgress(all: number, current: number, start: number): void {
const percentage = (current / all) * 100;
const timeLeft = Math.round(
(((new Date().valueOf() - start) / percentage) * (100 - percentage)) / 1000
);

process.stdout.clearLine?.(0);
process.stdout.cursorTo?.(0);
process.stdout.write(
`${Math.round(percentage)}% done, estimated time left ${timeLeft} seconds.`
);
}
Loading

0 comments on commit 31d3bd0

Please sign in to comment.