mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-12-28 19:08:32 +08:00
Merge branch 'master' into newtribemerge
This commit is contained in:
commit
c95d08dda8
480 changed files with 11525 additions and 8228 deletions
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
|
|
@ -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"]
|
||||
|
|
|
|||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
1
.gitignore
vendored
|
|
@ -67,6 +67,7 @@ node_modules_bak/
|
|||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env*
|
||||
|
||||
#vs code
|
||||
.vscode/*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
11
.prettierrc
11
.prettierrc
|
|
@ -2,13 +2,6 @@
|
|||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"endOfLine": "lf",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"options": {
|
||||
"parser": "typescript"
|
||||
}
|
||||
}
|
||||
]
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.`,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ describe("ConfigDal", () => {
|
|||
|
||||
//THEN
|
||||
const savedConfig = (await ConfigDal.getConfig(
|
||||
uid
|
||||
uid,
|
||||
)) as ConfigDal.DBConfig;
|
||||
|
||||
expect(savedConfig.config.ads).toBe("off");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
}),
|
||||
])
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export function enableMonkeyErrorExpects(): void {
|
|||
expect.extend({
|
||||
toMatchMonkeyError(
|
||||
received: MonkeyError,
|
||||
expected: MonkeyError
|
||||
expected: MonkeyError,
|
||||
): MatcherResult {
|
||||
return {
|
||||
pass:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ describe("Configuration Controller", () => {
|
|||
describe("updateConfiguration", () => {
|
||||
const patchConfigurationMock = vi.spyOn(
|
||||
Configuration,
|
||||
"patchConfiguration"
|
||||
"patchConfiguration",
|
||||
);
|
||||
beforeEach(() => {
|
||||
patchConfigurationMock.mockClear();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ describe("PublicController", () => {
|
|||
expect(getSpeedHistogramMock).toHaveBeenCalledWith(
|
||||
"english",
|
||||
"time",
|
||||
"60"
|
||||
"60",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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 } },
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export function setupCommonMocks() {
|
|||
auth: (): unknown => ({
|
||||
verifyIdToken: (
|
||||
_token: string,
|
||||
_checkRevoked: boolean
|
||||
_checkRevoked: boolean,
|
||||
): unknown /* Promise<DecodedIdToken> */ =>
|
||||
Promise.resolve({
|
||||
aud: "mockFirebaseProjectId",
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ describe("Validation", () => {
|
|||
|
||||
testCases.forEach((testCase) => {
|
||||
expect(Validation.isTestTooShort(testCase.result as any)).toBe(
|
||||
testCase.expected
|
||||
testCase.expected,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
3
backend/__tests__/vitest.d.ts
vendored
3
backend/__tests__/vitest.d.ts
vendored
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
// };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
);
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>) &
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export type TsRestRequestWithContext = {
|
|||
export type MonkeyRequest<
|
||||
TQuery = undefined,
|
||||
TBody = undefined,
|
||||
TParams = undefined
|
||||
TParams = undefined,
|
||||
> = {
|
||||
query: Readonly<TQuery>;
|
||||
body: Readonly<TBody>;
|
||||
|
|
|
|||
|
|
@ -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> = {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 } },
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -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...`);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ?? {};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ async function main(): Promise<void> {
|
|||
setQueueLength(queue.queueName, "active", active);
|
||||
setQueueLength(queue.queueName, "failed", failed);
|
||||
setQueueLength(queue.queueName, "waiting", waitingTotal);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 ?? {};
|
||||
|
|
|
|||
|
|
@ -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 = {};
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue