Merge branch 'master' into newtribemerge

This commit is contained in:
Miodec 2025-12-11 00:15:30 +01:00
commit c95d08dda8
480 changed files with 11525 additions and 8228 deletions

2
.github/labeler.yml vendored
View file

@ -18,4 +18,4 @@ packages:
- any: ["packages/**/*"]
local dev:
- any: ["**/turbo.json", "**/tsconfig.json", "**/knip.json", "**/.prettierrc", "**/.oxlintrc.json", "**/.eslintrc.cjs", "**/vite.config.dev.js"]
- any: ["**/turbo.json", "**/tsconfig.json", "**/knip.json", "**/.prettierrc", "**/.oxlintrc.json", "**/.eslintrc.cjs"]

View file

@ -4,6 +4,8 @@
### Checks
- [ ] Adding/modifying Typescript code?
- [ ] I have used `qs`,`qsa` or `qsr` instead of JQuery selectors.
- [ ] Adding quotes?
- [ ] Make sure to include translations for the quotes in the description (or another comment) so we can verify their content.
- [ ] Adding a language?

View file

@ -1,4 +1,4 @@
name: Prettier Fix
name: Fix formatting
env:
PNPM_VERSION: "9.6.0"
@ -13,9 +13,9 @@ on:
types: [labeled]
jobs:
prettify:
format:
runs-on: ubuntu-latest
if: github.event.label.name == 'prettify'
if: github.event.label.name == 'format'
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -33,8 +33,8 @@ jobs:
with:
version: ${{ env.PNPM_VERSION }}
- name: Install prettier
run: pnpm add -g prettier@2.8.8
- name: Install formatter
run: pnpm install -D -w prettier
- name: Get changed files
id: get-changed-files
@ -52,7 +52,7 @@ jobs:
return changedFiles.filter(file=> file.status !== "removed").map(file => file.filename).join(' ');
- name: Run Prettier fix
- name: Fix formatting
run: |
CHANGED_FILES=$(echo ${{ steps.get-changed-files.outputs.result }})
if [ -n "$CHANGED_FILES" ]; then
@ -62,9 +62,9 @@ jobs:
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "prettier fix"
commit_message: "fix formatting"
- name: Remove label
uses: actions-ecosystem/action-remove-labels@v1
with:
labels: prettify
labels: format

View file

@ -1,4 +1,4 @@
name: Prettier Check
name: Formatting check
env:
PNPM_VERSION: "9.6.0"
@ -13,7 +13,7 @@ permissions:
contents: read
concurrency:
group: group-pretty-check-${{ github.ref }}-${{ github.workflow }}
group: group-format-check-${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
@ -35,8 +35,9 @@ jobs:
with:
version: ${{ env.PNPM_VERSION }}
- name: Install prettier
run: pnpm add -g prettier@2.8.8
- name: Install formatter
run: pnpm install -D -w prettier
- name: Get changed files
id: get-changed-files
@ -53,7 +54,7 @@ jobs:
);
return changedFiles.filter(file=> file.status !== "removed").map(file => file.filename).join(' ');
- name: Check pretty (changed files)
- name: Check formatting (changed files)
run: |
CHANGED_FILES=$(echo ${{ steps.get-changed-files.outputs.result }})
if [ -n "$CHANGED_FILES" ]; then

1
.gitignore vendored
View file

@ -67,6 +67,7 @@ node_modules_bak/
# dotenv environment variables file
.env
.env*
#vs code
.vscode/*

View file

@ -9,7 +9,7 @@ export NVM_DIR="$HOME/.nvm"
if [ $(git branch --no-color | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/') = "master" ] && [ $(git remote get-url origin) = "https://github.com/monkeytypegame/monkeytype" ]; then
nvm install
echo "Running a full check before pushing to master..."
npm run full-check
pnpm run full-check
if [ $? -ne 0 ]; then
echo "Full check failed, aborting push."
exit 1

View file

@ -2,13 +2,6 @@
"tabWidth": 2,
"useTabs": false,
"htmlWhitespaceSensitivity": "ignore",
"endOfLine": "lf",
"overrides": [
{
"files": ["*.ts"],
"options": {
"parser": "typescript"
}
}
]
"trailingComma": "all",
"endOfLine": "lf"
}

View file

@ -27,10 +27,9 @@ if (require.main === module) {
async function main(): Promise<void> {
try {
console.log(
`Connecting to database ${process.env["DB_NAME"]} on ${process.env["DB_URI"]}...`
`Connecting to database ${process.env["DB_NAME"]} on ${process.env["DB_URI"]}...`,
);
//@ts-ignore
if (!readlineSync.keyInYN("Ready to start migration?")) {
appRunning = false;
}
@ -222,7 +221,7 @@ async function migrateUsers(uids: string[]): Promise<void> {
},
},
],
{ allowDiskUse: true }
{ allowDiskUse: true },
)
.toArray();
}
@ -232,7 +231,7 @@ async function handleUsersWithNoResults(uids: string[]): Promise<void> {
{
$and: [{ uid: { $in: uids } }, filter],
},
{ $set: { testActivity: {} } }
{ $set: { testActivity: {} } },
);
}
@ -240,11 +239,11 @@ function updateProgress(
all: number,
current: number,
start: number,
previousBatchSizeTime: number
previousBatchSizeTime: number,
): void {
const percentage = (current / all) * 100;
const timeLeft = Math.round(
(((new Date().valueOf() - start) / percentage) * (100 - percentage)) / 1000
(((new Date().valueOf() - start) / percentage) * (100 - percentage)) / 1000,
);
process.stdout.clearLine?.(0);
@ -253,7 +252,7 @@ function updateProgress(
`Previous batch took ${Math.round(previousBatchSizeTime)}ms (~${
previousBatchSizeTime / batchSize
}ms per user) ${Math.round(
percentage
)}% done, estimated time left ${timeLeft} seconds.`
percentage,
)}% done, estimated time left ${timeLeft} seconds.`,
);
}

View file

@ -36,7 +36,7 @@ describe("BlocklistDal", () => {
await expect(
BlacklistDal.getCollection().findOne({
emailHash: BlacklistDal.hash(email),
})
}),
).resolves.toMatchObject({
emailHash: BlacklistDal.hash(email),
timestamp: now,
@ -45,7 +45,7 @@ describe("BlocklistDal", () => {
await expect(
BlacklistDal.getCollection().findOne({
usernameHash: BlacklistDal.hash(name),
})
}),
).resolves.toMatchObject({
usernameHash: BlacklistDal.hash(name),
timestamp: now,
@ -67,7 +67,7 @@ describe("BlocklistDal", () => {
await expect(
BlacklistDal.getCollection().findOne({
discordIdHash: BlacklistDal.hash(discordId),
})
}),
).resolves.toMatchObject({
discordIdHash: BlacklistDal.hash(discordId),
timestamp: now,
@ -92,21 +92,21 @@ describe("BlocklistDal", () => {
.find({
usernameHash: BlacklistDal.hash(name),
})
.toArray()
.toArray(),
).resolves.toHaveLength(1);
await expect(
BlacklistDal.getCollection()
.find({
emailHash: BlacklistDal.hash(email),
})
.toArray()
.toArray(),
).resolves.toHaveLength(1);
await expect(
BlacklistDal.getCollection()
.find({
emailHash: BlacklistDal.hash(email2),
})
.toArray()
.toArray(),
).resolves.toHaveLength(1);
});
it("adds user should not create duplicate email", async () => {
@ -128,7 +128,7 @@ describe("BlocklistDal", () => {
.find({
emailHash: BlacklistDal.hash(email),
})
.toArray()
.toArray(),
).resolves.toHaveLength(1);
});
it("adds user should not create duplicate discordId", async () => {
@ -153,7 +153,7 @@ describe("BlocklistDal", () => {
.find({
discordIdHash: BlacklistDal.hash(discordId),
})
.toArray()
.toArray(),
).resolves.toHaveLength(1);
});
});
@ -170,33 +170,33 @@ describe("BlocklistDal", () => {
//by name
await expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name: name.toUpperCase() })
BlacklistDal.contains({ name: name.toUpperCase() }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name, email: "unknown", discordId: "unknown" })
BlacklistDal.contains({ name, email: "unknown", discordId: "unknown" }),
).resolves.toBeTruthy();
//by email
await expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: email.toUpperCase() })
BlacklistDal.contains({ email: email.toUpperCase() }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name: "unknown", email, discordId: "unknown" })
BlacklistDal.contains({ name: "unknown", email, discordId: "unknown" }),
).resolves.toBeTruthy();
//by discordId
await expect(BlacklistDal.contains({ discordId })).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ discordId: discordId.toUpperCase() })
BlacklistDal.contains({ discordId: discordId.toUpperCase() }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name: "unknown", email: "unknown", discordId })
BlacklistDal.contains({ name: "unknown", email: "unknown", discordId }),
).resolves.toBeTruthy();
//by name and email and discordId
await expect(
BlacklistDal.contains({ name, email, discordId })
BlacklistDal.contains({ name, email, discordId }),
).resolves.toBeTruthy();
});
it("does not contain user", async () => {
@ -206,20 +206,20 @@ describe("BlocklistDal", () => {
//WHEN / THEN
await expect(
BlacklistDal.contains({ name: "unknown" })
BlacklistDal.contains({ name: "unknown" }),
).resolves.toBeFalsy();
await expect(
BlacklistDal.contains({ email: "unknown" })
BlacklistDal.contains({ email: "unknown" }),
).resolves.toBeFalsy();
await expect(
BlacklistDal.contains({ discordId: "unknown" })
BlacklistDal.contains({ discordId: "unknown" }),
).resolves.toBeFalsy();
await expect(
BlacklistDal.contains({
name: "unknown",
email: "unknown",
discordId: "unknown",
})
}),
).resolves.toBeFalsy();
await expect(BlacklistDal.contains({})).resolves.toBeFalsy();
@ -243,10 +243,10 @@ describe("BlocklistDal", () => {
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" })
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" })
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
});
it("removes existing email", async () => {
@ -265,10 +265,10 @@ describe("BlocklistDal", () => {
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" })
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" })
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
});
it("removes existing discordId", async () => {
@ -293,13 +293,13 @@ describe("BlocklistDal", () => {
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" })
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" })
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ discordId: "testDiscordId" })
BlacklistDal.contains({ discordId: "testDiscordId" }),
).resolves.toBeTruthy();
});
it("removes existing username,email and discordId", async () => {
@ -324,13 +324,13 @@ describe("BlocklistDal", () => {
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" })
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" })
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ discordId: "testDiscordId" })
BlacklistDal.contains({ discordId: "testDiscordId" }),
).resolves.toBeTruthy();
});
@ -355,8 +355,8 @@ describe("BlocklistDal", () => {
it("hashes case insensitive", () => {
["test", "TEST", "tESt"].forEach((value) =>
expect(BlacklistDal.hash(value)).toEqual(
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
)
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
),
);
});
});

View file

@ -29,7 +29,7 @@ describe("ConfigDal", () => {
//THEN
const savedConfig = (await ConfigDal.getConfig(
uid
uid,
)) as ConfigDal.DBConfig;
expect(savedConfig.config.ads).toBe("off");

View file

@ -33,7 +33,7 @@ describe("ConnectionsDal", () => {
await ConnectionsDal.getConnections({
initiatorUid: uid,
receiverUid: uid,
})
}),
).toStrictEqual([initOne, initTwo, friendOne]);
});
@ -71,7 +71,7 @@ describe("ConnectionsDal", () => {
initiatorUid: uid,
receiverUid: uid,
status: ["accepted", "blocked"],
})
}),
).toStrictEqual([initAccepted, initBlocked, friendAccepted]);
});
});
@ -98,7 +98,7 @@ describe("ConnectionsDal", () => {
createConnection({
initiatorUid: first.receiverUid,
receiverUid: uid,
})
}),
).rejects.toThrow("Connection request already sent");
});
@ -111,7 +111,7 @@ describe("ConnectionsDal", () => {
const created = await ConnectionsDal.create(
{ uid, name: "Bob" },
{ uid: receiverUid, name: "Kevin" },
2
2,
);
//THEN
@ -135,7 +135,7 @@ describe("ConnectionsDal", () => {
//WHEN / THEM
await expect(createConnection({ initiatorUid }, 2)).rejects.toThrow(
"Maximum number of connections reached\nStack: create connection request"
"Maximum number of connections reached\nStack: create connection request",
);
});
@ -152,7 +152,7 @@ describe("ConnectionsDal", () => {
createConnection({
initiatorUid: first.receiverUid,
receiverUid: uid,
})
}),
).rejects.toThrow("Connection blocked");
});
});
@ -181,19 +181,19 @@ describe("ConnectionsDal", () => {
await ConnectionsDal.updateStatus(
uid,
first._id.toHexString(),
"accepted"
"accepted",
);
//THEN
expect(await ConnectionsDal.getConnections({ receiverUid: uid })).toEqual(
[{ ...first, status: "accepted", lastModified: now }, second]
[{ ...first, status: "accepted", lastModified: now }, second],
);
//can update twice to the same status
await ConnectionsDal.updateStatus(
uid,
first._id.toHexString(),
"accepted"
"accepted",
);
});
it("should fail if uid does not match the reeceiverUid", async () => {
@ -205,7 +205,7 @@ describe("ConnectionsDal", () => {
//WHEN / THEN
await expect(
ConnectionsDal.updateStatus(uid, first._id.toHexString(), "accepted")
ConnectionsDal.updateStatus(uid, first._id.toHexString(), "accepted"),
).rejects.toThrow("No permission or connection not found");
});
});
@ -226,7 +226,7 @@ describe("ConnectionsDal", () => {
//THEN
expect(
await ConnectionsDal.getConnections({ initiatorUid: uid })
await ConnectionsDal.getConnections({ initiatorUid: uid }),
).toStrictEqual([second]);
});
@ -248,7 +248,7 @@ describe("ConnectionsDal", () => {
expect(
await ConnectionsDal.getConnections({
initiatorUid: second.initiatorUid,
})
}),
).toStrictEqual([second]);
});
@ -261,7 +261,7 @@ describe("ConnectionsDal", () => {
//WHEN / THEN
await expect(
ConnectionsDal.deleteById("Bob", first._id.toHexString())
ConnectionsDal.deleteById("Bob", first._id.toHexString()),
).rejects.toThrow("No permission or connection not found");
});
@ -275,7 +275,7 @@ describe("ConnectionsDal", () => {
//WHEN / THEN
await expect(
ConnectionsDal.deleteById(uid, myRequestWasBlocked._id.toHexString())
ConnectionsDal.deleteById(uid, myRequestWasBlocked._id.toHexString()),
).rejects.toThrow("No permission or connection not found");
});
it("allow receiver to delete blocked", async () => {
@ -291,7 +291,7 @@ describe("ConnectionsDal", () => {
//THEN
expect(await ConnectionsDal.getConnections({ receiverUid: uid })).toEqual(
[]
[],
);
});
});
@ -313,13 +313,13 @@ describe("ConnectionsDal", () => {
await ConnectionsDal.getConnections({
initiatorUid: uid,
receiverUid: uid,
})
}),
).toEqual([]);
expect(
await ConnectionsDal.getConnections({
initiatorUid: decoy.initiatorUid,
})
}),
).toEqual([decoy]);
});
});
@ -349,7 +349,7 @@ describe("ConnectionsDal", () => {
await ConnectionsDal.getConnections({
initiatorUid: uid,
receiverUid: uid,
})
}),
).toEqual([
{ ...initOne, initiatorName: "King Bob" },
{ ...initTwo, initiatorName: "King Bob" },
@ -359,7 +359,7 @@ describe("ConnectionsDal", () => {
expect(
await ConnectionsDal.getConnections({
initiatorUid: decoy.initiatorUid,
})
}),
).toEqual([decoy]);
});
});
@ -472,7 +472,7 @@ describe("ConnectionsDal", () => {
connectionId: "$connectionMeta._id",
},
},
]
],
);
//THEN

View file

@ -38,7 +38,7 @@ describe("LeaderboardsDal", () => {
//THEN
expect(results).toHaveLength(1);
expect(
(results as LeaderboardsDal.DBLeaderboardEntry[])[0]
(results as LeaderboardsDal.DBLeaderboardEntry[])[0],
).toHaveProperty("uid", applicableUser.uid);
});
@ -56,7 +56,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0,
50
50,
)) as DBLeaderboardEntry[];
//THEN
@ -84,7 +84,7 @@ describe("LeaderboardsDal", () => {
"60",
"english",
0,
50
50,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
@ -111,7 +111,7 @@ describe("LeaderboardsDal", () => {
"60",
"english",
0,
50
50,
)) as DBLeaderboardEntry[];
//THEN
@ -135,7 +135,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0,
50
50,
)) as DBLeaderboardEntry[];
//THEN
@ -198,7 +198,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0,
50
50,
)) as DBLeaderboardEntry[];
//THEN
@ -237,7 +237,7 @@ describe("LeaderboardsDal", () => {
"english",
0,
50,
true
true,
)) as DBLeaderboardEntry[];
//THEN
@ -270,7 +270,7 @@ describe("LeaderboardsDal", () => {
"english",
0,
50,
false
false,
)) as DBLeaderboardEntry[];
//THEN
@ -295,7 +295,7 @@ describe("LeaderboardsDal", () => {
"english",
1,
2,
true
true,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
@ -334,7 +334,7 @@ describe("LeaderboardsDal", () => {
0,
50,
false,
uid
uid,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
@ -373,7 +373,7 @@ describe("LeaderboardsDal", () => {
1,
2,
false,
uid
uid,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
@ -395,7 +395,7 @@ describe("LeaderboardsDal", () => {
1,
2,
false,
uid
uid,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
expect(results).toEqual([]);
@ -421,7 +421,7 @@ describe("LeaderboardsDal", () => {
rank: 3,
name: me.name,
uid: me.uid,
})
}),
);
});
it("should get for friends only", async () => {
@ -450,7 +450,7 @@ describe("LeaderboardsDal", () => {
expect(await LeaderboardsDal.getCount("time", "60", "english", me.uid)) //
.toEqual(3);
expect(
await LeaderboardsDal.getRank("time", "60", "english", me.uid, true)
await LeaderboardsDal.getRank("time", "60", "english", me.uid, true),
) //
.toEqual(
expect.objectContaining({
@ -459,7 +459,7 @@ describe("LeaderboardsDal", () => {
friendsRank: 2,
name: me.name,
uid: me.uid,
})
}),
);
});
});
@ -467,7 +467,7 @@ describe("LeaderboardsDal", () => {
function expectedLbEntry(
time: string,
{ rank, user, badgeId, isPremium, friendsRank }: ExpectedLbEntry
{ rank, user, badgeId, isPremium, friendsRank }: ExpectedLbEntry,
) {
// @ts-expect-error
const lbBest: PersonalBest =
@ -493,7 +493,7 @@ function expectedLbEntry(
async function createUser(
lbPersonalBests?: LbPersonalBests,
userProperties?: Partial<UserDal.DBUser>
userProperties?: Partial<UserDal.DBUser>,
): Promise<UserDal.DBUser> {
const uid = new ObjectId().toHexString();
await UserDal.addUser("User " + uid, uid + "@example.com", uid);
@ -510,7 +510,7 @@ async function createUser(
...userProperties,
lbPersonalBests,
},
}
},
);
return await UserDal.getUser(uid, "test");

View file

@ -53,7 +53,7 @@ describe("PresetDal", () => {
showAverage: "off",
},
}),
])
]),
);
});
});
@ -68,7 +68,7 @@ describe("PresetDal", () => {
//WHEN / THEN
await expect(() =>
PresetDal.addPreset(uid, { name: "max", config: {} })
PresetDal.addPreset(uid, { name: "max", config: {} }),
).rejects.toThrowError("Too many presets");
});
it("should add preset", async () => {
@ -98,7 +98,7 @@ describe("PresetDal", () => {
name: "new",
config: { ads: "sellout" },
}),
])
]),
);
});
});
@ -162,7 +162,7 @@ describe("PresetDal", () => {
name: "second",
config: { ads: "result" },
}),
])
]),
);
expect(await PresetDal.getPresets(decoyUid)).toEqual(
expect.arrayContaining([
@ -172,7 +172,7 @@ describe("PresetDal", () => {
name: "unknown",
config: { ads: "result" },
}),
])
]),
);
});
@ -199,7 +199,7 @@ describe("PresetDal", () => {
name: "newName",
config: { ads: "sellout" },
}),
])
]),
);
});
it("should edit with name only - partial preset", async () => {
@ -237,7 +237,7 @@ describe("PresetDal", () => {
showAverage: "off",
},
}),
])
]),
);
});
it("should not edit present not matching uid", async () => {
@ -269,7 +269,7 @@ describe("PresetDal", () => {
name: "first",
config: { ads: "sellout" },
}),
])
]),
);
});
it("should edit when partial is edited to full", async () => {
@ -305,7 +305,7 @@ describe("PresetDal", () => {
config: { ads: "off" },
settingGroups: null,
}),
])
]),
);
});
it("should edit when full is edited to partial", async () => {
@ -348,7 +348,7 @@ describe("PresetDal", () => {
showAverage: "off",
},
}),
])
]),
);
});
});
@ -357,7 +357,7 @@ describe("PresetDal", () => {
it("should fail if preset is unknown", async () => {
const uid = new ObjectId().toHexString();
await expect(() =>
PresetDal.removePreset(uid, new ObjectId().toHexString())
PresetDal.removePreset(uid, new ObjectId().toHexString()),
).rejects.toThrowError("Preset not found");
});
it("should remove", async () => {
@ -394,7 +394,7 @@ describe("PresetDal", () => {
name: "second",
config: { ads: "result" },
}),
])
]),
);
expect(await PresetDal.getPresets(decoyUid)).toEqual(
expect.arrayContaining([
@ -404,7 +404,7 @@ describe("PresetDal", () => {
name: "unknown",
config: { ads: "result" },
}),
])
]),
);
});
it("should not remove present not matching uid", async () => {
@ -420,7 +420,7 @@ describe("PresetDal", () => {
//WHEN
await expect(() =>
PresetDal.removePreset(decoyUid, first)
PresetDal.removePreset(decoyUid, first),
).rejects.toThrowError("Preset not found");
//THEN
@ -434,7 +434,7 @@ describe("PresetDal", () => {
name: "first",
config: { ads: "sellout" },
}),
])
]),
);
});
});
@ -475,7 +475,7 @@ describe("PresetDal", () => {
name: "unknown",
config: { ads: "result" },
}),
])
]),
);
});
});

View file

@ -11,7 +11,7 @@ const timestamp = Date.now() - 60000;
async function createDummyData(
uid: string,
count: number,
modify?: Partial<DBResult>
modify?: Partial<DBResult>,
): Promise<void> {
const dummyUser: UserDal.DBUser = {
_id: new ObjectId(),

View file

@ -122,7 +122,7 @@ describe("UserDal", () => {
// then
// should error because user already exists
await expect(
UserDAL.addUser(newUser.name, newUser.email, newUser.uid)
UserDAL.addUser(newUser.name, newUser.email, newUser.uid),
).rejects.toThrow("User document already exists");
});
@ -168,7 +168,7 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.updateName(user1.uid, user2.name, user1.name)
UserDAL.updateName(user1.uid, user2.name, user1.name),
).rejects.toThrow("Username already taken");
});
@ -186,7 +186,7 @@ describe("UserDal", () => {
expect(updatedUser1.name).toBe(name1.toUpperCase());
await expect(
UserDAL.updateName(user2.uid, name1, user2.name)
UserDAL.updateName(user2.uid, name1, user2.name),
).rejects.toThrow("Username already taken");
});
@ -219,7 +219,7 @@ describe("UserDal", () => {
custom: {},
},
},
}
},
);
const { personalBests } =
@ -298,9 +298,9 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.addResultFilterPreset("non existing uid", mockResultFilter, 5)
UserDAL.addResultFilterPreset("non existing uid", mockResultFilter, 5),
).rejects.toThrow(
"Maximum number of custom filters reached\nStack: add result filter preset"
"Maximum number of custom filters reached\nStack: add result filter preset",
);
});
@ -312,9 +312,9 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.addResultFilterPreset(uid, mockResultFilter, 1)
UserDAL.addResultFilterPreset(uid, mockResultFilter, 1),
).rejects.toThrow(
"Maximum number of custom filters reached\nStack: add result filter preset"
"Maximum number of custom filters reached\nStack: add result filter preset",
);
});
@ -324,9 +324,9 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.addResultFilterPreset(uid, mockResultFilter, 0)
UserDAL.addResultFilterPreset(uid, mockResultFilter, 0),
).rejects.toThrow(
"Maximum number of custom filters reached\nStack: add result filter preset"
"Maximum number of custom filters reached\nStack: add result filter preset",
);
});
@ -340,7 +340,7 @@ describe("UserDal", () => {
const result = await UserDAL.addResultFilterPreset(
uid,
{ ...mockResultFilter },
2
2,
);
// then
@ -357,8 +357,8 @@ describe("UserDal", () => {
await expect(
UserDAL.removeResultFilterPreset(
"non existing uid",
new ObjectId().toHexString()
)
new ObjectId().toHexString(),
),
).rejects.toThrow("Custom filter not found\nStack: remove result filter");
});
@ -370,7 +370,7 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.removeResultFilterPreset(uid, new ObjectId().toHexString())
UserDAL.removeResultFilterPreset(uid, new ObjectId().toHexString()),
).rejects.toThrow("Custom filter not found\nStack: remove result filter");
});
it("should remove filter", async () => {
@ -394,7 +394,7 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.addTag("non existing uid", "tagName")
UserDAL.addTag("non existing uid", "tagName"),
).rejects.toThrow("Maximum number of tags reached\nStack: add tag");
});
@ -410,7 +410,7 @@ describe("UserDal", () => {
// when, then
await expect(UserDAL.addTag(uid, "new")).rejects.toThrow(
"Maximum number of tags reached\nStack: add tag"
"Maximum number of tags reached\nStack: add tag",
);
});
@ -442,7 +442,7 @@ describe("UserDal", () => {
expect.arrayContaining([
expect.objectContaining({ name: "first", personalBests: emptyPb }),
expect.objectContaining({ name: "newTag", personalBests: emptyPb }),
])
]),
);
});
});
@ -454,8 +454,8 @@ describe("UserDal", () => {
UserDAL.editTag(
"non existing uid",
new ObjectId().toHexString(),
"newName"
)
"newName",
),
).rejects.toThrow("Tag not found\nStack: edit tag");
});
@ -472,7 +472,7 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.editTag(uid, new ObjectId().toHexString(), "newName")
UserDAL.editTag(uid, new ObjectId().toHexString(), "newName"),
).rejects.toThrow("Tag not found\nStack: edit tag");
});
@ -502,7 +502,7 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeTag("non existing uid", new ObjectId().toHexString())
UserDAL.removeTag("non existing uid", new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag");
});
@ -519,7 +519,7 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.removeTag(uid, new ObjectId().toHexString())
UserDAL.removeTag(uid, new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag");
});
it("should remove tag", async () => {
@ -556,7 +556,7 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeTagPb("non existing uid", new ObjectId().toHexString())
UserDAL.removeTagPb("non existing uid", new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag pb");
});
@ -573,7 +573,7 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.removeTagPb(uid, new ObjectId().toHexString())
UserDAL.removeTagPb(uid, new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag pb");
});
it("should remove tag pb", async () => {
@ -637,7 +637,7 @@ describe("UserDal", () => {
},
{
badges: [],
}
},
);
const user = await UserDAL.getUser(uid, "test add result filters");
@ -663,7 +663,7 @@ describe("UserDal", () => {
selected: true,
},
],
}
},
);
const updatedUser = await UserDAL.getUser(uid, "test add result filters");
@ -698,12 +698,12 @@ describe("UserDal", () => {
id: 1,
},
],
}
},
);
const updatedUser2 = await UserDAL.getUser(
uid,
"test add result filters"
"test add result filters",
);
expect(updatedUser2.profileDetails).toStrictEqual({
bio: "test bio 2",
@ -767,7 +767,7 @@ describe("UserDal", () => {
},
{
badges: [],
}
},
);
await UserDAL.incrementBananas(uid, 100);
@ -813,7 +813,7 @@ describe("UserDal", () => {
{
enabled: true,
maxMail: 100,
}
},
);
const inbox = await UserDAL.getInbox(uid);
@ -841,7 +841,7 @@ describe("UserDal", () => {
subject: "Hello 1!",
} as any,
],
config
config,
);
await UserDAL.addToInbox(
@ -851,7 +851,7 @@ describe("UserDal", () => {
subject: "Hello 2!",
} as any,
],
config
config,
);
const inbox = await UserDAL.getInbox(uid);
@ -889,7 +889,7 @@ describe("UserDal", () => {
{
enabled: true,
maxMail: 100,
}
},
);
const inbox = await UserDAL.getInbox(user1);
@ -912,7 +912,7 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(UserDAL.updateStreak("non existing uid", 0)).rejects.toThrow(
"User not found\nStack: calculate streak"
"User not found\nStack: calculate streak",
);
});
@ -1125,7 +1125,7 @@ describe("UserDal", () => {
describe("getPartialUser", () => {
it("should throw for unknown user", async () => {
await expect(async () =>
UserDAL.getPartialUser("1234", "stack", [])
UserDAL.getPartialUser("1234", "stack", []),
).rejects.toThrowError("User not found\nStack: stack");
});
@ -1160,7 +1160,7 @@ describe("UserDal", () => {
describe("updateEmail", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.updateEmail("unknown", "test@example.com")
UserDAL.updateEmail("unknown", "test@example.com"),
).rejects.toThrowError("User not found\nStack: update email");
});
it("should update", async () => {
@ -1178,7 +1178,7 @@ describe("UserDal", () => {
describe("resetPb", () => {
it("throws for nonexisting user", async () => {
await expect(async () => UserDAL.resetPb("unknown")).rejects.toThrowError(
"User not found\nStack: reset pb"
"User not found\nStack: reset pb",
);
});
it("should reset", async () => {
@ -1204,7 +1204,7 @@ describe("UserDal", () => {
describe("linkDiscord", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.linkDiscord("unknown", "", "")
UserDAL.linkDiscord("unknown", "", ""),
).rejects.toThrowError("User not found\nStack: link discord");
});
it("should update", async () => {
@ -1240,7 +1240,7 @@ describe("UserDal", () => {
describe("unlinkDiscord", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.unlinkDiscord("unknown")
UserDAL.unlinkDiscord("unknown"),
).rejects.toThrowError("User not found\nStack: unlink discord");
});
it("should update", async () => {
@ -1309,7 +1309,7 @@ describe("UserDal", () => {
await UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id, rewardThree.id],
[]
[],
);
//THEN
@ -1423,7 +1423,7 @@ describe("UserDal", () => {
await UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id, rewardThree.id, rewardOne.id],
[]
[],
);
//THEN
@ -1466,7 +1466,7 @@ describe("UserDal", () => {
await UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id],
[rewardOne.id, rewardTwo.id]
[rewardOne.id, rewardTwo.id],
);
//THEN
@ -1518,8 +1518,8 @@ describe("UserDal", () => {
UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id, rewardThree.id],
[]
)
[],
),
);
await Promise.all(calls);
@ -1545,7 +1545,7 @@ describe("UserDal", () => {
// when, then
await expect(UserDAL.isDiscordIdAvailable(discordId)).resolves.toBe(
false
false,
);
});
});
@ -1558,8 +1558,8 @@ describe("UserDal", () => {
"time",
"15",
"english",
4711
)
4711,
),
).rejects.toThrow("User not found\nStack: update lb memory");
});
@ -1728,9 +1728,9 @@ describe("UserDal", () => {
UserDAL.addTheme("non existing uid", {
name: "new",
colors: [] as any,
})
}),
).rejects.toThrow(
"Maximum number of custom themes reached\nStack: add theme"
"Maximum number of custom themes reached\nStack: add theme",
);
});
@ -1746,9 +1746,9 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.addTheme(uid, { name: "new", colors: [] as any })
UserDAL.addTheme(uid, { name: "new", colors: [] as any }),
).rejects.toThrow(
"Maximum number of custom themes reached\nStack: add theme"
"Maximum number of custom themes reached\nStack: add theme",
);
});
@ -1782,7 +1782,7 @@ describe("UserDal", () => {
name: "newTheme",
colors: newTheme.colors,
}),
])
]),
);
});
});
@ -1794,7 +1794,7 @@ describe("UserDal", () => {
UserDAL.editTheme("non existing uid", new ObjectId().toHexString(), {
name: "newName",
colors: [] as any,
})
}),
).rejects.toThrow("Custom theme not found\nStack: edit theme");
});
@ -1814,7 +1814,7 @@ describe("UserDal", () => {
UserDAL.editTheme(uid, new ObjectId().toHexString(), {
name: "newName",
colors: [] as any,
})
}),
).rejects.toThrow("Custom theme not found\nStack: edit theme");
});
@ -1846,7 +1846,7 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeTheme("non existing uid", new ObjectId().toHexString())
UserDAL.removeTheme("non existing uid", new ObjectId().toHexString()),
).rejects.toThrow("Custom theme not found\nStack: remove theme");
});
@ -1863,7 +1863,7 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.removeTheme(uid, new ObjectId().toHexString())
UserDAL.removeTheme(uid, new ObjectId().toHexString()),
).rejects.toThrow("Custom theme not found\nStack: remove theme");
});
it("should remove theme", async () => {
@ -1901,9 +1901,9 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.addFavoriteQuote("non existing uid", "english", "1", 5)
UserDAL.addFavoriteQuote("non existing uid", "english", "1", 5),
).rejects.toThrow(
"Maximum number of favorite quotes reached\nStack: add favorite quote"
"Maximum number of favorite quotes reached\nStack: add favorite quote",
);
});
@ -1919,9 +1919,9 @@ describe("UserDal", () => {
// when, then
await expect(
UserDAL.addFavoriteQuote(uid, "polish", "6", 5)
UserDAL.addFavoriteQuote(uid, "polish", "6", 5),
).rejects.toThrow(
"Maximum number of favorite quotes reached\nStack: add favorite quote"
"Maximum number of favorite quotes reached\nStack: add favorite quote",
);
});
@ -1972,7 +1972,7 @@ describe("UserDal", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeFavoriteQuote("non existing uid", "english", "0")
UserDAL.removeFavoriteQuote("non existing uid", "english", "0"),
).rejects.toThrow("User not found\nStack: remove favorite quote");
});

View file

@ -14,7 +14,7 @@ export async function setup(): Promise<void> {
startedMongoContainer = await mongoContainer.start();
const mongoUrl = `mongodb://${startedMongoContainer?.getHost()}:${startedMongoContainer?.getMappedPort(
27017
27017,
)}`;
process.env["TEST_DB_URL"] = mongoUrl;
@ -26,7 +26,7 @@ export async function setup(): Promise<void> {
startedRedisContainer = await redisContainer.start();
const redisUrl = `redis://${startedRedisContainer.getHost()}:${startedRedisContainer.getMappedPort(
6379
6379,
)}`;
process.env["REDIS_URI"] = redisUrl;
}

View file

@ -22,7 +22,7 @@ describe("Weekly XP Leaderboards", () => {
describe("get", () => {
it("should get if enabled", () => {
expect(WeeklyXpLeaderboard.get(leaderboardsConfig)).toBeInstanceOf(
WeeklyXpLeaderboard.WeeklyXpLeaderboard
WeeklyXpLeaderboard.WeeklyXpLeaderboard,
);
});
it("should return null if disabled", () => {
@ -203,7 +203,10 @@ describe("Weekly XP Leaderboards", () => {
it("should return null for unknown user", async () => {
expect(await lb.getRank("decoy", leaderboardsConfig)).toBeNull();
expect(
await lb.getRank("decoy", leaderboardsConfig, ["unknown", "unknown2"])
await lb.getRank("decoy", leaderboardsConfig, [
"unknown",
"unknown2",
]),
).toBeNull();
});
@ -217,11 +220,11 @@ describe("Weekly XP Leaderboards", () => {
//WHEN / THEN
expect(
await lb.getRank(user2.uid, leaderboardsConfig, friends)
await lb.getRank(user2.uid, leaderboardsConfig, friends),
).toEqual({ rank: 2, friendsRank: 1, totalXp: 60, ...user2 });
expect(
await lb.getRank(user1.uid, leaderboardsConfig, friends)
await lb.getRank(user1.uid, leaderboardsConfig, friends),
).toEqual({ rank: 3, friendsRank: 2, totalXp: 50, ...user1 });
});
});
@ -234,7 +237,7 @@ describe("Weekly XP Leaderboards", () => {
//WHEN
await WeeklyXpLeaderboard.purgeUserFromXpLeaderboards(
cheater.uid,
leaderboardsConfig
leaderboardsConfig,
);
//THEN
expect(await lb.getRank(cheater.uid, leaderboardsConfig)).toBeNull();
@ -246,7 +249,7 @@ describe("Weekly XP Leaderboards", () => {
async function givenResult(
xpGained: number,
entry?: Partial<RedisXpLeaderboardEntry>
entry?: Partial<RedisXpLeaderboardEntry>,
): Promise<RedisXpLeaderboardEntry> {
const uid = new ObjectId().toHexString();
const result: RedisXpLeaderboardEntry = {

View file

@ -88,10 +88,10 @@ describe("Daily Leaderboards", () => {
language,
mode,
mode2 as any,
dailyLeaderboardsConfig
dailyLeaderboardsConfig,
);
expect(!!result).toBe(expected);
}
},
);
});
describe("DailyLeaderboard class", () => {
@ -100,7 +100,7 @@ describe("Daily Leaderboards", () => {
"english",
"time",
"60",
dailyLeaderboardsConfig
dailyLeaderboardsConfig,
)!;
describe("addResult", () => {
it("adds best result for user", async () => {
@ -117,7 +117,7 @@ describe("Daily Leaderboards", () => {
0,
5,
dailyLeaderboardsConfig,
true
true,
);
//THEN
expect(results).toEqual({
@ -138,10 +138,10 @@ describe("Daily Leaderboards", () => {
await Promise.all(
new Array(maxResults - 1)
.fill(0)
.map(() => givenResult({ wpm: 20 + Math.random() * 100 }))
.map(() => givenResult({ wpm: 20 + Math.random() * 100 })),
);
expect(
await lb.getResults(0, 5, dailyLeaderboardsConfig, true)
await lb.getResults(0, 5, dailyLeaderboardsConfig, true),
).toEqual(expect.objectContaining({ count: maxResults }));
expect(await lb.getRank(bob.uid, dailyLeaderboardsConfig)).toEqual({
@ -155,7 +155,7 @@ describe("Daily Leaderboards", () => {
//THEN
//max count is still the same, but bob is no longer on the leaderboard
expect(
await lb.getResults(0, 5, dailyLeaderboardsConfig, true)
await lb.getResults(0, 5, dailyLeaderboardsConfig, true),
).toEqual(expect.objectContaining({ count: maxResults }));
expect(await lb.getRank(bob.uid, dailyLeaderboardsConfig)).toBeNull();
});
@ -172,7 +172,7 @@ describe("Daily Leaderboards", () => {
0,
5,
dailyLeaderboardsConfig,
true
true,
);
//THEN
expect(results).toEqual({
@ -198,7 +198,7 @@ describe("Daily Leaderboards", () => {
1,
2,
dailyLeaderboardsConfig,
true
true,
);
//THEN
expect(results).toEqual({
@ -222,7 +222,7 @@ describe("Daily Leaderboards", () => {
0,
5,
dailyLeaderboardsConfig,
false
false,
);
//THEN
expect(results).toEqual({
@ -250,7 +250,7 @@ describe("Daily Leaderboards", () => {
5,
dailyLeaderboardsConfig,
true,
[user2.uid, user4.uid, new ObjectId().toHexString()]
[user2.uid, user4.uid, new ObjectId().toHexString()],
);
//THEN
expect(results).toEqual({
@ -278,7 +278,7 @@ describe("Daily Leaderboards", () => {
2,
dailyLeaderboardsConfig,
true,
[user1.uid, user2.uid, user4.uid, new ObjectId().toHexString()]
[user1.uid, user2.uid, user4.uid, new ObjectId().toHexString()],
);
//THEN
@ -298,7 +298,7 @@ describe("Daily Leaderboards", () => {
5,
dailyLeaderboardsConfig,
true,
[]
[],
);
//THEN
expect(results).toEqual({
@ -332,7 +332,7 @@ describe("Daily Leaderboards", () => {
await lb.getRank("decoy", dailyLeaderboardsConfig, [
"unknown",
"unknown2",
])
]),
).toBeNull();
});
@ -345,11 +345,11 @@ describe("Daily Leaderboards", () => {
//WHEN / THEN
expect(
await lb.getRank(user2.uid, dailyLeaderboardsConfig, friends)
await lb.getRank(user2.uid, dailyLeaderboardsConfig, friends),
).toEqual({ rank: 2, friendsRank: 1, ...user2 });
expect(
await lb.getRank(user1.uid, dailyLeaderboardsConfig, friends)
await lb.getRank(user1.uid, dailyLeaderboardsConfig, friends),
).toEqual({ rank: 3, friendsRank: 2, ...user1 });
});
});
@ -363,7 +363,7 @@ describe("Daily Leaderboards", () => {
//WHEN
await DailyLeaderboards.purgeUserFromDailyLeaderboards(
cheater.uid,
dailyLeaderboardsConfig
dailyLeaderboardsConfig,
);
//THEN
expect(await lb.getRank(cheater.uid, dailyLeaderboardsConfig)).toBeNull();
@ -375,12 +375,12 @@ describe("Daily Leaderboards", () => {
{ rank: 1, ...user1 },
{ rank: 2, ...user2 },
],
}
},
);
});
async function givenResult(
entry?: Partial<RedisDailyLeaderboardEntry>
entry?: Partial<RedisDailyLeaderboardEntry>,
): Promise<RedisDailyLeaderboardEntry> {
const uid = new ObjectId().toHexString();
const result = {

View file

@ -10,10 +10,11 @@ import * as AuthUtils from "../../src/utils/auth";
export async function mockAuthenticateWithApeKey(
uid: string,
config: Configuration
config: Configuration,
): Promise<string> {
if (!config.apeKeys.acceptKeys)
if (!config.apeKeys.acceptKeys) {
throw Error("config.apeKeys.acceptedKeys needs to be set to true");
}
const { apeKeyBytes, apeKeySaltRounds } = config.apeKeys;
const apiKey = randomBytes(apeKeyBytes).toString("base64url");

View file

@ -3,7 +3,7 @@ import * as ConnectionsDal from "../../src/dal/connections";
export async function createConnection(
data: Partial<ConnectionsDal.DBConnection>,
maxPerUser = 25
maxPerUser = 25,
): Promise<ConnectionsDal.DBConnection> {
const result = await ConnectionsDal.create(
{
@ -14,7 +14,7 @@ export async function createConnection(
uid: data.receiverUid ?? new ObjectId().toHexString(),
name: data.receiverName ?? "user" + new ObjectId().toHexString(),
},
maxPerUser
maxPerUser,
);
await ConnectionsDal.__testing
.getCollection()

View file

@ -6,7 +6,7 @@ export function enableMonkeyErrorExpects(): void {
expect.extend({
toMatchMonkeyError(
received: MonkeyError,
expected: MonkeyError
expected: MonkeyError,
): MatcherResult {
return {
pass:

View file

@ -7,7 +7,7 @@ export function enableRateLimitExpects(): void {
expect.extend({
toBeRateLimited: async (
received: SuperTest,
expected: ExpectedRateLimit
expected: ExpectedRateLimit,
): Promise<MatcherResult> => {
const now = Date.now();
const { headers } = await received.expect(200);

View file

@ -4,7 +4,7 @@ import { ObjectId } from "mongodb";
import { PersonalBest } from "@monkeytype/schemas/shared";
export async function createUser(
user?: Partial<UserDAL.DBUser>
user?: Partial<UserDAL.DBUser>,
): Promise<UserDAL.DBUser> {
const uid = new ObjectId().toHexString();
await UserDAL.addUser("user" + uid, uid + "@example.com", uid);
@ -13,14 +13,14 @@ export async function createUser(
}
export async function createUserWithoutMigration(
user?: Partial<UserDAL.DBUser>
user?: Partial<UserDAL.DBUser>,
): Promise<UserDAL.DBUser> {
const uid = new ObjectId().toHexString();
await UserDAL.addUser("user" + uid, uid + "@example.com", uid);
await DB.collection("users").updateOne({ uid }, { $set: { ...user } });
await DB.collection("users").updateOne(
{ uid },
{ $unset: { testActivity: "" } }
{ $unset: { testActivity: "" } },
);
return await UserDAL.getUser(uid, "test");
@ -29,7 +29,7 @@ export async function createUserWithoutMigration(
export function pb(
wpm: number,
acc: number = 90,
timestamp: number = 1
timestamp: number = 1,
): PersonalBest {
return {
acc,

View file

@ -46,17 +46,17 @@ describe("AdminController", () => {
});
it("should fail if user is no admin", async () => {
await expectFailForNonAdmin(
mockApp.get("/admin").set("Authorization", `Bearer ${uid}`)
mockApp.get("/admin").set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if admin endpoints are disabled", async () => {
await expectFailForDisabledEndpoint(
mockApp.get("/admin").set("Authorization", `Bearer ${uid}`)
mockApp.get("/admin").set("Authorization", `Bearer ${uid}`),
);
});
it("should be rate limited", async () => {
await expect(
mockApp.get("/admin").set("Authorization", `Bearer ${uid}`)
mockApp.get("/admin").set("Authorization", `Bearer ${uid}`),
).toBeRateLimited({ max: 1, windowMs: 5000 });
});
});
@ -68,7 +68,7 @@ describe("AdminController", () => {
beforeEach(() => {
[userBannedMock, georgeBannedMock, getUserMock].forEach((it) =>
it.mockClear()
it.mockClear(),
);
userBannedMock.mockResolvedValue();
});
@ -165,7 +165,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/toggleBan")
.send({ uid: new ObjectId().toHexString() })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if admin endpoints are disabled", async () => {
@ -174,7 +174,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/toggleBan")
.send({ uid: new ObjectId().toHexString() })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should be rate limited", async () => {
@ -190,7 +190,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/toggleBan")
.send({ uid: victimUid })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
).toBeRateLimited({ max: 1, windowMs: 5000 });
});
});
@ -258,7 +258,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/clearStreakHourOffset")
.send({ uid: new ObjectId().toHexString() })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if admin endpoints are disabled", async () => {
@ -267,7 +267,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/clearStreakHourOffset")
.send({ uid: new ObjectId().toHexString() })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should be rate limited", async () => {
@ -279,7 +279,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/clearStreakHourOffset")
.send({ uid: victimUid })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
).toBeRateLimited({ max: 1, windowMs: 5000 });
});
});
@ -291,7 +291,7 @@ describe("AdminController", () => {
beforeEach(() => {
[getReportsMock, deleteReportsMock, addToInboxMock].forEach((it) =>
it.mockClear()
it.mockClear(),
);
deleteReportsMock.mockResolvedValue();
});
@ -374,7 +374,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/report/accept")
.send({ reports: [] })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if admin endpoints are disabled", async () => {
@ -383,7 +383,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/report/accept")
.send({ reports: [] })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should be rate limited", async () => {
@ -395,7 +395,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/report/accept")
.send({ reports: [{ reportId: "1" }] })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
).toBeRateLimited({ max: 1, windowMs: 5000 });
});
});
@ -492,7 +492,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/report/reject")
.send({ reports: [] })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if admin endpoints are disabled", async () => {
@ -501,7 +501,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/report/reject")
.send({ reports: [] })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should be rate limited", async () => {
@ -513,14 +513,14 @@ describe("AdminController", () => {
mockApp
.post("/admin/report/reject")
.send({ reports: [{ reportId: "1" }] })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
).toBeRateLimited({ max: 1, windowMs: 5000 });
});
});
describe("send forgot password email", () => {
const sendForgotPasswordEmailMock = vi.spyOn(
AuthUtil,
"sendForgotPasswordEmail"
"sendForgotPasswordEmail",
);
beforeEach(() => {
@ -544,7 +544,7 @@ describe("AdminController", () => {
});
expect(sendForgotPasswordEmailMock).toHaveBeenCalledWith(
"meowdec@example.com"
"meowdec@example.com",
);
});
it("should be rate limited", async () => {
@ -553,7 +553,7 @@ describe("AdminController", () => {
mockApp
.post("/admin/sendForgotPasswordEmail")
.send({ email: "meowdec@example.com" })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
).toBeRateLimited({ max: 1, windowMs: 5000 });
});
});
@ -574,6 +574,6 @@ async function enableAdminEndpoints(enabled: boolean): Promise<void> {
mockConfig.admin = { ...mockConfig.admin, endpointsEnabled: enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}

View file

@ -65,12 +65,12 @@ describe("ApeKeyController", () => {
});
it("should fail if apeKeys endpoints are disabled", async () => {
await expectFailForDisabledEndpoint(
mockApp.get("/ape-keys").set("Authorization", `Bearer ${uid}`)
mockApp.get("/ape-keys").set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if user has no apeKey permissions", async () => {
await expectFailForNoPermissions(
mockApp.get("/ape-keys").set("Authorization", `Bearer ${uid}`)
mockApp.get("/ape-keys").set("Authorization", `Bearer ${uid}`),
);
});
});
@ -122,7 +122,7 @@ describe("ApeKeyController", () => {
name: "test",
uid: uid,
useCount: 0,
})
}),
);
});
it("should fail without mandatory properties", async () => {
@ -167,7 +167,7 @@ describe("ApeKeyController", () => {
//THEN
expect(body.message).toEqual(
"Maximum number of ApeKeys have been generated"
"Maximum number of ApeKeys have been generated",
);
});
it("should fail if apeKeys endpoints are disabled", async () => {
@ -175,7 +175,7 @@ describe("ApeKeyController", () => {
mockApp
.post("/ape-keys")
.send({ name: "test", enabled: false })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if user has no apeKey permissions", async () => {
@ -183,7 +183,7 @@ describe("ApeKeyController", () => {
mockApp
.post("/ape-keys")
.send({ name: "test", enabled: false })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
});
@ -228,7 +228,7 @@ describe("ApeKeyController", () => {
uid,
apeKeyId,
"new",
undefined
undefined,
);
});
it("should fail with missing path", async () => {
@ -261,7 +261,7 @@ describe("ApeKeyController", () => {
mockApp
.patch(`/ape-keys/${apeKeyId}`)
.send({ name: "test", enabled: false })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
it("should fail if user has no apeKey permissions", async () => {
@ -269,7 +269,7 @@ describe("ApeKeyController", () => {
mockApp
.patch(`/ape-keys/${apeKeyId}`)
.send({ name: "test", enabled: false })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
});
@ -308,7 +308,7 @@ describe("ApeKeyController", () => {
await expectFailForDisabledEndpoint(
mockApp
.delete(`/ape-keys/${apeKeyId}`)
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
@ -316,7 +316,7 @@ describe("ApeKeyController", () => {
await expectFailForNoPermissions(
mockApp
.delete(`/ape-keys/${apeKeyId}`)
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
});
@ -324,7 +324,7 @@ describe("ApeKeyController", () => {
getUserMock.mockResolvedValue(user(uid, { canManageApeKeys: false }));
const { body } = await call.expect(403);
expect(body.message).toEqual(
"You have lost access to ape keys, please contact support"
"You have lost access to ape keys, please contact support",
);
}
async function expectFailForDisabledEndpoint(call: SuperTest): Promise<void> {
@ -336,7 +336,7 @@ describe("ApeKeyController", () => {
function apeKeyDb(
uid: string,
data?: Partial<ApeKeyDal.DBApeKey>
data?: Partial<ApeKeyDal.DBApeKey>,
): ApeKeyDal.DBApeKey {
return {
_id: new ObjectId(),
@ -361,7 +361,7 @@ async function enableApeKeysEndpoints(enabled: boolean): Promise<void> {
};
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}

View file

@ -100,7 +100,7 @@ describe("Configuration Controller", () => {
describe("updateConfiguration", () => {
const patchConfigurationMock = vi.spyOn(
Configuration,
"patchConfiguration"
"patchConfiguration",
);
beforeEach(() => {
patchConfigurationMock.mockClear();

View file

@ -150,7 +150,7 @@ describe("ConnectionsController", () => {
it("should fail if connections endpoints are disabled", async () => {
await expectFailForDisabledEndpoint(
mockApp.get("/connections").set("Authorization", `Bearer ${uid}`)
mockApp.get("/connections").set("Authorization", `Bearer ${uid}`),
);
});
it("should fail without authentication", async () => {
@ -177,7 +177,7 @@ describe("ConnectionsController", () => {
beforeEach(() => {
[getUserByNameMock, getPartialUserMock, createUserMock].forEach((it) =>
it.mockClear()
it.mockClear(),
);
});
@ -220,12 +220,12 @@ describe("ConnectionsController", () => {
expect(getUserByNameMock).toHaveBeenCalledWith(
"Kevin",
"create connection"
"create connection",
);
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
"create connection",
["uid", "name"]
["uid", "name"],
);
expect(createUserMock).toHaveBeenCalledWith(me, myFriend, 100);
});
@ -282,7 +282,7 @@ describe("ConnectionsController", () => {
mockApp
.post("/connections")
.send({ receiverName: "1" })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
@ -310,7 +310,7 @@ describe("ConnectionsController", () => {
});
it("should fail if connections endpoints are disabled", async () => {
await expectFailForDisabledEndpoint(
mockApp.delete("/connections/1").set("Authorization", `Bearer ${uid}`)
mockApp.delete("/connections/1").set("Authorization", `Bearer ${uid}`),
);
});
@ -368,7 +368,7 @@ describe("ConnectionsController", () => {
mockApp
.patch("/connections/1")
.send({ status: "accepted" })
.set("Authorization", `Bearer ${uid}`)
.set("Authorization", `Bearer ${uid}`),
);
});
@ -386,7 +386,7 @@ async function enableConnectionsEndpoints(enabled: boolean): Promise<void> {
mockConfig.connections = { ...mockConfig.connections, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}

View file

@ -23,7 +23,7 @@ describe("DevController", () => {
.expect(503);
//THEN
expect(body.message).toEqual(
"Development endpoints are only available in DEV mode."
"Development endpoints are only available in DEV mode.",
);
});
it("should fail without mandatory properties", async () => {

View file

@ -95,14 +95,14 @@ describe("Loaderboard Controller", () => {
0,
50,
false,
undefined
undefined,
);
expect(getLeaderboardCountMock).toHaveBeenCalledWith(
"time",
"60",
"english",
undefined
undefined,
);
});
@ -143,7 +143,7 @@ describe("Loaderboard Controller", () => {
page,
pageSize,
false,
undefined
undefined,
);
});
@ -176,13 +176,13 @@ describe("Loaderboard Controller", () => {
0,
50,
false,
uid
uid,
);
expect(getLeaderboardCountMock).toHaveBeenCalledWith(
"time",
"60",
"english",
uid
uid,
);
});
@ -205,7 +205,7 @@ describe("Loaderboard Controller", () => {
.get("/leaderboards")
.query({ language, mode, mode2 })
.expect(expectStatus);
}
},
);
});
@ -275,7 +275,7 @@ describe("Loaderboard Controller", () => {
.expect(503);
expect(body.message).toEqual(
"Leaderboard is currently updating. Please try again in a few seconds."
"Leaderboard is currently updating. Please try again in a few seconds.",
);
});
});
@ -329,7 +329,7 @@ describe("Loaderboard Controller", () => {
"60",
"english",
uid,
false
false,
);
});
it("should get for english time 60 friends only", async () => {
@ -355,7 +355,7 @@ describe("Loaderboard Controller", () => {
"60",
"english",
uid,
true
true,
);
});
it("should get with ape key", async () => {
@ -458,7 +458,7 @@ describe("Loaderboard Controller", () => {
.expect(503);
expect(body.message).toEqual(
"Leaderboard is currently updating. Please try again in a few seconds."
"Leaderboard is currently updating. Please try again in a few seconds.",
);
});
});
@ -466,14 +466,14 @@ describe("Loaderboard Controller", () => {
describe("get daily leaderboard", () => {
const getDailyLeaderboardMock = vi.spyOn(
DailyLeaderboards,
"getDailyLeaderboard"
"getDailyLeaderboard",
);
const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");
const getResultMock = vi.fn();
beforeEach(async () => {
[getDailyLeaderboardMock, getFriendsUidsMock, getResultMock].forEach(
(it) => it.mockClear()
(it) => it.mockClear(),
);
vi.useFakeTimers();
vi.setSystemTime(1722606812000);
@ -551,7 +551,7 @@ describe("Loaderboard Controller", () => {
"time",
"60",
lbConf,
-1
-1,
);
expect(getResultMock).toHaveBeenCalledWith(
@ -559,7 +559,7 @@ describe("Loaderboard Controller", () => {
50,
lbConf,
premiumEnabled,
undefined
undefined,
);
});
@ -594,7 +594,7 @@ describe("Loaderboard Controller", () => {
"time",
"60",
lbConf,
1722470400000
1722470400000,
);
});
it("should get for english time 60 with page and pageSize", async () => {
@ -634,7 +634,7 @@ describe("Loaderboard Controller", () => {
"time",
"60",
lbConf,
-1
-1,
);
expect(getResultMock).toHaveBeenCalledWith(
@ -642,7 +642,7 @@ describe("Loaderboard Controller", () => {
pageSize,
lbConf,
premiumEnabled,
undefined
undefined,
);
});
@ -676,7 +676,7 @@ describe("Loaderboard Controller", () => {
"time",
"60",
lbConf,
-1
-1,
);
expect(getResultMock).toHaveBeenCalledWith(
@ -684,7 +684,7 @@ describe("Loaderboard Controller", () => {
50,
lbConf,
premiumEnabled,
friends
friends,
);
});
@ -711,7 +711,7 @@ describe("Loaderboard Controller", () => {
const { body } = await mockApp.get("/leaderboards/daily").expect(503);
expect(body.message).toEqual(
"Daily leaderboards are not available at this time."
"Daily leaderboards are not available at this time.",
);
});
@ -797,7 +797,7 @@ describe("Loaderboard Controller", () => {
.expect(404);
expect(body.message).toEqual(
"There is no daily leaderboard for this mode"
"There is no daily leaderboard for this mode",
);
});
});
@ -805,14 +805,14 @@ describe("Loaderboard Controller", () => {
describe("get daily leaderboard rank", () => {
const getDailyLeaderboardMock = vi.spyOn(
DailyLeaderboards,
"getDailyLeaderboard"
"getDailyLeaderboard",
);
const getRankMock = vi.fn();
const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");
beforeEach(async () => {
[getDailyLeaderboardMock, getRankMock, getFriendsUidsMock].forEach((it) =>
it.mockClear()
it.mockClear(),
);
getDailyLeaderboardMock.mockReturnValue({
@ -874,7 +874,7 @@ describe("Loaderboard Controller", () => {
"time",
"60",
lbConf,
-1
-1,
);
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
@ -907,7 +907,7 @@ describe("Loaderboard Controller", () => {
"time",
"60",
lbConf,
-1
-1,
);
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, friends);
@ -923,7 +923,7 @@ describe("Loaderboard Controller", () => {
.expect(503);
expect(body.message).toEqual(
"Daily leaderboards are not available at this time."
"Daily leaderboards are not available at this time.",
);
});
@ -1019,7 +1019,7 @@ describe("Loaderboard Controller", () => {
.expect(404);
expect(body.message).toEqual(
"There is no daily leaderboard for this mode"
"There is no daily leaderboard for this mode",
);
});
});
@ -1031,7 +1031,7 @@ describe("Loaderboard Controller", () => {
beforeEach(async () => {
[getXpWeeklyLeaderboardMock, getResultMock, getFriendsUidsMock].forEach(
(it) => it.mockClear()
(it) => it.mockClear(),
);
vi.useFakeTimers();
vi.setSystemTime(1722606812000);
@ -1100,7 +1100,7 @@ describe("Loaderboard Controller", () => {
50,
lbConf,
false,
undefined
undefined,
);
});
@ -1128,7 +1128,7 @@ describe("Loaderboard Controller", () => {
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
lbConf,
1721606400000
1721606400000,
);
});
@ -1164,7 +1164,7 @@ describe("Loaderboard Controller", () => {
pageSize,
lbConf,
false,
undefined
undefined,
);
});
@ -1200,7 +1200,7 @@ describe("Loaderboard Controller", () => {
pageSize,
lbConf,
false,
friends
friends,
);
});
@ -1210,7 +1210,7 @@ describe("Loaderboard Controller", () => {
const { body } = await mockApp.get("/leaderboards/xp/weekly").expect(503);
expect(body.message).toEqual(
"Weekly XP leaderboards are not available at this time."
"Weekly XP leaderboards are not available at this time.",
);
});
@ -1260,7 +1260,7 @@ describe("Loaderboard Controller", () => {
beforeEach(async () => {
[getXpWeeklyLeaderboardMock, getRankMock, getFriendsUidsMock].forEach(
(it) => it.mockClear()
(it) => it.mockClear(),
);
await weeklyLeaderboardEnabled(true);
@ -1330,7 +1330,7 @@ describe("Loaderboard Controller", () => {
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
lbConf,
1721606400000
1721606400000,
);
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
@ -1371,7 +1371,7 @@ describe("Loaderboard Controller", () => {
.expect(503);
expect(body.message).toEqual(
"Weekly XP leaderboards are not available at this time."
"Weekly XP leaderboards are not available at this time.",
);
});
@ -1425,7 +1425,7 @@ async function acceptApeKeys(enabled: boolean): Promise<void> {
mockConfig.apeKeys = { ...mockConfig.apeKeys, acceptKeys: enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -1437,7 +1437,7 @@ async function dailyLeaderboardEnabled(enabled: boolean): Promise<void> {
};
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
async function weeklyLeaderboardEnabled(enabled: boolean): Promise<void> {
@ -1448,7 +1448,7 @@ async function weeklyLeaderboardEnabled(enabled: boolean): Promise<void> {
};
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
async function enableConnectionsFeature(enabled: boolean): Promise<void> {
@ -1456,6 +1456,6 @@ async function enableConnectionsFeature(enabled: boolean): Promise<void> {
mockConfig.connections = { ...mockConfig.connections, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}

View file

@ -31,7 +31,7 @@ describe("PublicController", () => {
expect(getSpeedHistogramMock).toHaveBeenCalledWith(
"english",
"time",
"60"
"60",
);
});

View file

@ -192,7 +192,7 @@ describe("QuotesController", () => {
newQuote.text,
newQuote.source,
newQuote.language,
uid
uid,
);
expect(verifyCaptchaMock).toHaveBeenCalledWith(newQuote.captcha);
@ -212,7 +212,7 @@ describe("QuotesController", () => {
//THEN
expect(body.message).toEqual(
"Quote submission is disabled temporarily. The queue is quite long and we need some time to catch up."
"Quote submission is disabled temporarily. The queue is quite long and we need some time to catch up.",
);
});
it("should fail without mandatory properties", async () => {
@ -315,7 +315,7 @@ describe("QuotesController", () => {
quoteId,
"editedText",
"editedSource",
"Bob"
"Bob",
);
});
it("should approve with optional parameters as null", async () => {
@ -343,7 +343,7 @@ describe("QuotesController", () => {
quoteId,
undefined,
undefined,
"Bob"
"Bob",
);
});
it("should approve without optional parameters", async () => {
@ -371,7 +371,7 @@ describe("QuotesController", () => {
quoteId,
undefined,
undefined,
"Bob"
"Bob",
);
});
it("should fail without mandatory properties", async () => {
@ -794,7 +794,7 @@ describe("QuotesController", () => {
comment: "I don't like this.",
}),
10, //configuration maxReport
20 //configuration contentReportLimit
20, //configuration contentReportLimit
);
});
@ -877,7 +877,7 @@ async function enableQuotes(enabled: boolean): Promise<void> {
mockConfig.quotes = { ...mockConfig.quotes, submissionsEnabled: enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -891,6 +891,6 @@ async function enableQuoteReporting(enabled: boolean): Promise<void> {
};
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}

View file

@ -126,7 +126,7 @@ describe("result controller test", () => {
expect(body.message).toEqual(
`Max results limit of ${
(await configuration).results.limits.regularUser
} exceeded.`
} exceeded.`,
);
});
it("should get with higher max limit for premium user", async () => {
@ -199,7 +199,7 @@ describe("result controller test", () => {
expect(body.message).toEqual(
`Max results limit of ${
(await configuration).results.limits.premiumUser
} exceeded.`
} exceeded.`,
);
});
it("should get results within regular limits for premium users even if premium is globally disabled", async () => {
@ -274,7 +274,7 @@ describe("result controller test", () => {
});
it("should be rate limited", async () => {
await expect(
mockApp.get("/results").set("Authorization", `Bearer ${uid}`)
mockApp.get("/results").set("Authorization", `Bearer ${uid}`),
).toBeRateLimited({ max: 60, windowMs: 60 * 60 * 1000 });
});
it("should be rate limited for ape keys", async () => {
@ -284,7 +284,7 @@ describe("result controller test", () => {
//WHEN
await expect(
mockApp.get("/results").set("Authorization", `ApeKey ${apeKey}`)
mockApp.get("/results").set("Authorization", `ApeKey ${apeKey}`),
).toBeRateLimited({ max: 30, windowMs: 24 * 60 * 60 * 1000 });
});
});
@ -340,7 +340,7 @@ describe("result controller test", () => {
await expect(
mockApp
.get(`/results/id/${result._id}`)
.set("Authorization", `ApeKey ${apeKey}`)
.set("Authorization", `ApeKey ${apeKey}`),
).toBeRateLimited({ max: 60, windowMs: 60 * 60 * 1000 });
});
});
@ -394,7 +394,7 @@ describe("result controller test", () => {
//WHEN
await expect(
mockApp.get("/results/last").set("Authorization", `ApeKey ${apeKey}`)
mockApp.get("/results/last").set("Authorization", `ApeKey ${apeKey}`),
).toBeRateLimited({ max: 30, windowMs: 60 * 1000 }); //should use defaultApeRateLimit
});
});
@ -672,18 +672,18 @@ describe("result controller test", () => {
testDuration: 15.1,
uid: uid,
wpm: 80,
})
}),
);
expect(publicUpdateStatsMock).toHaveBeenCalledWith(
4,
15.1 + 2 - 5 //duration + incompleteTestSeconds-afk
15.1 + 2 - 5, //duration + incompleteTestSeconds-afk
);
expect(userIncrementXpMock).toHaveBeenCalledWith(uid, 0);
expect(userUpdateTypingStatsMock).toHaveBeenCalledWith(
uid,
4,
15.1 + 2 - 5 //duration + incompleteTestSeconds-afk
15.1 + 2 - 5, //duration + incompleteTestSeconds-afk
);
});
it("should fail if result saving is disabled", async () => {
@ -826,7 +826,7 @@ async function enablePremiumFeatures(enabled: boolean): Promise<void> {
mockConfig.users.premium = { ...mockConfig.users.premium, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
function givenDbResult(uid: string, customize?: Partial<DBResult>): DBResult {
@ -869,7 +869,7 @@ async function acceptApeKeys(enabled: boolean): Promise<void> {
};
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -878,7 +878,7 @@ async function enableResultsSaving(enabled: boolean): Promise<void> {
mockConfig.results = { ...mockConfig.results, savingEnabled: enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
async function enableUsersXpGain(enabled: boolean): Promise<void> {
@ -886,6 +886,6 @@ async function enableUsersXpGain(enabled: boolean): Promise<void> {
mockConfig.users.xp = { ...mockConfig.users.xp, enabled, funboxBonus: 1 };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}

View file

@ -330,11 +330,11 @@ describe("user controller test", () => {
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
"request verification email",
["uid", "name", "email"]
["uid", "name", "email"],
);
expect(adminGenerateVerificationLinkMock).toHaveBeenCalledWith(
"newuser@mail.com",
{ url: "http://localhost:3000" }
{ url: "http://localhost:3000" },
);
});
it("should fail with missing firebase user", async () => {
@ -349,7 +349,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toContain(
"Auth user not found, even though the token got decoded"
"Auth user not found, even though the token got decoded",
);
});
it("should fail with already verified email", async () => {
@ -379,7 +379,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Authenticated email does not match the email found in the database. This might happen if you recently changed your email. Please refresh and try again."
"Authenticated email does not match the email found in the database. This might happen if you recently changed your email. Please refresh and try again.",
);
});
@ -427,7 +427,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Auth user not found when the user was found in the database. Contact support with this error message and your email\n" +
'Stack: {"decodedTokenEmail":"newuser@mail.com","userInfoEmail":"newuser@mail.com"}'
'Stack: {"decodedTokenEmail":"newuser@mail.com","userInfoEmail":"newuser@mail.com"}',
);
});
it("should fail with unknown error", async () => {
@ -446,14 +446,14 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Failed to generate an email verification link: Internal server error"
"Failed to generate an email verification link: Internal server error",
);
});
});
describe("sendForgotPasswordEmail", () => {
const sendForgotPasswordEmailMock = vi.spyOn(
AuthUtils,
"sendForgotPasswordEmail"
"sendForgotPasswordEmail",
);
const verifyCaptchaMock = vi.spyOn(Captcha, "verify");
@ -478,7 +478,7 @@ describe("user controller test", () => {
});
expect(sendForgotPasswordEmailMock).toHaveBeenCalledWith(
"bob@example.com"
"bob@example.com",
);
});
it("should fail without mandatory properties", async () => {
@ -627,11 +627,11 @@ describe("user controller test", () => {
const deleteAllResultMock = vi.spyOn(ResultDal, "deleteAll");
const purgeUserFromDailyLeaderboardsMock = vi.spyOn(
DailyLeaderboards,
"purgeUserFromDailyLeaderboards"
"purgeUserFromDailyLeaderboards",
);
const purgeUserFromXpLeaderboardsMock = vi.spyOn(
WeeklyXpLeaderboard,
"purgeUserFromXpLeaderboards"
"purgeUserFromXpLeaderboards",
);
const blocklistAddMock = vi.spyOn(BlocklistDal, "add");
const connectionsDeletebyUidMock = vi.spyOn(ConnectionsDal, "deleteByUid");
@ -700,11 +700,11 @@ describe("user controller test", () => {
expect(connectionsDeletebyUidMock).toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).dailyLeaderboards
(await configuration).dailyLeaderboards,
);
expect(purgeUserFromXpLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).leaderboards.weeklyXp
(await configuration).leaderboards.weeklyXp,
);
expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
@ -736,11 +736,11 @@ describe("user controller test", () => {
expect(connectionsDeletebyUidMock).toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).dailyLeaderboards
(await configuration).dailyLeaderboards,
);
expect(purgeUserFromXpLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).leaderboards.weeklyXp
(await configuration).leaderboards.weeklyXp,
);
expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
@ -767,11 +767,11 @@ describe("user controller test", () => {
expect(connectionsDeletebyUidMock).toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).dailyLeaderboards
(await configuration).dailyLeaderboards,
);
expect(purgeUserFromXpLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).leaderboards.weeklyXp
(await configuration).leaderboards.weeklyXp,
);
expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
@ -797,11 +797,11 @@ describe("user controller test", () => {
expect(connectionsDeletebyUidMock).not.toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).not.toHaveBeenCalledWith(
uid,
(await configuration).dailyLeaderboards
(await configuration).dailyLeaderboards,
);
expect(purgeUserFromXpLeaderboardsMock).not.toHaveBeenCalledWith(
uid,
(await configuration).leaderboards.weeklyXp
(await configuration).leaderboards.weeklyXp,
);
expect(logsDeleteUserMock).not.toHaveBeenCalled();
});
@ -838,11 +838,11 @@ describe("user controller test", () => {
expect(connectionsDeletebyUidMock).toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).dailyLeaderboards
(await configuration).dailyLeaderboards,
);
expect(purgeUserFromXpLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).leaderboards.weeklyXp
(await configuration).leaderboards.weeklyXp,
);
expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
@ -879,11 +879,11 @@ describe("user controller test", () => {
expect(connectionsDeletebyUidMock).toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).dailyLeaderboards
(await configuration).dailyLeaderboards,
);
expect(purgeUserFromXpLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).leaderboards.weeklyXp
(await configuration).leaderboards.weeklyXp,
);
});
});
@ -896,11 +896,11 @@ describe("user controller test", () => {
const deleteConfigMock = vi.spyOn(ConfigDal, "deleteConfig");
const purgeUserFromDailyLeaderboardsMock = vi.spyOn(
DailyLeaderboards,
"purgeUserFromDailyLeaderboards"
"purgeUserFromDailyLeaderboards",
);
const purgeUserFromXpLeaderboardsMock = vi.spyOn(
WeeklyXpLeaderboard,
"purgeUserFromXpLeaderboards"
"purgeUserFromXpLeaderboards",
);
const unlinkDiscordMock = vi.spyOn(GeorgeQueue, "unlinkDiscord");
@ -951,11 +951,11 @@ describe("user controller test", () => {
}
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await Configuration.getLiveConfiguration()).dailyLeaderboards
(await Configuration.getLiveConfiguration()).dailyLeaderboards,
);
expect(purgeUserFromXpLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await configuration).leaderboards.weeklyXp
(await configuration).leaderboards.weeklyXp,
);
expect(unlinkDiscordMock).not.toHaveBeenCalled();
/*TODO
@ -1035,7 +1035,7 @@ describe("user controller test", () => {
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_name_updated",
"changed name from Bob to newName",
uid
uid,
);
expect(connectionsUpdateNameMock).toHaveBeenCalledWith(uid, "newName");
});
@ -1087,7 +1087,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"You can change your name once every 30 days"
"You can change your name once every 30 days",
);
expect(updateNameMock).not.toHaveBeenCalled();
});
@ -1161,7 +1161,7 @@ describe("user controller test", () => {
const clearPbMock = vi.spyOn(UserDal, "clearPb");
const purgeUserFromDailyLeaderboardsMock = vi.spyOn(
DailyLeaderboards,
"purgeUserFromDailyLeaderboards"
"purgeUserFromDailyLeaderboards",
);
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
@ -1190,12 +1190,12 @@ describe("user controller test", () => {
expect(clearPbMock).toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await Configuration.getLiveConfiguration()).dailyLeaderboards
(await Configuration.getLiveConfiguration()).dailyLeaderboards,
);
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_cleared_pbs",
"",
uid
uid,
);
});
});
@ -1203,7 +1203,7 @@ describe("user controller test", () => {
const optOutOfLeaderboardsMock = vi.spyOn(UserDal, "optOutOfLeaderboards");
const purgeUserFromDailyLeaderboardsMock = vi.spyOn(
DailyLeaderboards,
"purgeUserFromDailyLeaderboards"
"purgeUserFromDailyLeaderboards",
);
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
@ -1232,12 +1232,12 @@ describe("user controller test", () => {
expect(optOutOfLeaderboardsMock).toHaveBeenCalledWith(uid);
expect(purgeUserFromDailyLeaderboardsMock).toHaveBeenCalledWith(
uid,
(await Configuration.getLiveConfiguration()).dailyLeaderboards
(await Configuration.getLiveConfiguration()).dailyLeaderboards,
);
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_opted_out_of_leaderboards",
"",
uid
uid,
);
});
// it("should fail with unknown properties", async () => {
@ -1260,7 +1260,7 @@ describe("user controller test", () => {
beforeEach(() => {
[authUpdateEmailMock, userUpdateEmailMock, addImportantLogMock].forEach(
(it) => it.mockClear().mockResolvedValue(null as never)
(it) => it.mockClear().mockResolvedValue(null as never),
);
});
it("should update users email", async () => {
@ -1282,16 +1282,16 @@ describe("user controller test", () => {
expect(authUpdateEmailMock).toHaveBeenCalledWith(
uid,
newEmail.toLowerCase()
newEmail.toLowerCase(),
);
expect(userUpdateEmailMock).toHaveBeenCalledWith(
uid,
newEmail.toLowerCase()
newEmail.toLowerCase(),
);
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_email_updated",
"changed email from previousemail@example.com to newemail@example.com",
uid
uid,
);
});
it("should fail for duplicate email", async () => {
@ -1318,7 +1318,7 @@ describe("user controller test", () => {
.expect(409);
expect(body.message).toEqual(
"The email address is already in use by another account"
"The email address is already in use by another account",
);
expect(userUpdateEmailMock).not.toHaveBeenCalled();
@ -1402,7 +1402,7 @@ describe("user controller test", () => {
.expect(404);
expect(body.message).toEqual(
"User not found in the auth system\nStack: update email"
"User not found in the auth system\nStack: update email",
);
expect(userUpdateEmailMock).not.toHaveBeenCalled();
@ -1576,7 +1576,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Discord integration is not available at this time"
"Discord integration is not available at this time",
);
});
});
@ -1585,7 +1585,7 @@ describe("user controller test", () => {
const isDiscordIdAvailableMock = vi.spyOn(UserDal, "isDiscordIdAvailable");
const isStateValidForUserMock = vi.spyOn(
DiscordUtils,
"iStateValidForUser"
"iStateValidForUser",
);
const getDiscordUserMock = vi.spyOn(DiscordUtils, "getDiscordUser");
const blocklistContainsMock = vi.spyOn(BlocklistDal, "contains");
@ -1643,16 +1643,16 @@ describe("user controller test", () => {
});
expect(isStateValidForUserMock).toHaveBeenCalledWith(
"statestatestatestate",
uid
uid,
);
expect(getUserMock).toHaveBeenCalledWith(
uid,
"link discord",
expect.any(Array)
expect.any(Array),
);
expect(getDiscordUserMock).toHaveBeenCalledWith(
"tokenType",
"accessToken"
"accessToken",
);
expect(isDiscordIdAvailableMock).toHaveBeenCalledWith("discordUserId");
expect(blocklistContainsMock).toHaveBeenCalledWith({
@ -1661,17 +1661,17 @@ describe("user controller test", () => {
expect(userLinkDiscordMock).toHaveBeenCalledWith(
uid,
"discordUserId",
"discordUserAvatar"
"discordUserAvatar",
);
expect(georgeLinkDiscordMock).toHaveBeenCalledWith(
"discordUserId",
uid,
false
false,
);
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_discord_link",
"linked to discordUserId",
uid
uid,
);
});
@ -1701,7 +1701,7 @@ describe("user controller test", () => {
expect(userLinkDiscordMock).toHaveBeenCalledWith(
uid,
"existingDiscordId",
"discordUserAvatar"
"discordUserAvatar",
);
expect(isDiscordIdAvailableMock).not.toHaveBeenCalled();
expect(blocklistContainsMock).not.toHaveBeenCalled();
@ -1761,7 +1761,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Could not get Discord account info\nStack: discord id is undefined"
"Could not get Discord account info\nStack: discord id is undefined",
);
//THEN
@ -1784,7 +1784,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"This Discord account is linked to a different account"
"This Discord account is linked to a different account",
);
//THEN
@ -1893,7 +1893,7 @@ describe("user controller test", () => {
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_discord_unlinked",
"discordId",
uid
uid,
);
});
it("should fail for banned user", async () => {
@ -1925,7 +1925,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"User does not have a linked Discord account"
"User does not have a linked Discord account",
);
expect(userUnlinkDiscordMock).not.toHaveBeenCalled();
expect(georgeUnlinkDiscordMock).not.toHaveBeenCalled();
@ -1993,7 +1993,7 @@ describe("user controller test", () => {
const addResultFilterPresetMock = vi.spyOn(
UserDal,
"addResultFilterPreset"
"addResultFilterPreset",
);
beforeEach(async () => {
@ -2020,7 +2020,7 @@ describe("user controller test", () => {
uid,
validPreset,
(await Configuration.getLiveConfiguration()).results.filterPresets
.maxPresetsPerUser
.maxPresetsPerUser,
);
});
it("should fail without mandatory properties", async () => {
@ -2077,14 +2077,14 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Result filter presets are not available at this time."
"Result filter presets are not available at this time.",
);
});
});
describe("remove result filter preset", () => {
const removeResultFilterPresetMock = vi.spyOn(
UserDal,
"removeResultFilterPreset"
"removeResultFilterPreset",
);
beforeEach(() => {
@ -2118,7 +2118,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Result filter presets are not available at this time."
"Result filter presets are not available at this time.",
);
});
});
@ -2365,7 +2365,7 @@ describe("user controller test", () => {
"time",
"60",
"english",
7
7,
);
});
@ -2830,7 +2830,7 @@ describe("user controller test", () => {
uid,
"english",
"7",
(await Configuration.getLiveConfiguration()).quotes.maxFavorites
(await Configuration.getLiveConfiguration()).quotes.maxFavorites,
);
});
it("should fail without mandatory properties", async () => {
@ -3053,7 +3053,7 @@ describe("user controller test", () => {
expect.objectContaining({
lastDay: 1712102400000,
testsByDays: expect.arrayContaining([]),
})
}),
);
});
it("should not get testActivity if disabled", async () => {
@ -3220,7 +3220,7 @@ describe("user controller test", () => {
},
{
badges: [{ id: 4 }, { id: 2, selected: true }, { id: 3 }],
}
},
);
});
it("should update with empty strings", async () => {
@ -3264,7 +3264,7 @@ describe("user controller test", () => {
},
{
badges: [{ id: 4 }, { id: 2 }, { id: 3 }],
}
},
);
});
it("should fail with unknown properties", async () => {
@ -3308,7 +3308,7 @@ describe("user controller test", () => {
keyboard: "string with many spaces",
socialProfiles: {},
},
expect.objectContaining({})
expect.objectContaining({}),
);
});
it("should fail with profanity", async () => {
@ -3492,7 +3492,7 @@ describe("user controller test", () => {
expect(updateInboxMock).toHaveBeenCalledWith(
uid,
[mailIdOne, mailIdTwo],
[mailIdOne]
[mailIdOne],
);
});
it("should update without body", async () => {
@ -3593,7 +3593,7 @@ describe("user controller test", () => {
(await Configuration.getLiveConfiguration()).quotes.reporting
.maxReports,
(await Configuration.getLiveConfiguration()).quotes.reporting
.contentReportLimit
.contentReportLimit,
);
expect(verifyCaptchaMock).toHaveBeenCalledWith("captcha");
});
@ -3746,7 +3746,7 @@ describe("user controller test", () => {
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_streak_hour_offset_set",
{ hourOffset: -2 },
uid
uid,
);
});
it("should fail if offset already set", async () => {
@ -3824,7 +3824,7 @@ describe("user controller test", () => {
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_tokens_revoked",
"",
uid
uid,
);
});
});
@ -3946,7 +3946,7 @@ describe("user controller test", () => {
it("should fail if friends endpoints are disabled", async () => {
await expectFailForDisabledEndpoint(
mockApp.get("/users/friends").set("Authorization", `Bearer ${uid}`)
mockApp.get("/users/friends").set("Authorization", `Bearer ${uid}`),
);
});
@ -3969,7 +3969,7 @@ async function enablePremiumFeatures(enabled: boolean): Promise<void> {
mockConfig.users.premium = { ...mockConfig.users.premium, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -3978,7 +3978,7 @@ async function enableSignup(signUp: boolean): Promise<void> {
mockConfig.users = { ...mockConfig.users, signUp };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -3990,7 +3990,7 @@ async function enableDiscordIntegration(enabled: boolean): Promise<void> {
};
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -4002,7 +4002,7 @@ async function enableResultFilterPresets(enabled: boolean): Promise<void> {
};
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -4011,7 +4011,7 @@ async function acceptApeKeys(acceptKeys: boolean): Promise<void> {
mockConfig.apeKeys = { ...mockConfig.apeKeys, acceptKeys };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -4020,7 +4020,7 @@ async function enableProfiles(enabled: boolean): Promise<void> {
mockConfig.users.profiles = { ...mockConfig.users.profiles, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
async function enableInbox(enabled: boolean): Promise<void> {
@ -4028,7 +4028,7 @@ async function enableInbox(enabled: boolean): Promise<void> {
mockConfig.users.inbox = { ...mockConfig.users.inbox, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -4037,7 +4037,7 @@ async function enableReporting(enabled: boolean): Promise<void> {
mockConfig.quotes.reporting = { ...mockConfig.quotes.reporting, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}
@ -4046,7 +4046,7 @@ async function enableConnectionsEndpoints(enabled: boolean): Promise<void> {
mockConfig.connections = { ...mockConfig.connections, enabled };
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
mockConfig
mockConfig,
);
}

View file

@ -9,7 +9,7 @@ describe("WebhooksController", () => {
describe("githubRelease", () => {
const georgeSendReleaseAnnouncementMock = vi.spyOn(
GeorgeQueue,
"sendReleaseAnnouncement"
"sendReleaseAnnouncement",
);
const timingSafeEqualMock = vi.spyOn(crypto, "timingSafeEqual");
@ -37,9 +37,9 @@ describe("WebhooksController", () => {
expect(georgeSendReleaseAnnouncementMock).toHaveBeenCalledWith("1");
expect(timingSafeEqualMock).toHaveBeenCalledWith(
Buffer.from(
"sha256=ff0f3080539e9df19153f6b5b5780f66e558d61038e6cf5ecf4efdc7266a7751"
"sha256=ff0f3080539e9df19153f6b5b5780f66e558d61038e6cf5ecf4efdc7266a7751",
),
Buffer.from("the-signature")
Buffer.from("the-signature"),
);
});
it("should ignore non-published actions", async () => {

View file

@ -91,7 +91,7 @@ describe("middlewares/auth", () => {
beforeEach(() => {
timingSafeEqualMock.mockClear().mockReturnValue(true);
[prometheusIncrementAuthMock, prometheusRecordAuthTimeMock].forEach(
(it) => it.mockClear()
(it) => it.mockClear(),
);
});
@ -100,18 +100,18 @@ describe("middlewares/auth", () => {
Date.now = vi.fn(() => 60001);
const expectedError = new MonkeyError(
401,
"Unauthorized\nStack: This endpoint requires a fresh token"
"Unauthorized\nStack: This endpoint requires a fresh token",
);
//WHEN
await expect(() =>
authenticate({}, { requireFreshToken: true })
authenticate({}, { requireFreshToken: true }),
).rejects.toMatchMonkeyError(expectedError);
//THEN
expect(nextFunction).toHaveBeenLastCalledWith(
expect.toMatchMonkeyError(expectedError)
expect.toMatchMonkeyError(expectedError),
);
expect(prometheusIncrementAuthMock).not.toHaveBeenCalled();
expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
@ -137,7 +137,7 @@ describe("middlewares/auth", () => {
//WHEN
const result = await authenticate(
{ headers: { authorization: "ApeKey aWQua2V5" } },
{ acceptApeKeys: true }
{ acceptApeKeys: true },
);
//THEN
@ -152,8 +152,8 @@ describe("middlewares/auth", () => {
await expect(() =>
authenticate(
{ headers: { authorization: "ApeKey aWQua2V5" } },
{ acceptApeKeys: false }
)
{ acceptApeKeys: false },
),
).rejects.toThrowError("This endpoint does not accept ApeKeys");
//THEN
@ -168,8 +168,8 @@ describe("middlewares/auth", () => {
await expect(() =>
authenticate(
{ headers: { authorization: "ApeKey aWQua2V5" } },
{ acceptApeKeys: false }
)
{ acceptApeKeys: false },
),
).rejects.toThrowError("ApeKeys are not being accepted at this time");
//THEN
@ -203,7 +203,7 @@ describe("middlewares/auth", () => {
//WHEN
const result = await authenticate(
{ headers: { authorization: "ApeKey aWQua2V5" } },
{ isPublic: true }
{ isPublic: true },
);
//THEN
@ -247,14 +247,14 @@ describe("middlewares/auth", () => {
//WHEN / THEN
await expect(() =>
authenticate({ headers: { authorization: "Uid 123" } })
authenticate({ headers: { authorization: "Uid 123" } }),
).rejects.toMatchMonkeyError(
new MonkeyError(401, "Baerer type uid is not supported")
new MonkeyError(401, "Baerer type uid is not supported"),
);
});
it("should fail without authentication", async () => {
await expect(() => authenticate({ headers: {} })).rejects.toThrowError(
"Unauthorized\nStack: endpoint: /api/v1 no authorization header found"
"Unauthorized\nStack: endpoint: /api/v1 no authorization header found",
);
//THEH
@ -263,14 +263,14 @@ describe("middlewares/auth", () => {
"None",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
it("should fail with empty authentication", async () => {
await expect(() =>
authenticate({ headers: { authorization: "" } })
authenticate({ headers: { authorization: "" } }),
).rejects.toThrowError(
"Unauthorized\nStack: endpoint: /api/v1 no authorization header found"
"Unauthorized\nStack: endpoint: /api/v1 no authorization header found",
);
//THEH
@ -279,14 +279,14 @@ describe("middlewares/auth", () => {
"",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
it("should fail with missing authentication token", async () => {
await expect(() =>
authenticate({ headers: { authorization: "Bearer" } })
authenticate({ headers: { authorization: "Bearer" } }),
).rejects.toThrowError(
"Missing authentication token\nStack: authenticateWithAuthHeader"
"Missing authentication token\nStack: authenticateWithAuthHeader",
);
//THEH
@ -295,14 +295,14 @@ describe("middlewares/auth", () => {
"Bearer",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
it("should fail with unknown authentication scheme", async () => {
await expect(() =>
authenticate({ headers: { authorization: "unknown format" } })
authenticate({ headers: { authorization: "unknown format" } }),
).rejects.toThrowError(
'Unknown authentication scheme\nStack: The authentication scheme "unknown" is not implemented'
'Unknown authentication scheme\nStack: The authentication scheme "unknown" is not implemented',
);
//THEH
@ -311,24 +311,24 @@ describe("middlewares/auth", () => {
"unknown",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
it("should record country if provided", async () => {
const prometheusRecordRequestCountryMock = vi.spyOn(
Prometheus,
"recordRequestCountry"
"recordRequestCountry",
);
await authenticate(
{ headers: { "cf-ipcountry": "gb" } },
{ isPublic: true }
{ isPublic: true },
);
//THEN
expect(prometheusRecordRequestCountryMock).toHaveBeenCalledWith(
"gb",
expect.anything()
expect.anything(),
);
});
it("should allow the request with authentation on dev public endpoint", async () => {
@ -346,7 +346,7 @@ describe("middlewares/auth", () => {
//WHEN
const result = await authenticate(
{ headers: {} },
{ isPublicOnDev: true }
{ isPublicOnDev: true },
);
//THEN
@ -363,7 +363,7 @@ describe("middlewares/auth", () => {
//WHEN
const result = await authenticate(
{ headers: { authorization: "ApeKey aWQua2V5" } },
{ acceptApeKeys: true, isPublicOnDev: true }
{ acceptApeKeys: true, isPublicOnDev: true },
);
//THEN
@ -385,7 +385,7 @@ describe("middlewares/auth", () => {
//WHEN
const result = await authenticate(
{ headers: { authorization: "ApeKey aWQua2V5" } },
{ acceptApeKeys: true, isPublicOnDev: true }
{ acceptApeKeys: true, isPublicOnDev: true },
);
//THEN
@ -416,7 +416,7 @@ describe("middlewares/auth", () => {
//THEN
await expect(() =>
authenticate({ headers: {} }, { isPublicOnDev: true })
authenticate({ headers: {} }, { isPublicOnDev: true }),
).rejects.toThrowError("Unauthorized");
});
it("should allow with apeKey on dev public endpoint in production", async () => {
@ -424,7 +424,7 @@ describe("middlewares/auth", () => {
isDevModeMock.mockReturnValue(false);
const result = await authenticate(
{ headers: { authorization: "ApeKey aWQua2V5" } },
{ acceptApeKeys: true, isPublicOnDev: true }
{ acceptApeKeys: true, isPublicOnDev: true },
);
//THEN
@ -445,7 +445,7 @@ describe("middlewares/auth", () => {
headers: { "x-hub-signature-256": "the-signature" },
body: { action: "published", release: { id: 1 } },
},
{ isGithubWebhook: true }
{ isGithubWebhook: true },
);
//THEN
@ -459,9 +459,9 @@ describe("middlewares/auth", () => {
expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
expect(timingSafeEqualMock).toHaveBeenCalledWith(
Buffer.from(
"sha256=ff0f3080539e9df19153f6b5b5780f66e558d61038e6cf5ecf4efdc7266a7751"
"sha256=ff0f3080539e9df19153f6b5b5780f66e558d61038e6cf5ecf4efdc7266a7751",
),
Buffer.from("the-signature")
Buffer.from("the-signature"),
);
});
it("should fail githubwebhook with mismatched signature", async () => {
@ -474,8 +474,8 @@ describe("middlewares/auth", () => {
headers: { "x-hub-signature-256": "the-signature" },
body: { action: "published", release: { id: 1 } },
},
{ isGithubWebhook: true }
)
{ isGithubWebhook: true },
),
).rejects.toThrowError("Github webhook signature invalid");
//THEH
@ -484,7 +484,7 @@ describe("middlewares/auth", () => {
"None",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
it("should fail without header when endpoint is using githubwebhook", async () => {
@ -495,8 +495,8 @@ describe("middlewares/auth", () => {
headers: {},
body: { action: "published", release: { id: 1 } },
},
{ isGithubWebhook: true }
)
{ isGithubWebhook: true },
),
).rejects.toThrowError("Missing Github signature header");
//THEH
@ -505,7 +505,7 @@ describe("middlewares/auth", () => {
"None",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
it("should fail with missing GITHUB_WEBHOOK_SECRET when endpoint is using githubwebhook", async () => {
@ -516,8 +516,8 @@ describe("middlewares/auth", () => {
headers: { "x-hub-signature-256": "the-signature" },
body: { action: "published", release: { id: 1 } },
},
{ isGithubWebhook: true }
)
{ isGithubWebhook: true },
),
).rejects.toThrowError("Missing Github Webhook Secret");
//THEH
@ -526,7 +526,7 @@ describe("middlewares/auth", () => {
"None",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
it("should throw 500 if something went wrong when validating the signature when endpoint is using githubwebhook", async () => {
@ -540,10 +540,10 @@ describe("middlewares/auth", () => {
headers: { "x-hub-signature-256": "the-signature" },
body: { action: "published", release: { id: 1 } },
},
{ isGithubWebhook: true }
)
{ isGithubWebhook: true },
),
).rejects.toThrowError(
"Failed to authenticate Github webhook: could not validate"
"Failed to authenticate Github webhook: could not validate",
);
//THEH
@ -552,7 +552,7 @@ describe("middlewares/auth", () => {
"None",
"failure",
expect.anything(),
expect.anything()
expect.anything(),
);
});
});
@ -560,7 +560,7 @@ describe("middlewares/auth", () => {
async function authenticate(
request: Partial<Request>,
authenticationOptions?: RequestAuthenticationOptions
authenticationOptions?: RequestAuthenticationOptions,
): Promise<{ decodedToken: Auth.DecodedToken }> {
const mergedRequest = {
...mockRequest,
@ -573,7 +573,7 @@ async function authenticate(
await Auth.authenticateTsRestRequest()(
mergedRequest,
mockResponse as Response,
nextFunction
nextFunction,
);
return { decodedToken: mergedRequest.ctx.decodedToken };

View file

@ -45,7 +45,7 @@ describe("configuration middleware", () => {
//GIVEN
const req = givenRequest(
{ path: "users.xp.streak.enabled" },
{ users: { xp: { streak: { enabled: true } as any } as any } as any }
{ users: { xp: { streak: { enabled: true } as any } as any } as any },
);
//WHEN
@ -64,15 +64,15 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(503, "This endpoint is currently unavailable.")
)
new MonkeyError(503, "This endpoint is currently unavailable."),
),
);
});
it("should fail for disabled configuration and custom message", async () => {
//GIVEN
const req = givenRequest(
{ path: "maintenance", invalidMessage: "Feature not enabled." },
{ maintenance: false }
{ maintenance: false },
);
//WHEN
@ -80,7 +80,7 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(new MonkeyError(503, "Feature not enabled."))
expect.toMatchMonkeyError(new MonkeyError(503, "Feature not enabled.")),
);
});
it("should fail for invalid path", async () => {
@ -93,15 +93,15 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(500, 'Invalid configuration path: "invalid.path"')
)
new MonkeyError(500, 'Invalid configuration path: "invalid.path"'),
),
);
});
it("should fail for undefined value", async () => {
//GIVEN
const req = givenRequest(
{ path: "admin.endpointsEnabled" },
{ admin: {} as any }
{ admin: {} as any },
);
//WHEN
@ -112,16 +112,16 @@ describe("configuration middleware", () => {
expect.toMatchMonkeyError(
new MonkeyError(
500,
'Required configuration doesnt exist: "admin.endpointsEnabled"'
)
)
'Required configuration doesnt exist: "admin.endpointsEnabled"',
),
),
);
});
it("should fail for null value", async () => {
//GIVEN
const req = givenRequest(
{ path: "admin.endpointsEnabled" },
{ admin: { endpointsEnabled: null as any } }
{ admin: { endpointsEnabled: null as any } },
);
//WHEN
@ -132,16 +132,16 @@ describe("configuration middleware", () => {
expect.toMatchMonkeyError(
new MonkeyError(
500,
'Required configuration doesnt exist: "admin.endpointsEnabled"'
)
)
'Required configuration doesnt exist: "admin.endpointsEnabled"',
),
),
);
});
it("should fail for non booean value", async () => {
//GIVEN
const req = givenRequest(
{ path: "admin.endpointsEnabled" },
{ admin: { endpointsEnabled: "disabled" as any } }
{ admin: { endpointsEnabled: "disabled" as any } },
);
//WHEN
@ -152,16 +152,16 @@ describe("configuration middleware", () => {
expect.toMatchMonkeyError(
new MonkeyError(
500,
'Required configuration is not a boolean: "admin.endpointsEnabled"'
)
)
'Required configuration is not a boolean: "admin.endpointsEnabled"',
),
),
);
});
it("should pass for multiple configurations", async () => {
//GIVEN
const req = givenRequest(
[{ path: "maintenance" }, { path: "admin.endpointsEnabled" }],
{ maintenance: true, admin: { endpointsEnabled: true } }
{ maintenance: true, admin: { endpointsEnabled: true } },
);
//WHEN
@ -177,7 +177,7 @@ describe("configuration middleware", () => {
{ path: "maintenance", invalidMessage: "maintenance mode" },
{ path: "admin.endpointsEnabled", invalidMessage: "admin disabled" },
],
{ maintenance: true, admin: { endpointsEnabled: false } }
{ maintenance: true, admin: { endpointsEnabled: false } },
);
//WHEN
@ -185,14 +185,14 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(new MonkeyError(503, "admin disabled"))
expect.toMatchMonkeyError(new MonkeyError(503, "admin disabled")),
);
});
});
function givenRequest(
requireConfiguration: RequireConfiguration | RequireConfiguration[],
configuration: Partial<Configuration>
configuration: Partial<Configuration>,
): TsRestRequest {
return {
tsRestRoute: { metadata: { requireConfiguration } },

View file

@ -65,8 +65,8 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
new MonkeyError(403, "You don't have permission to do this."),
),
);
});
it("should pass without authentication if publicOnDev on dev", async () => {
@ -77,7 +77,7 @@ describe("permission middleware", () => {
...requireAdminPermission,
authenticationOptions: { isPublicOnDev: true },
},
{ uid }
{ uid },
);
//WHEN
await handler(req, res, next);
@ -92,7 +92,7 @@ describe("permission middleware", () => {
...requireAdminPermission,
authenticationOptions: { isPublicOnDev: true },
},
{ uid }
{ uid },
);
//WHEN
await handler(req, res, next);
@ -100,8 +100,8 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
new MonkeyError(403, "You don't have permission to do this."),
),
);
});
it("should fail without admin permissions", async () => {
@ -114,8 +114,8 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
new MonkeyError(403, "You don't have permission to do this."),
),
);
expect(isAdminMock).toHaveBeenCalledWith(uid);
});
@ -127,7 +127,7 @@ describe("permission middleware", () => {
{
requirePermission: ["canReport", "canManageApeKeys"],
},
{ uid }
{ uid },
);
//WHEN
@ -138,7 +138,7 @@ describe("permission middleware", () => {
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
"check user permissions",
["canReport", "canManageApeKeys"]
["canReport", "canManageApeKeys"],
);
});
it("should fail if authentication is missing", async () => {
@ -155,9 +155,9 @@ describe("permission middleware", () => {
expect.toMatchMonkeyError(
new MonkeyError(
403,
"Failed to check permissions, authentication required."
)
)
"Failed to check permissions, authentication required.",
),
),
);
});
});
@ -179,7 +179,7 @@ describe("permission middleware", () => {
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
"check user permissions",
["quoteMod"]
["quoteMod"],
);
});
it("should pass for specific language", async () => {
@ -195,7 +195,7 @@ describe("permission middleware", () => {
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
"check user permissions",
["quoteMod"]
["quoteMod"],
);
});
it("should fail for empty string", async () => {
@ -209,8 +209,8 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
new MonkeyError(403, "You don't have permission to do this."),
),
);
});
it("should fail for missing quoteMod", async () => {
@ -224,8 +224,8 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
new MonkeyError(403, "You don't have permission to do this."),
),
);
});
});
@ -245,13 +245,13 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
new MonkeyError(403, "You don't have permission to do this."),
),
);
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
"check user permissions",
["canReport"]
["canReport"],
);
});
it("should pass if user can report", async () => {
@ -295,14 +295,14 @@ describe("permission middleware", () => {
expect.toMatchMonkeyError(
new MonkeyError(
403,
"You have lost access to ape keys, please contact support"
)
)
"You have lost access to ape keys, please contact support",
),
),
);
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
"check user permissions",
["canManageApeKeys"]
["canManageApeKeys"],
);
});
it("should pass if user can report", async () => {
@ -332,7 +332,7 @@ describe("permission middleware", () => {
function givenRequest(
metadata: EndpointMetadata,
decodedToken?: Partial<DecodedToken>
decodedToken?: Partial<DecodedToken>,
): TsRestRequest {
return { tsRestRoute: { metadata }, ctx: { decodedToken } } as any;
}

View file

@ -28,7 +28,7 @@ export function setupCommonMocks() {
auth: (): unknown => ({
verifyIdToken: (
_token: string,
_checkRevoked: boolean
_checkRevoked: boolean,
): unknown /* Promise<DecodedIdToken> */ =>
Promise.resolve({
aud: "mockFirebaseProjectId",

View file

@ -37,10 +37,10 @@ describe("Misc Utils", () => {
({ pattern, cases, expected }) => {
cases.forEach((caseValue, index) => {
expect(Misc.matchesAPattern(caseValue, pattern)).toBe(
expected[index]
expected[index],
);
});
}
},
);
});
@ -90,7 +90,7 @@ describe("Misc Utils", () => {
"kogascore with wpm:$wpm, acc:$acc, timestamp:$timestamp = $expectedScore",
({ wpm, acc, timestamp, expectedScore }) => {
expect(Misc.kogascore(wpm, acc, timestamp)).toBe(expectedScore);
}
},
);
});
@ -121,7 +121,7 @@ describe("Misc Utils", () => {
"identity with $input = $expected",
({ input, expected }) => {
expect(Misc.identity(input)).toBe(expected);
}
},
);
});
@ -193,7 +193,7 @@ describe("Misc Utils", () => {
"flattenObjectDeep with $obj = $expected",
({ obj, expected }) => {
expect(Misc.flattenObjectDeep(obj)).toEqual(expected);
}
},
);
});
@ -386,7 +386,7 @@ describe("Misc Utils", () => {
number: 2,
};
expect(
Misc.replaceObjectIds([fromDatabase, fromDatabase2])
Misc.replaceObjectIds([fromDatabase, fromDatabase2]),
).toStrictEqual([
{
_id: fromDatabase._id.toHexString(),

View file

@ -35,7 +35,7 @@ describe("Pb Utils", () => {
({ funbox, expected }) => {
const result = pb.canFunboxGetPb({ funbox } as any);
expect(result).toBe(expected);
}
},
);
});
@ -65,7 +65,7 @@ describe("Pb Utils", () => {
const run = pb.checkAndUpdatePb(
userPbs,
{} as pb.LbPersonalBests,
result
result,
);
expect(run.isPb).toBe(true);
@ -117,7 +117,7 @@ describe("Pb Utils", () => {
expect.arrayContaining([
expect.objectContaining({ numbers: false, wpm: 100 }),
expect.objectContaining({ numbers: true, wpm: 110 }),
])
]),
);
});
});
@ -174,7 +174,7 @@ describe("Pb Utils", () => {
const lbPbPb = pb.updateLeaderboardPersonalBests(
userPbs,
structuredClone(lbPb) as pb.LbPersonalBests,
result15
result15,
);
expect(lbPbPb).toEqual({

View file

@ -37,7 +37,7 @@ describe("Result Utils", () => {
expect(result.charStats).toEqual(expectedCharStats);
expect(result.correctChars).toBeUndefined();
expect(result.incorrectChars).toBeUndefined();
}
},
);
it("should prioritise charStats when legacy data exists", () => {
@ -86,7 +86,7 @@ describe("Result Utils", () => {
expect(result.charStats).toBe(expectedCharStats);
expect(result.correctChars).toBe(expectedCorrectChars);
expect(result.incorrectChars).toBe(expectedIncorrectChars);
}
},
);
});

View file

@ -48,7 +48,7 @@ describe("Validation", () => {
testCases.forEach((testCase) => {
expect(Validation.isTestTooShort(testCase.result as any)).toBe(
testCase.expected
testCase.expected,
);
});
});

View file

@ -21,8 +21,7 @@ interface ThrowMatcher {
declare module "vitest" {
interface Assertion<T = any> extends RestRequestMatcher<T>, ThrowMatcher {}
interface AsymmetricMatchersContaining
extends RestRequestMatcher,
ThrowMatcher {}
extends RestRequestMatcher, ThrowMatcher {}
}
interface MatcherResult {

View file

@ -21,7 +21,7 @@
"gen-docs": "tsx scripts/openapi.ts dist/static/api/openapi.json && redocly build-docs -o dist/static/api/internal.html internal@v2 && redocly bundle -o dist/static/api/public.json public-filter && redocly build-docs -o dist/static/api/public.html public@v2"
},
"engines": {
"node": "24.11.0"
"node": "24.11.0 || 22.21.0"
},
"dependencies": {
"@date-fns/utc": "1.2.0",
@ -40,7 +40,7 @@
"date-fns": "3.6.0",
"dotenv": "16.4.5",
"etag": "1.8.1",
"express": "5.1.0",
"express": "5.2.0",
"express-rate-limit": "7.5.1",
"firebase-admin": "12.0.0",
"helmet": "4.6.0",
@ -49,7 +49,7 @@
"mjml": "4.15.0",
"mongodb": "6.3.0",
"mustache": "4.2.0",
"nodemailer": "7.0.7",
"nodemailer": "7.0.11",
"object-hash": "3.0.0",
"prom-client": "15.1.3",
"rate-limiter-flexible": "5.0.3",
@ -92,7 +92,7 @@
"supertest": "7.1.4",
"testcontainers": "11.4.0",
"tsx": "4.16.2",
"typescript": "5.5.4",
"typescript": "5.9.3",
"vitest": "4.0.8"
}
}

View file

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />

View file

@ -141,7 +141,7 @@ const render = (state, schema) => {
state,
parentState,
currentKey = "",
path = "configuration"
path = "configuration",
) => {
const parent = document.createElement("div");
parent.classList.add("form-element");
@ -166,7 +166,7 @@ const render = (state, schema) => {
state[key],
state,
key,
`${path}.${key}`
`${path}.${key}`,
);
parent.appendChild(childElement);
});
@ -181,13 +181,13 @@ const render = (state, schema) => {
element,
state,
index,
`${path}[${index}]`
`${path}[${index}]`,
);
const decoratedChildElement = arrayFormElementDecorator(
childElement,
state,
index
index,
);
parent.appendChild(decoratedChildElement);
});
@ -287,7 +287,7 @@ window.onload = async () => {
exportButton.addEventListener("click", async () => {
download(
"backend-configuration.json",
JSON.stringify({ configuration: state })
JSON.stringify({ configuration: state }),
);
});
};
@ -296,7 +296,7 @@ function download(filename, text) {
let element = document.createElement("a");
element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
);
element.setAttribute("download", filename);

View file

@ -162,14 +162,14 @@ export function getOpenApi(): OpenAPIObject {
addTags(operation, metadata);
return operation;
},
}
},
);
return openApiDocument;
}
function addAuth(
operation: OperationObject,
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
): void {
const auth = metadata?.authenticationOptions ?? {};
const permissions = getRequiredPermissions(metadata) ?? [];
@ -188,13 +188,13 @@ function addAuth(
if (permissions.length !== 0) {
operation.description += `**Required permissions:** ${permissions.join(
", "
", ",
)}\n\n`;
}
}
function getRequiredPermissions(
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
): PermissionId[] | undefined {
if (metadata === undefined || metadata.requirePermission === undefined)
return undefined;
@ -206,7 +206,7 @@ function getRequiredPermissions(
function addTags(
operation: OperationObject,
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
): void {
if (metadata === undefined || metadata.openApiTags === undefined) return;
operation.tags = Array.isArray(metadata.openApiTags)
@ -216,7 +216,7 @@ function addTags(
function addRateLimit(
operation: OperationObject,
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
): void {
if (metadata === undefined || metadata.rateLimit === undefined) return;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@ -252,7 +252,7 @@ function getRateLimitDescription(limit: RateLimiterId | RateLimitIds): string {
if (limits.apeKeyLimiter !== undefined) {
result += ` and up to ${limits.apeKeyLimiter.max} times ${formatWindow(
limits.apeKeyLimiter.window
limits.apeKeyLimiter.window,
)} with ApeKeys`;
}
@ -275,7 +275,7 @@ function formatWindow(window: Window): string {
function addRequiredConfiguration(
operation: OperationObject,
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
): void {
if (metadata === undefined || metadata.requireConfiguration === undefined)
return;

View file

@ -14,7 +14,7 @@ export function validateResult(
_result: object,
_version: string,
_uaStringifiedObject: string,
_lbOptOut: boolean
_lbOptOut: boolean,
): boolean {
Logger.warning("No anticheat module found, result will not be validated.");
return true;
@ -24,7 +24,7 @@ export function validateKeys(
_result: CompletedEvent,
_keySpacingStats: KeyStats,
_keyDurationStats: KeyStats,
_uid: string
_uid: string,
): boolean {
Logger.warning("No anticheat module found, key data will not be validated.");
return true;

View file

@ -22,7 +22,7 @@ export async function test(_req: MonkeyRequest): Promise<MonkeyResponse> {
}
export async function toggleBan(
req: MonkeyRequest<undefined, ToggleBanRequest>
req: MonkeyRequest<undefined, ToggleBanRequest>,
): Promise<ToggleBanResponse> {
const { uid } = req.body;
@ -44,7 +44,7 @@ export async function toggleBan(
}
export async function clearStreakHourOffset(
req: MonkeyRequest<undefined, ClearStreakHourOffsetRequest>
req: MonkeyRequest<undefined, ClearStreakHourOffsetRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.body;
@ -55,23 +55,23 @@ export async function clearStreakHourOffset(
}
export async function acceptReports(
req: MonkeyRequest<undefined, AcceptReportsRequest>
req: MonkeyRequest<undefined, AcceptReportsRequest>,
): Promise<MonkeyResponse> {
await handleReports(
req.body.reports.map((it) => ({ ...it })),
true,
req.ctx.configuration.users.inbox
req.ctx.configuration.users.inbox,
);
return new MonkeyResponse("Reports removed and users notified.", null);
}
export async function rejectReports(
req: MonkeyRequest<undefined, RejectReportsRequest>
req: MonkeyRequest<undefined, RejectReportsRequest>,
): Promise<MonkeyResponse> {
await handleReports(
req.body.reports.map((it) => ({ ...it })),
false,
req.ctx.configuration.users.inbox
req.ctx.configuration.users.inbox,
);
return new MonkeyResponse("Reports removed and users notified.", null);
}
@ -79,7 +79,7 @@ export async function rejectReports(
export async function handleReports(
reports: { reportId: string; reason?: string }[],
accept: boolean,
inboxConfig: Configuration["users"]["inbox"]
inboxConfig: Configuration["users"]["inbox"],
): Promise<void> {
const reportIds = reports.map(({ reportId }) => reportId);
@ -88,13 +88,13 @@ export async function handleReports(
const existingReportIds = new Set(reportsFromDb.map((report) => report.id));
const missingReportIds = reportIds.filter(
(reportId) => !existingReportIds.has(reportId)
(reportId) => !existingReportIds.has(reportId),
);
if (missingReportIds.length > 0) {
throw new MonkeyError(
404,
`Reports not found for some IDs ${missingReportIds.join(",")}`
`Reports not found for some IDs ${missingReportIds.join(",")}`,
);
}
@ -132,7 +132,7 @@ export async function handleReports(
} else {
throw new MonkeyError(
500,
"Error handling reports: " + getErrorMessage(e)
"Error handling reports: " + getErrorMessage(e),
);
}
}
@ -140,7 +140,7 @@ export async function handleReports(
}
export async function sendForgotPasswordEmail(
req: MonkeyRequest<undefined, SendForgotPasswordEmailRequest>
req: MonkeyRequest<undefined, SendForgotPasswordEmailRequest>,
): Promise<MonkeyResponse> {
const { email } = req.body;
await authSendForgotPasswordEmail(email);

View file

@ -21,20 +21,20 @@ function cleanApeKey(apeKey: ApeKeysDAL.DBApeKey): ApeKey {
}
export async function getApeKeys(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetApeKeyResponse> {
const { uid } = req.ctx.decodedToken;
const apeKeys = await ApeKeysDAL.getApeKeys(uid);
const cleanedKeys: Record<string, ApeKey> = Object.fromEntries(
apeKeys.map((item) => [item._id.toHexString(), cleanApeKey(item)])
apeKeys.map((item) => [item._id.toHexString(), cleanApeKey(item)]),
);
return new MonkeyResponse("ApeKeys retrieved", cleanedKeys);
}
export async function generateApeKey(
req: MonkeyRequest<undefined, AddApeKeyRequest>
req: MonkeyRequest<undefined, AddApeKeyRequest>,
): Promise<AddApeKeyResponse> {
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
@ -72,7 +72,7 @@ export async function generateApeKey(
}
export async function editApeKey(
req: MonkeyRequest<undefined, EditApeKeyRequest, ApeKeyParams>
req: MonkeyRequest<undefined, EditApeKeyRequest, ApeKeyParams>,
): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { name, enabled } = req.body;
@ -84,7 +84,7 @@ export async function editApeKey(
}
export async function deleteApeKey(
req: MonkeyRequest<undefined, undefined, ApeKeyParams>
req: MonkeyRequest<undefined, undefined, ApeKeyParams>,
): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { uid } = req.ctx.decodedToken;

View file

@ -5,7 +5,7 @@ import { GetConfigResponse } from "@monkeytype/contracts/configs";
import { MonkeyRequest } from "../types";
export async function getConfig(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetConfigResponse> {
const { uid } = req.ctx.decodedToken;
const data = (await ConfigDAL.getConfig(uid))?.config ?? null;
@ -14,7 +14,7 @@ export async function getConfig(
}
export async function saveConfig(
req: MonkeyRequest<undefined, PartialConfig>
req: MonkeyRequest<undefined, PartialConfig>,
): Promise<MonkeyResponse> {
const config = req.body;
const { uid } = req.ctx.decodedToken;
@ -25,7 +25,7 @@ export async function saveConfig(
}
export async function deleteConfig(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;

View file

@ -10,23 +10,23 @@ import MonkeyError from "../../utils/error";
import { MonkeyRequest } from "../types";
export async function getConfiguration(
_req: MonkeyRequest
_req: MonkeyRequest,
): Promise<GetConfigurationResponse> {
const currentConfiguration = await Configuration.getLiveConfiguration();
return new MonkeyResponse("Configuration retrieved", currentConfiguration);
}
export async function getSchema(
_req: MonkeyRequest
_req: MonkeyRequest,
): Promise<ConfigurationSchemaResponse> {
return new MonkeyResponse(
"Configuration schema retrieved",
CONFIGURATION_FORM_SCHEMA
CONFIGURATION_FORM_SCHEMA,
);
}
export async function updateConfiguration(
req: MonkeyRequest<undefined, PatchConfigurationRequest>
req: MonkeyRequest<undefined, PatchConfigurationRequest>,
): Promise<MonkeyResponse> {
const { configuration } = req.body;
const success = await Configuration.patchConfiguration(configuration);

View file

@ -19,7 +19,7 @@ function convert(db: ConnectionsDal.DBConnection): Connection {
return replaceObjectId(omit(db, ["key"]));
}
export async function getConnections(
req: MonkeyRequest<GetConnectionsQuery>
req: MonkeyRequest<GetConnectionsQuery>,
): Promise<GetConnectionsResponse> {
const { uid } = req.ctx.decodedToken;
const { status, type } = req.query;
@ -36,7 +36,7 @@ export async function getConnections(
}
export async function createConnection(
req: MonkeyRequest<undefined, CreateConnectionRequest>
req: MonkeyRequest<undefined, CreateConnectionRequest>,
): Promise<CreateConnectionResponse> {
const { uid } = req.ctx.decodedToken;
const { receiverName } = req.body;
@ -44,7 +44,7 @@ export async function createConnection(
const receiver = await UserDal.getUserByName(
receiverName,
"create connection"
"create connection",
);
if (uid === receiver.uid) {
@ -62,7 +62,7 @@ export async function createConnection(
}
export async function deleteConnection(
req: MonkeyRequest<undefined, undefined, IdPathParams>
req: MonkeyRequest<undefined, undefined, IdPathParams>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { id } = req.params;
@ -73,7 +73,7 @@ export async function deleteConnection(
}
export async function updateConnection(
req: MonkeyRequest<undefined, UpdateConnectionRequest, IdPathParams>
req: MonkeyRequest<undefined, UpdateConnectionRequest, IdPathParams>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { id } = req.params;

View file

@ -28,7 +28,7 @@ const CREATE_RESULT_DEFAULT_OPTIONS = {
};
export async function createTestData(
req: MonkeyRequest<undefined, GenerateDataRequest>
req: MonkeyRequest<undefined, GenerateDataRequest>,
): Promise<GenerateDataResponse> {
const { username, createUser } = req.body;
const user = await getOrCreateUser(username, "password", createUser);
@ -45,7 +45,7 @@ export async function createTestData(
async function getOrCreateUser(
username: string,
password: string,
createUser = false
createUser = false,
): Promise<UserDal.DBUser> {
const existingUser = await UserDal.findByName(username);
@ -70,7 +70,7 @@ async function getOrCreateUser(
async function createTestResults(
user: UserDal.DBUser,
configOptions: GenerateDataRequest
configOptions: GenerateDataRequest,
): Promise<void> {
const config = {
...CREATE_RESULT_DEFAULT_OPTIONS,
@ -90,14 +90,15 @@ async function createTestResults(
for (const day of days) {
Logger.success(
`User ${user.name} insert ${day.amount} results on ${new Date(
day.timestamp
)}`
day.timestamp,
)}`,
);
const results = createArray(day.amount, () =>
createResult(user, day.timestamp)
createResult(user, day.timestamp),
);
if (results.length > 0)
if (results.length > 0) {
await ResultDal.getResultCollection().insertMany(results);
}
}
}
@ -111,7 +112,7 @@ function random(min: number, max: number): number {
function createResult(
user: UserDal.DBUser,
timestamp: Date //evil, we modify this value
timestamp: Date, //evil, we modify this value
): DBResult {
const mode: Mode = randomValue(["time", "words"]);
const mode2: number =
@ -183,7 +184,7 @@ async function updateUser(uid: string): Promise<void> {
const timeTyping = stats.reduce((a, c) => (a + c["timeTyping"]) as number, 0);
const completedTests = stats.reduce(
(a, c) => (a + c["completedTests"]) as number,
0
0,
);
//update PBs
@ -207,7 +208,7 @@ async function updateUser(uid: string): Promise<void> {
language: Language;
mode: "time" | "custom" | "words" | "quote" | "zen";
mode2: `${number}` | "custom" | "zen";
}
},
);
for (const mode of modes) {
@ -218,12 +219,13 @@ async function updateUser(uid: string): Promise<void> {
mode: mode.mode,
mode2: mode.mode2,
},
{ sort: { wpm: -1, timestamp: 1 } }
{ sort: { wpm: -1, timestamp: 1 } },
)) as DBResult;
if (personalBests[mode.mode] === undefined) personalBests[mode.mode] = {};
if (personalBests[mode.mode][mode.mode2] === undefined)
personalBests[mode.mode] ??= {};
if (personalBests[mode.mode][mode.mode2] === undefined) {
personalBests[mode.mode][mode.mode2] = [];
}
const entry = {
acc: best.acc,
@ -241,8 +243,9 @@ async function updateUser(uid: string): Promise<void> {
(personalBests[mode.mode][mode.mode2] as PersonalBest[]).push(entry);
if (mode.mode === "time") {
if (lbPersonalBests[mode.mode][mode.mode2] === undefined)
if (lbPersonalBests[mode.mode][mode.mode2] === undefined) {
lbPersonalBests[mode.mode][mode.mode2] = {};
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
lbPersonalBests[mode.mode][mode.mode2][mode.language] = entry;
@ -263,7 +266,7 @@ async function updateUser(uid: string): Promise<void> {
personalBests: personalBests,
lbPersonalBests: lbPersonalBests,
},
}
},
);
}
@ -391,7 +394,7 @@ async function updateTestActicity(uid: string): Promise<void> {
},
},
],
{ allowDiskUse: true }
{ allowDiskUse: true },
)
.toArray();
}

View file

@ -29,7 +29,7 @@ import { MonkeyRequest } from "../types";
import { omit } from "../../utils/misc";
export async function getLeaderboard(
req: MonkeyRequest<GetLeaderboardQuery>
req: MonkeyRequest<GetLeaderboardQuery>,
): Promise<GetLeaderboardResponse> {
const { language, mode, mode2, page, pageSize, friendsOnly } = req.query;
const { uid } = req.ctx.decodedToken;
@ -52,13 +52,13 @@ export async function getLeaderboard(
page,
pageSize,
req.ctx.configuration.users.premium.enabled,
friendsOnlyUid
friendsOnlyUid,
);
if (leaderboard === false) {
throw new MonkeyError(
503,
"Leaderboard is currently updating. Please try again in a few seconds."
"Leaderboard is currently updating. Please try again in a few seconds.",
);
}
@ -66,7 +66,7 @@ export async function getLeaderboard(
mode,
mode2,
language,
friendsOnlyUid
friendsOnlyUid,
);
const normalizedLeaderboard = leaderboard.map((it) => omit(it, ["_id"]));
@ -78,7 +78,7 @@ export async function getLeaderboard(
}
export async function getRankFromLeaderboard(
req: MonkeyRequest<GetLeaderboardRankQuery>
req: MonkeyRequest<GetLeaderboardRankQuery>,
): Promise<GetLeaderboardRankResponse> {
const { language, mode, mode2, friendsOnly } = req.query;
const { uid } = req.ctx.decodedToken;
@ -89,24 +89,24 @@ export async function getRankFromLeaderboard(
mode2,
language,
uid,
getFriendsOnlyUid(uid, friendsOnly, connectionsConfig) !== undefined
getFriendsOnlyUid(uid, friendsOnly, connectionsConfig) !== undefined,
);
if (data === false) {
throw new MonkeyError(
503,
"Leaderboard is currently updating. Please try again in a few seconds."
"Leaderboard is currently updating. Please try again in a few seconds.",
);
}
return new MonkeyResponse(
"Rank retrieved",
omit(data as LeaderboardsDAL.DBLeaderboardEntry, ["_id"])
omit(data as LeaderboardsDAL.DBLeaderboardEntry, ["_id"]),
);
}
function getDailyLeaderboardWithError(
{ language, mode, mode2, daysBefore }: DailyLeaderboardQuery,
config: Configuration["dailyLeaderboards"]
config: Configuration["dailyLeaderboards"],
): DailyLeaderboards.DailyLeaderboard {
const customTimestamp =
daysBefore === undefined
@ -118,7 +118,7 @@ function getDailyLeaderboardWithError(
mode,
mode2,
config,
customTimestamp
customTimestamp,
);
if (!dailyLeaderboard) {
throw new MonkeyError(404, "There is no daily leaderboard for this mode");
@ -128,7 +128,7 @@ function getDailyLeaderboardWithError(
}
export async function getDailyLeaderboard(
req: MonkeyRequest<GetDailyLeaderboardQuery>
req: MonkeyRequest<GetDailyLeaderboardQuery>,
): Promise<GetDailyLeaderboardResponse> {
const { page, pageSize, friendsOnly } = req.query;
const { uid } = req.ctx.decodedToken;
@ -137,12 +137,12 @@ export async function getDailyLeaderboard(
const friendUids = await getFriendsUids(
uid,
friendsOnly === true,
connectionsConfig
connectionsConfig,
);
const dailyLeaderboard = getDailyLeaderboardWithError(
req.query,
req.ctx.configuration.dailyLeaderboards
req.ctx.configuration.dailyLeaderboards,
);
const results = await dailyLeaderboard.getResults(
@ -150,7 +150,7 @@ export async function getDailyLeaderboard(
pageSize,
req.ctx.configuration.dailyLeaderboards,
req.ctx.configuration.users.premium.enabled,
friendUids
friendUids,
);
return new MonkeyResponse("Daily leaderboard retrieved", {
@ -162,7 +162,7 @@ export async function getDailyLeaderboard(
}
export async function getDailyLeaderboardRank(
req: MonkeyRequest<GetDailyLeaderboardRankQuery>
req: MonkeyRequest<GetDailyLeaderboardRankQuery>,
): Promise<GetLeaderboardDailyRankResponse> {
const { friendsOnly } = req.query;
const { uid } = req.ctx.decodedToken;
@ -171,18 +171,18 @@ export async function getDailyLeaderboardRank(
const friendUids = await getFriendsUids(
uid,
friendsOnly === true,
connectionsConfig
connectionsConfig,
);
const dailyLeaderboard = getDailyLeaderboardWithError(
req.query,
req.ctx.configuration.dailyLeaderboards
req.ctx.configuration.dailyLeaderboards,
);
const rank = await dailyLeaderboard.getRank(
uid,
req.ctx.configuration.dailyLeaderboards,
friendUids
friendUids,
);
return new MonkeyResponse("Daily leaderboard rank retrieved", rank);
@ -190,7 +190,7 @@ export async function getDailyLeaderboardRank(
function getWeeklyXpLeaderboardWithError(
config: Configuration["leaderboards"]["weeklyXp"],
weeksBefore?: number
weeksBefore?: number,
): WeeklyXpLeaderboard.WeeklyXpLeaderboard {
const customTimestamp =
weeksBefore === undefined
@ -206,7 +206,7 @@ function getWeeklyXpLeaderboardWithError(
}
export async function getWeeklyXpLeaderboard(
req: MonkeyRequest<GetWeeklyXpLeaderboardQuery>
req: MonkeyRequest<GetWeeklyXpLeaderboardQuery>,
): Promise<GetWeeklyXpLeaderboardResponse> {
const { page, pageSize, weeksBefore, friendsOnly } = req.query;
@ -216,19 +216,19 @@ export async function getWeeklyXpLeaderboard(
const friendUids = await getFriendsUids(
uid,
friendsOnly === true,
connectionsConfig
connectionsConfig,
);
const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(
req.ctx.configuration.leaderboards.weeklyXp,
weeksBefore
weeksBefore,
);
const results = await weeklyXpLeaderboard.getResults(
page,
pageSize,
req.ctx.configuration.leaderboards.weeklyXp,
req.ctx.configuration.users.premium.enabled,
friendUids
friendUids,
);
return new MonkeyResponse("Weekly xp leaderboard retrieved", {
@ -239,7 +239,7 @@ export async function getWeeklyXpLeaderboard(
}
export async function getWeeklyXpLeaderboardRank(
req: MonkeyRequest<GetWeeklyXpLeaderboardRankQuery>
req: MonkeyRequest<GetWeeklyXpLeaderboardRankQuery>,
): Promise<GetWeeklyXpLeaderboardRankResponse> {
const { friendsOnly } = req.query;
const { uid } = req.ctx.decodedToken;
@ -248,17 +248,17 @@ export async function getWeeklyXpLeaderboardRank(
const friendUids = await getFriendsUids(
uid,
friendsOnly === true,
connectionsConfig
connectionsConfig,
);
const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(
req.ctx.configuration.leaderboards.weeklyXp,
req.query.weeksBefore
req.query.weeksBefore,
);
const rankEntry = await weeklyXpLeaderboard.getRank(
uid,
req.ctx.configuration.leaderboards.weeklyXp,
friendUids
friendUids,
);
return new MonkeyResponse("Weekly xp leaderboard rank retrieved", rankEntry);
@ -267,7 +267,7 @@ export async function getWeeklyXpLeaderboardRank(
async function getFriendsUids(
uid: string,
friendsOnly: boolean,
friendsConfig: Configuration["connections"]
friendsConfig: Configuration["connections"],
): Promise<string[] | undefined> {
if (uid !== "" && friendsOnly) {
if (!friendsConfig.enabled) {
@ -281,7 +281,7 @@ async function getFriendsUids(
function getFriendsOnlyUid(
uid: string,
friendsOnly: boolean | undefined,
friendsConfig: Configuration["connections"]
friendsConfig: Configuration["connections"],
): string | undefined {
if (uid !== "" && friendsOnly === true) {
if (!friendsConfig.enabled) {

View file

@ -11,7 +11,7 @@ import { EditPresetRequest } from "@monkeytype/schemas/presets";
import { MonkeyRequest } from "../types";
export async function getPresets(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetPresetResponse> {
const { uid } = req.ctx.decodedToken;
@ -26,7 +26,7 @@ export async function getPresets(
}
export async function addPreset(
req: MonkeyRequest<undefined, AddPresetRequest>
req: MonkeyRequest<undefined, AddPresetRequest>,
): Promise<AddPresetResponse> {
const { uid } = req.ctx.decodedToken;
@ -36,7 +36,7 @@ export async function addPreset(
}
export async function editPreset(
req: MonkeyRequest<undefined, EditPresetRequest>
req: MonkeyRequest<undefined, EditPresetRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
@ -46,7 +46,7 @@ export async function editPreset(
}
export async function removePreset(
req: MonkeyRequest<undefined, undefined, DeletePresetsParams>
req: MonkeyRequest<undefined, undefined, DeletePresetsParams>,
): Promise<MonkeyResponse> {
const { presetId } = req.params;
const { uid } = req.ctx.decodedToken;

View file

@ -8,7 +8,7 @@ import { MonkeyResponse } from "../../utils/monkey-response";
import { MonkeyRequest } from "../types";
export async function getSpeedHistogram(
req: MonkeyRequest<GetSpeedHistogramQuery>
req: MonkeyRequest<GetSpeedHistogramQuery>,
): Promise<GetSpeedHistogramResponse> {
const { language, mode, mode2 } = req.query;
const data = await PublicDAL.getSpeedHistogram(language, mode, mode2);
@ -16,7 +16,7 @@ export async function getSpeedHistogram(
}
export async function getTypingStats(
_req: MonkeyRequest
_req: MonkeyRequest,
): Promise<GetTypingStatsResponse> {
const data = await PublicDAL.getTypingStats();
return new MonkeyResponse("Public typing stats retrieved", data);

View file

@ -31,7 +31,7 @@ async function verifyCaptcha(captcha: string): Promise<void> {
}
export async function getQuotes(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetQuotesResponse> {
const { uid } = req.ctx.decodedToken;
const quoteMod = (await getPartialUser(uid, "get quotes", ["quoteMod"]))
@ -41,22 +41,22 @@ export async function getQuotes(
const data = await NewQuotesDAL.get(quoteModLanguage);
return new MonkeyResponse(
"Quote submissions retrieved",
replaceObjectIds(data)
replaceObjectIds(data),
);
}
export async function isSubmissionEnabled(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<IsSubmissionEnabledResponse> {
const { submissionsEnabled } = req.ctx.configuration.quotes;
return new MonkeyResponse(
"Quote submission " + (submissionsEnabled ? "enabled" : "disabled"),
{ isEnabled: submissionsEnabled }
{ isEnabled: submissionsEnabled },
);
}
export async function addQuote(
req: MonkeyRequest<undefined, AddQuoteRequest>
req: MonkeyRequest<undefined, AddQuoteRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { text, source, language, captcha } = req.body;
@ -68,7 +68,7 @@ export async function addQuote(
}
export async function approveQuote(
req: MonkeyRequest<undefined, ApproveQuoteRequest>
req: MonkeyRequest<undefined, ApproveQuoteRequest>,
): Promise<ApproveQuoteResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, editText, editSource } = req.body;
@ -86,7 +86,7 @@ export async function approveQuote(
}
export async function refuseQuote(
req: MonkeyRequest<undefined, RejectQuoteRequest>
req: MonkeyRequest<undefined, RejectQuoteRequest>,
): Promise<MonkeyResponse> {
const { quoteId } = req.body;
@ -95,7 +95,7 @@ export async function refuseQuote(
}
export async function getRating(
req: MonkeyRequest<GetQuoteRatingQuery>
req: MonkeyRequest<GetQuoteRatingQuery>,
): Promise<GetQuoteRatingResponse> {
const { quoteId, language } = req.query;
@ -105,7 +105,7 @@ export async function getRating(
}
export async function submitRating(
req: MonkeyRequest<undefined, AddQuoteRatingRequest>
req: MonkeyRequest<undefined, AddQuoteRatingRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, rating, language } = req.body;
@ -122,12 +122,10 @@ export async function submitRating(
quoteId,
language,
newRating,
shouldUpdateRating
shouldUpdateRating,
);
if (!userQuoteRatings[language]) {
userQuoteRatings[language] = {};
}
userQuoteRatings[language] ??= {};
userQuoteRatings[language][quoteId] = rating;
await updateQuoteRatings(uid, userQuoteRatings);
@ -139,7 +137,7 @@ export async function submitRating(
}
export async function reportQuote(
req: MonkeyRequest<undefined, ReportQuoteRequest>
req: MonkeyRequest<undefined, ReportQuoteRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {

View file

@ -30,18 +30,18 @@ try {
} catch (e) {
if (isDevEnvironment()) {
Logger.warning(
"No anticheat module found. Continuing in dev mode, results will not be validated."
"No anticheat module found. Continuing in dev mode, results will not be validated.",
);
} else {
Logger.error(
"No anticheat module found. To continue in dev mode, add MODE=dev to your .env file in the backend directory"
"No anticheat module found. To continue in dev mode, add MODE=dev to your .env file in the backend directory",
);
process.exit(1);
}
}
export async function getResults(
req: MonkeyRequest<GetResultsQuery>
req: MonkeyRequest<GetResultsQuery>,
): Promise<GetResultsResponse> {
const { uid } = req.ctx.decodedToken;
const premiumFeaturesEnabled = req.ctx.configuration.users.premium.enabled;
@ -88,14 +88,14 @@ export async function getResults(
onOrAfterTimestamp,
isPremium: userHasPremium,
},
uid
uid,
);
return new MonkeyResponse("Results retrieved", replaceObjectIds(results));
}
export async function getResultById(
req: MonkeyRequest<undefined, undefined, GetResultByIdPath>
req: MonkeyRequest<undefined, undefined, GetResultByIdPath>,
): Promise<GetResultByIdResponse> {
const { uid } = req.ctx.decodedToken;
const { resultId } = req.params;
@ -105,7 +105,7 @@ export async function getResultById(
}
export async function getLastResult(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetLastResultResponse> {
const { uid } = req.ctx.decodedToken;
const result = await ResultDAL.getLastResult(uid);
@ -121,7 +121,7 @@ export async function deleteAll(req: MonkeyRequest): Promise<MonkeyResponse> {
}
export async function updateTags(
req: MonkeyRequest<undefined, UpdateResultTagsRequest>
req: MonkeyRequest<undefined, UpdateResultTagsRequest>,
): Promise<UpdateResultTagsResponse> {
const { uid } = req.ctx.decodedToken;
const { tagIds, resultId } = req.body;
@ -129,24 +129,12 @@ export async function updateTags(
await ResultDAL.updateTags(uid, resultId, tagIds);
const result = await ResultDAL.getResult(uid, resultId);
if (!result.difficulty) {
result.difficulty = "normal";
}
if (!(result.language ?? "")) {
result.language = "english";
}
if (result.funbox === undefined) {
result.funbox = [];
}
if (!result.lazyMode) {
result.lazyMode = false;
}
if (!result.punctuation) {
result.punctuation = false;
}
if (!result.numbers) {
result.numbers = false;
}
result.difficulty ??= "normal";
result.language ??= "english";
result.funbox ??= [];
result.lazyMode ??= false;
result.punctuation ??= false;
result.numbers ??= false;
const user = await UserDAL.getPartialUser(uid, "update tags", ["tags"]);
const tagPbs = await UserDAL.checkIfTagPb(uid, user, result);
@ -156,7 +144,7 @@ export async function updateTags(
}
export async function addResult(
_req: MonkeyRequest<undefined, AddResultRequest>
_req: MonkeyRequest<undefined, AddResultRequest>,
): Promise<AddResultResponse> {
// todo remove
return new MonkeyResponse("Result added", {
@ -168,4 +156,645 @@ export async function addResult(
xpBreakdown: {},
streak: 0,
});
// const { uid } = req.ctx.decodedToken;
// const user = await UserDAL.getUser(uid, "add result");
// if (user.needsToChangeName) {
// throw new MonkeyError(
// 403,
// "Please change your name before submitting a result",
// );
// }
// const completedEvent = req.body.result;
// completedEvent.uid = uid;
// if (isTestTooShort(completedEvent)) {
// const status = MonkeyStatusCodes.TEST_TOO_SHORT;
// throw new MonkeyError(status.code, status.message);
// }
// if (user.lbOptOut !== true && completedEvent.acc < 75) {
// throw new MonkeyError(400, "Accuracy too low");
// }
// const resulthash = completedEvent.hash;
// if (req.ctx.configuration.results.objectHashCheckEnabled) {
// const objectToHash = omit(completedEvent, ["hash"]);
// const serverhash = objectHash(objectToHash);
// if (serverhash !== resulthash) {
// void addLog(
// "incorrect_result_hash",
// {
// serverhash,
// resulthash,
// result: completedEvent,
// },
// uid,
// );
// const status = MonkeyStatusCodes.RESULT_HASH_INVALID;
// throw new MonkeyError(status.code, "Incorrect result hash");
// }
// } else {
// Logger.warning("Object hash check is disabled, skipping hash check");
// }
// if (completedEvent.funbox.length !== new Set(completedEvent.funbox).size) {
// throw new MonkeyError(400, "Duplicate funboxes");
// }
// if (!checkCompatibility(completedEvent.funbox)) {
// throw new MonkeyError(400, "Impossible funbox combination");
// }
// let keySpacingStats: KeyStats | undefined = undefined;
// if (
// completedEvent.keySpacing !== "toolong" &&
// completedEvent.keySpacing.length > 0
// ) {
// keySpacingStats = {
// average:
// completedEvent.keySpacing.reduce(
// (previous, current) => (current += previous),
// ) / completedEvent.keySpacing.length,
// sd: stdDev(completedEvent.keySpacing),
// };
// }
// let keyDurationStats: KeyStats | undefined = undefined;
// if (
// completedEvent.keyDuration !== "toolong" &&
// completedEvent.keyDuration.length > 0
// ) {
// keyDurationStats = {
// average:
// completedEvent.keyDuration.reduce(
// (previous, current) => (current += previous),
// ) / completedEvent.keyDuration.length,
// sd: stdDev(completedEvent.keyDuration),
// };
// }
// if (user.suspicious && completedEvent.testDuration <= 120) {
// await addImportantLog("suspicious_user_result", completedEvent, uid);
// }
// if (
// completedEvent.mode === "time" &&
// (completedEvent.mode2 === "60" || completedEvent.mode2 === "15") &&
// completedEvent.wpm > 250 &&
// user.lbOptOut !== true
// ) {
// await addImportantLog("highwpm_user_result", completedEvent, uid);
// }
// if (anticheatImplemented()) {
// if (
// !validateResult(
// completedEvent,
// ((req.raw.headers["x-client-version"] as string) ||
// req.raw.headers["client-version"]) as string,
// JSON.stringify(new UAParser(req.raw.headers["user-agent"]).getResult()),
// user.lbOptOut === true,
// )
// ) {
// const status = MonkeyStatusCodes.RESULT_DATA_INVALID;
// throw new MonkeyError(status.code, "Result data doesn't make sense");
// } else if (isDevEnvironment()) {
// Logger.success("Result data validated");
// }
// } else {
// if (!isDevEnvironment()) {
// throw new Error("No anticheat module found");
// }
// Logger.warning(
// "No anticheat module found. Continuing in dev mode, results will not be validated.",
// );
// }
// //dont use - result timestamp is unreliable, can be changed by system time and stuff
// // if (result.timestamp > Math.round(Date.now() / 1000) * 1000 + 10) {
// // log(
// // "time_traveler",
// // {
// // resultTimestamp: result.timestamp,
// // serverTimestamp: Math.round(Date.now() / 1000) * 1000 + 10,
// // },
// // uid
// // );
// // return res.status(400).json({ message: "Time traveler detected" });
// const { data: lastResultTimestamp } = await tryCatch(
// ResultDAL.getLastResultTimestamp(uid),
// );
// //convert result test duration to miliseconds
// completedEvent.timestamp = Math.floor(Date.now() / 1000) * 1000;
// //check if now is earlier than last result plus duration (-1 second as a buffer)
// const testDurationMilis = completedEvent.testDuration * 1000;
// const incompleteTestsMilis = completedEvent.incompleteTestSeconds * 1000;
// const earliestPossible =
// (lastResultTimestamp ?? 0) + testDurationMilis + incompleteTestsMilis;
// const nowNoMilis = Math.floor(Date.now() / 1000) * 1000;
// if (
// isSafeNumber(lastResultTimestamp) &&
// nowNoMilis < earliestPossible - 1000
// ) {
// void addLog(
// "invalid_result_spacing",
// {
// lastTimestamp: lastResultTimestamp,
// earliestPossible,
// now: nowNoMilis,
// testDuration: testDurationMilis,
// difference: nowNoMilis - earliestPossible,
// },
// uid,
// );
// const status = MonkeyStatusCodes.RESULT_SPACING_INVALID;
// throw new MonkeyError(status.code, "Invalid result spacing");
// }
// //check keyspacing and duration here for bots
// if (
// completedEvent.mode === "time" &&
// completedEvent.wpm > 130 &&
// completedEvent.testDuration < 122 &&
// (user.verified === false || user.verified === undefined) &&
// user.lbOptOut !== true
// ) {
// if (!keySpacingStats || !keyDurationStats) {
// const status = MonkeyStatusCodes.MISSING_KEY_DATA;
// throw new MonkeyError(status.code, "Missing key data");
// }
// if (completedEvent.keyOverlap === undefined) {
// throw new MonkeyError(400, "Old key data format");
// }
// if (anticheatImplemented()) {
// if (
// !validateKeys(completedEvent, keySpacingStats, keyDurationStats, uid)
// ) {
// //autoban
// const autoBanConfig = req.ctx.configuration.users.autoBan;
// if (autoBanConfig.enabled) {
// const didUserGetBanned = await UserDAL.recordAutoBanEvent(
// uid,
// autoBanConfig.maxCount,
// autoBanConfig.maxHours,
// );
// if (didUserGetBanned) {
// const mail = buildMonkeyMail({
// subject: "Banned",
// body: "Your account has been automatically banned for triggering the anticheat system. If you believe this is a mistake, please contact support.",
// });
// await UserDAL.addToInbox(
// uid,
// [mail],
// req.ctx.configuration.users.inbox,
// );
// user.banned = true;
// }
// }
// const status = MonkeyStatusCodes.BOT_DETECTED;
// throw new MonkeyError(status.code, "Possible bot detected");
// }
// } else {
// if (!isDevEnvironment()) {
// throw new Error("No anticheat module found");
// }
// Logger.warning(
// "No anticheat module found. Continuing in dev mode, results will not be validated.",
// );
// }
// }
// if (req.ctx.configuration.users.lastHashesCheck.enabled) {
// let lastHashes = user.lastReultHashes ?? [];
// if (lastHashes.includes(resulthash)) {
// void addLog(
// "duplicate_result",
// {
// lastHashes,
// resulthash,
// result: completedEvent,
// },
// uid,
// );
// const status = MonkeyStatusCodes.DUPLICATE_RESULT;
// throw new MonkeyError(status.code, "Duplicate result");
// } else {
// lastHashes.unshift(resulthash);
// const maxHashes = req.ctx.configuration.users.lastHashesCheck.maxHashes;
// if (lastHashes.length > maxHashes) {
// lastHashes = lastHashes.slice(0, maxHashes);
// }
// await UserDAL.updateLastHashes(uid, lastHashes);
// }
// }
// if (keyDurationStats) {
// keyDurationStats.average = roundTo2(keyDurationStats.average);
// keyDurationStats.sd = roundTo2(keyDurationStats.sd);
// }
// if (keySpacingStats) {
// keySpacingStats.average = roundTo2(keySpacingStats.average);
// keySpacingStats.sd = roundTo2(keySpacingStats.sd);
// }
// let isPb = false;
// let tagPbs: string[] = [];
// if (!completedEvent.bailedOut) {
// [isPb, tagPbs] = await Promise.all([
// UserDAL.checkIfPb(uid, user, completedEvent),
// UserDAL.checkIfTagPb(uid, user, completedEvent),
// ]);
// }
// if (completedEvent.mode === "time" && completedEvent.mode2 === "60") {
// void UserDAL.incrementBananas(uid, completedEvent.wpm);
// if (
// isPb &&
// user.discordId !== undefined &&
// user.discordId !== "" &&
// user.lbOptOut !== true
// ) {
// void GeorgeQueue.updateDiscordRole(user.discordId, completedEvent.wpm);
// }
// }
// if (
// completedEvent.challenge !== null &&
// completedEvent.challenge !== undefined &&
// AutoRoleList.includes(completedEvent.challenge) &&
// user.discordId !== undefined &&
// user.discordId !== ""
// ) {
// void GeorgeQueue.awardChallenge(user.discordId, completedEvent.challenge);
// } else {
// delete completedEvent.challenge;
// }
// const afk = completedEvent.afkDuration ?? 0;
// const totalDurationTypedSeconds =
// completedEvent.testDuration + completedEvent.incompleteTestSeconds - afk;
// void UserDAL.updateTypingStats(
// uid,
// completedEvent.restartCount,
// totalDurationTypedSeconds,
// );
// void PublicDAL.updateStats(
// completedEvent.restartCount,
// totalDurationTypedSeconds,
// );
// const dailyLeaderboardsConfig = req.ctx.configuration.dailyLeaderboards;
// const dailyLeaderboard = getDailyLeaderboard(
// completedEvent.language,
// completedEvent.mode,
// completedEvent.mode2,
// dailyLeaderboardsConfig,
// );
// let dailyLeaderboardRank = -1;
// const stopOnLetterTriggered =
// completedEvent.stopOnLetter && completedEvent.acc < 100;
// const minTimeTyping = (await getCachedConfiguration(true)).leaderboards
// .minTimeTyping;
// const userEligibleForLeaderboard =
// user.banned !== true &&
// user.lbOptOut !== true &&
// (isDevEnvironment() || (user.timeTyping ?? 0) > minTimeTyping);
// const validResultCriteria =
// canFunboxGetPb(completedEvent) &&
// !completedEvent.bailedOut &&
// userEligibleForLeaderboard &&
// !stopOnLetterTriggered;
// const selectedBadgeId = user.inventory?.badges?.find((b) => b.selected)?.id;
// const isPremium =
// (await UserDAL.checkIfUserIsPremium(user.uid, user)) || undefined;
// if (dailyLeaderboard && validResultCriteria) {
// incrementDailyLeaderboard(
// completedEvent.mode,
// completedEvent.mode2,
// completedEvent.language,
// );
// dailyLeaderboardRank = await dailyLeaderboard.addResult(
// {
// name: user.name,
// wpm: completedEvent.wpm,
// raw: completedEvent.rawWpm,
// acc: completedEvent.acc,
// consistency: completedEvent.consistency,
// timestamp: completedEvent.timestamp,
// uid,
// discordAvatar: user.discordAvatar,
// discordId: user.discordId,
// badgeId: selectedBadgeId,
// isPremium,
// },
// dailyLeaderboardsConfig,
// );
// if (
// dailyLeaderboardRank >= 1 &&
// dailyLeaderboardRank <= 10 &&
// completedEvent.testDuration <= 120
// ) {
// const now = Date.now();
// const reset = getCurrentDayTimestamp();
// const limit = 6 * 60 * 60 * 1000;
// if (now - reset >= limit) {
// await addLog("daily_leaderboard_top_10_result", completedEvent, uid);
// }
// }
// }
// const streak = await UserDAL.updateStreak(uid, completedEvent.timestamp);
// const badgeWaitingInInbox = (
// user.inbox?.flatMap((i) =>
// (i.rewards ?? []).map((r) => (r.type === "badge" ? r.item.id : null)),
// ) ?? []
// ).includes(14);
// const shouldGetBadge =
// streak >= 365 &&
// user.inventory?.badges?.find((b) => b.id === 14) === undefined &&
// !badgeWaitingInInbox;
// if (shouldGetBadge) {
// const mail = buildMonkeyMail({
// subject: "Badge",
// body: "Congratulations for reaching a 365 day streak! You have been awarded a special badge. Now, go touch some grass.",
// rewards: [
// {
// type: "badge",
// item: {
// id: 14,
// },
// },
// ],
// });
// await UserDAL.addToInbox(uid, [mail], req.ctx.configuration.users.inbox);
// }
// const xpGained = await calculateXp(
// completedEvent,
// req.ctx.configuration.users.xp,
// lastResultTimestamp,
// user.xp ?? 0,
// streak,
// );
// if (xpGained.xp < 0) {
// throw new MonkeyError(
// 500,
// "Calculated XP is negative",
// JSON.stringify({
// xpGained,
// result: completedEvent,
// }),
// uid,
// );
// }
// const weeklyXpLeaderboardConfig = req.ctx.configuration.leaderboards.weeklyXp;
// let weeklyXpLeaderboardRank = -1;
// const weeklyXpLeaderboard = WeeklyXpLeaderboard.get(
// weeklyXpLeaderboardConfig,
// );
// if (userEligibleForLeaderboard && xpGained.xp > 0 && weeklyXpLeaderboard) {
// weeklyXpLeaderboardRank = await weeklyXpLeaderboard.addResult(
// weeklyXpLeaderboardConfig,
// {
// entry: {
// uid,
// name: user.name,
// discordAvatar: user.discordAvatar,
// discordId: user.discordId,
// badgeId: selectedBadgeId,
// lastActivityTimestamp: Date.now(),
// isPremium,
// timeTypedSeconds: totalDurationTypedSeconds,
// },
// xpGained: xpGained.xp,
// },
// );
// }
// const dbresult = buildDbResult(completedEvent, user.name, isPb);
// if (keySpacingStats !== undefined) {
// dbresult.keySpacingStats = keySpacingStats;
// }
// if (keyDurationStats !== undefined) {
// dbresult.keyDurationStats = keyDurationStats;
// }
// const addedResult = await ResultDAL.addResult(uid, dbresult);
// await UserDAL.incrementXp(uid, xpGained.xp);
// await UserDAL.incrementTestActivity(user, completedEvent.timestamp);
// if (isPb) {
// void addLog(
// "user_new_pb",
// `${completedEvent.mode + " " + completedEvent.mode2} ${
// completedEvent.wpm
// } ${completedEvent.acc}% ${completedEvent.rawWpm} ${
// completedEvent.consistency
// }% (${addedResult.insertedId})`,
// uid,
// );
// }
// const data: PostResultResponse = {
// isPb,
// tagPbs,
// insertedId: addedResult.insertedId.toHexString(),
// xp: xpGained.xp,
// dailyXpBonus: xpGained.dailyBonus ?? false,
// xpBreakdown: xpGained.breakdown ?? {},
// streak,
// };
// if (dailyLeaderboardRank !== -1) {
// data.dailyLeaderboardRank = dailyLeaderboardRank;
// }
// if (weeklyXpLeaderboardRank !== -1) {
// data.weeklyXpLeaderboardRank = weeklyXpLeaderboardRank;
// }
// incrementResult(completedEvent, dbresult.isPb);
// return new MonkeyResponse("Result saved", data);
// }
// type XpResult = {
// xp: number;
// dailyBonus?: boolean;
// breakdown?: XpBreakdown;
// };
// async function calculateXp(
// result: CompletedEvent,
// xpConfiguration: Configuration["users"]["xp"],
// lastResultTimestamp: number | null,
// currentTotalXp: number,
// streak: number,
// ): Promise<XpResult> {
// const {
// mode,
// acc,
// testDuration,
// incompleteTestSeconds,
// incompleteTests,
// afkDuration,
// charStats,
// punctuation,
// numbers,
// funbox: resultFunboxes,
// } = result;
// const {
// enabled,
// gainMultiplier,
// maxDailyBonus,
// minDailyBonus,
// funboxBonus: funboxBonusConfiguration,
// } = xpConfiguration;
// if (mode === "zen" || !enabled) {
// return {
// xp: 0,
// };
// }
// const breakdown: XpBreakdown = {};
// const baseXp = Math.round((testDuration - afkDuration) * 2);
// breakdown.base = baseXp;
// let modifier = 1;
// const correctedEverything = charStats
// .slice(1)
// .every((charStat: number) => charStat === 0);
// if (acc === 100) {
// modifier += 0.5;
// breakdown.fullAccuracy = Math.round(baseXp * 0.5);
// } else if (correctedEverything) {
// // corrected everything bonus
// modifier += 0.25;
// breakdown["corrected"] = Math.round(baseXp * 0.25);
// }
// if (mode === "quote") {
// // real sentences bonus
// modifier += 0.5;
// breakdown.quote = Math.round(baseXp * 0.5);
// } else {
// // punctuation bonus
// if (punctuation) {
// modifier += 0.4;
// breakdown.punctuation = Math.round(baseXp * 0.4);
// }
// if (numbers) {
// modifier += 0.1;
// breakdown.numbers = Math.round(baseXp * 0.1);
// }
// }
// if (funboxBonusConfiguration > 0 && resultFunboxes.length !== 0) {
// const funboxModifier = resultFunboxes.reduce((sum, funboxName) => {
// const funbox = getFunbox(funboxName);
// const difficultyLevel = funbox?.difficultyLevel ?? 0;
// return sum + Math.max(difficultyLevel * funboxBonusConfiguration, 0);
// }, 0);
// if (funboxModifier > 0) {
// modifier += funboxModifier;
// breakdown.funbox = Math.round(baseXp * funboxModifier);
// }
// }
// if (xpConfiguration.streak.enabled) {
// const streakModifier = parseFloat(
// mapRange(
// streak,
// 0,
// xpConfiguration.streak.maxStreakDays,
// 0,
// xpConfiguration.streak.maxStreakMultiplier,
// true,
// ).toFixed(1),
// );
// if (streakModifier > 0) {
// modifier += streakModifier;
// breakdown.streak = Math.round(baseXp * streakModifier);
// }
// }
// let incompleteXp = 0;
// if (incompleteTests !== undefined && incompleteTests.length > 0) {
// incompleteTests.forEach((it: { acc: number; seconds: number }) => {
// let modifier = (it.acc - 50) / 50;
// if (modifier < 0) modifier = 0;
// incompleteXp += Math.round(it.seconds * modifier);
// });
// breakdown.incomplete = incompleteXp;
// } else if (incompleteTestSeconds && incompleteTestSeconds > 0) {
// incompleteXp = Math.round(incompleteTestSeconds);
// breakdown.incomplete = incompleteXp;
// }
// const accuracyModifier = (acc - 50) / 50;
// let dailyBonus = 0;
// if (isSafeNumber(lastResultTimestamp)) {
// const lastResultDay = getStartOfDayTimestamp(lastResultTimestamp);
// const today = getCurrentDayTimestamp();
// if (lastResultDay !== today) {
// const proportionalXp = Math.round(currentTotalXp * 0.05);
// dailyBonus = Math.max(
// Math.min(maxDailyBonus, proportionalXp),
// minDailyBonus,
// );
// breakdown.daily = dailyBonus;
// }
// }
// const xpWithModifiers = Math.round(baseXp * modifier);
// const xpAfterAccuracy = Math.round(xpWithModifiers * accuracyModifier);
// breakdown.accPenalty = xpWithModifiers - xpAfterAccuracy;
// const totalXp =
// Math.round((xpAfterAccuracy + incompleteXp) * gainMultiplier) + dailyBonus;
// if (gainMultiplier > 1) {
// // breakdown.push([
// // "configMultiplier",
// // Math.round((xpAfterAccuracy + incompleteXp) * (gainMultiplier - 1)),
// // ]);
// breakdown.configMultiplier = gainMultiplier;
// }
// const isAwardingDailyBonus = dailyBonus > 0;
// return {
// xp: totalXp,
// dailyBonus: isAwardingDailyBonus,
// breakdown,
// };
}

View file

@ -99,7 +99,7 @@ async function verifyCaptcha(captcha: string): Promise<void> {
if (error) {
throw new MonkeyError(
422,
"Request to the Captcha API failed, please try again later"
"Request to the Captcha API failed, please try again later",
);
}
if (!verified) {
@ -108,7 +108,7 @@ async function verifyCaptcha(captcha: string): Promise<void> {
}
export async function createNewUser(
req: MonkeyRequest<undefined, CreateUserRequest>
req: MonkeyRequest<undefined, CreateUserRequest>,
): Promise<MonkeyResponse> {
const { name, captcha } = req.body;
const { email, uid } = req.ctx.decodedToken;
@ -142,7 +142,7 @@ export async function createNewUser(
}
export async function sendVerificationEmail(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<MonkeyResponse> {
const { email, uid } = req.ctx.decodedToken;
const isVerified = (
@ -158,7 +158,7 @@ export async function sendVerificationEmail(
email,
stack: e instanceof Error ? e.stack : JSON.stringify(e),
}),
uid
uid,
);
})
).emailVerified;
@ -169,20 +169,20 @@ export async function sendVerificationEmail(
const userInfo = await UserDAL.getPartialUser(
uid,
"request verification email",
["uid", "name", "email"]
["uid", "name", "email"],
);
if (userInfo.email !== email) {
throw new MonkeyError(
400,
"Authenticated email does not match the email found in the database. This might happen if you recently changed your email. Please refresh and try again."
"Authenticated email does not match the email found in the database. This might happen if you recently changed your email. Please refresh and try again.",
);
}
const { data: link, error } = await tryCatch(
FirebaseAdmin()
.auth()
.generateEmailVerificationLink(email, { url: getFrontendUrl() })
.generateEmailVerificationLink(email, { url: getFrontendUrl() }),
);
if (error) {
@ -195,7 +195,7 @@ export async function sendVerificationEmail(
decodedTokenEmail: email,
userInfoEmail: userInfo.email,
}),
userInfo.uid
userInfo.uid,
);
} else if (error.errorInfo.code === "auth/too-many-requests") {
throw new MonkeyError(429, "Too many requests. Please try again later");
@ -205,14 +205,14 @@ export async function sendVerificationEmail(
) {
throw new MonkeyError(
429,
"Too many Firebase requests. Please try again later"
"Too many Firebase requests. Please try again later",
);
} else {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link: " +
error.errorInfo.message,
JSON.stringify(error)
JSON.stringify(error),
);
}
} else {
@ -220,19 +220,19 @@ export async function sendVerificationEmail(
if (message === undefined) {
throw new MonkeyError(
500,
"Failed to generate an email verification link. Unknown error occured"
"Failed to generate an email verification link. Unknown error occured",
);
} else {
if (message.toLowerCase().includes("too_many_attempts")) {
throw new MonkeyError(
429,
"Too many requests. Please try again later"
"Too many requests. Please try again later",
);
} else {
throw new MonkeyError(
500,
"Failed to generate an email verification link: " + message,
error.stack
error.stack,
);
}
}
@ -245,14 +245,14 @@ export async function sendVerificationEmail(
}
export async function sendForgotPasswordEmail(
req: MonkeyRequest<undefined, ForgotPasswordEmailRequest>
req: MonkeyRequest<undefined, ForgotPasswordEmailRequest>,
): Promise<MonkeyResponse> {
const { email, captcha } = req.body;
await verifyCaptcha(captcha);
await authSendForgotPasswordEmail(email);
return new MonkeyResponse(
"Password reset request received. If the email is valid, you will receive an email shortly.",
null
null,
);
}
@ -265,7 +265,7 @@ export async function deleteUser(req: MonkeyRequest): Promise<MonkeyResponse> {
"name",
"email",
"discordId",
])
]),
);
if (error) {
@ -290,11 +290,11 @@ export async function deleteUser(req: MonkeyRequest): Promise<MonkeyResponse> {
deleteAllResults(uid),
purgeUserFromDailyLeaderboards(
uid,
req.ctx.configuration.dailyLeaderboards
req.ctx.configuration.dailyLeaderboards,
),
purgeUserFromXpLeaderboards(
uid,
req.ctx.configuration.leaderboards.weeklyXp
req.ctx.configuration.leaderboards.weeklyXp,
),
ConnectionsDal.deleteByUid(uid),
]);
@ -313,7 +313,7 @@ export async function deleteUser(req: MonkeyRequest): Promise<MonkeyResponse> {
void addImportantLog(
"user_deleted",
`${userInfo?.email} ${userInfo?.name}`,
uid
uid,
);
return new MonkeyResponse("User deleted", null);
@ -340,11 +340,11 @@ export async function resetUser(req: MonkeyRequest): Promise<MonkeyResponse> {
deleteConfig(uid),
purgeUserFromDailyLeaderboards(
uid,
req.ctx.configuration.dailyLeaderboards
req.ctx.configuration.dailyLeaderboards,
),
purgeUserFromXpLeaderboards(
uid,
req.ctx.configuration.leaderboards.weeklyXp
req.ctx.configuration.leaderboards.weeklyXp,
),
];
@ -358,7 +358,7 @@ export async function resetUser(req: MonkeyRequest): Promise<MonkeyResponse> {
}
export async function updateName(
req: MonkeyRequest<undefined, UpdateUserNameRequest>
req: MonkeyRequest<undefined, UpdateUserNameRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { name } = req.body;
@ -392,7 +392,7 @@ export async function updateName(
void addImportantLog(
"user_name_updated",
`changed name from ${user.name} to ${name}`,
uid
uid,
);
return new MonkeyResponse("User's name updated", null);
@ -404,7 +404,7 @@ export async function clearPb(req: MonkeyRequest): Promise<MonkeyResponse> {
await UserDAL.clearPb(uid);
await purgeUserFromDailyLeaderboards(
uid,
req.ctx.configuration.dailyLeaderboards
req.ctx.configuration.dailyLeaderboards,
);
void addImportantLog("user_cleared_pbs", "", uid);
@ -412,18 +412,18 @@ export async function clearPb(req: MonkeyRequest): Promise<MonkeyResponse> {
}
export async function optOutOfLeaderboards(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await UserDAL.optOutOfLeaderboards(uid);
await purgeUserFromDailyLeaderboards(
uid,
req.ctx.configuration.dailyLeaderboards
req.ctx.configuration.dailyLeaderboards,
);
await purgeUserFromXpLeaderboards(
uid,
req.ctx.configuration.leaderboards.weeklyXp
req.ctx.configuration.leaderboards.weeklyXp,
);
void addImportantLog("user_opted_out_of_leaderboards", "", uid);
@ -431,7 +431,7 @@ export async function optOutOfLeaderboards(
}
export async function checkName(
req: MonkeyRequest<undefined, undefined, CheckNamePathParameters>
req: MonkeyRequest<undefined, undefined, CheckNamePathParameters>,
): Promise<CheckNameResponse> {
const { name } = req.params;
const { uid } = req.ctx.decodedToken;
@ -444,7 +444,7 @@ export async function checkName(
}
export async function updateEmail(
req: MonkeyRequest<undefined, UpdateEmailRequest>
req: MonkeyRequest<undefined, UpdateEmailRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
let { newEmail, previousEmail } = req.body;
@ -460,7 +460,7 @@ export async function updateEmail(
if (e.code === "auth/email-already-exists") {
throw new MonkeyError(
409,
"The email address is already in use by another account"
"The email address is already in use by another account",
);
} else if (e.code === "auth/invalid-email") {
throw new MonkeyError(400, "Invalid email address");
@ -471,7 +471,7 @@ export async function updateEmail(
404,
"User not found in the auth system",
"update email",
uid
uid,
);
} else if (e.code === "auth/invalid-user-token") {
throw new MonkeyError(401, "Invalid user token", "update email", uid);
@ -484,14 +484,14 @@ export async function updateEmail(
void addImportantLog(
"user_email_updated",
`changed email from ${previousEmail} to ${newEmail}`,
uid
uid,
);
return new MonkeyResponse("Email updated", null);
}
export async function updatePassword(
req: MonkeyRequest<undefined, UpdatePasswordRequest>
req: MonkeyRequest<undefined, UpdatePasswordRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { newPassword } = req.body;
@ -536,7 +536,7 @@ export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
const { uid } = req.ctx.decodedToken;
const { data: userInfo, error } = await tryCatch(
UserDAL.getUser(uid, "get user")
UserDAL.getUser(uid, "get user"),
);
if (error) {
@ -550,7 +550,7 @@ export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
404,
"User not found in the database, but found in the auth system. We have deleted the ghost user from the auth system. Please sign up again.",
"get user",
uid
uid,
);
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
@ -559,7 +559,7 @@ export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
404,
"User not found in the database or the auth system. Please sign up again.",
"get user",
uid
uid,
);
} else {
throw e;
@ -607,7 +607,7 @@ export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
delete relevantUserInfo.tags;
const customThemes = (relevantUserInfo.customThemes ?? []).map((it) =>
replaceObjectId(it)
replaceObjectId(it),
);
delete relevantUserInfo.customThemes;
@ -628,7 +628,7 @@ export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
}
export async function getOauthLink(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetDiscordOauthLinkResponse> {
const { uid } = req.ctx.decodedToken;
@ -642,7 +642,7 @@ export async function getOauthLink(
}
export async function linkDiscord(
req: MonkeyRequest<undefined, LinkDiscordRequest>
req: MonkeyRequest<undefined, LinkDiscordRequest>,
): Promise<LinkDiscordResponse> {
const { uid } = req.ctx.decodedToken;
const { tokenType, accessToken, state } = req.body;
@ -675,7 +675,7 @@ export async function linkDiscord(
throw new MonkeyError(
500,
"Could not get Discord account info",
"discord id is undefined"
"discord id is undefined",
);
}
@ -683,7 +683,7 @@ export async function linkDiscord(
if (!discordIdAvailable) {
throw new MonkeyError(
409,
"This Discord account is linked to a different account"
"This Discord account is linked to a different account",
);
}
@ -703,7 +703,7 @@ export async function linkDiscord(
}
export async function unlinkDiscord(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
@ -729,7 +729,7 @@ export async function unlinkDiscord(
}
export async function addResultFilterPreset(
req: MonkeyRequest<undefined, AddResultFilterPresetRequest>
req: MonkeyRequest<undefined, AddResultFilterPresetRequest>,
): Promise<AddResultFilterPresetResponse> {
const { uid } = req.ctx.decodedToken;
const filter = req.body;
@ -738,16 +738,16 @@ export async function addResultFilterPreset(
const createdId = await UserDAL.addResultFilterPreset(
uid,
filter,
maxPresetsPerUser
maxPresetsPerUser,
);
return new MonkeyResponse(
"Result filter preset created",
createdId.toHexString()
createdId.toHexString(),
);
}
export async function removeResultFilterPreset(
req: MonkeyRequest<undefined, undefined, RemoveResultFilterPresetPathParams>
req: MonkeyRequest<undefined, undefined, RemoveResultFilterPresetPathParams>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { presetId } = req.params;
@ -757,7 +757,7 @@ export async function removeResultFilterPreset(
}
export async function addTag(
req: MonkeyRequest<undefined, AddTagRequest>
req: MonkeyRequest<undefined, AddTagRequest>,
): Promise<AddTagResponse> {
const { uid } = req.ctx.decodedToken;
const { tagName } = req.body;
@ -767,7 +767,7 @@ export async function addTag(
}
export async function clearTagPb(
req: MonkeyRequest<undefined, undefined, TagIdPathParams>
req: MonkeyRequest<undefined, undefined, TagIdPathParams>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagId } = req.params;
@ -777,7 +777,7 @@ export async function clearTagPb(
}
export async function editTag(
req: MonkeyRequest<undefined, EditTagRequest>
req: MonkeyRequest<undefined, EditTagRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagId, newName } = req.body;
@ -787,7 +787,7 @@ export async function editTag(
}
export async function removeTag(
req: MonkeyRequest<undefined, undefined, TagIdPathParams>
req: MonkeyRequest<undefined, undefined, TagIdPathParams>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagId } = req.params;
@ -804,7 +804,7 @@ export async function getTags(req: MonkeyRequest): Promise<GetTagsResponse> {
}
export async function updateLbMemory(
req: MonkeyRequest<undefined, UpdateLeaderboardMemoryRequest>
req: MonkeyRequest<undefined, UpdateLeaderboardMemoryRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, language, rank } = req.body;
@ -815,18 +815,18 @@ export async function updateLbMemory(
}
export async function getCustomThemes(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetCustomThemesResponse> {
const { uid } = req.ctx.decodedToken;
const customThemes = await UserDAL.getThemes(uid);
return new MonkeyResponse(
"Custom themes retrieved",
replaceObjectIds(customThemes)
replaceObjectIds(customThemes),
);
}
export async function addCustomTheme(
req: MonkeyRequest<undefined, AddCustomThemeRequest>
req: MonkeyRequest<undefined, AddCustomThemeRequest>,
): Promise<AddCustomThemeResponse> {
const { uid } = req.ctx.decodedToken;
const { name, colors } = req.body;
@ -836,7 +836,7 @@ export async function addCustomTheme(
}
export async function removeCustomTheme(
req: MonkeyRequest<undefined, DeleteCustomThemeRequest>
req: MonkeyRequest<undefined, DeleteCustomThemeRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId } = req.body;
@ -845,7 +845,7 @@ export async function removeCustomTheme(
}
export async function editCustomTheme(
req: MonkeyRequest<undefined, EditCustomThemeRequst>
req: MonkeyRequest<undefined, EditCustomThemeRequst>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId, theme } = req.body;
@ -855,7 +855,7 @@ export async function editCustomTheme(
}
export async function getPersonalBests(
req: MonkeyRequest<GetPersonalBestsQuery>
req: MonkeyRequest<GetPersonalBestsQuery>,
): Promise<GetPersonalBestsResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, mode2 } = req.query;
@ -872,7 +872,7 @@ export async function getStats(req: MonkeyRequest): Promise<GetStatsResponse> {
}
export async function getFavoriteQuotes(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetFavoriteQuotesResponse> {
const { uid } = req.ctx.decodedToken;
@ -882,7 +882,7 @@ export async function getFavoriteQuotes(
}
export async function addFavoriteQuote(
req: MonkeyRequest<undefined, AddFavoriteQuoteRequest>
req: MonkeyRequest<undefined, AddFavoriteQuoteRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
@ -892,14 +892,14 @@ export async function addFavoriteQuote(
uid,
language,
quoteId,
req.ctx.configuration.quotes.maxFavorites
req.ctx.configuration.quotes.maxFavorites,
);
return new MonkeyResponse("Quote added to favorites", null);
}
export async function removeFavoriteQuote(
req: MonkeyRequest<undefined, RemoveFavoriteQuoteRequest>
req: MonkeyRequest<undefined, RemoveFavoriteQuoteRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
@ -910,7 +910,7 @@ export async function removeFavoriteQuote(
}
export async function getProfile(
req: MonkeyRequest<GetProfileQuery, undefined, GetProfilePathParams>
req: MonkeyRequest<GetProfileQuery, undefined, GetProfilePathParams>,
): Promise<GetProfileResponse> {
const { uidOrName } = req.params;
@ -937,7 +937,7 @@ export async function getProfile(
const extractValid = (
src: Record<string, PersonalBest[]>,
validKeys: string[]
validKeys: string[],
): Record<string, PersonalBest[]> => {
return validKeys.reduce((obj, key) => {
if (src?.[key] !== undefined) {
@ -1009,7 +1009,7 @@ export async function getProfile(
}
export async function updateProfile(
req: MonkeyRequest<undefined, UpdateUserProfileRequest>
req: MonkeyRequest<undefined, UpdateUserProfileRequest>,
): Promise<UpdateUserProfileResponse> {
const { uid } = req.ctx.decodedToken;
const {
@ -1044,7 +1044,7 @@ export async function updateProfile(
Object.entries(socialProfiles ?? {}).map(([key, value]) => [
key,
sanitizeString(value),
])
]),
),
showActivityOnPublicProfile,
};
@ -1055,7 +1055,7 @@ export async function updateProfile(
}
export async function getInbox(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetUserInboxResponse> {
const { uid } = req.ctx.decodedToken;
@ -1068,7 +1068,7 @@ export async function getInbox(
}
export async function updateInbox(
req: MonkeyRequest<undefined, UpdateUserInboxRequest>
req: MonkeyRequest<undefined, UpdateUserInboxRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mailIdsToMarkRead, mailIdsToDelete } = req.body;
@ -1076,14 +1076,14 @@ export async function updateInbox(
await UserDAL.updateInbox(
uid,
mailIdsToMarkRead ?? [],
mailIdsToDelete ?? []
mailIdsToDelete ?? [],
);
return new MonkeyResponse("Inbox updated", null);
}
export async function reportUser(
req: MonkeyRequest<undefined, ReportUserRequest>
req: MonkeyRequest<undefined, ReportUserRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {
@ -1111,7 +1111,7 @@ export async function reportUser(
}
export async function setStreakHourOffset(
req: MonkeyRequest<undefined, SetStreakHourOffsetRequest>
req: MonkeyRequest<undefined, SetStreakHourOffsetRequest>,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { hourOffset } = req.body;
@ -1135,7 +1135,7 @@ export async function setStreakHourOffset(
}
export async function revokeAllTokens(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await AuthUtil.revokeTokensByUid(uid);
@ -1148,26 +1148,26 @@ async function getAllTimeLbs(uid: string): Promise<AllTimeLbs> {
"time",
"15",
"english",
uid
uid,
);
const allTime15EnglishCount = await LeaderboardsDAL.getCount(
"time",
"15",
"english"
"english",
);
const allTime60English = await LeaderboardsDAL.getRank(
"time",
"60",
"english",
uid
uid,
);
const allTime60EnglishCount = await LeaderboardsDAL.getCount(
"time",
"60",
"english"
"english",
);
const english15 =
@ -1199,7 +1199,7 @@ async function getAllTimeLbs(uid: string): Promise<AllTimeLbs> {
}
export function generateCurrentTestActivity(
testActivity: CountByYearAndDay | undefined
testActivity: CountByYearAndDay | undefined,
): TestActivity | undefined {
const thisYear = Dates.startOfYear(new UTCDateMini());
const lastYear = Dates.startOfYear(Dates.subYears(thisYear, 1));
@ -1207,8 +1207,9 @@ export function generateCurrentTestActivity(
let thisYearData = testActivity?.[thisYear.getFullYear().toString()];
let lastYearData = testActivity?.[lastYear.getFullYear().toString()];
if (lastYearData === undefined && thisYearData === undefined)
if (lastYearData === undefined && thisYearData === undefined) {
return undefined;
}
lastYearData = lastYearData ?? [];
thisYearData = thisYearData ?? [];
@ -1217,15 +1218,15 @@ export function generateCurrentTestActivity(
if (lastYearData.length < Dates.getDaysInYear(lastYear)) {
lastYearData.push(
...(new Array(Dates.getDaysInYear(lastYear) - lastYearData.length).fill(
undefined
) as (number | null)[])
undefined,
) as (number | null)[]),
);
}
//use enough days of the last year to have 372 days in total to always fill the first week of the graph
lastYearData = lastYearData.slice(-372 + thisYearData.length);
const lastDay = Dates.startOfDay(
Dates.addDays(thisYear, thisYearData.length - 1)
Dates.addDays(thisYear, thisYearData.length - 1),
);
return {
@ -1235,7 +1236,7 @@ export function generateCurrentTestActivity(
}
export async function getTestActivity(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetTestActivityResponse> {
const { uid } = req.ctx.decodedToken;
const premiumFeaturesEnabled = req.ctx.configuration.users.premium.enabled;
@ -1255,7 +1256,7 @@ export async function getTestActivity(
return new MonkeyResponse(
"Test activity data retrieved",
user.testActivity ?? null
user.testActivity ?? null,
);
}
@ -1268,7 +1269,7 @@ async function firebaseDeleteUserIgnoreError(uid: string): Promise<void> {
}
export async function getCurrentTestActivity(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetCurrentTestActivityResponse> {
const { uid } = req.ctx.decodedToken;
@ -1278,12 +1279,12 @@ export async function getCurrentTestActivity(
const data = generateCurrentTestActivity(user.testActivity);
return new MonkeyResponse(
"Current test activity data retrieved",
data ?? null
data ?? null,
);
}
export async function getStreak(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetStreakResponse> {
const { uid } = req.ctx.decodedToken;
@ -1293,7 +1294,7 @@ export async function getStreak(
}
export async function getFriends(
req: MonkeyRequest
req: MonkeyRequest,
): Promise<GetFriendsResponse> {
const { uid } = req.ctx.decodedToken;
const premiumEnabled = req.ctx.configuration.users.premium.enabled;

View file

@ -5,14 +5,15 @@ import MonkeyError from "../../utils/error";
import { MonkeyRequest } from "../types";
export async function githubRelease(
req: MonkeyRequest<undefined, PostGithubReleaseRequest>
req: MonkeyRequest<undefined, PostGithubReleaseRequest>,
): Promise<MonkeyResponse> {
const action = req.body.action;
if (action === "published") {
const releaseId = req.body.release?.id;
if (releaseId === undefined)
if (releaseId === undefined) {
throw new MonkeyError(422, 'Missing property "release.id".');
}
await GeorgeQueue.sendReleaseAnnouncement(releaseId);
return new MonkeyResponse("Added release announcement task to queue", null);

View file

@ -29,6 +29,6 @@ export default router;
function setCsp(res: Response): void {
res.setHeader(
"Content-Security-Policy",
"default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' monkeytype.com cdn.redocly.com data:;object-src 'none';script-src 'self' cdn.redocly.com 'unsafe-inline'; worker-src blob: data;script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests"
"default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' monkeytype.com cdn.redocly.com data:;object-src 'none';script-src 'self' cdn.redocly.com 'unsafe-inline'; worker-src blob: data;script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests",
);
}

View file

@ -76,8 +76,8 @@ export function addApiRoutes(app: Application): void {
.json(
new MonkeyResponse(
`Unknown request URL (${req.method}: ${req.path})`,
null
)
null,
),
);
});
}
@ -105,7 +105,7 @@ function applyTsRestApiRoutes(app: IRouter): void {
Logger.error(
`Unknown validation error for ${req.method} ${
req.path
}: ${JSON.stringify(err)}`
}: ${JSON.stringify(err)}`,
);
res
.status(500)
@ -145,7 +145,7 @@ function applyDevApiRoutes(app: Application): void {
const slowdown = (await getLiveConfiguration()).dev.responseSlowdownMs;
if (slowdown > 0) {
Logger.info(
`Simulating ${slowdown}ms delay for ${req.method} ${req.path}`
`Simulating ${slowdown}ms delay for ${req.method} ${req.path}`,
);
await new Promise((resolve) => setTimeout(resolve, slowdown));
}
@ -161,7 +161,7 @@ function applyApiRoutes(app: Application): void {
(
req: ExpressRequestWithContext,
res: Response,
next: NextFunction
next: NextFunction,
): void => {
if (req.path.startsWith("/configuration")) {
next();
@ -178,7 +178,7 @@ function applyApiRoutes(app: Application): void {
}
next();
}
},
);
app.get("/", (_req, res) => {
@ -186,7 +186,7 @@ function applyApiRoutes(app: Application): void {
new MonkeyResponse("ok", {
uptime: Date.now() - APP_START_TIME,
version,
})
}),
);
});

View file

@ -10,12 +10,12 @@ function addSwaggerMiddlewares(app: Application): void {
const { data: spec, error } = tryCatchSync(
() =>
JSON.parse(readFileSync(openApiSpec, "utf8")) as Record<string, unknown>
JSON.parse(readFileSync(openApiSpec, "utf8")) as Record<string, unknown>,
);
if (error) {
Logger.warning(
`Cannot read openApi specification from ${openApiSpec}. Swagger stats will not fully work.`
`Cannot read openApi specification from ${openApiSpec}. Swagger stats will not fully work.`,
);
}
@ -32,7 +32,7 @@ function addSwaggerMiddlewares(app: Application): void {
password === process.env["STATS_PASSWORD"]
);
},
})
}),
);
}

View file

@ -12,9 +12,9 @@ export function callController<
TResponse,
//ignoring as it might be used in the future
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
TStatus = 200
TStatus = 200,
>(
handler: MonkeyHandler<TQuery, TBody, TParams, TResponse>
handler: MonkeyHandler<TQuery, TBody, TParams, TResponse>,
): (all: TypeSafeTsRestRequest<TRoute, TQuery, TBody, TParams>) => Promise<{
status: TStatus;
body: MonkeyResponse<TResponse>;
@ -63,14 +63,14 @@ type WithoutParams = {
};
type MonkeyHandler<TQuery, TBody, TParams, TResponse> = (
req: MonkeyRequest<TQuery, TBody, TParams>
req: MonkeyRequest<TQuery, TBody, TParams>,
) => Promise<MonkeyResponse<TResponse>>;
type TypeSafeTsRestRequest<
TRoute extends AppRoute | AppRouter,
TQuery,
TBody,
TParams
TParams,
> = {
req: TsRestRequest<TRoute>;
} & (TQuery extends undefined ? WithoutQuery : WithQuery<TQuery>) &

View file

@ -17,7 +17,7 @@ export type TsRestRequestWithContext = {
export type MonkeyRequest<
TQuery = undefined,
TBody = undefined,
TParams = undefined
TParams = undefined,
> = {
query: Readonly<TQuery>;
body: Readonly<TBody>;

View file

@ -139,14 +139,14 @@ type Schema<T> = {
[P in keyof T]: T[P] extends unknown[]
? ArraySchema<T[P]>
: T[P] extends number
? NumberSchema
: T[P] extends boolean
? BooleanSchema
: T[P] extends string
? StringSchema
: T[P] extends object
? ObjectSchema<T[P]>
: never;
? NumberSchema
: T[P] extends boolean
? BooleanSchema
: T[P] extends string
? StringSchema
: T[P] extends object
? ObjectSchema<T[P]>
: never;
};
export const CONFIGURATION_FORM_SCHEMA: ObjectSchema<Configuration> = {

View file

@ -70,7 +70,7 @@ const statuses: Statuses = {
};
const CUSTOM_STATUS_CODES = new Set<number>(
Object.values(statuses).map((status) => status.code)
Object.values(statuses).map((status) => status.code),
);
export function isCustomCode(code: number): boolean {

View file

@ -46,7 +46,7 @@ export async function addApeKey(apeKey: DBApeKey): Promise<string> {
async function updateApeKey(
uid: string,
keyId: string,
updates: MatchKeysAndValues<DBApeKey>
updates: MatchKeysAndValues<DBApeKey>,
): Promise<void> {
const updateResult = await getApeKeysCollection().updateOne(
getApeKeyFilter(uid, keyId),
@ -54,10 +54,10 @@ async function updateApeKey(
$inc: { useCount: "lastUsedOn" in updates ? 1 : 0 },
$set: Object.fromEntries(
Object.entries(updates).filter(
([_, value]) => value !== null && value !== undefined
)
([_, value]) => value !== null && value !== undefined,
),
),
}
},
);
if (updateResult.modifiedCount === 0) {
@ -69,7 +69,7 @@ export async function editApeKey(
uid: string,
keyId: string,
name?: string,
enabled?: boolean
enabled?: boolean,
): Promise<void> {
//check if there is a change
if (name === undefined && enabled === undefined) return;
@ -84,7 +84,7 @@ export async function editApeKey(
export async function updateLastUsedOn(
uid: string,
keyId: string
keyId: string,
): Promise<void> {
const apeKeyUpdates = {
lastUsedOn: Date.now(),
@ -95,7 +95,7 @@ export async function updateLastUsedOn(
export async function deleteApeKey(uid: string, keyId: string): Promise<void> {
const deletionResult = await getApeKeysCollection().deleteOne(
getApeKeyFilter(uid, keyId)
getApeKeyFilter(uid, keyId),
);
if (deletionResult.deletedCount === 0) {

View file

@ -33,7 +33,7 @@ export async function add(user: BlocklistEntryProperties): Promise<void> {
usernameHash,
timestamp,
},
{ upsert: true }
{ upsert: true },
),
getCollection().replaceOne(
{ emailHash },
@ -41,8 +41,8 @@ export async function add(user: BlocklistEntryProperties): Promise<void> {
emailHash,
timestamp,
},
{ upsert: true }
)
{ upsert: true },
),
);
if (user.discordId !== undefined && user.discordId !== "") {
@ -54,15 +54,15 @@ export async function add(user: BlocklistEntryProperties): Promise<void> {
discordIdHash,
timestamp,
},
{ upsert: true }
)
{ upsert: true },
),
);
}
await Promise.all(inserts);
}
export async function remove(
user: Partial<BlocklistEntryProperties>
user: Partial<BlocklistEntryProperties>,
): Promise<void> {
const filter = getFilter(user);
if (filter.length === 0) return;
@ -70,7 +70,7 @@ export async function remove(
}
export async function contains(
user: Partial<BlocklistEntryProperties>
user: Partial<BlocklistEntryProperties>,
): Promise<boolean> {
const filter = getFilter(user);
if (filter.length === 0) return false;
@ -86,7 +86,7 @@ export function hash(value: string): string {
}
function getFilter(
user: Partial<BlocklistEntryProperties>
user: Partial<BlocklistEntryProperties>,
): Partial<DBBlocklistEntry>[] {
const filter: Partial<DBBlocklistEntry>[] = [];
if (user.email !== undefined) {
@ -107,20 +107,20 @@ export async function createIndicies(): Promise<void> {
{
unique: true,
partialFilterExpression: { usernameHash: { $exists: true } },
}
},
);
await getCollection().createIndex(
{ emailHash: 1 },
{
unique: true,
partialFilterExpression: { emailHash: { $exists: true } },
}
},
);
await getCollection().createIndex(
{ discordIdHash: 1 },
{
unique: true,
partialFilterExpression: { discordIdHash: { $exists: true } },
}
},
);
}

View file

@ -33,16 +33,16 @@ const getConfigCollection = (): Collection<DBConfig> =>
export async function saveConfig(
uid: string,
config: Partial<Config>
config: Partial<Config>,
): Promise<UpdateResult> {
const configChanges = Object.fromEntries(
Object.entries(config).map(([key, value]) => [`config.${key}`, value])
Object.entries(config).map(([key, value]) => [`config.${key}`, value]),
);
return await getConfigCollection().updateOne(
{ uid },
{ $set: configChanges, $unset: configLegacyProperties },
{ upsert: true }
{ upsert: true },
);
}

View file

@ -20,8 +20,9 @@ export async function getConnections(options: {
}): Promise<DBConnection[]> {
const { initiatorUid, receiverUid, status } = options;
if (initiatorUid === undefined && receiverUid === undefined)
if (initiatorUid === undefined && receiverUid === undefined) {
throw new Error("Missing filter");
}
let filter: Filter<DBConnection> = { $or: [] };
@ -43,7 +44,7 @@ export async function getConnections(options: {
export async function create(
initiator: { uid: string; name: string },
receiver: { uid: string; name: string },
maxPerUser: number
maxPerUser: number,
): Promise<DBConnection> {
const count = await getCollection().countDocuments({
initiatorUid: initiator.uid,
@ -53,7 +54,7 @@ export async function create(
throw new MonkeyError(
409,
"Maximum number of connections reached",
"create connection request"
"create connection request",
);
}
const key = getKey(initiator.uid, receiver.uid);
@ -77,7 +78,7 @@ export async function create(
if (e.name === "MongoServerError" && e.code === 11000) {
const existing = await getCollection().findOne(
{ key },
{ projection: { status: 1 } }
{ projection: { status: 1 } },
);
let message = "";
@ -113,14 +114,14 @@ export async function create(
export async function updateStatus(
receiverUid: string,
id: string,
status: ConnectionStatus
status: ConnectionStatus,
): Promise<void> {
const updateResult = await getCollection().updateOne(
{
_id: new ObjectId(id),
receiverUid,
},
{ $set: { status, lastModified: Date.now() } }
{ $set: { status, lastModified: Date.now() } },
);
if (updateResult.matchedCount === 0) {
@ -201,11 +202,11 @@ export async function getFriendsUids(uid: string): Promise<string[]> {
status: "accepted",
$or: [{ initiatorUid: uid }, { receiverUid: uid }],
},
{ projection: { initiatorUid: true, receiverUid: true } }
{ projection: { initiatorUid: true, receiverUid: true } },
)
.toArray()
).flatMap((it) => [it.initiatorUid, it.receiverUid])
)
).flatMap((it) => [it.initiatorUid, it.receiverUid]),
),
);
}
@ -232,7 +233,7 @@ export async function aggregateWithAcceptedConnections<T>(
*/
includeMetaData?: boolean;
},
pipeline: Document[]
pipeline: Document[],
): Promise<T[]> {
const metaData = options.includeMetaData
? {

View file

@ -40,7 +40,7 @@ export async function get(
page: number,
pageSize: number,
premiumFeaturesEnabled: boolean = false,
uid?: string
uid?: string,
): Promise<DBLeaderboardEntry[] | false> {
if (page < 0 || pageSize < 0) {
throw new MonkeyError(500, "Invalid page or pageSize");
@ -72,7 +72,7 @@ export async function get(
},
},
...pipeline,
]
],
);
} else {
leaderboard = await getCollection({ language, mode, mode2 })
@ -100,7 +100,7 @@ export async function getCount(
mode: string,
mode2: string,
language: string,
uid?: string
uid?: string,
): Promise<number> {
const key = `${language}_${mode}_${mode2}`;
if (uid === undefined && cachedCounts.has(key)) {
@ -121,7 +121,7 @@ export async function getCount(
collectionName: getCollectionName({ language, mode, mode2 }),
uid,
},
[{ $project: { _id: true } }]
[{ $project: { _id: true } }],
)
).length;
}
@ -133,7 +133,7 @@ export async function getRank(
mode2: string,
language: string,
uid: string,
friendsOnly: boolean = false
friendsOnly: boolean = false,
): Promise<DBLeaderboardEntry | null | false> {
try {
if (!friendsOnly) {
@ -157,7 +157,7 @@ export async function getRank(
},
},
{ $match: { uid } },
]
],
);
return results[0] ?? null;
}
@ -174,7 +174,7 @@ export async function getRank(
export async function update(
mode: string,
mode2: string,
language: string
language: string,
): Promise<{
message: string;
rank?: number;
@ -271,7 +271,7 @@ export async function update(
},
{ $out: lbCollectionName },
],
{ allowDiskUse: true }
{ allowDiskUse: true },
);
const start1 = performance.now();
@ -322,7 +322,7 @@ export async function update(
},
},
],
{ allowDiskUse: true }
{ allowDiskUse: true },
);
const start3 = performance.now();
await histogram.toArray();
@ -334,7 +334,7 @@ export async function update(
void addLog(
`system_lb_update_${language}_${mode}_${mode2}`,
`Aggregate ${timeToRunAggregate}s, loop 0s, insert 0s, index ${timeToRunIndex}s, histogram ${timeToSaveHistogram}`
`Aggregate ${timeToRunAggregate}s, loop 0s, insert 0s, index ${timeToRunIndex}s, histogram ${timeToSaveHistogram}`,
);
setLeaderboard(language, mode, mode2, [
@ -352,7 +352,7 @@ export async function update(
async function createIndex(
key: string,
minTimeTyping: number,
dropIfMismatch = true
dropIfMismatch = true,
): Promise<void> {
const index = {
[`${key}.wpm`]: -1,
@ -387,7 +387,7 @@ async function createIndex(
if (!dropIfMismatch) throw e;
if (
(e as Error).message.startsWith(
"An existing index has the same name as the requested index"
"An existing index has the same name as the requested index",
)
) {
Logger.warning(`Index ${key} not matching, dropping and recreating...`);

View file

@ -19,7 +19,7 @@ async function insertIntoDb(
event: string,
message: string | Record<string, unknown>,
uid = "",
important = false
important = false,
): Promise<void> {
const dbLog: DbLog = {
_id: new ObjectId(),
@ -37,7 +37,7 @@ async function insertIntoDb(
Logger.info(
`${event}\t${uid}\t${
stringified.length > 100 ? stringified.slice(0, 100) + "..." : stringified
}`
}`,
);
await getLogsCollection().insertOne(dbLog);
@ -46,7 +46,7 @@ async function insertIntoDb(
export async function addLog(
event: string,
message: string | Record<string, unknown>,
uid = ""
uid = "",
): Promise<void> {
await insertIntoDb(event, message, uid);
}
@ -54,7 +54,7 @@ export async function addLog(
export async function addImportantLog(
event: string,
message: string | Record<string, unknown>,
uid = ""
uid = "",
): Promise<void> {
console.log("log", event, message, uid);
await insertIntoDb(event, message, uid, true);

View file

@ -31,7 +31,7 @@ const QuoteDataSchema = z.object({
const PATH_TO_REPO = "../../../../monkeytype-new-quotes";
const { data: git, error } = tryCatchSync(() =>
simpleGit(path.join(__dirname, PATH_TO_REPO))
simpleGit(path.join(__dirname, PATH_TO_REPO)),
);
if (error) {
@ -54,7 +54,7 @@ export async function add(
text: string,
source: string,
language: string,
uid: string
uid: string,
): Promise<AddQuoteReturn | undefined> {
if (git === undefined) throw new MonkeyError(500, "Git not available.");
const quote = {
@ -78,14 +78,14 @@ export async function add(
if (count >= 100) {
throw new MonkeyError(
409,
"There are already 100 quotes in the queue for this language."
"There are already 100 quotes in the queue for this language.",
);
}
//check for duplicate first
const fileDir = path.join(
__dirname,
`${PATH_TO_REPO}/frontend/static/quotes/${language}.json`
`${PATH_TO_REPO}/frontend/static/quotes/${language}.json`,
);
let duplicateId = -1;
let similarityScore = -1;
@ -93,7 +93,7 @@ export async function add(
const quoteFile = await readFile(fileDir);
const quoteFileJSON = parseJsonWithSchema(
quoteFile.toString(),
QuoteDataSchema
QuoteDataSchema,
);
quoteFileJSON.quotes.every((old) => {
if (compareTwoStrings(old.text, quote.text) > 0.9) {
@ -145,7 +145,7 @@ export async function approve(
quoteId: string,
editQuote: string | undefined,
editSource: string | undefined,
name: string
name: string,
): Promise<ApproveReturn> {
if (git === null) throw new MonkeyError(500, "Git not available.");
//check mod status
@ -155,7 +155,7 @@ export async function approve(
if (!targetQuote) {
throw new MonkeyError(
404,
"Quote not found. It might have already been reviewed. Please refresh the list."
"Quote not found. It might have already been reviewed. Please refresh the list.",
);
}
const language = targetQuote.language;
@ -174,14 +174,14 @@ export async function approve(
const fileDir = path.join(
__dirname,
`${PATH_TO_REPO}/frontend/static/quotes/${language}.json`
`${PATH_TO_REPO}/frontend/static/quotes/${language}.json`,
);
await git.pull("upstream", "master");
if (existsSync(fileDir)) {
const quoteFile = await readFile(fileDir);
const quoteObject = parseJsonWithSchema(
quoteFile.toString(),
QuoteDataSchema
QuoteDataSchema,
);
quoteObject.quotes.every((old) => {
if (compareTwoStrings(old.text, quote.text) > 0.8) {
@ -218,7 +218,7 @@ export async function approve(
[601, 9999],
],
quotes: [quote],
})
}),
);
message = `Created file ${language}.json and added quote.`;
}

View file

@ -14,7 +14,7 @@ type DBConfigPreset = WithObjectId<
function getPresetKeyFilter(
uid: string,
keyId: string
keyId: string,
): Filter<DBConfigPreset> {
return {
_id: new ObjectId(keyId),
@ -39,7 +39,7 @@ export async function getPresets(uid: string): Promise<DBConfigPreset[]> {
export async function addPreset(
uid: string,
preset: Omit<Preset, "_id">
preset: Omit<Preset, "_id">,
): Promise<PresetCreationResult> {
const presets = await getPresetsCollection().countDocuments({ uid });
@ -59,7 +59,7 @@ export async function addPreset(
export async function editPreset(
uid: string,
preset: EditPresetRequest
preset: EditPresetRequest,
): Promise<void> {
const update: Partial<Omit<Preset, "_id">> = omit(preset, ["_id"]);
if (
@ -77,10 +77,10 @@ export async function editPreset(
export async function removePreset(
uid: string,
presetId: string
presetId: string,
): Promise<void> {
const deleteResult = await getPresetsCollection().deleteOne(
getPresetKeyFilter(uid, presetId)
getPresetKeyFilter(uid, presetId),
);
if (deleteResult.deletedCount === 0) {

View file

@ -12,7 +12,7 @@ export type PublicSpeedStatsDB = {
export async function updateStats(
restartCount: number,
time: number
time: number,
): Promise<boolean> {
await db.collection<PublicTypingStatsDB>("public").updateOne(
{ _id: "stats" },
@ -23,7 +23,7 @@ export async function updateStats(
timeTyping: roundTo2(time),
},
},
{ upsert: true }
{ upsert: true },
);
return true;
}
@ -34,7 +34,7 @@ export async function updateStats(
export async function getSpeedHistogram(
language: string,
mode: string,
mode2: string
mode2: string,
): Promise<SpeedHistogram> {
const key = `${language}_${mode}_${mode2}` as keyof PublicSpeedStatsDB;
@ -42,7 +42,7 @@ export async function getSpeedHistogram(
throw new MonkeyError(
400,
"Invalid speed histogram key",
"get speed histogram"
"get speed histogram",
);
}
@ -62,7 +62,7 @@ export async function getTypingStats(): Promise<PublicTypingStatsDB> {
throw new MonkeyError(
404,
"Public typing stats not found",
"get typing stats"
"get typing stats",
);
}
return stats;

View file

@ -14,19 +14,19 @@ export async function submit(
quoteId: number,
language: Language,
rating: number,
update: boolean
update: boolean,
): Promise<void> {
if (update) {
await getQuoteRatingCollection().updateOne(
{ quoteId, language },
{ $inc: { totalRating: rating } },
{ upsert: true }
{ upsert: true },
);
} else {
await getQuoteRatingCollection().updateOne(
{ quoteId, language },
{ $inc: { ratings: 1, totalRating: rating } },
{ upsert: true }
{ upsert: true },
);
}
@ -37,18 +37,18 @@ export async function submit(
const average = parseFloat(
(
Math.round((quoteRating.totalRating / quoteRating.ratings) * 10) / 10
).toFixed(1)
).toFixed(1),
);
await getQuoteRatingCollection().updateOne(
{ quoteId, language },
{ $set: { average } }
{ $set: { average } },
);
}
export async function get(
quoteId: number,
language: Language
language: Language,
): Promise<DBQuoteRating | null> {
return await getQuoteRatingCollection().findOne({ quoteId, language });
}

View file

@ -30,7 +30,7 @@ export async function deleteReports(reportIds: string[]): Promise<void> {
export async function createReport(
report: DBReport,
maxReports: number,
contentReportLimit: number
contentReportLimit: number,
): Promise<void> {
if (report.type === "user" && report.contentId === report.uid) {
throw new MonkeyError(400, "You cannot report yourself.");
@ -43,7 +43,7 @@ export async function createReport(
if (reportsCount >= maxReports) {
throw new MonkeyError(
503,
"Reports are not being accepted at this time due to a large backlog of reports. Please try again later."
"Reports are not being accepted at this time due to a large backlog of reports. Please try again later.",
);
}
@ -55,7 +55,7 @@ export async function createReport(
if (sameReports.length >= contentReportLimit) {
throw new MonkeyError(
409,
"A report limit for this content has been reached."
"A report limit for this content has been reached.",
);
}

View file

@ -16,12 +16,12 @@ export const getResultCollection = (): Collection<DBResult> =>
export async function addResult(
uid: string,
result: DBResult
result: DBResult,
): Promise<{ insertedId: ObjectId }> {
const { data: user } = await tryCatch(getUser(uid, "add result"));
if (!user) throw new MonkeyError(404, "User not found", "add result");
if (result.uid === undefined) result.uid = uid;
result.uid ??= uid;
// result.ir = true;
const res = await getResultCollection().insertOne(result);
return {
@ -36,7 +36,7 @@ export async function deleteAll(uid: string): Promise<DeleteResult> {
export async function updateTags(
uid: string,
resultId: string,
tags: string[]
tags: string[],
): Promise<UpdateResult> {
const result = await getResultCollection().findOne({
_id: new ObjectId(resultId),
@ -54,7 +54,7 @@ export async function updateTags(
}
return await getResultCollection().updateOne(
{ _id: new ObjectId(resultId), uid },
{ $set: { tags } }
{ $set: { tags } },
);
}
@ -71,7 +71,7 @@ export async function getResult(uid: string, id: string): Promise<DBResult> {
export async function getLastResult(uid: string): Promise<DBResult> {
const lastResult = await getResultCollection().findOne(
{ uid },
{ sort: { timestamp: -1 } }
{ sort: { timestamp: -1 } },
);
if (lastResult === null) throw new MonkeyError(404, "No last result found");
@ -84,7 +84,7 @@ export async function getLastResultTimestamp(uid: string): Promise<number> {
{
projection: { timestamp: 1, _id: 0 },
sort: { timestamp: -1 },
}
},
);
if (lastResult === null) throw new MonkeyError(404, "No last result found");
@ -93,7 +93,7 @@ export async function getLastResultTimestamp(uid: string): Promise<number> {
export async function getResultByTimestamp(
uid: string,
timestamp: number
timestamp: number,
): Promise<DBResult | null> {
const result = await getResultCollection().findOne({ uid, timestamp });
if (result === null) return null;
@ -108,7 +108,7 @@ type GetResultsOpts = {
export async function getResults(
uid: string,
opts?: GetResultsOpts
opts?: GetResultsOpts,
): Promise<DBResult[]> {
const { onOrAfterTimestamp, offset, limit } = opts ?? {};

View file

@ -77,7 +77,7 @@ export const getUsersCollection = (): Collection<DBUser> =>
export async function addUser(
name: string,
email: string,
uid: string
uid: string,
): Promise<void> {
const newUserDocument: Partial<DBUser> = {
name,
@ -97,7 +97,7 @@ export async function addUser(
const result = await getUsersCollection().updateOne(
{ uid },
{ $setOnInsert: newUserDocument },
{ upsert: true }
{ upsert: true },
);
if (result.upsertedCount === 0) {
@ -151,14 +151,14 @@ export async function resetUser(uid: string): Promise<void> {
lbOptOut: "",
inbox: "",
},
}
},
);
}
export async function updateName(
uid: string,
name: string,
previousName: string
previousName: string,
): Promise<void> {
if (name === previousName) {
throw new MonkeyError(400, "New name is the same as the old name");
@ -177,14 +177,14 @@ export async function updateName(
$set: { name, lastNameChange: Date.now() },
$unset: { needsToChangeName: "" },
$push: { nameHistory: previousName },
}
},
);
}
export async function flagForNameChange(uid: string): Promise<void> {
await getUsersCollection().updateOne(
{ uid },
{ $set: { needsToChangeName: true } }
{ $set: { needsToChangeName: true } },
);
}
@ -204,7 +204,7 @@ export async function clearPb(uid: string): Promise<void> {
time: {},
},
},
}
},
);
}
@ -218,25 +218,25 @@ export async function optOutOfLeaderboards(uid: string): Promise<void> {
time: {},
},
},
}
},
);
}
export async function updateQuoteRatings(
uid: string,
quoteRatings: UserQuoteRatings
quoteRatings: UserQuoteRatings,
): Promise<boolean> {
await updateUser(
{ uid },
{ $set: { quoteRatings } },
{ stack: "update quote ratings" }
{ stack: "update quote ratings" },
);
return true;
}
export async function updateEmail(
uid: string,
email: string
email: string,
): Promise<boolean> {
await updateUser({ uid }, { $set: { email } }, { stack: "update email" });
@ -260,7 +260,7 @@ export async function getUser(uid: string, stack: string): Promise<DBUser> {
export async function getPartialUser<K extends keyof DBUser>(
uid: string,
stack: string,
fields: K[]
fields: K[],
): Promise<Pick<DBUser, K>> {
const projection = new Map(fields.map((it) => [it, 1]));
const results = await getUsersCollection().findOne({ uid }, { projection });
@ -272,15 +272,15 @@ export async function getPartialUser<K extends keyof DBUser>(
export async function findByName(name: string): Promise<DBUser | undefined> {
const found = await getUsersCollection().findOne(
{ name },
{ collation: { locale: "en", strength: 1 } }
{ collation: { locale: "en", strength: 1 } },
);
return found !== null ? found : undefined;
return found ?? undefined;
}
export async function isNameAvailable(
name: string,
uid: string
uid: string,
): Promise<boolean> {
const user = await findByName(name);
// if the user found by name is the same as the user we are checking for, then the name is available
@ -290,7 +290,7 @@ export async function isNameAvailable(
export async function getUserByName(
name: string,
stack: string
stack: string,
): Promise<DBUser> {
const user = await findByName(name);
if (!user) throw new MonkeyError(404, "User not found", stack);
@ -298,11 +298,11 @@ export async function getUserByName(
}
export async function isDiscordIdAvailable(
discordId: string
discordId: string,
): Promise<boolean> {
const user = await getUsersCollection().findOne(
{ discordId },
{ projection: { _id: 1 } }
{ projection: { _id: 1 } },
);
return user === null;
}
@ -310,13 +310,13 @@ export async function isDiscordIdAvailable(
export async function addResultFilterPreset(
uid: string,
resultFilter: ResultFilters,
maxFiltersPerUser: number
maxFiltersPerUser: number,
): Promise<ObjectId> {
if (maxFiltersPerUser === 0) {
throw new MonkeyError(
409,
"Maximum number of custom filters reached",
"add result filter preset"
"add result filter preset",
);
}
@ -331,7 +331,7 @@ export async function addResultFilterPreset(
statusCode: 409,
message: "Maximum number of custom filters reached",
stack: "add result filter preset",
}
},
);
return _id;
@ -339,7 +339,7 @@ export async function addResultFilterPreset(
export async function removeResultFilterPreset(
uid: string,
_id: string
_id: string,
): Promise<void> {
const presetId = new ObjectId(_id);
@ -350,7 +350,7 @@ export async function removeResultFilterPreset(
statusCode: 404,
message: "Custom filter not found",
stack: "remove result filter preset",
}
},
);
}
@ -374,7 +374,7 @@ export async function addTag(uid: string, name: string): Promise<DBUserTag> {
statusCode: 400,
message: "Maximum number of tags reached",
stack: "add tag",
}
},
);
return toPush;
@ -389,14 +389,14 @@ export async function getTags(uid: string): Promise<DBUserTag[]> {
export async function editTag(
uid: string,
_id: string,
name: string
name: string,
): Promise<void> {
const tagId = new ObjectId(_id);
await updateUser(
{ uid, "tags._id": tagId },
{ $set: { "tags.$.name": name } },
{ statusCode: 404, message: "Tag not found", stack: "edit tag" }
{ statusCode: 404, message: "Tag not found", stack: "edit tag" },
);
}
@ -406,7 +406,7 @@ export async function removeTag(uid: string, _id: string): Promise<void> {
await updateUser(
{ uid, "tags._id": tagId },
{ $pull: { tags: { _id: tagId } } },
{ statusCode: 404, message: "Tag not found", stack: "remove tag" }
{ statusCode: 404, message: "Tag not found", stack: "remove tag" },
);
}
@ -426,7 +426,7 @@ export async function removeTagPb(uid: string, _id: string): Promise<void> {
},
},
},
{ statusCode: 404, message: "Tag not found", stack: "remove tag pb" }
{ statusCode: 404, message: "Tag not found", stack: "remove tag pb" },
);
}
@ -435,7 +435,7 @@ export async function updateLbMemory(
mode: Mode,
mode2: Mode2<Mode>,
language: string,
rank: number
rank: number,
): Promise<void> {
const partialUpdate = {};
partialUpdate[`lbMemory.${mode}.${mode2}.${language}`] = rank;
@ -443,14 +443,14 @@ export async function updateLbMemory(
await updateUser(
{ uid },
{ $set: partialUpdate },
{ stack: "update lb memory" }
{ stack: "update lb memory" },
);
}
export async function checkIfPb(
uid: string,
user: Pick<DBUser, "personalBests" | "lbPersonalBests">,
result: Result
result: Result,
): Promise<boolean> {
const { mode } = result;
@ -459,8 +459,9 @@ export async function checkIfPb(
"stopOnLetter" in result &&
result.stopOnLetter === true &&
result.acc < 100
)
) {
return false;
}
if (mode === "quote") {
return false;
@ -483,13 +484,13 @@ export async function checkIfPb(
await getUsersCollection().updateOne(
{ uid },
{ $set: { personalBests: pb.personalBests } }
{ $set: { personalBests: pb.personalBests } },
);
if (pb.lbPersonalBests) {
await getUsersCollection().updateOne(
{ uid },
{ $set: { lbPersonalBests: pb.lbPersonalBests } }
{ $set: { lbPersonalBests: pb.lbPersonalBests } },
);
}
return true;
@ -498,7 +499,7 @@ export async function checkIfPb(
export async function checkIfTagPb(
uid: string,
user: Pick<DBUser, "tags">,
result: Result
result: Result,
): Promise<string[]> {
if (user.tags === undefined || user.tags.length === 0) {
return [];
@ -510,8 +511,9 @@ export async function checkIfTagPb(
"stopOnLetter" in result &&
result.stopOnLetter === true &&
result.acc < 100
)
) {
return [];
}
if (mode === "quote") {
return [];
@ -542,7 +544,7 @@ export async function checkIfTagPb(
ret.push(tag._id.toHexString());
await getUsersCollection().updateOne(
{ uid, "tags._id": new ObjectId(tag._id) },
{ $set: { "tags.$.personalBests": tagpb.personalBests } }
{ $set: { "tags.$.personalBests": tagpb.personalBests } },
);
}
}
@ -564,13 +566,13 @@ export async function resetPb(uid: string): Promise<void> {
},
},
},
{ stack: "reset pb" }
{ stack: "reset pb" },
);
}
export async function updateLastHashes(
uid: string,
lastHashes: string[]
lastHashes: string[],
): Promise<void> {
await getUsersCollection().updateOne(
{ uid },
@ -578,14 +580,14 @@ export async function updateLastHashes(
$set: {
lastReultHashes: lastHashes, //TODO fix typo
},
}
},
);
}
export async function updateTypingStats(
uid: string,
restartCount: number,
timeTyping: number
timeTyping: number,
): Promise<void> {
await getUsersCollection().updateOne(
{ uid },
@ -595,18 +597,19 @@ export async function updateTypingStats(
completedTests: 1,
timeTyping,
},
}
},
);
}
export async function linkDiscord(
uid: string,
discordId: string,
discordAvatar?: string
discordAvatar?: string,
): Promise<void> {
const updates: Partial<DBUser> = { discordId };
if (discordAvatar !== undefined && discordAvatar !== null)
if (discordAvatar !== undefined && discordAvatar !== null) {
updates.discordAvatar = discordAvatar;
}
await updateUser({ uid }, { $set: updates }, { stack: "link discord" });
}
@ -615,13 +618,13 @@ export async function unlinkDiscord(uid: string): Promise<void> {
await updateUser(
{ uid },
{ $unset: { discordId: "", discordAvatar: "" } },
{ stack: "unlink discord" }
{ stack: "unlink discord" },
);
}
export async function incrementBananas(
uid: string,
wpm: number
wpm: number,
): Promise<void> {
//don't throw on missing user
await getUsersCollection().updateOne(
@ -655,7 +658,7 @@ export async function incrementBananas(
],
},
},
{ $inc: { bananas: 1 } }
{ $inc: { bananas: 1 } },
);
}
@ -666,7 +669,7 @@ export async function incrementXp(uid: string, xp: number): Promise<void> {
export async function incrementTestActivity(
user: DBUser,
timestamp: number
timestamp: number,
): Promise<void> {
if (user.testActivity === undefined) {
//migration script did not run yet
@ -680,19 +683,19 @@ export async function incrementTestActivity(
if (user.testActivity[year] === undefined) {
await getUsersCollection().updateOne(
{ uid: user.uid },
{ $set: { [`testActivity.${date.getFullYear()}`]: [] } }
{ $set: { [`testActivity.${date.getFullYear()}`]: [] } },
);
}
await getUsersCollection().updateOne(
{ uid: user.uid },
{ $inc: { [`testActivity.${date.getFullYear()}.${dayOfYear - 1}`]: 1 } }
{ $inc: { [`testActivity.${date.getFullYear()}.${dayOfYear - 1}`]: 1 } },
);
}
export async function addTheme(
uid: string,
{ name, colors }: Omit<CustomTheme, "_id">
{ name, colors }: Omit<CustomTheme, "_id">,
): Promise<{ _id: ObjectId; name: string }> {
const _id = new ObjectId();
@ -711,7 +714,7 @@ export async function addTheme(
statusCode: 409,
message: "Maximum number of custom themes reached",
stack: "add theme",
}
},
);
return {
@ -729,14 +732,14 @@ export async function removeTheme(uid: string, id: string): Promise<void> {
statusCode: 404,
message: "Custom theme not found",
stack: "remove theme",
}
},
);
}
export async function editTheme(
uid: string,
id: string,
{ name, colors }: Omit<CustomTheme, "_id">
{ name, colors }: Omit<CustomTheme, "_id">,
): Promise<void> {
const themeId = new ObjectId(id);
@ -748,7 +751,7 @@ export async function editTheme(
"customThemes.$.colors": colors,
},
},
{ statusCode: 404, message: "Custom theme not found", stack: "edit theme" }
{ statusCode: 404, message: "Custom theme not found", stack: "edit theme" },
);
}
@ -762,7 +765,7 @@ export async function getThemes(uid: string): Promise<DBCustomTheme[]> {
export async function getPersonalBests(
uid: string,
mode: string,
mode2?: string
mode2?: string,
): Promise<PersonalBest> {
const user = await getPartialUser(uid, "get personal bests", [
"personalBests",
@ -777,7 +780,7 @@ export async function getPersonalBests(
}
export async function getStats(
uid: string
uid: string,
): Promise<Pick<DBUser, "startedTests" | "completedTests" | "timeTyping">> {
const user = await getPartialUser(uid, "get stats", [
"startedTests",
@ -789,7 +792,7 @@ export async function getStats(
}
export async function getFavoriteQuotes(
uid: string
uid: string,
): Promise<NonNullable<DBUser["favoriteQuotes"]>> {
const user = await getPartialUser(uid, "get favorite quotes", [
"favoriteQuotes",
@ -802,7 +805,7 @@ export async function addFavoriteQuote(
uid: string,
language: string,
quoteId: string,
maxQuotes: number
maxQuotes: number,
): Promise<void> {
await updateUser(
{
@ -831,26 +834,26 @@ export async function addFavoriteQuote(
statusCode: 409,
message: "Maximum number of favorite quotes reached",
stack: "add favorite quote",
}
},
);
}
export async function removeFavoriteQuote(
uid: string,
language: string,
quoteId: string
quoteId: string,
): Promise<void> {
await updateUser(
{ uid },
{ $pull: { [`favoriteQuotes.${language}`]: quoteId } },
{ stack: "remove favorite quote" }
{ stack: "remove favorite quote" },
);
}
export async function recordAutoBanEvent(
uid: string,
maxCount: number,
maxHours: number
maxHours: number,
): Promise<boolean> {
const user = await getPartialUser(uid, "record auto ban event", [
"banned",
@ -868,7 +871,7 @@ export async function recordAutoBanEvent(
//only keep events within the last maxHours
const recentAutoBanTimestamps = autoBanTimestamps.filter(
(timestamp) => timestamp >= now - maxHours * SECONDS_PER_HOUR * 1000
(timestamp) => timestamp >= now - maxHours * SECONDS_PER_HOUR * 1000,
);
//push new event
@ -889,7 +892,7 @@ export async function recordAutoBanEvent(
void addImportantLog(
"user_auto_banned",
{ autoBanTimestamps, banningUser },
uid
uid,
);
if (banningUser) {
@ -906,17 +909,17 @@ export async function recordAutoBanEvent(
export async function updateProfile(
uid: string,
profileDetailUpdates: Partial<UserProfileDetails>,
inventory?: UserInventory
inventory?: UserInventory,
): Promise<void> {
let profileUpdates = flattenObjectDeep(
Object.fromEntries(
Object.entries(profileDetailUpdates).filter(
([_, value]) =>
value !== undefined &&
!(isPlainObject(value) && Object.keys(value).length === 0)
)
!(isPlainObject(value) && Object.keys(value).length === 0),
),
),
"profileDetails"
"profileDetails",
);
const updates = {
@ -931,12 +934,12 @@ export async function updateProfile(
{
uid,
},
updates
updates,
);
}
export async function getInbox(
uid: string
uid: string,
): Promise<NonNullable<DBUser["inbox"]>> {
const user = await getPartialUser(uid, "get inbox", ["inbox"]);
return user.inbox ?? [];
@ -949,7 +952,7 @@ type AddToInboxBulkEntry = {
export async function addToInboxBulk(
entries: AddToInboxBulkEntry[],
inboxConfig: Configuration["users"]["inbox"]
inboxConfig: Configuration["users"]["inbox"],
): Promise<void> {
const { enabled, maxMail } = inboxConfig;
@ -977,7 +980,7 @@ export async function addToInboxBulk(
export async function addToInbox(
uid: string,
mail: MonkeyMail[],
inboxConfig: Configuration["users"]["inbox"]
inboxConfig: Configuration["users"]["inbox"],
): Promise<void> {
const { enabled, maxMail } = inboxConfig;
@ -997,21 +1000,21 @@ export async function addToInbox(
$slice: maxMail, // Keeps inbox size to maxMail, discarding the oldest
},
},
}
},
);
}
export async function updateInbox(
uid: string,
mailToRead: string[],
mailToDelete: string[]
mailToDelete: string[],
): Promise<void> {
const deleteSet = [...new Set(mailToDelete)];
//we don't need to read mails that are going to be deleted because
//Rewards will be claimed on unread mails on deletion
const readSet = [...new Set(mailToRead)].filter(
(it) => !deleteSet.includes(it)
(it) => !deleteSet.includes(it),
);
const update = await getUsersCollection().updateOne({ uid }, [
@ -1026,14 +1029,14 @@ export async function updateInbox(
xp: number,
inventory: UserInventory,
deletedIds: string[],
readIds: string[]
readIds: string[],
): Pick<DBUser, "xp" | "inventory" | "inbox"> {
const toBeDeleted = inbox.filter((it) =>
deletedIds.includes(it.id)
deletedIds.includes(it.id),
);
const toBeRead = inbox.filter(
(it) => readIds.includes(it.id) && !it.read
(it) => readIds.includes(it.id) && !it.read,
);
//flatMap rewards
@ -1053,10 +1056,15 @@ export async function updateInbox(
.filter((it) => it.type === "badge")
.map((it) => it.item);
if (inventory === null)
// mongo doesnt support ??= i think
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (inventory === null) {
inventory = {
badges: [],
};
}
// mongo doesnt support ??= i think
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (inventory.badges === null) inventory.badges = [];
const uniqueBadgeIds = new Set();
@ -1100,13 +1108,14 @@ export async function updateInbox(
{ $unset: "tmp" },
]);
if (update.matchedCount !== 1)
if (update.matchedCount !== 1) {
throw new MonkeyError(404, "User not found", "update inbox");
}
}
export async function updateStreak(
uid: string,
timestamp: number
timestamp: number,
): Promise<number> {
const user = await getPartialUser(uid, "calculate streak", ["streak"]);
const streak: UserStreak = {
@ -1141,7 +1150,7 @@ export async function updateStreak(
export async function setStreakHourOffset(
uid: string,
hourOffset: number
hourOffset: number,
): Promise<void> {
await getUsersCollection().updateOne(
{ uid },
@ -1150,7 +1159,7 @@ export async function setStreakHourOffset(
"streak.hourOffset": hourOffset,
"streak.lastResultTimestamp": Date.now(),
},
}
},
);
}
@ -1169,13 +1178,13 @@ export async function clearStreakHourOffset(uid: string): Promise<void> {
$unset: {
"streak.hourOffset": "",
},
}
},
);
}
export async function checkIfUserIsPremium(
uid: string,
userInfoOverride?: Pick<DBUser, "premium">
userInfoOverride?: Pick<DBUser, "premium">,
): Promise<boolean> {
const premiumFeaturesEnabled = (await getCachedConfiguration(true)).users
.premium.enabled;
@ -1195,7 +1204,7 @@ export async function checkIfUserIsPremium(
export async function logIpAddress(
uid: string,
ip: string,
userInfoOverride?: Pick<DBUser, "ips">
userInfoOverride?: Pick<DBUser, "ips">,
): Promise<void> {
const user =
userInfoOverride ?? (await getPartialUser(uid, "logIpAddress", ["ips"]));
@ -1221,16 +1230,17 @@ export async function logIpAddress(
async function updateUser(
filter: Filter<DBUser>,
update: UpdateFilter<DBUser>,
error: { stack: string; statusCode?: number; message?: string }
error: { stack: string; statusCode?: number; message?: string },
): Promise<void> {
const result = await getUsersCollection().updateOne(filter, update);
if (result.matchedCount !== 1)
if (result.matchedCount !== 1) {
throw new MonkeyError(
error.statusCode ?? 404,
error.message ?? "User not found",
error.stack
error.stack,
);
}
}
export async function getFriends(uid: string): Promise<DBFriend[]> {
@ -1346,6 +1356,6 @@ export async function getFriends(uid: string): Promise<DBFriend[]> {
premium: false,
},
},
]
],
);
}

View file

@ -19,12 +19,12 @@ import { intersect } from "@monkeytype/util/arrays";
const CONFIG_UPDATE_INTERVAL = 10 * 60 * 1000; // 10 Minutes
const SERVER_CONFIG_FILE_PATH = join(
__dirname,
"../backend-configuration.json"
"../backend-configuration.json",
);
function mergeConfigurations(
baseConfiguration: Configuration,
liveConfiguration: PartialConfiguration
liveConfiguration: PartialConfiguration,
): void {
if (!isPlainObject(baseConfiguration) || !isPlainObject(liveConfiguration)) {
return;
@ -56,7 +56,7 @@ let lastFetchTime = 0;
let serverConfigurationUpdated = false;
export async function getCachedConfiguration(
attemptCacheUpdate = false
attemptCacheUpdate = false,
): Promise<Configuration> {
if (
attemptCacheUpdate &&
@ -97,7 +97,7 @@ export async function getLiveConfiguration(): Promise<Configuration> {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"fetch_configuration_failure",
`Could not fetch configuration: ${errorMessage}`
`Could not fetch configuration: ${errorMessage}`,
);
}
@ -116,13 +116,13 @@ async function pushConfiguration(configuration: Configuration): Promise<void> {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"push_configuration_failure",
`Could not push configuration: ${errorMessage}`
`Could not push configuration: ${errorMessage}`,
);
}
}
export async function patchConfiguration(
configurationUpdates: PartialConfiguration
configurationUpdates: PartialConfiguration,
): Promise<boolean> {
try {
const currentConfiguration = structuredClone(configuration);
@ -137,7 +137,7 @@ export async function patchConfiguration(
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"patch_configuration_failure",
`Could not patch configuration: ${errorMessage}`
`Could not patch configuration: ${errorMessage}`,
);
return false;
@ -149,14 +149,14 @@ export async function patchConfiguration(
export async function updateFromConfigurationFile(): Promise<void> {
if (existsSync(SERVER_CONFIG_FILE_PATH)) {
Logger.info(
`Reading server configuration from file ${SERVER_CONFIG_FILE_PATH}`
`Reading server configuration from file ${SERVER_CONFIG_FILE_PATH}`,
);
const json = readFileSync(SERVER_CONFIG_FILE_PATH, "utf-8");
const data = parseJsonWithSchema(
json,
z.object({
configuration: PartialConfigurationSchema,
})
}),
);
await patchConfiguration(data.configuration);

View file

@ -51,7 +51,7 @@ export async function connect(): Promise<void> {
mongoClient = new MongoClient(
DB_URI ?? global.__MONGO_URI__, // Set in tests only
connectionOptions
connectionOptions,
);
try {
@ -60,7 +60,7 @@ export async function connect(): Promise<void> {
} catch (error) {
Logger.error(getErrorMessage(error) ?? "Unknown error");
Logger.error(
"Failed to connect to database. Exiting with exit status code 1."
"Failed to connect to database. Exiting with exit status code 1.",
);
process.exit(1);
}

View file

@ -49,7 +49,7 @@ export async function init(): Promise<void> {
if (!(EMAIL_HOST ?? "") || !(EMAIL_USER ?? "") || !(EMAIL_PASS ?? "")) {
if (isDevEnvironment()) {
Logger.warning(
"No email client configuration provided. Running without email."
"No email client configuration provided. Running without email.",
);
} else if (process.env["BYPASS_EMAILCLIENT"] === "true") {
Logger.warning("BYPASS_EMAILCLIENT is enabled! Running without email.");
@ -76,7 +76,8 @@ export async function init(): Promise<void> {
if (!result) {
throw new Error(
`Could not verify email client configuration: ` + JSON.stringify(result)
`Could not verify email client configuration: ` +
JSON.stringify(result),
);
}
@ -96,7 +97,7 @@ type MailResult = {
export async function sendEmail(
templateName: EmailType,
to: string,
data: EmailTaskContexts[EmailType]
data: EmailTaskContexts[EmailType],
): Promise<MailResult> {
if (!isInitialized()) {
return {
@ -117,7 +118,7 @@ export async function sendEmail(
type Result = { response: string; accepted: string[] };
const { data: result, error } = await tryCatch(
transporter.sendMail(mailOptions) as Promise<Result>
transporter.sendMail(mailOptions) as Promise<Result>,
);
if (error) {
@ -148,7 +149,7 @@ async function getTemplate(name: string): Promise<string> {
const template = await fs.promises.readFile(
`${EMAIL_TEMPLATES_DIRECTORY}/${name}`,
"utf-8"
"utf-8",
);
const html = mjml2html(template).html;
@ -159,7 +160,7 @@ async function getTemplate(name: string): Promise<string> {
async function fillTemplate<M extends EmailType>(
type: M,
data: EmailTaskContexts[M]
data: EmailTaskContexts[M],
): Promise<string> {
const template = await getTemplate(templates[type].templateName);
return mustache.render(template, data);

View file

@ -7,14 +7,14 @@ import { isDevEnvironment } from "../utils/misc";
const SERVICE_ACCOUNT_PATH = path.join(
__dirname,
"../../src/credentials/serviceAccountKey.json"
"../../src/credentials/serviceAccountKey.json",
);
export function init(): void {
if (!existsSync(SERVICE_ACCOUNT_PATH)) {
if (isDevEnvironment()) {
Logger.warning(
"Firebase service account key not found! Continuing in dev mode, but authentication will throw errors."
"Firebase service account key not found! Continuing in dev mode, but authentication will throw errors.",
);
} else if (process.env["BYPASS_FIREBASE"] === "true") {
Logger.warning("BYPASS_FIREBASE is enabled! Running without firebase.");
@ -22,7 +22,7 @@ export function init(): void {
throw new MonkeyError(
500,
"Firebase service account key not found! Make sure generate a service account key and place it in credentials/serviceAccountKey.json.",
"init() firebase-admin.ts"
"init() firebase-admin.ts",
);
}
} else {
@ -38,7 +38,7 @@ function get(): typeof admin {
throw new MonkeyError(
500,
"Firebase app not initialized! Make sure generate a service account key and place it in credentials/serviceAccountKey.json.",
"get() firebase-admin.ts"
"get() firebase-admin.ts",
);
}
return admin;

View file

@ -16,7 +16,7 @@ export type RedisConnectionWithCustomMethods = Redis & {
expirationTime: number,
uid: string,
score: number,
data: string
data: string,
) => Promise<number>;
addResultIncrement: (
keyCount: number,
@ -25,7 +25,7 @@ export type RedisConnectionWithCustomMethods = Redis & {
expirationTime: number,
uid: string,
score: number,
data: string
data: string,
) => Promise<number>;
getResults: (
keyCount: number,
@ -34,7 +34,7 @@ export type RedisConnectionWithCustomMethods = Redis & {
minRank: number,
maxRank: number,
withScores: string,
userIds: string
userIds: string,
) => Promise<
[string[], string[], string, [string, string | number], string[]]
>; //entries, scores(optional), count, min_score(optiona)[uid, score], ranks(optional)
@ -44,12 +44,12 @@ export type RedisConnectionWithCustomMethods = Redis & {
resultsKey: string,
uid: string,
withScores: string,
userIds: string
userIds: string,
) => Promise<[number, string, string, number]>; //rank, score(optional), entry json, friendsRank(optional)
purgeResults: (
keyCount: number,
uid: string,
namespace: string
namespace: string,
) => Promise<void>;
};
@ -105,11 +105,11 @@ export async function connect(): Promise<void> {
if (isDevEnvironment()) {
await connection.quit();
Logger.warning(
`Failed to connect to redis. Continuing in dev mode, running without redis.`
`Failed to connect to redis. Continuing in dev mode, running without redis.`,
);
} else {
Logger.error(
"Failed to connect to redis. Exiting with exit status code 1."
"Failed to connect to redis. Exiting with exit status code 1.",
);
process.exit(1);
}

View file

@ -21,7 +21,7 @@ async function deleteOldLogs(): Promise<void> {
void addLog(
"system_logs_deleted",
`${data.deletedCount} logs deleted older than ${LOG_MAX_AGE_DAYS} day(s)`,
undefined
undefined,
);
}

View file

@ -24,7 +24,7 @@ async function main(): Promise<void> {
setQueueLength(queue.queueName, "active", active);
setQueueLength(queue.queueName, "failed", failed);
setQueueLength(queue.queueName, "waiting", waitingTotal);
})
}),
);
}

View file

@ -8,26 +8,26 @@ const RECENT_AGE_MINUTES = 10;
const RECENT_AGE_MILLISECONDS = RECENT_AGE_MINUTES * 60 * 1000;
async function getTop10(
leaderboardTime: string
leaderboardTime: string,
): Promise<LeaderboardsDAL.DBLeaderboardEntry[]> {
return (await LeaderboardsDAL.get(
"time",
leaderboardTime,
"english",
0,
10
10,
)) as LeaderboardsDAL.DBLeaderboardEntry[]; //can do that because gettop10 will not be called during an update
}
async function updateLeaderboardAndNotifyChanges(
leaderboardTime: string
leaderboardTime: string,
): Promise<void> {
const top10BeforeUpdate = await getTop10(leaderboardTime);
const previousRecordsMap = Object.fromEntries(
top10BeforeUpdate.map((record) => {
return [record.uid, record];
})
}),
);
await LeaderboardsDAL.update("time", leaderboardTime, "english");
@ -46,7 +46,7 @@ async function updateLeaderboardAndNotifyChanges(
const isRecentRecord =
record.timestamp > Date.now() - RECENT_AGE_MILLISECONDS;
return (userImprovedRank || newUserInTop10) && isRecentRecord;
return (userImprovedRank === true || newUserInTop10) && isRecentRecord;
});
if (newRecords.length > 0) {

View file

@ -42,12 +42,12 @@ const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
* @returns
*/
export function authenticateTsRestRequest<
T extends AppRouter | AppRoute
T extends AppRouter | AppRoute,
>(): TsRestRequestHandler<T> {
return async (
req: TsRestRequestWithContext,
_res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> => {
const options = {
...DEFAULT_OPTIONS,
@ -60,7 +60,8 @@ export function authenticateTsRestRequest<
let authType = "None";
const isPublic =
options.isPublic || (options.isPublicOnDev && isDevEnvironment());
options.isPublic === true ||
(options.isPublicOnDev && isDevEnvironment());
const {
authorization: authHeader,
@ -74,7 +75,7 @@ export function authenticateTsRestRequest<
token = await authenticateWithAuthHeader(
authHeader,
req.ctx.configuration,
options
options,
);
} else if (isPublic === true) {
token = {
@ -86,7 +87,7 @@ export function authenticateTsRestRequest<
throw new MonkeyError(
401,
"Unauthorized",
`endpoint: ${req.baseUrl} no authorization header found`
`endpoint: ${req.baseUrl} no authorization header found`,
);
}
@ -103,7 +104,7 @@ export function authenticateTsRestRequest<
authType,
"failure",
Math.round(performance.now() - startTime),
req
req,
);
next(error);
@ -113,7 +114,7 @@ export function authenticateTsRestRequest<
token.type,
"success",
Math.round(performance.now() - startTime),
req
req,
);
const country = req.headers["cf-ipcountry"] as string;
@ -132,7 +133,7 @@ export function authenticateTsRestRequest<
async function authenticateWithAuthHeader(
authHeader: string,
configuration: Configuration,
options: RequestAuthenticationOptions
options: RequestAuthenticationOptions,
): Promise<DecodedToken> {
const [authScheme, token] = authHeader.split(" ");
@ -140,7 +141,7 @@ async function authenticateWithAuthHeader(
throw new MonkeyError(
401,
"Missing authentication token",
"authenticateWithAuthHeader"
"authenticateWithAuthHeader",
);
}
@ -158,18 +159,18 @@ async function authenticateWithAuthHeader(
throw new MonkeyError(
401,
"Unknown authentication scheme",
`The authentication scheme "${authScheme}" is not implemented`
`The authentication scheme "${authScheme}" is not implemented`,
);
}
async function authenticateWithBearerToken(
token: string,
options: RequestAuthenticationOptions
options: RequestAuthenticationOptions,
): Promise<DecodedToken> {
try {
const decodedToken = await verifyIdToken(
token,
(options.requireFreshToken ?? false) || (options.noCache ?? false)
(options.requireFreshToken ?? false) || (options.noCache ?? false),
);
if (options.requireFreshToken) {
@ -180,7 +181,7 @@ async function authenticateWithBearerToken(
throw new MonkeyError(
401,
"Unauthorized",
`This endpoint requires a fresh token`
`This endpoint requires a fresh token`,
);
}
}
@ -198,7 +199,7 @@ async function authenticateWithBearerToken(
throw new MonkeyError(
503,
"Firebase returned an internal error when trying to verify the token.",
"authenticateWithBearerToken"
"authenticateWithBearerToken",
);
}
@ -209,25 +210,25 @@ async function authenticateWithBearerToken(
throw new MonkeyError(
401,
"Token expired - please login again",
"authenticateWithBearerToken"
"authenticateWithBearerToken",
);
} else if (errorCode?.includes("auth/id-token-revoked")) {
throw new MonkeyError(
401,
"Token revoked - please login again",
"authenticateWithBearerToken"
"authenticateWithBearerToken",
);
} else if (errorCode?.includes("auth/user-not-found")) {
throw new MonkeyError(
404,
"User not found",
"authenticateWithBearerToken"
"authenticateWithBearerToken",
);
} else if (errorCode?.includes("auth/argument-error")) {
throw new MonkeyError(
400,
"Incorrect Bearer token format",
"authenticateWithBearerToken"
"authenticateWithBearerToken",
);
} else {
throw error;
@ -238,10 +239,10 @@ async function authenticateWithBearerToken(
async function authenticateWithApeKey(
key: string,
configuration: Configuration,
options: RequestAuthenticationOptions
options: RequestAuthenticationOptions,
): Promise<DecodedToken> {
const isPublic =
options.isPublic || (options.isPublicOnDev && isDevEnvironment());
options.isPublic === true || (options.isPublicOnDev && isDevEnvironment());
if (!isPublic) {
if (!configuration.apeKeys.acceptKeys) {
@ -318,7 +319,7 @@ async function authenticateWithUid(token: string): Promise<DecodedToken> {
export function authenticateGithubWebhook(
req: TsRestRequestWithContext,
authHeader: string | string[] | undefined
authHeader: string | string[] | undefined,
): DecodedToken {
try {
const webhookSecret = process.env["GITHUB_WEBHOOK_SECRET"];
@ -358,7 +359,7 @@ export function authenticateGithubWebhook(
}
throw new MonkeyError(
500,
"Failed to authenticate Github webhook: " + (error as Error).message
"Failed to authenticate Github webhook: " + (error as Error).message,
);
}
}

View file

@ -13,7 +13,7 @@ import type { Response, NextFunction, Request } from "express";
export async function compatibilityCheckMiddleware(
_req: Request,
res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> {
res.setHeader(COMPATIBILITY_CHECK_HEADER, COMPATIBILITY_CHECK);
next();

View file

@ -12,12 +12,12 @@ import { TsRestRequestWithContext } from "../api/types";
import { AppRoute, AppRouter } from "@ts-rest/core";
export function verifyRequiredConfiguration<
T extends AppRouter | AppRoute
T extends AppRouter | AppRoute,
>(): TsRestRequestHandler<T> {
return async (
req: TsRestRequestWithContext,
_res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> => {
const requiredConfigurations = getRequireConfigurations(getMetadata(req));
@ -29,13 +29,13 @@ export function verifyRequiredConfiguration<
for (const requireConfiguration of requiredConfigurations) {
const value = getValue(
req.ctx.configuration,
requireConfiguration.path
requireConfiguration.path,
);
if (!value) {
throw new MonkeyError(
503,
requireConfiguration.invalidMessage ??
"This endpoint is currently unavailable."
"This endpoint is currently unavailable.",
);
}
}
@ -50,7 +50,7 @@ export function verifyRequiredConfiguration<
function getValue(
configuration: Configuration,
path: ConfigurationPath
path: ConfigurationPath,
): boolean {
const keys = (path as string).split(".");
let result: unknown = configuration;
@ -62,26 +62,30 @@ function getValue(
result = result[key];
}
if (result === undefined || result === null)
if (result === undefined || result === null) {
throw new MonkeyError(
500,
`Required configuration doesnt exist: "${path}"`
`Required configuration doesnt exist: "${path}"`,
);
if (typeof result !== "boolean")
}
if (typeof result !== "boolean") {
throw new MonkeyError(
500,
`Required configuration is not a boolean: "${path}"`
`Required configuration is not a boolean: "${path}"`,
);
}
return result;
}
function getRequireConfigurations(
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
): RequireConfiguration[] | undefined {
if (metadata === undefined || metadata.requireConfiguration === undefined)
if (metadata === undefined || metadata.requireConfiguration === undefined) {
return undefined;
}
if (Array.isArray(metadata.requireConfiguration))
if (Array.isArray(metadata.requireConfiguration)) {
return metadata.requireConfiguration;
}
return [metadata.requireConfiguration];
}

View file

@ -22,7 +22,7 @@ export type Context = {
async function contextMiddleware(
req: ExpressRequest,
_res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> {
const configuration = await getCachedConfiguration(true);

View file

@ -36,7 +36,7 @@ async function errorHandlingMiddleware(
error: Error,
req: ExpressRequestWithContext,
res: Response,
_next: NextFunction
_next: NextFunction,
): Promise<void> {
try {
const monkeyError = error as MonkeyError;
@ -77,7 +77,7 @@ async function errorHandlingMiddleware(
await addLog(
"system_error",
`${status} ${errorId} ${error.message} ${error.stack}`,
uid
uid,
);
await db.collection<DBError>("errors").insertOne({
_id: errorId,
@ -114,7 +114,7 @@ async function errorHandlingMiddleware(
handleErrorResponse(
res,
500,
"Something went really wrong, please contact support."
"Something went really wrong, please contact support.",
);
}
@ -122,7 +122,7 @@ function handleErrorResponse(
res: Response,
status: number,
message: string,
data?: ErrorData
data?: ErrorData,
): void {
res.status(status);
if (isCustomCode(status)) {

View file

@ -18,7 +18,7 @@ type RequestPermissionCheck = {
type: "request";
criteria: (
req: TsRestRequestWithContext,
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
) => Promise<boolean>;
invalidMessage?: string;
};
@ -35,7 +35,7 @@ type PermissionCheck = UserPermissionCheck | RequestPermissionCheck;
function buildUserPermission<K extends keyof DBUser>(
fields: K[],
criteria: (user: Pick<DBUser, K>) => boolean,
invalidMessage?: string
invalidMessage?: string,
): UserPermissionCheck {
return {
type: "user",
@ -51,33 +51,33 @@ const permissionChecks: Record<PermissionId, PermissionCheck> = {
criteria: async (req, metadata) =>
await checkIfUserIsAdmin(
req.ctx.decodedToken,
metadata?.authenticationOptions
metadata?.authenticationOptions,
),
},
quoteMod: buildUserPermission(
["quoteMod"],
(user) =>
user.quoteMod === true ||
(typeof user.quoteMod === "string" && (user.quoteMod as string) !== "")
(typeof user.quoteMod === "string" && (user.quoteMod as string) !== ""),
),
canReport: buildUserPermission(
["canReport"],
(user) => user.canReport !== false
(user) => user.canReport !== false,
),
canManageApeKeys: buildUserPermission(
["canManageApeKeys"],
(user) => user.canManageApeKeys ?? true,
"You have lost access to ape keys, please contact support"
"You have lost access to ape keys, please contact support",
),
};
export function verifyPermissions<
T extends AppRouter | AppRoute
T extends AppRouter | AppRoute,
>(): TsRestRequestHandler<T> {
return async (
req: TsRestRequestWithContext,
_res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> => {
const metadata = getMetadata(req);
const requiredPermissionIds = getRequiredPermissionIds(metadata);
@ -103,8 +103,8 @@ export function verifyPermissions<
next(
new MonkeyError(
403,
check.invalidMessage ?? "You don't have permission to do this."
)
check.invalidMessage ?? "You don't have permission to do this.",
),
);
return;
}
@ -114,15 +114,15 @@ export function verifyPermissions<
const userChecks = checks.filter((it) => it.type === "user");
const checkResult = await checkUserPermissions(
req.ctx.decodedToken,
userChecks
userChecks,
);
if (!checkResult.passed) {
next(
new MonkeyError(
403,
checkResult.invalidMessage ?? "You don't have permission to do this."
)
checkResult.invalidMessage ?? "You don't have permission to do this.",
),
);
return;
}
@ -134,19 +134,21 @@ export function verifyPermissions<
}
function getRequiredPermissionIds(
metadata: EndpointMetadata | undefined
metadata: EndpointMetadata | undefined,
): PermissionId[] | undefined {
if (metadata === undefined || metadata.requirePermission === undefined)
if (metadata === undefined || metadata.requirePermission === undefined) {
return undefined;
}
if (Array.isArray(metadata.requirePermission))
if (Array.isArray(metadata.requirePermission)) {
return metadata.requirePermission;
}
return [metadata.requirePermission];
}
async function checkIfUserIsAdmin(
decodedToken: DecodedToken | undefined,
options: RequestAuthenticationOptions | undefined
options: RequestAuthenticationOptions | undefined,
): Promise<boolean> {
if (decodedToken === undefined) return false;
if (options?.isPublicOnDev && isDevEnvironment()) return true;
@ -165,7 +167,7 @@ type CheckResult =
async function checkUserPermissions(
decodedToken: DecodedToken | undefined,
checks: UserPermissionCheck[]
checks: UserPermissionCheck[],
): Promise<CheckResult> {
if (checks === undefined || checks.length === 0) {
return {
@ -182,15 +184,16 @@ async function checkUserPermissions(
const user = (await getPartialUser(
decodedToken.uid,
"check user permissions",
checks.flatMap((it) => it.fields)
checks.flatMap((it) => it.fields),
)) as DBUser;
for (const check of checks) {
if (!check.criteria(user))
if (!check.criteria(user)) {
return {
passed: false,
invalidMessage: check.invalidMessage,
};
}
}
return {

View file

@ -28,12 +28,12 @@ export const customHandler = (
req: ExpressRequestWithContext,
_res: Response,
_next: NextFunction,
_options: Options
_options: Options,
): void => {
if (req.ctx.decodedToken.type === "ApeKey") {
throw new MonkeyError(
statuses.APE_KEY_RATE_LIMIT_EXCEEDED.code,
statuses.APE_KEY_RATE_LIMIT_EXCEEDED.message
statuses.APE_KEY_RATE_LIMIT_EXCEEDED.message,
);
}
throw new MonkeyError(429, "Request limit reached, please try again later.");
@ -50,7 +50,7 @@ const getKey = (req: Request, _res: Response): string => {
const getKeyWithUid = (
req: ExpressRequestWithContext,
_res: Response
_res: Response,
): string => {
const uid = req?.ctx?.decodedToken?.uid;
const useUid = uid !== undefined && uid !== "";
@ -95,12 +95,12 @@ export const requestLimiters: Record<RateLimiterId, RateLimitRequestHandler> =
initialiseLimiters();
export function rateLimitRequest<
T extends AppRouter | AppRoute
T extends AppRouter | AppRoute,
>(): TsRestRequestHandler<T> {
return async (
req: TsRestRequestWithContext,
res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> => {
const rateLimit = getMetadata(req).rateLimit;
if (rateLimit === undefined) {
@ -124,8 +124,8 @@ export function rateLimitRequest<
next(
new MonkeyError(
500,
`Unknown rateLimiterId '${rateLimiterId}', how did you manage to do this?`
)
`Unknown rateLimiterId '${rateLimiterId}', how did you manage to do this?`,
),
);
} else {
await rateLimiter(req, res, next);
@ -141,7 +141,7 @@ export const rootRateLimiter = rateLimit({
handler: (_req, _res, _next, _options): void => {
throw new MonkeyError(
429,
"Maximum API request (root) limit reached. Please try again later."
"Maximum API request (root) limit reached. Please try again later.",
);
},
});
@ -155,7 +155,7 @@ const badAuthRateLimiter = new RateLimiterMemory({
export async function badAuthRateLimiterHandler(
req: ExpressRequestWithContext,
res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> {
const badAuthEnabled =
req?.ctx?.configuration?.rateLimiting?.badAuthentication?.enabled;
@ -171,7 +171,7 @@ export async function badAuthRateLimiterHandler(
if (rateLimitStatus !== null && rateLimitStatus?.remainingPoints <= 0) {
throw new MonkeyError(
429,
"Too many bad authentication attempts, please try again later."
"Too many bad authentication attempts, please try again later.",
);
}
} catch (error) {
@ -185,7 +185,7 @@ export async function badAuthRateLimiterHandler(
export async function incrementBadAuth(
req: ExpressRequestWithContext,
res: Response,
status: number
status: number,
): Promise<void> {
const { enabled, penalty, flaggedStatusCodes } =
req?.ctx?.configuration?.rateLimiting?.badAuthentication ?? {};

View file

@ -25,14 +25,14 @@ export function onlyAvailableOnDev(): RequestHandler {
return (
_req: TsRestRequestWithContext,
_res: Response,
next: NextFunction
next: NextFunction,
) => {
if (!isDevEnvironment()) {
next(
new MonkeyError(
503,
"Development endpoints are only available in DEV mode."
)
"Development endpoints are only available in DEV mode.",
),
);
} else {
next();
@ -55,7 +55,7 @@ export function getMetadata(req: TsRestRequestWithContext): EndpointMetadata {
export async function v4RequestBody(
req: Request,
_res: Response,
next: NextFunction
next: NextFunction,
): Promise<void> {
if (req.body === undefined) {
req.body = {};

View file

@ -24,7 +24,7 @@ export type EmailTaskContexts = {
function buildTask(
taskName: EmailType,
email: string,
taskContext: EmailTaskContexts[EmailType]
taskContext: EmailTaskContexts[EmailType],
): EmailTask<EmailType> {
return {
type: taskName,
@ -37,7 +37,7 @@ class EmailQueue extends MonkeyQueue<EmailTask<EmailType>> {
async sendVerificationEmail(
email: string,
name: string,
verificationLink: string
verificationLink: string,
): Promise<void> {
const taskName = "verify";
const task = buildTask(taskName, email, { name, verificationLink });
@ -47,7 +47,7 @@ class EmailQueue extends MonkeyQueue<EmailTask<EmailType>> {
async sendForgotPasswordEmail(
email: string,
name: string,
passwordResetLink: string
passwordResetLink: string,
): Promise<void> {
const taskName = "resetPassword";
const task = buildTask(taskName, email, { name, passwordResetLink });

View file

@ -33,7 +33,7 @@ class GeorgeQueue extends MonkeyQueue<GeorgeTask> {
async linkDiscord(
discordId: string,
uid: string,
lbOptOut: boolean
lbOptOut: boolean,
): Promise<void> {
const taskName = "linkDiscord";
const linkDiscordTask = buildGeorgeTask(taskName, [
@ -52,7 +52,7 @@ class GeorgeQueue extends MonkeyQueue<GeorgeTask> {
async awardChallenge(
discordId: string,
challengeName: string
challengeName: string,
): Promise<void> {
const taskName = "awardChallenge";
const awardChallengeTask = buildGeorgeTask(taskName, [
@ -70,7 +70,7 @@ class GeorgeQueue extends MonkeyQueue<GeorgeTask> {
async announceLeaderboardUpdate(
newRecords: Omit<LeaderboardEntry, "_id">[],
leaderboardId: string
leaderboardId: string,
): Promise<void> {
const taskName = "announceLeaderboardUpdate";
@ -97,7 +97,7 @@ class GeorgeQueue extends MonkeyQueue<GeorgeTask> {
async announceDailyLeaderboardTopResults(
leaderboardId: string,
leaderboardTimestamp: number,
topResults: LeaderboardEntry[]
topResults: LeaderboardEntry[],
): Promise<void> {
const taskName = "announceDailyLeaderboardTopResults";

View file

@ -40,7 +40,7 @@ class LaterQueue extends MonkeyQueue<LaterTask<LaterTaskType>> {
taskName: string,
task: LaterTask<LaterTaskType>,
jobId: string,
delay: number
delay: number,
): Promise<void> {
await this.add(taskName, task, {
delay,
@ -52,13 +52,13 @@ class LaterQueue extends MonkeyQueue<LaterTask<LaterTaskType>> {
this.scheduledJobCache.set(jobId, true);
Logger.info(
`Scheduled ${task.taskName} for ${new Date(Date.now() + delay)}`
`Scheduled ${task.taskName} for ${new Date(Date.now() + delay)}`,
);
}
async scheduleForNextWeek(
taskName: LaterTaskType,
taskId: string
taskId: string,
): Promise<void> {
const currentWeekTimestamp = getCurrentWeekTimestamp();
const jobId = `${taskName}:${currentWeekTimestamp}:${taskId}`;
@ -86,7 +86,7 @@ class LaterQueue extends MonkeyQueue<LaterTask<LaterTaskType>> {
async scheduleForTomorrow(
taskName: LaterTaskType,
taskId: string,
modeRule: ValidModeRule
modeRule: ValidModeRule,
): Promise<void> {
const currentDayTimestamp = getCurrentDayTimestamp();
const jobId = `${taskName}:${currentDayTimestamp}:${taskId}`;

Some files were not shown because too many files have changed in this diff Show more