mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-19 23:06:15 +08:00
refactor: restructure project to a true monorepo (#5626)
* split ts files * packages * nomore workspace? * blah * build, exports * declaration * blargh * turrrrbo * fix fontawesome paths * allow file in eslint, fix ts errors * optimise package, update tsconfig * fix ts * update turbo config * eslint split * fix imports * fix types * lock * add turbo task * script * test task * pretty scripts * update prettier ignore * no cache for tests * lint task * turbo * no out * depend on env * fix mono breaking things * odl * fix version dependency of mongodb-memory-server * Revert "fix version dependency of mongodb-memory-server" This reverts commit52ffac47b1
. * update vitest-mongodb * release scripts * update ci * update dev script * ignore issues around firebase config * add check ts tasks * import isaxioserror * shared types package * replace tsnodedev with tsx * fix import * shared types * module * backend imports * declaration * node version * test code * assert json * verbatim * type * lodash * ts ver * fix fix fix? * remove assert * remove module and resolution * cleanup * tsconfig * fix frontend * remove unecessary props * more unused * remove skiplib * declaration map, dev script * remove install scripts * fix regex * move shared types to package * dont include shared types * remove path * update scripts * test code * test code * fix backend types * fully fix backend * fix frontend d.ts * add .js to imports * remove module * revert add .js * update tsconfig * use bundler module resolution * almost all frontend types * mooore * date fns * fix backend docker * fix ape keys * fix type * clean rimraf type * fix shared-types in workspace * fix import resolving * fix docker builds * ignore type problems on slim-select until new version is released * turrrrbo * fix npm ci * fix lint task * expose env variables needed by frontend build * fix dependencies * package-lock * backend watch ts and lint * add fe and be build scripts to root * fix dev not building packages * shared-types missing eslint * move shared types back to dev-deps * add packages to labeler * add packages step to ci * typo * filter update * remove concurrently from root * add scripts * abbreviate * rename * yeet * fixed path * test pkg * consistent ordering * rename * Revert "backend imports" This reverts commitd715198829
. * fix missing imports, remove last .js * remove test package --------- Co-authored-by: Christian Fehmer <cfe@sexy-developer.com>
This commit is contained in:
parent
c96185e90a
commit
3e88ac2f12
112
.eslintrc.json
112
.eslintrc.json
|
@ -1,112 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"$": "readonly",
|
||||
"jQuery": "readonly",
|
||||
"html2canvas": "readonly",
|
||||
"ClipboardItem": "readonly",
|
||||
"grecaptcha": "readonly"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"backend/__tests__/**/*",
|
||||
"backend/jest.config.ts",
|
||||
"**/*.css",
|
||||
"**/*.scss",
|
||||
"frontend/static/js/*",
|
||||
"frontend/__tests__/**/*",
|
||||
"frontend/jest.config.ts"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:json/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["json", "require-path-exists", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"json/*": ["error"],
|
||||
"indent": ["off"],
|
||||
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||
"no-var": 2,
|
||||
"no-duplicate-imports": ["error"],
|
||||
// "import/default": "off",
|
||||
"import/no-unresolved": [
|
||||
"error",
|
||||
{
|
||||
"ignore": ["^./constants/firebase-config$"]
|
||||
}
|
||||
],
|
||||
"import/no-duplicates": "off",
|
||||
"no-mixed-operators": [
|
||||
"error",
|
||||
{
|
||||
"groups": [["+", "??"]]
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {
|
||||
"alwaysTryTypes": true,
|
||||
"project": ["frontend/tsconfig.json", "backend/tsconfig.json"]
|
||||
},
|
||||
"node": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
// enable the rule specifically for TypeScript files
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/strict"
|
||||
// "plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": ["error"],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-empty-function": "warn",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{ "argsIgnorePattern": "^(_|e|event)", "varsIgnorePattern": "^_" }
|
||||
],
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/no-this-alias": "off",
|
||||
"@typescript-eslint/no-misused-promises": [
|
||||
"error",
|
||||
{
|
||||
"checksVoidReturn": false
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/promise-function-async": "warn",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/strict-boolean-expressions": [
|
||||
"error",
|
||||
{ "allowNullableBoolean": true, "allowNullableNumber": true }
|
||||
],
|
||||
//
|
||||
"@typescript-eslint/non-nullable-type-assertion-style": "off",
|
||||
"@typescript-eslint/no-unnecessary-condition": "off",
|
||||
"@typescript-eslint/consistent-type-definitions": ["warn", "type"],
|
||||
"@typescript-eslint/no-invalid-void-type": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"project": "**/tsconfig.json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["backend/**/*.ts"],
|
||||
"rules": {
|
||||
"eqeqeq": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
3
.github/labeler.yml
vendored
3
.github/labeler.yml
vendored
|
@ -14,5 +14,8 @@ docs:
|
|||
frontend:
|
||||
- any: ["frontend/**/*"]
|
||||
|
||||
packages:
|
||||
- any: ["packages/**/*"]
|
||||
|
||||
local dev:
|
||||
- any: ["**/gulpfile.js", "**/tsconfig.json"]
|
||||
|
|
45
.github/workflows/monkey-ci.yml
vendored
45
.github/workflows/monkey-ci.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
name: Monkey CI
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18.19.1"
|
||||
NODE_VERSION: "18.20.4"
|
||||
RECAPTCHA_SITE_KEY: "6Lc-V8McAAAAAJ7s6LGNe7MBZnRiwbsbiWts87aj"
|
||||
|
||||
permissions:
|
||||
|
@ -28,6 +28,7 @@ jobs:
|
|||
outputs:
|
||||
should-build-be: ${{ steps.export-changes.outputs.should-build-be }}
|
||||
should-build-fe: ${{ steps.export-changes.outputs.should-build-fe }}
|
||||
should-build-pkg: ${{ steps.export-changes.outputs.should-build-pkg }}
|
||||
assets-json: ${{ steps.export-changes.outputs.assets-json }}
|
||||
|
||||
steps:
|
||||
|
@ -40,8 +41,12 @@ jobs:
|
|||
- 'frontend/**/*.json'
|
||||
be-src:
|
||||
- 'backend/**/*.{ts,js,json,lua,css,html}'
|
||||
- 'backend/package.json'
|
||||
fe-src:
|
||||
- 'frontend/**/*.{ts,scss}'
|
||||
- 'frontend/package.json'
|
||||
pkg-src:
|
||||
- 'packages/**/*'
|
||||
anti-cheat:
|
||||
- 'backend/**/anticheat/**'
|
||||
|
||||
|
@ -52,6 +57,7 @@ jobs:
|
|||
- name: Export changes
|
||||
id: export-changes
|
||||
run: |
|
||||
echo "should-build-pkg=${{ steps.filter.outputs.pkg-src }}" >> $GITHUB_OUTPUT
|
||||
echo "should-build-be=${{ steps.filter.outputs.be-src }}" >> $GITHUB_OUTPUT
|
||||
echo "should-build-fe=${{ steps.filter.outputs.fe-src }}" >> $GITHUB_OUTPUT
|
||||
echo "assets-json=${{ steps.filter.outputs.json }}" >> $GITHUB_OUTPUT
|
||||
|
@ -69,7 +75,7 @@ jobs:
|
|||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci & cd backend && npm ci
|
||||
run: npm ci
|
||||
|
||||
- name: Check pretty
|
||||
run: npm run pretty-code-be
|
||||
|
@ -78,7 +84,7 @@ jobs:
|
|||
run: npm run lint-be
|
||||
|
||||
- name: Build
|
||||
run: npm run pr-check-build-be
|
||||
run: npm run build-be
|
||||
|
||||
- name: Test
|
||||
run: npm run test-be
|
||||
|
@ -100,7 +106,7 @@ jobs:
|
|||
run: mv ./firebase-config-example.ts ./firebase-config.ts && cp ./firebase-config.ts ./firebase-config-live.ts
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci & cd frontend && npm ci
|
||||
run: npm ci
|
||||
|
||||
- name: Check pretty
|
||||
run: npm run pretty-code-fe
|
||||
|
@ -109,7 +115,7 @@ jobs:
|
|||
run: npm run lint-fe
|
||||
|
||||
- name: Build
|
||||
run: npm run pr-check-build-fe
|
||||
run: npm run build-fe
|
||||
|
||||
- name: Test
|
||||
run: npm run test-fe
|
||||
|
@ -141,7 +147,7 @@ jobs:
|
|||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci & cd frontend && npm ci
|
||||
run: npm ci
|
||||
|
||||
- name: Lint JSON
|
||||
run: npm run pr-check-lint-json
|
||||
|
@ -158,6 +164,33 @@ jobs:
|
|||
if: steps.filter.outputs.other-json == 'true'
|
||||
run: npm run pr-check-other-json
|
||||
|
||||
ci-pkg:
|
||||
name: ci-pkg
|
||||
needs: [pre-ci]
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.pre-ci.outputs.should-build-pkg == 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check pretty
|
||||
run: npm run pretty-code-pkg
|
||||
|
||||
- name: Check lint
|
||||
run: npm run lint-pkg
|
||||
|
||||
- name: Build
|
||||
run: npm run build-pkg
|
||||
|
||||
- name: Test
|
||||
run: npm run test-pkg
|
||||
|
||||
on-failure:
|
||||
permissions: write-all
|
||||
name: on-failure
|
||||
|
|
2
.github/workflows/pretty-fix.yml
vendored
2
.github/workflows/pretty-fix.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
|||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18.19.1"
|
||||
node-version: "18.20.4"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm i prettier@2.5.1 --save-dev --save-exact
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -126,3 +126,5 @@ copyAnticheatToDev.sh
|
|||
# ignore generated fonts
|
||||
frontend/src/webfonts-generated
|
||||
frontend/static/webfonts-preview
|
||||
|
||||
.turbo
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.turbo
|
||||
.github
|
||||
*.md
|
||||
docker/
|
||||
*.min.js
|
||||
*.min.css
|
||||
layouts.ts
|
||||
|
@ -7,8 +11,10 @@ sound/*
|
|||
node_modules
|
||||
css/balloon.css
|
||||
_list.json
|
||||
backend/build
|
||||
backend/logs
|
||||
backend/coverage
|
||||
backend/globalConfig.json
|
||||
frontend/public
|
||||
dist/
|
||||
build/
|
||||
frontend/coverage
|
|
@ -1,9 +1,7 @@
|
|||
{
|
||||
"hooks": {
|
||||
"before:init": [
|
||||
"npm run lint-fe",
|
||||
"npm run test-fe",
|
||||
"cd frontend && npm run validate-json && npm run build"
|
||||
"npx turbo lint test validate-json build --filter @monkeytype/frontend"
|
||||
],
|
||||
"before:release": [
|
||||
"cd frontend && npx firebase deploy -P live --only hosting",
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"hooks": {
|
||||
"before:init": [
|
||||
"npm run lint",
|
||||
"npm run test",
|
||||
"cd frontend && npm run validate-json && npm run build"
|
||||
],
|
||||
"before:init": ["npx turbo lint test validate-json build"],
|
||||
"before:release": [
|
||||
"sh ./bin/deployBackend.sh",
|
||||
"cd frontend && npx firebase deploy -P live --only hosting",
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
## Prerequisites
|
||||
|
||||
This contribution guide is for cases in which you need to test the functionality of your changes, or if you need to take screenshots of your changes. You will need a computer with a stable internet connection, a text editor, Git, and NodeJS with version 18.19.1. There are some additional requirements depending on what you're looking to contribute, such as Firebase for authentication, and Mongo and Docker for the backend. Read the below sections to understand how to set up each of these tools.
|
||||
This contribution guide is for cases in which you need to test the functionality of your changes, or if you need to take screenshots of your changes. You will need a computer with a stable internet connection, a text editor, Git, and NodeJS with version 18.20.4. There are some additional requirements depending on what you're looking to contribute, such as Firebase for authentication, and Mongo and Docker for the backend. Read the below sections to understand how to set up each of these tools.
|
||||
|
||||
### Git
|
||||
|
||||
|
@ -33,9 +33,9 @@ Git is optional but we recommend you utilize it. Monkeytype uses the Git source
|
|||
|
||||
### NodeJS and NPM
|
||||
|
||||
Currently, the project is using version `18.19.1 LTS`.
|
||||
Currently, the project is using version `18.20.4 LTS`.
|
||||
|
||||
If you use `nvm` (if you use Windows, use [nvm-windows](https://github.com/coreybutler/nvm-windows)) then you can run `nvm install` and `nvm use` (you might need to specify the exact version eg: `nvm install 18.19.1` then `nvm use 18.19.1`) to use the version of Node.js in the `.nvmrc` file.
|
||||
If you use `nvm` (if you use Windows, use [nvm-windows](https://github.com/coreybutler/nvm-windows)) then you can run `nvm install` and `nvm use` (you might need to specify the exact version eg: `nvm install 18.20.4` then `nvm use 18.20.4`) to use the version of Node.js in the `.nvmrc` file.
|
||||
|
||||
Alternatively, you can navigate to the NodeJS [website](https://nodejs.org/en/) to download it from there.
|
||||
|
||||
|
@ -120,10 +120,7 @@ Its time to run Monkeytype. Just like with the databases, you can run the fronte
|
|||
|
||||
### Dependencies (if running manually)
|
||||
|
||||
Run `npm run install-all` in the project root to install all dependencies.
|
||||
|
||||
- If you are on Windows, use `npm run install-windows`.
|
||||
- If neither works, you will have to run `npm install` in root, frontend, and backend directories.
|
||||
Run `npm i` in the project root to install all dependencies.
|
||||
|
||||
### Both Frontend and Backend
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ Stop the running docker containers using `docker compose down` before making any
|
|||
#uncomment to enable the account system, check the SELF_HOSTING.md file
|
||||
- type: bind
|
||||
source: ./serviceAccountKey.json
|
||||
target: /src/credentials/serviceAccountKey.json
|
||||
target: /app/backend/src/credentials/serviceAccountKey.json
|
||||
read_only: true
|
||||
```
|
||||
|
||||
|
|
16
backend/.eslintrc.cjs
Normal file
16
backend/.eslintrc.cjs
Normal file
|
@ -0,0 +1,16 @@
|
|||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@monkeytype/eslint-config"],
|
||||
ignorePatterns: [
|
||||
"node_modules/",
|
||||
"dist/",
|
||||
"build/",
|
||||
"__tests__/",
|
||||
"jest.config.ts",
|
||||
"__migration__/",
|
||||
],
|
||||
rules: {
|
||||
eqeqeq: "error",
|
||||
},
|
||||
};
|
|
@ -21,7 +21,7 @@ services:
|
|||
|
||||
api-server:
|
||||
container_name: monkeytype-api-server
|
||||
image: node:18.19.1
|
||||
image: node:18.20.4
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- redis
|
||||
|
|
10071
backend/package-lock.json
generated
10071
backend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,23 +1,25 @@
|
|||
{
|
||||
"name": "monkeytype-backend",
|
||||
"name": "@monkeytype/backend",
|
||||
"version": "1.14.3",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint \"./src/**/*.ts\"",
|
||||
"build": "tsc --build",
|
||||
"watch": "tsc --build --watch",
|
||||
"clean": "tsc --build --clean",
|
||||
"ts-check": "tsc --noEmit",
|
||||
"start": "npm run build && node ./build/server.js",
|
||||
"test": "vitest run",
|
||||
"test-coverage": "vitest run --coverage",
|
||||
"dev": "ts-node-dev --transpile-only --inspect -- ./src/server.ts",
|
||||
"dev": "concurrently \"tsx watch --clear-screen=false ./src/server.ts\" \"tsc --preserveWatchOutput --noEmit --watch\" \"npx eslint-watch \"./src/**/*.ts\"\"",
|
||||
"knip": "knip",
|
||||
"docker-db-only": "docker compose -f docker/compose.db-only.yml up",
|
||||
"docker": "docker compose -f docker/compose.yml up"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.19.1",
|
||||
"npm": "10.2.4"
|
||||
"node": "18.20.4",
|
||||
"npm": "10.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@date-fns/utc": "1.2.0",
|
||||
|
@ -55,6 +57,9 @@
|
|||
"winston": "3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@monkeytype/shared-types": "*",
|
||||
"@monkeytype/typescript-config": "*",
|
||||
"@monkeytype/eslint-config": "*",
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/cors": "2.8.12",
|
||||
"@types/cron": "1.7.3",
|
||||
|
@ -62,27 +67,27 @@
|
|||
"@types/ioredis": "4.28.10",
|
||||
"@types/lodash": "4.14.178",
|
||||
"@types/mustache": "4.2.2",
|
||||
"@types/node": "18.19.1",
|
||||
"@types/node": "20.14.11",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/nodemailer": "6.4.7",
|
||||
"@types/object-hash": "2.2.1",
|
||||
"@types/readline-sync": "1.4.8",
|
||||
"@types/string-similarity": "4.0.0",
|
||||
"@types/supertest": "2.0.12",
|
||||
"@types/swagger-stats": "0.95.4",
|
||||
"@types/swagger-stats": "0.95.11",
|
||||
"@types/swagger-ui-express": "4.1.3",
|
||||
"@types/ua-parser-js": "0.7.36",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@vitest/coverage-v8": "1.6.0",
|
||||
"concurrently": "8.2.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-watch": "8.0.0",
|
||||
"ioredis-mock": "7.4.0",
|
||||
"readline-sync": "1.4.10",
|
||||
"supertest": "6.2.3",
|
||||
"ts-node-dev": "2.0.0",
|
||||
"typescript": "5.3.3",
|
||||
"tsx": "4.16.2",
|
||||
"typescript": "5.5.3",
|
||||
"vitest": "1.6.0",
|
||||
"vitest-mongodb": "0.0.5"
|
||||
},
|
||||
"overrides": {
|
||||
"mongodb-memory-server": "9.2.0"
|
||||
"vitest-mongodb": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ import MonkeyError from "../../utils/error";
|
|||
import { MonkeyResponse } from "../../utils/monkey-response";
|
||||
import { base64UrlEncode } from "../../utils/misc";
|
||||
import { ObjectId } from "mongodb";
|
||||
import { ApeKey } from "@monkeytype/shared-types";
|
||||
|
||||
function cleanApeKey(apeKey: MonkeyTypes.ApeKeyDB): SharedTypes.ApeKey {
|
||||
function cleanApeKey(apeKey: MonkeyTypes.ApeKeyDB): ApeKey {
|
||||
return _.omit(apeKey, "hash", "_id", "uid", "useCount");
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ import * as ResultDal from "../../dal/result";
|
|||
import { roundTo2 } from "../../utils/misc";
|
||||
import { ObjectId } from "mongodb";
|
||||
import * as LeaderboardDal from "../../dal/leaderboards";
|
||||
import { isNumber } from "lodash";
|
||||
import MonkeyError from "../../utils/error";
|
||||
import isNumber from "lodash/isNumber";
|
||||
import { Mode } from "@monkeytype/shared-types/config";
|
||||
import { PersonalBest, PersonalBests } from "@monkeytype/shared-types/user";
|
||||
|
||||
type GenerateDataOptions = {
|
||||
firstTestTimestamp: Date;
|
||||
|
@ -113,7 +115,7 @@ function createResult(
|
|||
user: MonkeyTypes.DBUser,
|
||||
timestamp: Date //evil, we modify this value
|
||||
): MonkeyTypes.DBResult {
|
||||
const mode: SharedTypes.Config.Mode = randomValue(["time", "words"]);
|
||||
const mode: Mode = randomValue(["time", "words"]);
|
||||
const mode2: number =
|
||||
mode === "time"
|
||||
? randomValue([15, 30, 60, 120])
|
||||
|
@ -129,7 +131,7 @@ function createResult(
|
|||
charStats: [131, 0, 0, 0],
|
||||
acc: random(80, 100),
|
||||
language: "english",
|
||||
mode: mode as SharedTypes.Config.Mode,
|
||||
mode: mode as Mode,
|
||||
mode2: mode2 as unknown as never,
|
||||
timestamp: timestamp.valueOf(),
|
||||
testDuration: testDuration,
|
||||
|
@ -191,7 +193,7 @@ async function updateUser(uid: string): Promise<void> {
|
|||
},
|
||||
};
|
||||
|
||||
const personalBests: SharedTypes.PersonalBests = {
|
||||
const personalBests: PersonalBests = {
|
||||
time: {},
|
||||
custom: {},
|
||||
words: {},
|
||||
|
@ -228,7 +230,7 @@ async function updateUser(uid: string): Promise<void> {
|
|||
wpm: best.wpm,
|
||||
numbers: best.numbers,
|
||||
timestamp: best.timestamp,
|
||||
} as SharedTypes.PersonalBest;
|
||||
} as PersonalBest;
|
||||
|
||||
personalBests[mode.mode][mode.mode2].push(entry);
|
||||
|
||||
|
@ -251,7 +253,7 @@ async function updateUser(uid: string): Promise<void> {
|
|||
timeTyping: timeTyping,
|
||||
completedTests: completedTests,
|
||||
startedTests: Math.round(completedTests * 1.25),
|
||||
personalBests: personalBests as SharedTypes.PersonalBests,
|
||||
personalBests: personalBests as PersonalBests,
|
||||
lbPersonalBests: lbPersonalBests,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -36,6 +36,11 @@ import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard";
|
|||
import { UAParser } from "ua-parser-js";
|
||||
import { canFunboxGetPb } from "../../utils/pb";
|
||||
import { buildDbResult } from "../../utils/result";
|
||||
import {
|
||||
CompletedEvent,
|
||||
Configuration,
|
||||
PostResultResponse,
|
||||
} from "@monkeytype/shared-types";
|
||||
|
||||
try {
|
||||
if (!anticheatImplemented()) throw new Error("undefined");
|
||||
|
@ -178,10 +183,7 @@ export async function addResult(
|
|||
);
|
||||
}
|
||||
|
||||
const completedEvent = Object.assign(
|
||||
{},
|
||||
req.body.result
|
||||
) as SharedTypes.CompletedEvent;
|
||||
const completedEvent = Object.assign({}, req.body.result) as CompletedEvent;
|
||||
if (!user.lbOptOut && completedEvent.acc < 75) {
|
||||
throw new MonkeyError(
|
||||
400,
|
||||
|
@ -600,7 +602,7 @@ export async function addResult(
|
|||
);
|
||||
}
|
||||
|
||||
const data: Omit<SharedTypes.PostResultResponse, "insertedId"> & {
|
||||
const data: Omit<PostResultResponse, "insertedId"> & {
|
||||
insertedId: ObjectId;
|
||||
} = {
|
||||
isPb,
|
||||
|
@ -632,8 +634,8 @@ type XpResult = {
|
|||
};
|
||||
|
||||
async function calculateXp(
|
||||
result: SharedTypes.CompletedEvent,
|
||||
xpConfiguration: SharedTypes.Configuration["users"]["xp"],
|
||||
result: CompletedEvent,
|
||||
xpConfiguration: Configuration["users"]["xp"],
|
||||
uid: string,
|
||||
currentTotalXp: number,
|
||||
streak: number
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
sanitizeString,
|
||||
} from "../../utils/misc";
|
||||
import GeorgeQueue from "../../queues/george-queue";
|
||||
import admin, { FirebaseError } from "firebase-admin";
|
||||
import admin, { type FirebaseError } from "firebase-admin";
|
||||
import { deleteAllApeKeys } from "../../dal/ape-keys";
|
||||
import { deleteAllPresets } from "../../dal/preset";
|
||||
import { deleteAll as deleteAllResults } from "../../dal/result";
|
||||
|
@ -25,10 +25,18 @@ import * as ReportDAL from "../../dal/report";
|
|||
import emailQueue from "../../queues/email-queue";
|
||||
import FirebaseAdmin from "../../init/firebase-admin";
|
||||
import * as AuthUtil from "../../utils/auth";
|
||||
|
||||
import * as Dates from "date-fns";
|
||||
import { UTCDateMini } from "@date-fns/utc";
|
||||
import * as BlocklistDal from "../../dal/blocklist";
|
||||
import { Mode, Mode2 } from "@monkeytype/shared-types/config";
|
||||
import {
|
||||
AllTimeLbs,
|
||||
CountByYearAndDay,
|
||||
RankAndCount,
|
||||
TestActivity,
|
||||
UserProfile,
|
||||
UserProfileDetails,
|
||||
} from "@monkeytype/shared-types";
|
||||
|
||||
async function verifyCaptcha(captcha: string): Promise<void> {
|
||||
if (!(await verify(captcha))) {
|
||||
|
@ -661,8 +669,7 @@ export async function updateLbMemory(
|
|||
): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { mode, language, rank } = req.body;
|
||||
const mode2 = req.body
|
||||
.mode2 as SharedTypes.Config.Mode2<SharedTypes.Config.Mode>;
|
||||
const mode2 = req.body.mode2 as Mode2<Mode>;
|
||||
|
||||
await UserDAL.updateLbMemory(uid, mode, mode2, language, rank);
|
||||
return new MonkeyResponse("Leaderboard memory updated");
|
||||
|
@ -837,7 +844,7 @@ export async function getProfile(
|
|||
details: profileDetails,
|
||||
allTimeLbs,
|
||||
uid: user.uid,
|
||||
} as SharedTypes.UserProfile;
|
||||
} as UserProfile;
|
||||
|
||||
return new MonkeyResponse("Profile retrieved", profileData);
|
||||
}
|
||||
|
@ -865,13 +872,13 @@ export async function updateProfile(
|
|||
}
|
||||
});
|
||||
|
||||
const profileDetailsUpdates: Partial<SharedTypes.UserProfileDetails> = {
|
||||
const profileDetailsUpdates: Partial<UserProfileDetails> = {
|
||||
bio: sanitizeString(bio),
|
||||
keyboard: sanitizeString(keyboard),
|
||||
socialProfiles: _.mapValues(
|
||||
socialProfiles,
|
||||
sanitizeString
|
||||
) as SharedTypes.UserProfileDetails["socialProfiles"],
|
||||
) as UserProfileDetails["socialProfiles"],
|
||||
};
|
||||
|
||||
await UserDAL.updateProfile(uid, profileDetailsUpdates, user.inventory);
|
||||
|
@ -986,7 +993,7 @@ export async function revokeAllTokens(
|
|||
return new MonkeyResponse("All tokens revoked");
|
||||
}
|
||||
|
||||
async function getAllTimeLbs(uid: string): Promise<SharedTypes.AllTimeLbs> {
|
||||
async function getAllTimeLbs(uid: string): Promise<AllTimeLbs> {
|
||||
const allTime15English = await LeaderboardsDAL.getRank(
|
||||
"time",
|
||||
"15",
|
||||
|
@ -1007,7 +1014,7 @@ async function getAllTimeLbs(uid: string): Promise<SharedTypes.AllTimeLbs> {
|
|||
: ({
|
||||
rank: allTime15English.rank,
|
||||
count: allTime15English.count,
|
||||
} as SharedTypes.RankAndCount);
|
||||
} as RankAndCount);
|
||||
|
||||
const english60 =
|
||||
allTime60English === false
|
||||
|
@ -1015,7 +1022,7 @@ async function getAllTimeLbs(uid: string): Promise<SharedTypes.AllTimeLbs> {
|
|||
: ({
|
||||
rank: allTime60English.rank,
|
||||
count: allTime60English.count,
|
||||
} as SharedTypes.RankAndCount);
|
||||
} as RankAndCount);
|
||||
|
||||
return {
|
||||
time: {
|
||||
|
@ -1030,8 +1037,8 @@ async function getAllTimeLbs(uid: string): Promise<SharedTypes.AllTimeLbs> {
|
|||
}
|
||||
|
||||
export function generateCurrentTestActivity(
|
||||
testActivity: SharedTypes.CountByYearAndDay | undefined
|
||||
): SharedTypes.TestActivity | undefined {
|
||||
testActivity: CountByYearAndDay | undefined
|
||||
): TestActivity | undefined {
|
||||
const thisYear = Dates.startOfYear(new UTCDateMini());
|
||||
const lastYear = Dates.startOfYear(Dates.subYears(thisYear, 1));
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ import { asyncHandler } from "../../middlewares/utility";
|
|||
import { MonkeyResponse } from "../../utils/monkey-response";
|
||||
import { recordClientVersion } from "../../utils/prometheus";
|
||||
import {
|
||||
Application,
|
||||
NextFunction,
|
||||
Response,
|
||||
type Application,
|
||||
type NextFunction,
|
||||
type Response,
|
||||
Router,
|
||||
static as expressStatic,
|
||||
} from "express";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from "lodash";
|
||||
import { Application } from "express";
|
||||
import { type Application } from "express";
|
||||
import { getMiddleware as getSwaggerMiddleware } from "swagger-stats";
|
||||
import {
|
||||
serve as serveSwagger,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { Configuration } from "@monkeytype/shared-types";
|
||||
|
||||
/**
|
||||
* This is the base schema for the configuration of the API backend.
|
||||
* To add a new configuration. Simply add it to this object.
|
||||
* When changing this template, please follow the principle of "Secure by default" (https://en.wikipedia.org/wiki/Secure_by_default).
|
||||
*/
|
||||
export const BASE_CONFIGURATION: SharedTypes.Configuration = {
|
||||
export const BASE_CONFIGURATION: Configuration = {
|
||||
maintenance: false,
|
||||
dev: {
|
||||
responseSlowdownMs: 0,
|
||||
|
@ -145,449 +147,447 @@ type Schema<T> = {
|
|||
: never;
|
||||
};
|
||||
|
||||
export const CONFIGURATION_FORM_SCHEMA: ObjectSchema<SharedTypes.Configuration> =
|
||||
{
|
||||
type: "object",
|
||||
label: "Server Configuration",
|
||||
fields: {
|
||||
maintenance: {
|
||||
type: "boolean",
|
||||
label: "In Maintenance",
|
||||
export const CONFIGURATION_FORM_SCHEMA: ObjectSchema<Configuration> = {
|
||||
type: "object",
|
||||
label: "Server Configuration",
|
||||
fields: {
|
||||
maintenance: {
|
||||
type: "boolean",
|
||||
label: "In Maintenance",
|
||||
},
|
||||
dev: {
|
||||
type: "object",
|
||||
label: "Development",
|
||||
fields: {
|
||||
responseSlowdownMs: {
|
||||
type: "number",
|
||||
label: "Response Slowdown (miliseconds)",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
type: "object",
|
||||
label: "Development",
|
||||
fields: {
|
||||
responseSlowdownMs: {
|
||||
type: "number",
|
||||
label: "Response Slowdown (miliseconds)",
|
||||
min: 0,
|
||||
},
|
||||
results: {
|
||||
type: "object",
|
||||
label: "Results",
|
||||
fields: {
|
||||
savingEnabled: {
|
||||
type: "boolean",
|
||||
label: "Saving Results",
|
||||
},
|
||||
objectHashCheckEnabled: {
|
||||
type: "boolean",
|
||||
label: "Object Hash Check",
|
||||
},
|
||||
filterPresets: {
|
||||
type: "object",
|
||||
label: "Filter Presets",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxPresetsPerUser: {
|
||||
type: "number",
|
||||
label: "Max Presets Per User",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
limits: {
|
||||
type: "object",
|
||||
label: "maximum results",
|
||||
fields: {
|
||||
regularUser: {
|
||||
type: "number",
|
||||
label: "for regular users",
|
||||
min: 0,
|
||||
},
|
||||
premiumUser: {
|
||||
type: "number",
|
||||
label: "for premium users",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
maxBatchSize: {
|
||||
type: "number",
|
||||
label: "results endpoint max batch size",
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
quotes: {
|
||||
type: "object",
|
||||
label: "Quotes",
|
||||
fields: {
|
||||
reporting: {
|
||||
type: "object",
|
||||
label: "Reporting",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxReports: {
|
||||
type: "number",
|
||||
label: "Max Reports",
|
||||
},
|
||||
contentReportLimit: {
|
||||
type: "number",
|
||||
label: "Content Report Limit",
|
||||
},
|
||||
},
|
||||
},
|
||||
submissionsEnabled: {
|
||||
type: "boolean",
|
||||
label: "Submissions Enabled",
|
||||
},
|
||||
maxFavorites: {
|
||||
type: "number",
|
||||
label: "Max Favorites",
|
||||
},
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
type: "object",
|
||||
label: "Admin",
|
||||
fields: {
|
||||
endpointsEnabled: {
|
||||
type: "boolean",
|
||||
label: "Endpoints Enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
apeKeys: {
|
||||
type: "object",
|
||||
label: "Ape Keys",
|
||||
fields: {
|
||||
endpointsEnabled: {
|
||||
type: "boolean",
|
||||
label: "Endpoints Enabled",
|
||||
},
|
||||
acceptKeys: {
|
||||
type: "boolean",
|
||||
label: "Accept Keys",
|
||||
},
|
||||
maxKeysPerUser: {
|
||||
type: "number",
|
||||
label: "Max Keys Per User",
|
||||
min: 0,
|
||||
},
|
||||
apeKeyBytes: {
|
||||
type: "number",
|
||||
label: "Ape Key Bytes",
|
||||
min: 24,
|
||||
},
|
||||
apeKeySaltRounds: {
|
||||
type: "number",
|
||||
label: "Ape Key Salt Rounds",
|
||||
min: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
type: "object",
|
||||
label: "Users",
|
||||
fields: {
|
||||
premium: {
|
||||
type: "object",
|
||||
label: "Premium",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
signUp: {
|
||||
type: "boolean",
|
||||
label: "Sign Up Enabled",
|
||||
},
|
||||
lastHashesCheck: {
|
||||
type: "object",
|
||||
label: "Last Hashes Check",
|
||||
fields: {
|
||||
enabled: { type: "boolean", label: "Enabled" },
|
||||
maxHashes: { type: "number", label: "Hashes to store" },
|
||||
},
|
||||
},
|
||||
xp: {
|
||||
type: "object",
|
||||
label: "XP",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
gainMultiplier: {
|
||||
type: "number",
|
||||
label: "Gain Multiplier",
|
||||
},
|
||||
funboxBonus: {
|
||||
type: "number",
|
||||
label: "Funbox Bonus",
|
||||
},
|
||||
maxDailyBonus: {
|
||||
type: "number",
|
||||
label: "Max Daily Bonus",
|
||||
},
|
||||
minDailyBonus: {
|
||||
type: "number",
|
||||
label: "Min Daily Bonus",
|
||||
},
|
||||
streak: {
|
||||
type: "object",
|
||||
label: "Streak",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxStreakDays: {
|
||||
type: "number",
|
||||
label: "Max Streak Days",
|
||||
},
|
||||
maxStreakMultiplier: {
|
||||
type: "number",
|
||||
label: "Max Streak Multiplier",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
discordIntegration: {
|
||||
type: "object",
|
||||
label: "Discord Integration",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
autoBan: {
|
||||
type: "object",
|
||||
label: "Auto Ban",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxCount: {
|
||||
type: "number",
|
||||
label: "Max Count",
|
||||
min: 0,
|
||||
},
|
||||
maxHours: {
|
||||
type: "number",
|
||||
label: "Max Hours",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
inbox: {
|
||||
type: "object",
|
||||
label: "Inbox",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxMail: {
|
||||
type: "number",
|
||||
label: "Max Messages",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
type: "object",
|
||||
label: "User Profiles",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
results: {
|
||||
type: "object",
|
||||
label: "Results",
|
||||
fields: {
|
||||
savingEnabled: {
|
||||
type: "boolean",
|
||||
label: "Saving Results",
|
||||
},
|
||||
objectHashCheckEnabled: {
|
||||
type: "boolean",
|
||||
label: "Object Hash Check",
|
||||
},
|
||||
filterPresets: {
|
||||
type: "object",
|
||||
label: "Filter Presets",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxPresetsPerUser: {
|
||||
},
|
||||
rateLimiting: {
|
||||
type: "object",
|
||||
label: "Rate Limiting",
|
||||
fields: {
|
||||
badAuthentication: {
|
||||
type: "object",
|
||||
label: "Bad Authentication Rate Limiter",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
penalty: {
|
||||
type: "number",
|
||||
label: "Penalty",
|
||||
min: 0,
|
||||
},
|
||||
flaggedStatusCodes: {
|
||||
type: "array",
|
||||
label: "Flagged Status Codes",
|
||||
items: {
|
||||
label: "Status Code",
|
||||
type: "number",
|
||||
label: "Max Presets Per User",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
limits: {
|
||||
},
|
||||
},
|
||||
},
|
||||
dailyLeaderboards: {
|
||||
type: "object",
|
||||
label: "Daily Leaderboards",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxResults: {
|
||||
type: "number",
|
||||
label: "Max Results",
|
||||
min: 0,
|
||||
},
|
||||
leaderboardExpirationTimeInDays: {
|
||||
type: "number",
|
||||
label: "Leaderboard Expiration Time In Days",
|
||||
min: 0,
|
||||
},
|
||||
validModeRules: {
|
||||
type: "array",
|
||||
label: "Valid Mode Rules",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "maximum results",
|
||||
label: "Rule",
|
||||
fields: {
|
||||
regularUser: {
|
||||
language: {
|
||||
type: "string",
|
||||
label: "Language",
|
||||
},
|
||||
mode: {
|
||||
type: "string",
|
||||
label: "Mode",
|
||||
},
|
||||
mode2: {
|
||||
type: "string",
|
||||
label: "Secondary Mode",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
scheduleRewardsModeRules: {
|
||||
type: "array",
|
||||
label: "Schedule Rewards Mode Rules",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "Rule",
|
||||
fields: {
|
||||
language: {
|
||||
type: "string",
|
||||
label: "Language",
|
||||
},
|
||||
mode: {
|
||||
type: "string",
|
||||
label: "Mode",
|
||||
},
|
||||
mode2: {
|
||||
type: "string",
|
||||
label: "Secondary Mode",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
topResultsToAnnounce: {
|
||||
type: "number",
|
||||
label: "Top Results To Announce",
|
||||
min: 1,
|
||||
hint: "This should atleast be 1. Setting to zero is very bad.",
|
||||
},
|
||||
xpRewardBrackets: {
|
||||
type: "array",
|
||||
label: "XP Reward Brackets",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "Bracket",
|
||||
fields: {
|
||||
minRank: {
|
||||
type: "number",
|
||||
label: "for regular users",
|
||||
label: "Min Rank",
|
||||
min: 1,
|
||||
},
|
||||
maxRank: {
|
||||
type: "number",
|
||||
label: "Max Rank",
|
||||
min: 1,
|
||||
},
|
||||
minReward: {
|
||||
type: "number",
|
||||
label: "Min Reward",
|
||||
min: 0,
|
||||
},
|
||||
premiumUser: {
|
||||
maxReward: {
|
||||
type: "number",
|
||||
label: "for premium users",
|
||||
label: "Max Reward",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
maxBatchSize: {
|
||||
type: "number",
|
||||
label: "results endpoint max batch size",
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
quotes: {
|
||||
type: "object",
|
||||
label: "Quotes",
|
||||
fields: {
|
||||
reporting: {
|
||||
type: "object",
|
||||
label: "Reporting",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxReports: {
|
||||
type: "number",
|
||||
label: "Max Reports",
|
||||
},
|
||||
contentReportLimit: {
|
||||
type: "number",
|
||||
label: "Content Report Limit",
|
||||
},
|
||||
},
|
||||
leaderboards: {
|
||||
type: "object",
|
||||
label: "Leaderboards",
|
||||
fields: {
|
||||
weeklyXp: {
|
||||
type: "object",
|
||||
label: "Weekly XP",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
},
|
||||
submissionsEnabled: {
|
||||
type: "boolean",
|
||||
label: "Submissions Enabled",
|
||||
},
|
||||
maxFavorites: {
|
||||
type: "number",
|
||||
label: "Max Favorites",
|
||||
},
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
type: "object",
|
||||
label: "Admin",
|
||||
fields: {
|
||||
endpointsEnabled: {
|
||||
type: "boolean",
|
||||
label: "Endpoints Enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
apeKeys: {
|
||||
type: "object",
|
||||
label: "Ape Keys",
|
||||
fields: {
|
||||
endpointsEnabled: {
|
||||
type: "boolean",
|
||||
label: "Endpoints Enabled",
|
||||
},
|
||||
acceptKeys: {
|
||||
type: "boolean",
|
||||
label: "Accept Keys",
|
||||
},
|
||||
maxKeysPerUser: {
|
||||
type: "number",
|
||||
label: "Max Keys Per User",
|
||||
min: 0,
|
||||
},
|
||||
apeKeyBytes: {
|
||||
type: "number",
|
||||
label: "Ape Key Bytes",
|
||||
min: 24,
|
||||
},
|
||||
apeKeySaltRounds: {
|
||||
type: "number",
|
||||
label: "Ape Key Salt Rounds",
|
||||
min: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
users: {
|
||||
type: "object",
|
||||
label: "Users",
|
||||
fields: {
|
||||
premium: {
|
||||
type: "object",
|
||||
label: "Premium",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
expirationTimeInDays: {
|
||||
type: "number",
|
||||
label: "Expiration time in days",
|
||||
min: 0,
|
||||
hint: "This should atleast be 15, to allow for past week queries.",
|
||||
},
|
||||
},
|
||||
signUp: {
|
||||
type: "boolean",
|
||||
label: "Sign Up Enabled",
|
||||
},
|
||||
lastHashesCheck: {
|
||||
type: "object",
|
||||
label: "Last Hashes Check",
|
||||
fields: {
|
||||
enabled: { type: "boolean", label: "Enabled" },
|
||||
maxHashes: { type: "number", label: "Hashes to store" },
|
||||
},
|
||||
},
|
||||
xp: {
|
||||
type: "object",
|
||||
label: "XP",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
gainMultiplier: {
|
||||
type: "number",
|
||||
label: "Gain Multiplier",
|
||||
},
|
||||
funboxBonus: {
|
||||
type: "number",
|
||||
label: "Funbox Bonus",
|
||||
},
|
||||
maxDailyBonus: {
|
||||
type: "number",
|
||||
label: "Max Daily Bonus",
|
||||
},
|
||||
minDailyBonus: {
|
||||
type: "number",
|
||||
label: "Min Daily Bonus",
|
||||
},
|
||||
streak: {
|
||||
xpRewardBrackets: {
|
||||
type: "array",
|
||||
label: "XP Reward Brackets",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "Streak",
|
||||
label: "Bracket",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxStreakDays: {
|
||||
minRank: {
|
||||
type: "number",
|
||||
label: "Max Streak Days",
|
||||
label: "Min Rank",
|
||||
min: 1,
|
||||
},
|
||||
maxStreakMultiplier: {
|
||||
maxRank: {
|
||||
type: "number",
|
||||
label: "Max Streak Multiplier",
|
||||
label: "Max Rank",
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
discordIntegration: {
|
||||
type: "object",
|
||||
label: "Discord Integration",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
autoBan: {
|
||||
type: "object",
|
||||
label: "Auto Ban",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxCount: {
|
||||
type: "number",
|
||||
label: "Max Count",
|
||||
min: 0,
|
||||
},
|
||||
maxHours: {
|
||||
type: "number",
|
||||
label: "Max Hours",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
inbox: {
|
||||
type: "object",
|
||||
label: "Inbox",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxMail: {
|
||||
type: "number",
|
||||
label: "Max Messages",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
type: "object",
|
||||
label: "User Profiles",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rateLimiting: {
|
||||
type: "object",
|
||||
label: "Rate Limiting",
|
||||
fields: {
|
||||
badAuthentication: {
|
||||
type: "object",
|
||||
label: "Bad Authentication Rate Limiter",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
penalty: {
|
||||
type: "number",
|
||||
label: "Penalty",
|
||||
min: 0,
|
||||
},
|
||||
flaggedStatusCodes: {
|
||||
type: "array",
|
||||
label: "Flagged Status Codes",
|
||||
items: {
|
||||
label: "Status Code",
|
||||
type: "number",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dailyLeaderboards: {
|
||||
type: "object",
|
||||
label: "Daily Leaderboards",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
maxResults: {
|
||||
type: "number",
|
||||
label: "Max Results",
|
||||
min: 0,
|
||||
},
|
||||
leaderboardExpirationTimeInDays: {
|
||||
type: "number",
|
||||
label: "Leaderboard Expiration Time In Days",
|
||||
min: 0,
|
||||
},
|
||||
validModeRules: {
|
||||
type: "array",
|
||||
label: "Valid Mode Rules",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "Rule",
|
||||
fields: {
|
||||
language: {
|
||||
type: "string",
|
||||
label: "Language",
|
||||
},
|
||||
mode: {
|
||||
type: "string",
|
||||
label: "Mode",
|
||||
},
|
||||
mode2: {
|
||||
type: "string",
|
||||
label: "Secondary Mode",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
scheduleRewardsModeRules: {
|
||||
type: "array",
|
||||
label: "Schedule Rewards Mode Rules",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "Rule",
|
||||
fields: {
|
||||
language: {
|
||||
type: "string",
|
||||
label: "Language",
|
||||
},
|
||||
mode: {
|
||||
type: "string",
|
||||
label: "Mode",
|
||||
},
|
||||
mode2: {
|
||||
type: "string",
|
||||
label: "Secondary Mode",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
topResultsToAnnounce: {
|
||||
type: "number",
|
||||
label: "Top Results To Announce",
|
||||
min: 1,
|
||||
hint: "This should atleast be 1. Setting to zero is very bad.",
|
||||
},
|
||||
xpRewardBrackets: {
|
||||
type: "array",
|
||||
label: "XP Reward Brackets",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "Bracket",
|
||||
fields: {
|
||||
minRank: {
|
||||
type: "number",
|
||||
label: "Min Rank",
|
||||
min: 1,
|
||||
},
|
||||
maxRank: {
|
||||
type: "number",
|
||||
label: "Max Rank",
|
||||
min: 1,
|
||||
},
|
||||
minReward: {
|
||||
type: "number",
|
||||
label: "Min Reward",
|
||||
min: 0,
|
||||
},
|
||||
maxReward: {
|
||||
type: "number",
|
||||
label: "Max Reward",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
leaderboards: {
|
||||
type: "object",
|
||||
label: "Leaderboards",
|
||||
fields: {
|
||||
weeklyXp: {
|
||||
type: "object",
|
||||
label: "Weekly XP",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
expirationTimeInDays: {
|
||||
type: "number",
|
||||
label: "Expiration time in days",
|
||||
min: 0,
|
||||
hint: "This should atleast be 15, to allow for past week queries.",
|
||||
},
|
||||
xpRewardBrackets: {
|
||||
type: "array",
|
||||
label: "XP Reward Brackets",
|
||||
items: {
|
||||
type: "object",
|
||||
label: "Bracket",
|
||||
fields: {
|
||||
minRank: {
|
||||
type: "number",
|
||||
label: "Min Rank",
|
||||
min: 1,
|
||||
},
|
||||
maxRank: {
|
||||
type: "number",
|
||||
label: "Max Rank",
|
||||
min: 1,
|
||||
},
|
||||
minReward: {
|
||||
type: "number",
|
||||
label: "Min Reward",
|
||||
min: 0,
|
||||
},
|
||||
maxReward: {
|
||||
type: "number",
|
||||
label: "Max Reward",
|
||||
min: 0,
|
||||
},
|
||||
minReward: {
|
||||
type: "number",
|
||||
label: "Min Reward",
|
||||
min: 0,
|
||||
},
|
||||
maxReward: {
|
||||
type: "number",
|
||||
label: "Max Reward",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -596,4 +596,5 @@ export const CONFIGURATION_FORM_SCHEMA: ObjectSchema<SharedTypes.Configuration>
|
|||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import _ from "lodash";
|
||||
import * as db from "../init/db";
|
||||
import {
|
||||
Filter,
|
||||
type Filter,
|
||||
type MatchKeysAndValues,
|
||||
type WithId,
|
||||
ObjectId,
|
||||
MatchKeysAndValues,
|
||||
Collection,
|
||||
WithId,
|
||||
} from "mongodb";
|
||||
import MonkeyError from "../utils/error";
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { Collection } from "mongodb";
|
||||
import * as db from "../init/db";
|
||||
import { createHash } from "crypto";
|
||||
import { User } from "@monkeytype/shared-types";
|
||||
|
||||
type BlocklistEntryProperties = Pick<
|
||||
SharedTypes.User,
|
||||
"name" | "email" | "discordId"
|
||||
>;
|
||||
type BlocklistEntryProperties = Pick<User, "name" | "email" | "discordId">;
|
||||
// Export for use in tests
|
||||
export const getCollection = (): Collection<MonkeyTypes.DBBlocklistEntry> =>
|
||||
db.collection("blocklist");
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { UpdateResult } from "mongodb";
|
||||
import { type UpdateResult } from "mongodb";
|
||||
import * as db from "../init/db";
|
||||
import _ from "lodash";
|
||||
import { Config } from "@monkeytype/shared-types/config";
|
||||
|
||||
const configLegacyProperties = [
|
||||
"swapEscAndTab",
|
||||
|
@ -24,7 +25,7 @@ const configLegacyProperties = [
|
|||
|
||||
export async function saveConfig(
|
||||
uid: string,
|
||||
config: SharedTypes.Config
|
||||
config: Config
|
||||
): Promise<UpdateResult> {
|
||||
const configChanges = _.mapKeys(config, (_value, key) => `config.${key}`);
|
||||
|
||||
|
@ -33,7 +34,7 @@ export async function saveConfig(
|
|||
) as Record<string, "">;
|
||||
|
||||
return await db
|
||||
.collection<SharedTypes.Config>("configs")
|
||||
.collection<Config>("configs")
|
||||
.updateOne(
|
||||
{ uid },
|
||||
{ $set: configChanges, $unset: unset },
|
||||
|
@ -41,15 +42,11 @@ export async function saveConfig(
|
|||
);
|
||||
}
|
||||
|
||||
export async function getConfig(
|
||||
uid: string
|
||||
): Promise<SharedTypes.Config | null> {
|
||||
const config = await db
|
||||
.collection<SharedTypes.Config>("configs")
|
||||
.findOne({ uid });
|
||||
export async function getConfig(uid: string): Promise<Config | null> {
|
||||
const config = await db.collection<Config>("configs").findOne({ uid });
|
||||
return config;
|
||||
}
|
||||
|
||||
export async function deleteConfig(uid: string): Promise<void> {
|
||||
await db.collection<SharedTypes.Config>("configs").deleteOne({ uid });
|
||||
await db.collection<Config>("configs").deleteOne({ uid });
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { performance } from "perf_hooks";
|
|||
import { setLeaderboard } from "../utils/prometheus";
|
||||
import { isDevEnvironment } from "../utils/misc";
|
||||
import { getCachedConfiguration } from "../init/configuration";
|
||||
import { LeaderboardEntry } from "@monkeytype/shared-types";
|
||||
|
||||
export async function get(
|
||||
mode: string,
|
||||
|
@ -11,16 +12,14 @@ export async function get(
|
|||
language: string,
|
||||
skip: number,
|
||||
limit = 50
|
||||
): Promise<SharedTypes.LeaderboardEntry[] | false> {
|
||||
): Promise<LeaderboardEntry[] | false> {
|
||||
//if (leaderboardUpdating[`${language}_${mode}_${mode2}`]) return false;
|
||||
|
||||
if (limit > 50 || limit <= 0) limit = 50;
|
||||
if (skip < 0) skip = 0;
|
||||
try {
|
||||
const preset = await db
|
||||
.collection<SharedTypes.LeaderboardEntry>(
|
||||
`leaderboards.${language}.${mode}.${mode2}`
|
||||
)
|
||||
.collection<LeaderboardEntry>(`leaderboards.${language}.${mode}.${mode2}`)
|
||||
.find()
|
||||
.sort({ rank: 1 })
|
||||
.skip(skip)
|
||||
|
@ -46,7 +45,7 @@ export async function get(
|
|||
type GetRankResponse = {
|
||||
count: number;
|
||||
rank: number | null;
|
||||
entry: SharedTypes.LeaderboardEntry | null;
|
||||
entry: LeaderboardEntry | null;
|
||||
};
|
||||
|
||||
export async function getRank(
|
||||
|
@ -57,9 +56,7 @@ export async function getRank(
|
|||
): Promise<GetRankResponse | false> {
|
||||
try {
|
||||
const entry = await db
|
||||
.collection<SharedTypes.LeaderboardEntry>(
|
||||
`leaderboards.${language}.${mode}.${mode2}`
|
||||
)
|
||||
.collection<LeaderboardEntry>(`leaderboards.${language}.${mode}.${mode2}`)
|
||||
.findOne({ uid });
|
||||
const count = await db
|
||||
.collection(`leaderboards.${language}.${mode}.${mode2}`)
|
||||
|
@ -91,7 +88,7 @@ export async function update(
|
|||
const lbCollectionName = `leaderboards.${language}.${mode}.${mode2}`;
|
||||
const lb = db
|
||||
.collection<MonkeyTypes.DBUser>("users")
|
||||
.aggregate<SharedTypes.LeaderboardEntry>(
|
||||
.aggregate<LeaderboardEntry>(
|
||||
[
|
||||
{
|
||||
$match: {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import MonkeyError from "../utils/error";
|
||||
import * as db from "../init/db";
|
||||
import { ObjectId, Filter, Collection, WithId } from "mongodb";
|
||||
import { ObjectId, type Filter, Collection, type WithId } from "mongodb";
|
||||
import {
|
||||
ConfigPreset,
|
||||
DBConfigPreset as SharedDBConfigPreset,
|
||||
} from "@monkeytype/shared-types";
|
||||
|
||||
const MAX_PRESETS = 10;
|
||||
|
||||
type DBConfigPreset = MonkeyTypes.WithObjectId<SharedTypes.DBConfigPreset>;
|
||||
type DBConfigPreset = MonkeyTypes.WithObjectId<SharedDBConfigPreset>;
|
||||
|
||||
function getPresetKeyFilter(
|
||||
uid: string,
|
||||
|
@ -34,7 +38,7 @@ export async function getPresets(uid: string): Promise<DBConfigPreset[]> {
|
|||
export async function addPreset(
|
||||
uid: string,
|
||||
name: string,
|
||||
config: SharedTypes.ConfigPreset
|
||||
config: ConfigPreset
|
||||
): Promise<PresetCreationResult> {
|
||||
const presets = await getPresets(uid);
|
||||
if (presets.length >= MAX_PRESETS) {
|
||||
|
@ -56,7 +60,7 @@ export async function editPreset(
|
|||
uid: string,
|
||||
presetId: string,
|
||||
name: string,
|
||||
config: SharedTypes.ConfigPreset | null | undefined
|
||||
config: ConfigPreset | null | undefined
|
||||
): Promise<void> {
|
||||
const presetUpdates =
|
||||
config !== undefined && config !== null && Object.keys(config).length > 0
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { PSA } from "@monkeytype/shared-types";
|
||||
import * as db from "../init/db";
|
||||
|
||||
type PSA = MonkeyTypes.WithObjectId<SharedTypes.PSA>;
|
||||
type DBPSA = MonkeyTypes.WithObjectId<PSA>;
|
||||
|
||||
export async function get(): Promise<PSA[]> {
|
||||
return await db.collection<PSA>("psa").find().toArray();
|
||||
export async function get(): Promise<DBPSA[]> {
|
||||
return await db.collection<DBPSA>("psa").find().toArray();
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import * as db from "../init/db";
|
||||
import { roundTo2 } from "../utils/misc";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { PublicTypingStats, SpeedHistogram } from "@monkeytype/shared-types";
|
||||
|
||||
type PublicTypingStatsDB = SharedTypes.PublicTypingStats & { _id: "stats" };
|
||||
type PublicTypingStatsDB = PublicTypingStats & { _id: "stats" };
|
||||
type PublicSpeedStatsDB = {
|
||||
_id: "speedStatsHistogram";
|
||||
english_time_15: SharedTypes.SpeedHistogram;
|
||||
english_time_60: SharedTypes.SpeedHistogram;
|
||||
english_time_15: SpeedHistogram;
|
||||
english_time_60: SpeedHistogram;
|
||||
};
|
||||
|
||||
export async function updateStats(
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import _ from "lodash";
|
||||
import { Collection, DeleteResult, ObjectId, UpdateResult } from "mongodb";
|
||||
import {
|
||||
Collection,
|
||||
type DeleteResult,
|
||||
ObjectId,
|
||||
type UpdateResult,
|
||||
} from "mongodb";
|
||||
import MonkeyError from "../utils/error";
|
||||
import * as db from "../init/db";
|
||||
|
||||
import { DBResult as SharedDBResult } from "@monkeytype/shared-types";
|
||||
import { getUser, getTags } from "./user";
|
||||
import { Mode } from "@monkeytype/shared-types/config";
|
||||
|
||||
type DBResult = MonkeyTypes.WithObjectId<
|
||||
SharedTypes.DBResult<SharedTypes.Config.Mode>
|
||||
>;
|
||||
type DBResult = MonkeyTypes.WithObjectId<SharedDBResult<Mode>>;
|
||||
|
||||
export const getResultCollection = (): Collection<DBResult> =>
|
||||
db.collection<DBResult>("results");
|
||||
|
|
|
@ -3,19 +3,37 @@ import { containsProfanity, isUsernameValid } from "../utils/validation";
|
|||
import { canFunboxGetPb, checkAndUpdatePb } from "../utils/pb";
|
||||
import * as db from "../init/db";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Collection, ObjectId, Long, UpdateFilter, Filter } from "mongodb";
|
||||
import {
|
||||
Collection,
|
||||
ObjectId,
|
||||
Long,
|
||||
type UpdateFilter,
|
||||
type Filter,
|
||||
} from "mongodb";
|
||||
import Logger from "../utils/logger";
|
||||
import { flattenObjectDeep, isToday, isYesterday } from "../utils/misc";
|
||||
import { getCachedConfiguration } from "../init/configuration";
|
||||
import { getDayOfYear } from "date-fns";
|
||||
import { UTCDate } from "@date-fns/utc";
|
||||
import {
|
||||
AllRewards,
|
||||
Badge,
|
||||
Configuration,
|
||||
CustomTheme,
|
||||
DBResult,
|
||||
MonkeyMail,
|
||||
ResultFilters,
|
||||
UserInventory,
|
||||
UserProfileDetails,
|
||||
UserQuoteRatings,
|
||||
UserStreak,
|
||||
} from "@monkeytype/shared-types";
|
||||
import { Mode, Mode2 } from "@monkeytype/shared-types/config";
|
||||
import { PersonalBest } from "@monkeytype/shared-types/user";
|
||||
|
||||
const SECONDS_PER_HOUR = 3600;
|
||||
|
||||
type Result = Omit<
|
||||
SharedTypes.DBResult<SharedTypes.Config.Mode>,
|
||||
"_id" | "name"
|
||||
>;
|
||||
type Result = Omit<DBResult<Mode>, "_id" | "name">;
|
||||
|
||||
// Export for use in tests
|
||||
export const getUsersCollection = (): Collection<MonkeyTypes.DBUser> =>
|
||||
|
@ -177,7 +195,7 @@ export async function optOutOfLeaderboards(uid: string): Promise<void> {
|
|||
|
||||
export async function updateQuoteRatings(
|
||||
uid: string,
|
||||
quoteRatings: SharedTypes.UserQuoteRatings
|
||||
quoteRatings: UserQuoteRatings
|
||||
): Promise<boolean> {
|
||||
await updateUser(
|
||||
{ uid },
|
||||
|
@ -268,7 +286,7 @@ export async function isDiscordIdAvailable(
|
|||
|
||||
export async function addResultFilterPreset(
|
||||
uid: string,
|
||||
resultFilter: SharedTypes.ResultFilters,
|
||||
resultFilter: ResultFilters,
|
||||
maxFiltersPerUser: number
|
||||
): Promise<ObjectId> {
|
||||
if (maxFiltersPerUser === 0) {
|
||||
|
@ -394,8 +412,8 @@ export async function removeTagPb(uid: string, _id: string): Promise<void> {
|
|||
|
||||
export async function updateLbMemory(
|
||||
uid: string,
|
||||
mode: SharedTypes.Config.Mode,
|
||||
mode2: SharedTypes.Config.Mode2<SharedTypes.Config.Mode>,
|
||||
mode: Mode,
|
||||
mode2: Mode2<Mode>,
|
||||
language: string,
|
||||
rank: number
|
||||
): Promise<void> {
|
||||
|
@ -644,7 +662,7 @@ export async function incrementTestActivity(
|
|||
|
||||
export async function addTheme(
|
||||
uid: string,
|
||||
{ name, colors }: Omit<SharedTypes.CustomTheme, "_id">
|
||||
{ name, colors }: Omit<CustomTheme, "_id">
|
||||
): Promise<{ _id: ObjectId; name: string }> {
|
||||
const _id = new ObjectId();
|
||||
|
||||
|
@ -688,7 +706,7 @@ export async function removeTheme(uid: string, id: string): Promise<void> {
|
|||
export async function editTheme(
|
||||
uid: string,
|
||||
id: string,
|
||||
{ name, colors }: Omit<SharedTypes.CustomTheme, "_id">
|
||||
{ name, colors }: Omit<CustomTheme, "_id">
|
||||
): Promise<void> {
|
||||
const themeId = new ObjectId(id);
|
||||
|
||||
|
@ -715,7 +733,7 @@ export async function getPersonalBests(
|
|||
uid: string,
|
||||
mode: string,
|
||||
mode2?: string
|
||||
): Promise<SharedTypes.PersonalBest> {
|
||||
): Promise<PersonalBest> {
|
||||
const user = await getPartialUser(uid, "get personal bests", [
|
||||
"personalBests",
|
||||
]);
|
||||
|
@ -848,8 +866,8 @@ export async function recordAutoBanEvent(
|
|||
|
||||
export async function updateProfile(
|
||||
uid: string,
|
||||
profileDetailUpdates: Partial<SharedTypes.UserProfileDetails>,
|
||||
inventory?: SharedTypes.UserInventory
|
||||
profileDetailUpdates: Partial<UserProfileDetails>,
|
||||
inventory?: UserInventory
|
||||
): Promise<void> {
|
||||
const profileUpdates = _.omitBy(
|
||||
flattenObjectDeep(profileDetailUpdates, "profileDetails"),
|
||||
|
@ -882,12 +900,12 @@ export async function getInbox(
|
|||
|
||||
type AddToInboxBulkEntry = {
|
||||
uid: string;
|
||||
mail: SharedTypes.MonkeyMail[];
|
||||
mail: MonkeyMail[];
|
||||
};
|
||||
|
||||
export async function addToInboxBulk(
|
||||
entries: AddToInboxBulkEntry[],
|
||||
inboxConfig: SharedTypes.Configuration["users"]["inbox"]
|
||||
inboxConfig: Configuration["users"]["inbox"]
|
||||
): Promise<void> {
|
||||
const { enabled, maxMail } = inboxConfig;
|
||||
|
||||
|
@ -914,8 +932,8 @@ export async function addToInboxBulk(
|
|||
|
||||
export async function addToInbox(
|
||||
uid: string,
|
||||
mail: SharedTypes.MonkeyMail[],
|
||||
inboxConfig: SharedTypes.Configuration["users"]["inbox"]
|
||||
mail: MonkeyMail[],
|
||||
inboxConfig: Configuration["users"]["inbox"]
|
||||
): Promise<void> {
|
||||
const { enabled, maxMail } = inboxConfig;
|
||||
|
||||
|
@ -960,9 +978,9 @@ export async function updateInbox(
|
|||
lang: "js",
|
||||
args: ["$inbox", "$xp", "$inventory", deleteSet, readSet],
|
||||
body: function (
|
||||
inbox: SharedTypes.MonkeyMail[],
|
||||
inbox: MonkeyMail[],
|
||||
xp: number,
|
||||
inventory: SharedTypes.UserInventory,
|
||||
inventory: UserInventory,
|
||||
deletedIds: string[],
|
||||
readIds: string[]
|
||||
): Pick<MonkeyTypes.DBUser, "xp" | "inventory" | "inbox"> {
|
||||
|
@ -975,10 +993,7 @@ export async function updateInbox(
|
|||
);
|
||||
|
||||
//flatMap rewards
|
||||
const rewards: SharedTypes.AllRewards[] = [
|
||||
...toBeRead,
|
||||
...toBeDeleted,
|
||||
]
|
||||
const rewards: AllRewards[] = [...toBeRead, ...toBeDeleted]
|
||||
.filter((it) => it.read === false)
|
||||
.reduce((arr, current) => {
|
||||
return [...arr, ...current.rewards];
|
||||
|
@ -991,7 +1006,7 @@ export async function updateInbox(
|
|||
|
||||
const badgesToClaim = rewards
|
||||
.filter((it) => it.type === "badge")
|
||||
.map((it) => it.item as SharedTypes.Badge);
|
||||
.map((it) => it.item as Badge);
|
||||
|
||||
if (inventory === null)
|
||||
inventory = {
|
||||
|
@ -1000,7 +1015,7 @@ export async function updateInbox(
|
|||
if (inventory.badges === null) inventory.badges = [];
|
||||
|
||||
const uniqueBadgeIds = new Set();
|
||||
const newBadges: SharedTypes.Badge[] = [];
|
||||
const newBadges: Badge[] = [];
|
||||
|
||||
for (const badge of [...inventory.badges, ...badgesToClaim]) {
|
||||
if (uniqueBadgeIds.has(badge.id)) continue;
|
||||
|
@ -1049,7 +1064,7 @@ export async function updateStreak(
|
|||
timestamp: number
|
||||
): Promise<number> {
|
||||
const user = await getPartialUser(uid, "calculate streak", ["streak"]);
|
||||
const streak: SharedTypes.UserStreak = {
|
||||
const streak: UserStreak = {
|
||||
lastResultTimestamp: user.streak?.lastResultTimestamp ?? 0,
|
||||
length: user.streak?.length ?? 0,
|
||||
maxLength: user.streak?.maxLength ?? 0,
|
||||
|
|
|
@ -4,12 +4,13 @@ import { ObjectId } from "mongodb";
|
|||
import Logger from "../utils/logger";
|
||||
import { identity } from "../utils/misc";
|
||||
import { BASE_CONFIGURATION } from "../constants/base-configuration";
|
||||
import { Configuration } from "@monkeytype/shared-types";
|
||||
|
||||
const CONFIG_UPDATE_INTERVAL = 10 * 60 * 1000; // 10 Minutes
|
||||
|
||||
function mergeConfigurations(
|
||||
baseConfiguration: SharedTypes.Configuration,
|
||||
liveConfiguration: Partial<SharedTypes.Configuration>
|
||||
baseConfiguration: Configuration,
|
||||
liveConfiguration: Partial<Configuration>
|
||||
): void {
|
||||
if (
|
||||
!_.isPlainObject(baseConfiguration) ||
|
||||
|
@ -45,7 +46,7 @@ let serverConfigurationUpdated = false;
|
|||
|
||||
export async function getCachedConfiguration(
|
||||
attemptCacheUpdate = false
|
||||
): Promise<SharedTypes.Configuration> {
|
||||
): Promise<Configuration> {
|
||||
if (
|
||||
attemptCacheUpdate &&
|
||||
lastFetchTime < Date.now() - CONFIG_UPDATE_INTERVAL
|
||||
|
@ -57,7 +58,7 @@ export async function getCachedConfiguration(
|
|||
return configuration;
|
||||
}
|
||||
|
||||
export async function getLiveConfiguration(): Promise<SharedTypes.Configuration> {
|
||||
export async function getLiveConfiguration(): Promise<Configuration> {
|
||||
lastFetchTime = Date.now();
|
||||
|
||||
const configurationCollection = db.collection("configuration");
|
||||
|
@ -71,7 +72,7 @@ export async function getLiveConfiguration(): Promise<SharedTypes.Configuration>
|
|||
const liveConfigurationWithoutId = _.omit(
|
||||
liveConfiguration,
|
||||
"_id"
|
||||
) as SharedTypes.Configuration;
|
||||
) as Configuration;
|
||||
mergeConfigurations(baseConfiguration, liveConfigurationWithoutId);
|
||||
|
||||
await pushConfiguration(baseConfiguration);
|
||||
|
@ -92,9 +93,7 @@ export async function getLiveConfiguration(): Promise<SharedTypes.Configuration>
|
|||
return configuration;
|
||||
}
|
||||
|
||||
async function pushConfiguration(
|
||||
configuration: SharedTypes.Configuration
|
||||
): Promise<void> {
|
||||
async function pushConfiguration(configuration: Configuration): Promise<void> {
|
||||
if (serverConfigurationUpdated) {
|
||||
return;
|
||||
}
|
||||
|
@ -111,7 +110,7 @@ async function pushConfiguration(
|
|||
}
|
||||
|
||||
export async function patchConfiguration(
|
||||
configurationUpdates: Partial<SharedTypes.Configuration>
|
||||
configurationUpdates: Partial<Configuration>
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const currentConfiguration = _.cloneDeep(configuration);
|
||||
|
|
|
@ -3,8 +3,8 @@ import {
|
|||
Collection,
|
||||
Db,
|
||||
MongoClient,
|
||||
MongoClientOptions,
|
||||
WithId,
|
||||
type MongoClientOptions,
|
||||
type WithId,
|
||||
} from "mongodb";
|
||||
import MonkeyError from "../utils/error";
|
||||
import Logger from "../utils/logger";
|
||||
|
|
|
@ -5,7 +5,7 @@ import { join } from "path";
|
|||
import mjml2html from "mjml";
|
||||
import mustache from "mustache";
|
||||
import { recordEmail } from "../utils/prometheus";
|
||||
import { EmailTaskContexts, EmailType } from "../queues/email-queue";
|
||||
import type { EmailTaskContexts, EmailType } from "../queues/email-queue";
|
||||
import { isDevEnvironment } from "../utils/misc";
|
||||
|
||||
type EmailMetadata = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import admin, { ServiceAccount } from "firebase-admin";
|
||||
import admin, { type ServiceAccount } from "firebase-admin";
|
||||
import Logger from "../utils/logger";
|
||||
import { readFileSync, existsSync } from "fs";
|
||||
import MonkeyError from "../utils/error";
|
||||
|
|
|
@ -2,21 +2,20 @@ import { CronJob } from "cron";
|
|||
import GeorgeQueue from "../queues/george-queue";
|
||||
import * as LeaderboardsDAL from "../dal/leaderboards";
|
||||
import { getCachedConfiguration } from "../init/configuration";
|
||||
import { LeaderboardEntry } from "@monkeytype/shared-types";
|
||||
|
||||
const CRON_SCHEDULE = "30 14/15 * * * *";
|
||||
const RECENT_AGE_MINUTES = 10;
|
||||
const RECENT_AGE_MILLISECONDS = RECENT_AGE_MINUTES * 60 * 1000;
|
||||
|
||||
async function getTop10(
|
||||
leaderboardTime: string
|
||||
): Promise<SharedTypes.LeaderboardEntry[]> {
|
||||
async function getTop10(leaderboardTime: string): Promise<LeaderboardEntry[]> {
|
||||
return (await LeaderboardsDAL.get(
|
||||
"time",
|
||||
leaderboardTime,
|
||||
"english",
|
||||
0,
|
||||
10
|
||||
)) as SharedTypes.LeaderboardEntry[]; //can do that because gettop10 will not be called during an update
|
||||
)) as LeaderboardEntry[]; //can do that because gettop10 will not be called during an update
|
||||
}
|
||||
|
||||
async function updateLeaderboardAndNotifyChanges(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import MonkeyError from "../utils/error";
|
||||
import { Response, NextFunction, RequestHandler } from "express";
|
||||
import type { Response, NextFunction, RequestHandler } from "express";
|
||||
import statuses from "../constants/monkey-status-codes";
|
||||
import rateLimit, {
|
||||
RateLimitRequestHandler,
|
||||
Options,
|
||||
type RateLimitRequestHandler,
|
||||
type Options,
|
||||
} from "express-rate-limit";
|
||||
import { isDevEnvironment } from "../utils/misc";
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { getApeKey, updateLastUsedOn } from "../dal/ape-keys";
|
|||
import MonkeyError from "../utils/error";
|
||||
import { verifyIdToken } from "../utils/auth";
|
||||
import { base64UrlDecode, isDevEnvironment } from "../utils/misc";
|
||||
import { NextFunction, Response, Handler } from "express";
|
||||
import type { NextFunction, Response, Handler } from "express";
|
||||
import statuses from "../constants/monkey-status-codes";
|
||||
import {
|
||||
incrementAuth,
|
||||
|
@ -13,6 +13,7 @@ import {
|
|||
} from "../utils/prometheus";
|
||||
import crypto from "crypto";
|
||||
import { performance } from "perf_hooks";
|
||||
import { Configuration } from "@monkeytype/shared-types";
|
||||
|
||||
type RequestAuthenticationOptions = {
|
||||
isPublic?: boolean;
|
||||
|
@ -105,7 +106,7 @@ function authenticateRequest(authOptions = DEFAULT_OPTIONS): Handler {
|
|||
|
||||
async function authenticateWithAuthHeader(
|
||||
authHeader: string,
|
||||
configuration: SharedTypes.Configuration,
|
||||
configuration: Configuration,
|
||||
options: RequestAuthenticationOptions
|
||||
): Promise<MonkeyTypes.DecodedToken> {
|
||||
if (authHeader === undefined || authHeader === "") {
|
||||
|
@ -221,7 +222,7 @@ async function authenticateWithBearerToken(
|
|||
|
||||
async function authenticateWithApeKey(
|
||||
key: string,
|
||||
configuration: SharedTypes.Configuration,
|
||||
configuration: Configuration,
|
||||
options: RequestAuthenticationOptions
|
||||
): Promise<MonkeyTypes.DecodedToken> {
|
||||
if (!configuration.apeKeys.acceptKeys) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Response, NextFunction, RequestHandler } from "express";
|
||||
import type { Response, NextFunction, RequestHandler } from "express";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Configuration } from "@monkeytype/shared-types";
|
||||
|
||||
export type ValidationOptions<T> = {
|
||||
criteria: (data: T) => boolean;
|
||||
|
@ -11,7 +12,7 @@ export type ValidationOptions<T> = {
|
|||
* the criteria.
|
||||
*/
|
||||
export function validate(
|
||||
options: ValidationOptions<SharedTypes.Configuration>
|
||||
options: ValidationOptions<Configuration>
|
||||
): RequestHandler {
|
||||
const {
|
||||
criteria,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { getCachedConfiguration } from "../init/configuration";
|
||||
import { Response, NextFunction } from "express";
|
||||
import type { Response, NextFunction } from "express";
|
||||
|
||||
async function contextMiddleware(
|
||||
req: MonkeyTypes.Request,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||
import Logger from "../utils/logger";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { incrementBadAuth } from "./rate-limit";
|
||||
import { NextFunction, Response } from "express";
|
||||
import type { NextFunction, Response } from "express";
|
||||
import { MonkeyResponse, handleMonkeyResponse } from "../utils/monkey-response";
|
||||
import {
|
||||
recordClientErrorByVersion,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import _ from "lodash";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Response, NextFunction, RequestHandler } from "express";
|
||||
import type { Response, NextFunction, RequestHandler } from "express";
|
||||
import { getUser } from "../dal/user";
|
||||
import { isAdmin } from "../dal/admin-uids";
|
||||
import { ValidationOptions } from "./configuration";
|
||||
import type { ValidationOptions } from "./configuration";
|
||||
|
||||
/**
|
||||
* Check if the user is an admin before handling request.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import _ from "lodash";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Response, NextFunction } from "express";
|
||||
import type { Response, NextFunction } from "express";
|
||||
import { RateLimiterMemory } from "rate-limiter-flexible";
|
||||
import rateLimit, { Options } from "express-rate-limit";
|
||||
import rateLimit, { type Options } from "express-rate-limit";
|
||||
import { isDevEnvironment } from "../utils/misc";
|
||||
|
||||
const REQUEST_MULTIPLIER = isDevEnvironment() ? 100 : 1;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from "lodash";
|
||||
import { Response, NextFunction, RequestHandler } from "express";
|
||||
import type { Response, NextFunction, RequestHandler } from "express";
|
||||
import { handleMonkeyResponse, MonkeyResponse } from "../utils/monkey-response";
|
||||
import { isDevEnvironment } from "../utils/misc";
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import _ from "lodash";
|
||||
import joi from "joi";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Response, NextFunction, RequestHandler } from "express";
|
||||
import type { Response, NextFunction, RequestHandler } from "express";
|
||||
|
||||
type ValidationSchema = {
|
||||
body?: object;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { LbEntryWithRank } from "../utils/daily-leaderboards";
|
||||
import { LeaderboardEntry } from "@monkeytype/shared-types";
|
||||
import { type LbEntryWithRank } from "../utils/daily-leaderboards";
|
||||
import { MonkeyQueue } from "./monkey-queue";
|
||||
|
||||
const QUEUE_NAME = "george-tasks";
|
||||
|
@ -61,7 +62,7 @@ class GeorgeQueue extends MonkeyQueue<GeorgeTask> {
|
|||
}
|
||||
|
||||
async announceLeaderboardUpdate(
|
||||
newRecords: SharedTypes.LeaderboardEntry[],
|
||||
newRecords: LeaderboardEntry[],
|
||||
leaderboardId: string
|
||||
): Promise<void> {
|
||||
const taskName = "announceLeaderboardUpdate";
|
||||
|
|
|
@ -2,6 +2,7 @@ import LRUCache from "lru-cache";
|
|||
import Logger from "../utils/logger";
|
||||
import { MonkeyQueue } from "./monkey-queue";
|
||||
import { getCurrentDayTimestamp, getCurrentWeekTimestamp } from "../utils/misc";
|
||||
import { ValidModeRule } from "@monkeytype/shared-types";
|
||||
|
||||
const QUEUE_NAME = "later";
|
||||
|
||||
|
@ -17,7 +18,7 @@ export type LaterTask<T extends LaterTaskType> = {
|
|||
export type LaterTaskContexts = {
|
||||
"daily-leaderboard-results": {
|
||||
yesterdayTimestamp: number;
|
||||
modeRule: SharedTypes.ValidModeRule;
|
||||
modeRule: ValidModeRule;
|
||||
};
|
||||
"weekly-xp-leaderboard-results": {
|
||||
lastWeekTimestamp: number;
|
||||
|
@ -82,7 +83,7 @@ class LaterQueue extends MonkeyQueue<LaterTask<LaterTaskType>> {
|
|||
async scheduleForTomorrow(
|
||||
taskName: LaterTaskType,
|
||||
taskId: string,
|
||||
modeRule: SharedTypes.ValidModeRule
|
||||
modeRule: ValidModeRule
|
||||
): Promise<void> {
|
||||
const currentDayTimestamp = getCurrentDayTimestamp();
|
||||
const jobId = `${taskName}:${currentDayTimestamp}:${taskId}`;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import IORedis from "ioredis";
|
||||
import {
|
||||
BulkJobOptions,
|
||||
ConnectionOptions,
|
||||
JobsOptions,
|
||||
type BulkJobOptions,
|
||||
type ConnectionOptions,
|
||||
type JobsOptions,
|
||||
Queue,
|
||||
QueueOptions,
|
||||
type QueueOptions,
|
||||
QueueScheduler,
|
||||
} from "bullmq";
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import workers from "./workers";
|
|||
import Logger from "./utils/logger";
|
||||
import * as EmailClient from "./init/email-client";
|
||||
import { init as initFirebaseAdmin } from "./init/firebase-admin";
|
||||
|
||||
import { createIndicies as leaderboardDbSetup } from "./dal/leaderboards";
|
||||
import { createIndicies as blocklistDbSetup } from "./dal/blocklist";
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Configuration } from "@monkeytype/shared-types";
|
||||
import * as RedisClient from "../init/redis";
|
||||
import LaterQueue from "../queues/later-queue";
|
||||
import { getCurrentWeekTimestamp } from "../utils/misc";
|
||||
|
@ -59,7 +60,7 @@ export class WeeklyXpLeaderboard {
|
|||
}
|
||||
|
||||
public async addResult(
|
||||
weeklyXpLeaderboardConfig: SharedTypes.Configuration["leaderboards"]["weeklyXp"],
|
||||
weeklyXpLeaderboardConfig: Configuration["leaderboards"]["weeklyXp"],
|
||||
opts: AddResultOpts
|
||||
): Promise<number> {
|
||||
const { entry, xpGained, timeTypedSeconds } = opts;
|
||||
|
@ -121,7 +122,7 @@ export class WeeklyXpLeaderboard {
|
|||
public async getResults(
|
||||
minRank: number,
|
||||
maxRank: number,
|
||||
weeklyXpLeaderboardConfig: SharedTypes.Configuration["leaderboards"]["weeklyXp"]
|
||||
weeklyXpLeaderboardConfig: Configuration["leaderboards"]["weeklyXp"]
|
||||
): Promise<WeeklyXpLeaderboardEntry[]> {
|
||||
const connection = RedisClient.getConnection();
|
||||
if (!connection || !weeklyXpLeaderboardConfig.enabled) {
|
||||
|
@ -166,7 +167,7 @@ export class WeeklyXpLeaderboard {
|
|||
|
||||
public async getRank(
|
||||
uid: string,
|
||||
weeklyXpLeaderboardConfig: SharedTypes.Configuration["leaderboards"]["weeklyXp"]
|
||||
weeklyXpLeaderboardConfig: Configuration["leaderboards"]["weeklyXp"]
|
||||
): Promise<WeeklyXpLeaderboardEntry | null> {
|
||||
const connection = RedisClient.getConnection();
|
||||
if (!connection || !weeklyXpLeaderboardConfig.enabled) {
|
||||
|
@ -202,7 +203,7 @@ export class WeeklyXpLeaderboard {
|
|||
}
|
||||
|
||||
export function get(
|
||||
weeklyXpLeaderboardConfig: SharedTypes.Configuration["leaderboards"]["weeklyXp"],
|
||||
weeklyXpLeaderboardConfig: Configuration["leaderboards"]["weeklyXp"],
|
||||
customTimestamp?: number
|
||||
): WeeklyXpLeaderboard | null {
|
||||
const { enabled } = weeklyXpLeaderboardConfig;
|
||||
|
|
29
backend/src/types/types.d.ts
vendored
29
backend/src/types/types.d.ts
vendored
|
@ -10,7 +10,7 @@ declare namespace MonkeyTypes {
|
|||
};
|
||||
|
||||
type Context = {
|
||||
configuration: SharedTypes.Configuration;
|
||||
configuration: import("@monkeytype/shared-types").Configuration;
|
||||
decodedToken: DecodedToken;
|
||||
};
|
||||
|
||||
|
@ -19,7 +19,7 @@ declare namespace MonkeyTypes {
|
|||
} & ExpressRequest;
|
||||
|
||||
type DBUser = Omit<
|
||||
SharedTypes.User,
|
||||
import("@monkeytype/shared-types").User,
|
||||
| "resultFilterPresets"
|
||||
| "tags"
|
||||
| "customThemes"
|
||||
|
@ -28,33 +28,40 @@ declare namespace MonkeyTypes {
|
|||
| "testActivity"
|
||||
> & {
|
||||
_id: ObjectId;
|
||||
resultFilterPresets?: WithObjectId<SharedTypes.ResultFilters>[];
|
||||
resultFilterPresets?: WithObjectId<
|
||||
import("@monkeytype/shared-types").ResultFilters
|
||||
>[];
|
||||
tags?: DBUserTag[];
|
||||
lbPersonalBests?: LbPersonalBests;
|
||||
customThemes?: DBCustomTheme[];
|
||||
autoBanTimestamps?: number[];
|
||||
inbox?: SharedTypes.MonkeyMail[];
|
||||
inbox?: import("@monkeytype/shared-types").MonkeyMail[];
|
||||
ips?: string[];
|
||||
canReport?: boolean;
|
||||
lastNameChange?: number;
|
||||
canManageApeKeys?: boolean;
|
||||
bananas?: number;
|
||||
testActivity?: SharedTypes.CountByYearAndDay;
|
||||
testActivity?: import("@monkeytype/shared-types").CountByYearAndDay;
|
||||
};
|
||||
|
||||
type DBCustomTheme = WithObjectId<SharedTypes.CustomTheme>;
|
||||
type DBCustomTheme = WithObjectId<
|
||||
import("@monkeytype/shared-types").CustomTheme
|
||||
>;
|
||||
|
||||
type DBUserTag = WithObjectId<SharedTypes.UserTag>;
|
||||
type DBUserTag = WithObjectId<import("@monkeytype/shared-types").UserTag>;
|
||||
|
||||
type LbPersonalBests = {
|
||||
time: Record<number, Record<string, SharedTypes.PersonalBest>>;
|
||||
time: Record<
|
||||
number,
|
||||
Record<string, import("@monkeytype/shared-types/user").PersonalBest>
|
||||
>;
|
||||
};
|
||||
|
||||
type WithObjectId<T extends { _id: string }> = Omit<T, "_id"> & {
|
||||
_id: ObjectId;
|
||||
};
|
||||
|
||||
type ApeKeyDB = SharedTypes.ApeKey & {
|
||||
type ApeKeyDB = import("@monkeytype/shared-types").ApeKey & {
|
||||
_id: ObjectId;
|
||||
uid: string;
|
||||
hash: string;
|
||||
|
@ -103,7 +110,9 @@ declare namespace MonkeyTypes {
|
|||
};
|
||||
|
||||
type DBResult = MonkeyTypes.WithObjectId<
|
||||
SharedTypes.DBResult<SharedTypes.Config.Mode>
|
||||
import("@monkeytype/shared-types").DBResult<
|
||||
import("@monkeytype/shared-types/config").Mode
|
||||
>
|
||||
>;
|
||||
|
||||
type BlocklistEntry = {
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import FirebaseAdmin from "./../init/firebase-admin";
|
||||
import { UserRecord } from "firebase-admin/lib/auth/user-record";
|
||||
import { DecodedIdToken } from "firebase-admin/lib/auth/token-verifier";
|
||||
import LRUCache from "lru-cache";
|
||||
import {
|
||||
recordTokenCacheAccess,
|
||||
setTokenCacheLength,
|
||||
setTokenCacheSize,
|
||||
} from "./prometheus";
|
||||
import { type DecodedIdToken, UserRecord } from "firebase-admin/auth";
|
||||
|
||||
const tokenCache = new LRUCache<string, DecodedIdToken>({
|
||||
max: 20000,
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from "lodash";
|
|||
import * as RedisClient from "../init/redis";
|
||||
import LaterQueue from "../queues/later-queue";
|
||||
import { getCurrentDayTimestamp, matchesAPattern, kogascore } from "./misc";
|
||||
import { Configuration, ValidModeRule } from "@monkeytype/shared-types";
|
||||
|
||||
type DailyLeaderboardEntry = {
|
||||
uid: string;
|
||||
|
@ -37,9 +38,9 @@ export class DailyLeaderboard {
|
|||
private leaderboardScoresKeyName: string;
|
||||
private leaderboardModeKey: string;
|
||||
private customTime: number;
|
||||
private modeRule: SharedTypes.ValidModeRule;
|
||||
private modeRule: ValidModeRule;
|
||||
|
||||
constructor(modeRule: SharedTypes.ValidModeRule, customTime = -1) {
|
||||
constructor(modeRule: ValidModeRule, customTime = -1) {
|
||||
const { language, mode, mode2 } = modeRule;
|
||||
|
||||
this.leaderboardModeKey = `${language}:${mode}:${mode2}`;
|
||||
|
@ -68,7 +69,7 @@ export class DailyLeaderboard {
|
|||
|
||||
public async addResult(
|
||||
entry: DailyLeaderboardEntry,
|
||||
dailyLeaderboardsConfig: SharedTypes.Configuration["dailyLeaderboards"]
|
||||
dailyLeaderboardsConfig: Configuration["dailyLeaderboards"]
|
||||
): Promise<number> {
|
||||
const connection = RedisClient.getConnection();
|
||||
if (!connection || !dailyLeaderboardsConfig.enabled) {
|
||||
|
@ -124,7 +125,7 @@ export class DailyLeaderboard {
|
|||
public async getResults(
|
||||
minRank: number,
|
||||
maxRank: number,
|
||||
dailyLeaderboardsConfig: SharedTypes.Configuration["dailyLeaderboards"],
|
||||
dailyLeaderboardsConfig: Configuration["dailyLeaderboards"],
|
||||
premiumFeaturesEnabled: boolean
|
||||
): Promise<LbEntryWithRank[]> {
|
||||
const connection = RedisClient.getConnection();
|
||||
|
@ -167,7 +168,7 @@ export class DailyLeaderboard {
|
|||
|
||||
public async getRank(
|
||||
uid: string,
|
||||
dailyLeaderboardsConfig: SharedTypes.Configuration["dailyLeaderboards"]
|
||||
dailyLeaderboardsConfig: Configuration["dailyLeaderboards"]
|
||||
): Promise<GetRankResponse | null> {
|
||||
const connection = RedisClient.getConnection();
|
||||
if (!connection || !dailyLeaderboardsConfig.enabled) {
|
||||
|
@ -210,7 +211,7 @@ export class DailyLeaderboard {
|
|||
|
||||
export async function purgeUserFromDailyLeaderboards(
|
||||
uid: string,
|
||||
configuration: SharedTypes.Configuration["dailyLeaderboards"]
|
||||
configuration: Configuration["dailyLeaderboards"]
|
||||
): Promise<void> {
|
||||
const connection = RedisClient.getConnection();
|
||||
if (!connection || !configuration.enabled) {
|
||||
|
@ -222,8 +223,8 @@ export async function purgeUserFromDailyLeaderboards(
|
|||
}
|
||||
|
||||
function isValidModeRule(
|
||||
modeRule: SharedTypes.ValidModeRule,
|
||||
modeRules: SharedTypes.ValidModeRule[]
|
||||
modeRule: ValidModeRule,
|
||||
modeRules: ValidModeRule[]
|
||||
): boolean {
|
||||
const { language, mode, mode2 } = modeRule;
|
||||
|
||||
|
@ -239,7 +240,7 @@ export function getDailyLeaderboard(
|
|||
language: string,
|
||||
mode: string,
|
||||
mode2: string,
|
||||
dailyLeaderboardsConfig: SharedTypes.Configuration["dailyLeaderboards"],
|
||||
dailyLeaderboardsConfig: Configuration["dailyLeaderboards"],
|
||||
customTimestamp = -1
|
||||
): DailyLeaderboard | null {
|
||||
const { validModeRules, enabled } = dailyLeaderboardsConfig;
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import * as db from "../init/db";
|
||||
import chalk from "chalk";
|
||||
import { format, createLogger, transports, Logger } from "winston";
|
||||
import {
|
||||
format,
|
||||
createLogger,
|
||||
transports,
|
||||
type Logger as LoggerType,
|
||||
} from "winston";
|
||||
import { resolve } from "path";
|
||||
import { ObjectId } from "mongodb";
|
||||
|
||||
|
@ -107,10 +112,10 @@ const logToDb = async (
|
|||
};
|
||||
|
||||
const Logger = {
|
||||
error: (message: string): Logger => logger.error(message),
|
||||
warning: (message: string): Logger => logger.warning(message),
|
||||
info: (message: string): Logger => logger.info(message),
|
||||
success: (message: string): Logger => logger.log("success", message),
|
||||
error: (message: string): LoggerType => logger.error(message),
|
||||
warning: (message: string): LoggerType => logger.warning(message),
|
||||
info: (message: string): LoggerType => logger.info(message),
|
||||
success: (message: string): LoggerType => logger.log("success", message),
|
||||
logToDb,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { MonkeyMail } from "@monkeytype/shared-types";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
type MonkeyMailOptions = Partial<Omit<SharedTypes.MonkeyMail, "id" | "read">>;
|
||||
type MonkeyMailOptions = Partial<Omit<MonkeyMail, "id" | "read">>;
|
||||
|
||||
export function buildMonkeyMail(
|
||||
options: MonkeyMailOptions
|
||||
): SharedTypes.MonkeyMail {
|
||||
export function buildMonkeyMail(options: MonkeyMailOptions): MonkeyMail {
|
||||
return {
|
||||
id: v4(),
|
||||
subject: options.subject ?? "",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Response } from "express";
|
||||
import { type Response } from "express";
|
||||
import { isCustomCode } from "../constants/monkey-status-codes";
|
||||
|
||||
//TODO FIX ANYS
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import _ from "lodash";
|
||||
import FunboxList from "../constants/funbox-list";
|
||||
import { PersonalBest, PersonalBests } from "@monkeytype/shared-types/user";
|
||||
import { DBResult } from "@monkeytype/shared-types";
|
||||
import { Mode, Mode2 } from "@monkeytype/shared-types/config";
|
||||
|
||||
type CheckAndUpdatePbResult = {
|
||||
isPb: boolean;
|
||||
personalBests: SharedTypes.PersonalBests;
|
||||
personalBests: PersonalBests;
|
||||
lbPersonalBests?: MonkeyTypes.LbPersonalBests;
|
||||
};
|
||||
|
||||
type Result = Omit<
|
||||
SharedTypes.DBResult<SharedTypes.Config.Mode>,
|
||||
"_id" | "name"
|
||||
>;
|
||||
type Result = Omit<DBResult<Mode>, "_id" | "name">;
|
||||
|
||||
export function canFunboxGetPb(result: Result): boolean {
|
||||
const funbox = result.funbox;
|
||||
|
@ -30,20 +30,20 @@ export function canFunboxGetPb(result: Result): boolean {
|
|||
}
|
||||
|
||||
export function checkAndUpdatePb(
|
||||
userPersonalBests: SharedTypes.PersonalBests,
|
||||
userPersonalBests: PersonalBests,
|
||||
lbPersonalBests: MonkeyTypes.LbPersonalBests | undefined,
|
||||
result: Result
|
||||
): CheckAndUpdatePbResult {
|
||||
const mode = result.mode;
|
||||
const mode2 = result.mode2 as SharedTypes.Config.Mode2<"time">;
|
||||
const mode2 = result.mode2 as Mode2<"time">;
|
||||
|
||||
const userPb = userPersonalBests ?? {};
|
||||
userPb[mode] ??= {};
|
||||
userPb[mode][mode2] ??= [];
|
||||
|
||||
const personalBestMatch = (
|
||||
userPb[mode][mode2] as SharedTypes.PersonalBest[]
|
||||
).find((pb) => matchesPersonalBest(result, pb));
|
||||
const personalBestMatch = (userPb[mode][mode2] as PersonalBest[]).find((pb) =>
|
||||
matchesPersonalBest(result, pb)
|
||||
);
|
||||
|
||||
let isPb = true;
|
||||
|
||||
|
@ -67,7 +67,7 @@ export function checkAndUpdatePb(
|
|||
|
||||
function matchesPersonalBest(
|
||||
result: Result,
|
||||
personalBest: SharedTypes.PersonalBest
|
||||
personalBest: PersonalBest
|
||||
): boolean {
|
||||
if (
|
||||
result.difficulty === undefined ||
|
||||
|
@ -98,7 +98,7 @@ function matchesPersonalBest(
|
|||
}
|
||||
|
||||
function updatePersonalBest(
|
||||
personalBest: SharedTypes.PersonalBest,
|
||||
personalBest: PersonalBest,
|
||||
result: Result
|
||||
): boolean {
|
||||
if (personalBest.wpm >= result.wpm) {
|
||||
|
@ -133,7 +133,7 @@ function updatePersonalBest(
|
|||
return true;
|
||||
}
|
||||
|
||||
function buildPersonalBest(result: Result): SharedTypes.PersonalBest {
|
||||
function buildPersonalBest(result: Result): PersonalBest {
|
||||
if (
|
||||
result.difficulty === undefined ||
|
||||
result.language === undefined ||
|
||||
|
@ -162,7 +162,7 @@ function buildPersonalBest(result: Result): SharedTypes.PersonalBest {
|
|||
}
|
||||
|
||||
function updateLeaderboardPersonalBests(
|
||||
userPersonalBests: SharedTypes.PersonalBests,
|
||||
userPersonalBests: PersonalBests,
|
||||
lbPersonalBests: MonkeyTypes.LbPersonalBests,
|
||||
result: Result
|
||||
): void {
|
||||
|
@ -171,7 +171,7 @@ function updateLeaderboardPersonalBests(
|
|||
}
|
||||
|
||||
const mode = result.mode;
|
||||
const mode2 = result.mode2 as SharedTypes.Config.Mode2<"time">;
|
||||
const mode2 = result.mode2 as Mode2<"time">;
|
||||
|
||||
lbPersonalBests[mode] = lbPersonalBests[mode] ?? {};
|
||||
const lbMode2 = lbPersonalBests[mode][mode2] as MonkeyTypes.LbPersonalBests;
|
||||
|
@ -181,7 +181,7 @@ function updateLeaderboardPersonalBests(
|
|||
|
||||
const bestForEveryLanguage = {};
|
||||
|
||||
userPersonalBests[mode][mode2].forEach((pb: SharedTypes.PersonalBest) => {
|
||||
userPersonalBests[mode][mode2].forEach((pb: PersonalBest) => {
|
||||
const language = pb.language;
|
||||
if (
|
||||
bestForEveryLanguage[language] === undefined ||
|
||||
|
@ -191,20 +191,17 @@ function updateLeaderboardPersonalBests(
|
|||
}
|
||||
});
|
||||
|
||||
_.each(
|
||||
bestForEveryLanguage,
|
||||
(pb: SharedTypes.PersonalBest, language: string) => {
|
||||
const languageDoesNotExist =
|
||||
lbPersonalBests[mode][mode2][language] === undefined;
|
||||
_.each(bestForEveryLanguage, (pb: PersonalBest, language: string) => {
|
||||
const languageDoesNotExist =
|
||||
lbPersonalBests[mode][mode2][language] === undefined;
|
||||
|
||||
if (
|
||||
languageDoesNotExist ||
|
||||
lbPersonalBests[mode][mode2][language].wpm < pb.wpm
|
||||
) {
|
||||
lbPersonalBests[mode][mode2][language] = pb;
|
||||
}
|
||||
if (
|
||||
languageDoesNotExist ||
|
||||
lbPersonalBests[mode][mode2][language].wpm < pb.wpm
|
||||
) {
|
||||
lbPersonalBests[mode][mode2][language] = pb;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function shouldUpdateLeaderboardPersonalBests(result: Result): boolean {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Result } from "@monkeytype/shared-types";
|
||||
import { Mode } from "@monkeytype/shared-types/config";
|
||||
import "dotenv/config";
|
||||
import { Counter, Histogram, Gauge } from "prom-client";
|
||||
|
||||
|
@ -88,9 +90,7 @@ export function setLeaderboard(
|
|||
leaderboardUpdate.set({ language, mode, mode2, step: "index" }, times[3]);
|
||||
}
|
||||
|
||||
export function incrementResult(
|
||||
res: SharedTypes.Result<SharedTypes.Config.Mode>
|
||||
): void {
|
||||
export function incrementResult(res: Result<Mode>): void {
|
||||
const {
|
||||
mode,
|
||||
mode2,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { CompletedEvent, DBResult } from "@monkeytype/shared-types";
|
||||
import { Mode } from "@monkeytype/shared-types/config";
|
||||
import { ObjectId } from "mongodb";
|
||||
|
||||
type Result = MonkeyTypes.WithObjectId<
|
||||
SharedTypes.DBResult<SharedTypes.Config.Mode>
|
||||
>;
|
||||
type Result = MonkeyTypes.WithObjectId<DBResult<Mode>>;
|
||||
|
||||
export function buildDbResult(
|
||||
completedEvent: SharedTypes.CompletedEvent,
|
||||
completedEvent: CompletedEvent,
|
||||
userName: string,
|
||||
isPb: boolean
|
||||
): Result {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { replaceHomoglyphs } from "../constants/homoglyphs";
|
|||
import { profanities } from "../constants/profanities";
|
||||
import { intersect, sanitizeString } from "./misc";
|
||||
import { default as FunboxList } from "../constants/funbox-list";
|
||||
import { CompletedEvent } from "@monkeytype/shared-types";
|
||||
|
||||
export function inRange(value: number, min: number, max: number): boolean {
|
||||
return value >= min && value <= max;
|
||||
|
@ -48,7 +49,7 @@ export function isTagPresetNameValid(name: string): boolean {
|
|||
return VALID_NAME_PATTERN.test(name);
|
||||
}
|
||||
|
||||
export function isTestTooShort(result: SharedTypes.CompletedEvent): boolean {
|
||||
export function isTestTooShort(result: CompletedEvent): boolean {
|
||||
const { mode, mode2, customText, testDuration, bailedOut } = result;
|
||||
|
||||
if (mode === "time") {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import _ from "lodash";
|
||||
import IORedis from "ioredis";
|
||||
import { Worker, Job, ConnectionOptions } from "bullmq";
|
||||
import { Worker, Job, type ConnectionOptions } from "bullmq";
|
||||
import Logger from "../utils/logger";
|
||||
import EmailQueue, {
|
||||
EmailTaskContexts,
|
||||
EmailType,
|
||||
type EmailTaskContexts,
|
||||
type EmailType,
|
||||
} from "../queues/email-queue";
|
||||
import { sendEmail } from "../init/email-client";
|
||||
import { recordTimeToCompleteJob } from "../utils/prometheus";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import _ from "lodash";
|
||||
import IORedis from "ioredis";
|
||||
import { Worker, Job, ConnectionOptions } from "bullmq";
|
||||
import { Worker, Job, type ConnectionOptions } from "bullmq";
|
||||
import Logger from "../utils/logger";
|
||||
import { addToInboxBulk } from "../dal/user";
|
||||
import GeorgeQueue from "../queues/george-queue";
|
||||
|
@ -9,12 +9,13 @@ import { DailyLeaderboard } from "../utils/daily-leaderboards";
|
|||
import { getCachedConfiguration } from "../init/configuration";
|
||||
import { formatSeconds, getOrdinalNumberString, mapRange } from "../utils/misc";
|
||||
import LaterQueue, {
|
||||
LaterTask,
|
||||
LaterTaskContexts,
|
||||
LaterTaskType,
|
||||
type LaterTask,
|
||||
type LaterTaskContexts,
|
||||
type LaterTaskType,
|
||||
} from "../queues/later-queue";
|
||||
import { WeeklyXpLeaderboard } from "../services/weekly-xp-leaderboard";
|
||||
import { recordTimeToCompleteJob } from "../utils/prometheus";
|
||||
import { WeeklyXpLeaderboard } from "../services/weekly-xp-leaderboard";
|
||||
import { MonkeyMail } from "@monkeytype/shared-types";
|
||||
|
||||
async function handleDailyLeaderboardResults(
|
||||
ctx: LaterTaskContexts["daily-leaderboard-results"]
|
||||
|
@ -44,7 +45,7 @@ async function handleDailyLeaderboardResults(
|
|||
if (inboxConfig.enabled && xpRewardBrackets.length > 0) {
|
||||
const mailEntries: {
|
||||
uid: string;
|
||||
mail: SharedTypes.MonkeyMail[];
|
||||
mail: MonkeyMail[];
|
||||
}[] = [];
|
||||
|
||||
allResults.forEach((entry) => {
|
||||
|
@ -133,7 +134,7 @@ async function handleWeeklyXpLeaderboardResults(
|
|||
|
||||
const mailEntries: {
|
||||
uid: string;
|
||||
mail: SharedTypes.MonkeyMail[];
|
||||
mail: MonkeyMail[];
|
||||
}[] = [];
|
||||
|
||||
allResults.forEach((entry) => {
|
||||
|
|
|
@ -1,36 +1,18 @@
|
|||
{
|
||||
"extends": "@monkeytype/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"sourceMap": false,
|
||||
"allowJs": false,
|
||||
"checkJs": false,
|
||||
"outDir": "build",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": false,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"paths": {
|
||||
"@shared/*": ["../shared-types/*"]
|
||||
}
|
||||
"target": "ES6",
|
||||
"noImplicitAny": false,
|
||||
"strictFunctionTypes": false,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"strictPropertyInitialization": false
|
||||
},
|
||||
"ts-node": {
|
||||
"files": true
|
||||
},
|
||||
"files": [
|
||||
"./src/types/types.d.ts",
|
||||
"../shared-types/types.d.ts",
|
||||
"../shared-types/config.d.ts"
|
||||
],
|
||||
"include": ["./src/**/*", "../shared-types/**/*.d.ts"],
|
||||
"files": ["./src/types/types.d.ts"],
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build",
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
call npm ci
|
||||
cd .\frontend
|
||||
call npm ci
|
||||
cd ..\backend
|
||||
call npm ci
|
||||
cd ..\
|
||||
PAUSE
|
|
@ -1,4 +0,0 @@
|
|||
npm ci &
|
||||
cd ./frontend && npm ci &
|
||||
cd ./backend && npm ci &
|
||||
wait
|
8
docker/BUILD.md
Normal file
8
docker/BUILD.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Build locally
|
||||
|
||||
From root directoy:
|
||||
|
||||
```
|
||||
docker build --progress=plain --no-cache -t monkeytype/monkeytype-backend:latest . -f ./docker/backend/Dockerfile
|
||||
docker build --progress=plain --no-cache -t monkeytype/monkeytype-frontend:latest . -f ./docker/frontend/Dockerfile
|
||||
```
|
|
@ -13,4 +13,4 @@
|
|||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +1,37 @@
|
|||
FROM node:18.19.1-alpine3.19 as builder
|
||||
|
||||
FROM node:18.20.4-alpine3.19
|
||||
|
||||
##install wget, used by the applyConfig script
|
||||
RUN apk add wget
|
||||
WORKDIR /app
|
||||
|
||||
#copy
|
||||
COPY .eslintignore .eslintignore
|
||||
COPY .eslintrc.json .eslintrc.json
|
||||
COPY package.json package.json
|
||||
COPY package-lock.json package-lock.json
|
||||
COPY shared-types shared-types
|
||||
COPY turbo.json turbo.json
|
||||
COPY packages packages
|
||||
COPY backend backend
|
||||
|
||||
#build
|
||||
RUN npm ci
|
||||
RUN cd backend && npm ci
|
||||
RUN cd backend && npm run build
|
||||
RUN npm run build
|
||||
|
||||
# target
|
||||
FROM node:18.19.1-alpine3.19
|
||||
RUN apk add wget
|
||||
WORKDIR /
|
||||
COPY backend/redis-scripts /redis-scripts
|
||||
|
||||
WORKDIR /app
|
||||
COPY backend/package.json package.json
|
||||
COPY backend/package-lock.json package-lock.json
|
||||
COPY docker/backend/entry-point.sh entry-point.sh
|
||||
COPY docker/backend/applyConfig.sh applyConfig.sh
|
||||
COPY --from=builder /app/backend/build .
|
||||
|
||||
#install deps (no dev-dependencies)
|
||||
RUN npm ci --omit=dev
|
||||
## remove dev dependencies
|
||||
RUN npm install --install-strategy=nested --omit=dev --workspace=backend --ignore-scripts
|
||||
|
||||
## to build directory
|
||||
WORKDIR /app/backend/build
|
||||
|
||||
## logs
|
||||
RUN mkdir logs
|
||||
|
||||
COPY docker/backend/entry-point.sh entry-point.sh
|
||||
COPY docker/backend/applyConfig.sh applyConfig.sh
|
||||
|
||||
#run in env mode (no anticheat)
|
||||
ENV MODE=dev
|
||||
|
||||
|
||||
EXPOSE 5005
|
||||
USER node
|
||||
|
||||
CMD [ "/bin/sh", "./entry-point.sh" ]
|
||||
CMD [ "/bin/sh", "./entry-point.sh" ]
|
||||
|
|
|
@ -39,11 +39,11 @@ services:
|
|||
#uncomment to enable the account system, check the SELF_HOSTING.md file
|
||||
#- type: bind
|
||||
# source: ./serviceAccountKey.json
|
||||
# target: /src/credentials/serviceAccountKey.json
|
||||
# target: /app/backend/src/credentials/serviceAccountKey.json
|
||||
# read_only: true
|
||||
- type: bind
|
||||
source: ./backend-configuration.json
|
||||
target: /app/backend-configuration.json
|
||||
target: /app/backend/build/backend-configuration.json
|
||||
read_only: true
|
||||
depends_on:
|
||||
monkeytype-redis:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:18.19.1-alpine3.19 as builder
|
||||
FROM node:18.20.4-alpine3.19 AS builder
|
||||
WORKDIR /app
|
||||
|
||||
#ENV
|
||||
|
@ -6,19 +6,18 @@ ENV BACKEND_URL=###MONKEYTYPE_BACKENDURL###
|
|||
ENV RECAPTCHA_SITE_KEY=###RECAPTCHA_SITE_KEY###
|
||||
|
||||
#COPY
|
||||
COPY .eslintrc.json .eslintrc.json
|
||||
COPY package.json package.json
|
||||
COPY package-lock.json package-lock.json
|
||||
COPY shared-types shared-types
|
||||
COPY turbo.json turbo.json
|
||||
COPY packages packages
|
||||
COPY frontend frontend
|
||||
|
||||
COPY docker/frontend/firebase-config-live.ts frontend/src/ts/constants/firebase-config.ts
|
||||
COPY docker/frontend/firebase-config-live.ts frontend/src/ts/constants/firebase-config-live.ts
|
||||
|
||||
#BUILD
|
||||
RUN npm ci
|
||||
RUN cd frontend && npm ci
|
||||
RUN cd frontend && npx vite build
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# COPY to target
|
||||
FROM nginx:mainline-alpine
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
*.d.ts
|
||||
node_modules
|
24
frontend/.eslintrc.cjs
Normal file
24
frontend/.eslintrc.cjs
Normal file
|
@ -0,0 +1,24 @@
|
|||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@monkeytype/eslint-config"],
|
||||
globals: {
|
||||
$: "readonly",
|
||||
jQuery: "readonly",
|
||||
html2canvas: "readonly",
|
||||
ClipboardItem: "readonly",
|
||||
grecaptcha: "readonly",
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
ignorePatterns: [
|
||||
"node_modules/",
|
||||
"dist/",
|
||||
"static/js/",
|
||||
"__tests__/",
|
||||
"jest.config.ts",
|
||||
],
|
||||
};
|
|
@ -2,7 +2,7 @@ name: monkeytype
|
|||
services:
|
||||
frontend:
|
||||
container_name: monkeytype-frontend
|
||||
image: node:18.19.1
|
||||
image: node:18.20.4
|
||||
# restart: on-failure
|
||||
environment:
|
||||
- SERVER_OPEN=false
|
||||
|
|
20636
frontend/package-lock.json
generated
20636
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "monkeytype-frontend",
|
||||
"name": "@monkeytype/frontend",
|
||||
"license": "GPL-3.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
@ -8,6 +8,7 @@
|
|||
"validate-json": "npx gulp validate-json-schema",
|
||||
"audit": "vite-bundle-visualizer",
|
||||
"dep-graph": "madge -c -i \"dep-graph.png\" ./src/ts",
|
||||
"ts-check": "tsc --noEmit",
|
||||
"build": "npm run madge && vite build",
|
||||
"madge": " madge --circular --extensions ts ./src",
|
||||
"live": "npm run build && vite preview --port 3000",
|
||||
|
@ -22,26 +23,31 @@
|
|||
"docker": "docker compose -f docker/compose.dev.yml up"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.19.1",
|
||||
"npm": "10.2.4"
|
||||
"node": "18.20.4",
|
||||
"npm": "10.7.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "5.15.4",
|
||||
"@monkeytype/shared-types": "*",
|
||||
"@monkeytype/eslint-config": "*",
|
||||
"@monkeytype/typescript-config": "*",
|
||||
"@types/canvas-confetti": "1.4.3",
|
||||
"@types/chartjs-plugin-trendline": "1.0.1",
|
||||
"@types/damerau-levenshtein": "1.0.0",
|
||||
"@types/howler": "2.2.7",
|
||||
"@types/jquery": "3.5.14",
|
||||
"@types/node": "18.19.1",
|
||||
"@types/node": "20.14.11",
|
||||
"@types/object-hash": "2.2.1",
|
||||
"@types/subset-font": "1.4.3",
|
||||
"@types/throttle-debounce": "2.1.0",
|
||||
"@vitest/coverage-v8": "1.6.0",
|
||||
"ajv": "8.12.0",
|
||||
"autoprefixer": "10.4.14",
|
||||
"dotenv": "16.4.5",
|
||||
"eslint": "8.57.0",
|
||||
"firebase-tools": "13.13.3",
|
||||
"fontawesome-subset": "4.4.0",
|
||||
"gulp": "4.0.2",
|
||||
|
|
|
@ -44,7 +44,11 @@ export async function generatePreviewFonts(
|
|||
}
|
||||
}
|
||||
|
||||
async function generateSubset(source, target, name): Promise<void> {
|
||||
async function generateSubset(
|
||||
source: string,
|
||||
target: string,
|
||||
name: string
|
||||
): Promise<void> {
|
||||
const font = fs.readFileSync(source);
|
||||
const subset = await subsetFont(font, name, {
|
||||
targetFormat: "woff2",
|
||||
|
@ -52,6 +56,6 @@ async function generateSubset(source, target, name): Promise<void> {
|
|||
fs.writeFileSync(target, subset);
|
||||
}
|
||||
//detect if we run this as a main
|
||||
if (import.meta.url.endsWith(process.argv[1])) {
|
||||
await generatePreviewFonts(true);
|
||||
if (import.meta.url.endsWith(process.argv[1] as string)) {
|
||||
void generatePreviewFonts(true);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as fs from "fs";
|
||||
import { createRequire } from "module";
|
||||
import * as path from "path";
|
||||
|
||||
type FontawesomeConfig = {
|
||||
|
@ -81,7 +82,7 @@ export function getFontawesomeConfig(debug = false): FontawesomeConfig {
|
|||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
const [icon] = match.split(" ");
|
||||
usedClassesSet.add(icon.substring(3));
|
||||
usedClassesSet.add((icon as string).substring(3));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -130,11 +131,11 @@ export function getFontawesomeConfig(debug = false): FontawesomeConfig {
|
|||
}
|
||||
|
||||
//detect if we run this as a main
|
||||
if (import.meta.url.endsWith(process.argv[1])) {
|
||||
if (import.meta.url.endsWith(process.argv[1] as string)) {
|
||||
getFontawesomeConfig(true);
|
||||
}
|
||||
|
||||
function toFileAndDir(dir, file): FileObject {
|
||||
function toFileAndDir(dir: string, file: string): FileObject {
|
||||
const name = path.join(dir, file);
|
||||
return { name, isDirectory: fs.statSync(name).isDirectory() };
|
||||
}
|
||||
|
@ -143,21 +144,28 @@ function findAllFiles(
|
|||
dir: string,
|
||||
filter: (filename: string) => boolean = (_it): boolean => true
|
||||
): string[] {
|
||||
return fs
|
||||
const files = fs
|
||||
.readdirSync(dir)
|
||||
.map((it) => toFileAndDir(dir, it))
|
||||
.filter((file) => file.isDirectory || filter(file.name))
|
||||
.reduce((files, file) => {
|
||||
return file.isDirectory
|
||||
? [...files, ...findAllFiles(file.name, filter)]
|
||||
: [...files, file.name];
|
||||
}, []);
|
||||
.filter((file) => file.isDirectory || filter(file.name));
|
||||
|
||||
const out: string[] = [];
|
||||
for (const file of files) {
|
||||
if (file.isDirectory) {
|
||||
out.push(...findAllFiles(file.name, filter));
|
||||
} else {
|
||||
out.push(file.name);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseIcons(iconSet: string): string[] {
|
||||
const file: string | null = fs
|
||||
.readFileSync(`node_modules/@fortawesome/fontawesome-free/js/${iconSet}.js`)
|
||||
.toString();
|
||||
const require = createRequire(import.meta.url);
|
||||
const path = require.resolve(
|
||||
`@fortawesome/fontawesome-free/js/${iconSet}.js`
|
||||
);
|
||||
const file: string | null = fs.readFileSync(path).toString();
|
||||
|
||||
return file
|
||||
?.match(/"(.*)": \[.*\],/g)
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"sourceMap": false,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"outDir": "build",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"ts-node": {
|
||||
"files": true
|
||||
},
|
||||
"files": ["../src/ts/types/types.d.ts"],
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
@import "node_modules/@fortawesome/fontawesome-free/scss/_variables.scss";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_mixins.scss";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_core.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_variables.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_mixins.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_core.scss";
|
||||
|
||||
/** fixed-with **/
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_fixed-width.scss"; //fa-fw
|
||||
@import "@fortawesome/fontawesome-free/scss/_fixed-width.scss"; //fa-fw
|
||||
/** animated **/
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_animated.scss"; //fa-spin
|
||||
@import "@fortawesome/fontawesome-free/scss/_animated.scss"; //fa-spin
|
||||
/** rotated **/
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_rotated-flipped.scss"; //fa-rotate-90
|
||||
@import "@fortawesome/fontawesome-free/scss/_rotated-flipped.scss"; //fa-rotate-90
|
||||
|
||||
@font-face {
|
||||
font-family: "Font Awesome";
|
||||
|
@ -58,4 +58,4 @@
|
|||
}
|
||||
}
|
||||
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_icons.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_icons.scss";
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
@use "sass:list";
|
||||
@use "sass:map";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_functions.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_functions.scss";
|
||||
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_variables.scss";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_mixins.scss";
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_core.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_variables.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_mixins.scss";
|
||||
@import "@fortawesome/fontawesome-free/scss/_core.scss";
|
||||
|
||||
/** fixed-with **/
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_fixed-width.scss"; //fa-fw
|
||||
@import "@fortawesome/fontawesome-free/scss/_fixed-width.scss"; //fa-fw
|
||||
/** animated **/
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_animated.scss"; //fa-spin
|
||||
@import "@fortawesome/fontawesome-free/scss/_animated.scss"; //fa-spin
|
||||
/** rotated **/
|
||||
@import "node_modules/@fortawesome/fontawesome-free/scss/_rotated-flipped.scss"; //fa-rotate-90
|
||||
@import "@fortawesome/fontawesome-free/scss/_rotated-flipped.scss"; //fa-rotate-90
|
||||
|
||||
@font-face {
|
||||
font-family: "Font Awesome";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getAuthenticatedUser, isAuthenticated } from "../../firebase";
|
||||
import { getIdToken } from "firebase/auth";
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import axios, { AxiosRequestConfig, AxiosResponse, isAxiosError } from "axios";
|
||||
import { envConfig } from "../../constants/env-config";
|
||||
import { createErrorMessage } from "../../utils/misc";
|
||||
|
||||
|
@ -100,7 +100,7 @@ function apeifyClientMethod(
|
|||
const typedError = error as Error;
|
||||
errorMessage = typedError.message;
|
||||
|
||||
if (axios.isAxiosError(typedError)) {
|
||||
if (isAxiosError(typedError)) {
|
||||
return {
|
||||
status: typedError.response?.status ?? 500,
|
||||
message: typedError.message,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Config } from "@monkeytype/shared-types/config";
|
||||
|
||||
const BASE_PATH = "/configs";
|
||||
|
||||
export default class Configs {
|
||||
|
@ -9,9 +11,7 @@ export default class Configs {
|
|||
return await this.httpClient.get(BASE_PATH);
|
||||
}
|
||||
|
||||
async save(
|
||||
config: SharedTypes.Config
|
||||
): Ape.EndpointResponse<Ape.Configs.PostConfig> {
|
||||
async save(config: Config): Ape.EndpointResponse<Ape.Configs.PostConfig> {
|
||||
return await this.httpClient.patch(BASE_PATH, { payload: { config } });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { Configuration } from "@monkeytype/shared-types";
|
||||
|
||||
export default class Root {
|
||||
constructor(private httpClient: Ape.HttpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
async get(): Ape.EndpointResponse<SharedTypes.Configuration> {
|
||||
async get(): Ape.EndpointResponse<Configuration> {
|
||||
return await this.httpClient.get("/configuration");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { PSA } from "@monkeytype/shared-types";
|
||||
|
||||
const BASE_PATH = "/psas";
|
||||
|
||||
export default class Psas {
|
||||
|
@ -5,7 +7,7 @@ export default class Psas {
|
|||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
async get(): Ape.EndpointResponse<SharedTypes.PSA[]> {
|
||||
async get(): Ape.EndpointResponse<PSA[]> {
|
||||
return await this.httpClient.get(BASE_PATH);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { PublicTypingStats, SpeedHistogram } from "@monkeytype/shared-types";
|
||||
|
||||
const BASE_PATH = "/public";
|
||||
|
||||
type SpeedStatsQuery = {
|
||||
|
@ -13,13 +15,13 @@ export default class Public {
|
|||
|
||||
async getSpeedHistogram(
|
||||
searchQuery: SpeedStatsQuery
|
||||
): Ape.EndpointResponse<SharedTypes.SpeedHistogram> {
|
||||
): Ape.EndpointResponse<SpeedHistogram> {
|
||||
return await this.httpClient.get(`${BASE_PATH}/speedHistogram`, {
|
||||
searchQuery,
|
||||
});
|
||||
}
|
||||
|
||||
async getTypingStats(): Ape.EndpointResponse<SharedTypes.PublicTypingStats> {
|
||||
async getTypingStats(): Ape.EndpointResponse<PublicTypingStats> {
|
||||
return await this.httpClient.get(`${BASE_PATH}/typingStats`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { DBResult, Result } from "@monkeytype/shared-types";
|
||||
import { Mode } from "@monkeytype/shared-types/config";
|
||||
|
||||
const BASE_PATH = "/results";
|
||||
|
||||
export default class Results {
|
||||
|
@ -5,14 +8,12 @@ export default class Results {
|
|||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
async get(
|
||||
offset?: number
|
||||
): Ape.EndpointResponse<SharedTypes.DBResult<SharedTypes.Config.Mode>[]> {
|
||||
async get(offset?: number): Ape.EndpointResponse<DBResult<Mode>[]> {
|
||||
return await this.httpClient.get(BASE_PATH, { searchQuery: { offset } });
|
||||
}
|
||||
|
||||
async save(
|
||||
result: SharedTypes.Result<SharedTypes.Config.Mode>
|
||||
result: Result<Mode>
|
||||
): Ape.EndpointResponse<Ape.Results.PostResult> {
|
||||
return await this.httpClient.post(BASE_PATH, {
|
||||
payload: { result },
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
import {
|
||||
CountByYearAndDay,
|
||||
CustomTheme,
|
||||
ResultFilters,
|
||||
UserProfile,
|
||||
UserProfileDetails,
|
||||
UserTag,
|
||||
} from "@monkeytype/shared-types";
|
||||
import { Mode, Mode2 } from "@monkeytype/shared-types/config";
|
||||
|
||||
const BASE_PATH = "/users";
|
||||
|
||||
export default class Users {
|
||||
|
@ -48,9 +58,9 @@ export default class Users {
|
|||
});
|
||||
}
|
||||
|
||||
async updateLeaderboardMemory<M extends SharedTypes.Config.Mode>(
|
||||
async updateLeaderboardMemory<M extends Mode>(
|
||||
mode: string,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
language: string,
|
||||
rank: number
|
||||
): Ape.EndpointResponse<null> {
|
||||
|
@ -89,7 +99,7 @@ export default class Users {
|
|||
}
|
||||
|
||||
async addResultFilterPreset(
|
||||
filter: SharedTypes.ResultFilters
|
||||
filter: ResultFilters
|
||||
): Ape.EndpointResponse<string> {
|
||||
return await this.httpClient.post(`${BASE_PATH}/resultFilterPresets`, {
|
||||
payload: filter,
|
||||
|
@ -103,7 +113,7 @@ export default class Users {
|
|||
);
|
||||
}
|
||||
|
||||
async createTag(tagName: string): Ape.EndpointResponse<SharedTypes.UserTag> {
|
||||
async createTag(tagName: string): Ape.EndpointResponse<UserTag> {
|
||||
return await this.httpClient.post(`${BASE_PATH}/tags`, {
|
||||
payload: { tagName },
|
||||
});
|
||||
|
@ -130,7 +140,7 @@ export default class Users {
|
|||
);
|
||||
}
|
||||
|
||||
async getCustomThemes(): Ape.EndpointResponse<SharedTypes.CustomTheme[]> {
|
||||
async getCustomThemes(): Ape.EndpointResponse<CustomTheme[]> {
|
||||
return await this.httpClient.get(`${BASE_PATH}/customThemes`);
|
||||
}
|
||||
|
||||
|
@ -161,7 +171,7 @@ export default class Users {
|
|||
|
||||
async addCustomTheme(
|
||||
newTheme: Partial<MonkeyTypes.CustomTheme>
|
||||
): Ape.EndpointResponse<SharedTypes.CustomTheme> {
|
||||
): Ape.EndpointResponse<CustomTheme> {
|
||||
const payload = { name: newTheme.name, colors: newTheme.colors };
|
||||
return await this.httpClient.post(`${BASE_PATH}/customThemes`, { payload });
|
||||
}
|
||||
|
@ -204,24 +214,20 @@ export default class Users {
|
|||
});
|
||||
}
|
||||
|
||||
async getProfileByUid(
|
||||
uid: string
|
||||
): Ape.EndpointResponse<SharedTypes.UserProfile> {
|
||||
async getProfileByUid(uid: string): Ape.EndpointResponse<UserProfile> {
|
||||
const encoded = encodeURIComponent(uid);
|
||||
return await this.httpClient.get(`${BASE_PATH}/${encoded}/profile?isUid`);
|
||||
}
|
||||
|
||||
async getProfileByName(
|
||||
name: string
|
||||
): Ape.EndpointResponse<SharedTypes.UserProfile> {
|
||||
async getProfileByName(name: string): Ape.EndpointResponse<UserProfile> {
|
||||
const encoded = encodeURIComponent(name);
|
||||
return await this.httpClient.get(`${BASE_PATH}/${encoded}/profile`);
|
||||
}
|
||||
|
||||
async updateProfile(
|
||||
profileUpdates: Partial<SharedTypes.UserProfileDetails>,
|
||||
profileUpdates: Partial<UserProfileDetails>,
|
||||
selectedBadgeId?: number
|
||||
): Ape.EndpointResponse<SharedTypes.UserProfileDetails> {
|
||||
): Ape.EndpointResponse<UserProfileDetails> {
|
||||
return await this.httpClient.patch(`${BASE_PATH}/profile`, {
|
||||
payload: {
|
||||
...profileUpdates,
|
||||
|
@ -281,7 +287,7 @@ export default class Users {
|
|||
return await this.httpClient.post(`${BASE_PATH}/revokeAllTokens`);
|
||||
}
|
||||
|
||||
async getTestActivity(): Ape.EndpointResponse<SharedTypes.CountByYearAndDay> {
|
||||
async getTestActivity(): Ape.EndpointResponse<CountByYearAndDay> {
|
||||
return await this.httpClient.get(`${BASE_PATH}/testActivity`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Configuration } from "@monkeytype/shared-types";
|
||||
import Ape from ".";
|
||||
|
||||
let config: SharedTypes.Configuration | undefined = undefined;
|
||||
let config: Configuration | undefined = undefined;
|
||||
|
||||
export function get(): SharedTypes.Configuration | undefined {
|
||||
export function get(): Configuration | undefined {
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
4
frontend/src/ts/ape/types/ape-keys.d.ts
vendored
4
frontend/src/ts/ape/types/ape-keys.d.ts
vendored
|
@ -1,11 +1,11 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// for some reason when using the dot notaion, the types are not being recognized as used
|
||||
declare namespace Ape.ApeKeys {
|
||||
type GetApeKeys = Record<string, SharedTypes.ApeKey>;
|
||||
type GetApeKeys = Record<string, import("@monkeytype/shared-types").ApeKey>;
|
||||
|
||||
type GenerateApeKey = {
|
||||
apeKey: string;
|
||||
apeKeyId: string;
|
||||
apeKeyDetails: SharedTypes.ApeKey;
|
||||
apeKeyDetails: import("@monkeytype/shared-types").ApeKey;
|
||||
};
|
||||
}
|
||||
|
|
2
frontend/src/ts/ape/types/configs.d.ts
vendored
2
frontend/src/ts/ape/types/configs.d.ts
vendored
|
@ -4,7 +4,7 @@ declare namespace Ape.Configs {
|
|||
type GetConfig = {
|
||||
_id: string;
|
||||
uid: string;
|
||||
config: Partial<SharedTypes.Config>;
|
||||
config: Partial<Config>;
|
||||
};
|
||||
type PostConfig = null;
|
||||
}
|
||||
|
|
6
frontend/src/ts/ape/types/leaderboards.d.ts
vendored
6
frontend/src/ts/ape/types/leaderboards.d.ts
vendored
|
@ -3,7 +3,7 @@
|
|||
declare namespace Ape.Leaderboards {
|
||||
type Query = {
|
||||
language: string;
|
||||
mode: SharedTypes.Config.Mode;
|
||||
mode: Config.Mode;
|
||||
mode2: string;
|
||||
isDaily?: boolean;
|
||||
daysBefore?: number;
|
||||
|
@ -14,12 +14,12 @@ declare namespace Ape.Leaderboards {
|
|||
limit?: number;
|
||||
} & Query;
|
||||
|
||||
type GetLeaderboard = SharedTypes.LeaderboardEntry[];
|
||||
type GetLeaderboard = LeaderboardEntry[];
|
||||
|
||||
type GetRank = {
|
||||
minWpm: number;
|
||||
count: number;
|
||||
rank: number | null;
|
||||
entry: SharedTypes.LeaderboardEntry | null;
|
||||
entry: import("@monkeytype/shared-types").LeaderboardEntry | null;
|
||||
};
|
||||
}
|
||||
|
|
2
frontend/src/ts/ape/types/presets.d.ts
vendored
2
frontend/src/ts/ape/types/presets.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// for some reason when using the dot notaion, the types are not being recognized as used
|
||||
declare namespace Ape.Presets {
|
||||
type GetPresets = SharedTypes.DBConfigPreset[];
|
||||
type GetPresets = DBConfigPreset[];
|
||||
type PostPreset = {
|
||||
presetId: string;
|
||||
};
|
||||
|
|
2
frontend/src/ts/ape/types/results.d.ts
vendored
2
frontend/src/ts/ape/types/results.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// for some reason when using the dot notaion, the types are not being recognized as used
|
||||
declare namespace Ape.Results {
|
||||
type PostResult = SharedTypes.PostResultResponse;
|
||||
type PostResult = import("@monkeytype/shared-types").PostResultResponse;
|
||||
type PatchResult = {
|
||||
tagPbs: string[];
|
||||
};
|
||||
|
|
4
frontend/src/ts/ape/types/users.d.ts
vendored
4
frontend/src/ts/ape/types/users.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
// for some reason when using the dot notaion, the types are not being recognized as used
|
||||
declare namespace Ape.Users {
|
||||
type GetUser = SharedTypes.User & {
|
||||
type GetUser = import("@monkeytype/shared-types").User & {
|
||||
inboxUnreadSize: number;
|
||||
isPremium: boolean;
|
||||
};
|
||||
|
@ -13,6 +13,6 @@ declare namespace Ape.Users {
|
|||
discordAvatar: string;
|
||||
};
|
||||
type GetInbox = {
|
||||
inbox: SharedTypes.MonkeyMail[] | undefined;
|
||||
inbox: MonkeyMail[] | undefined;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ import {
|
|||
canSetFunboxWithConfig,
|
||||
} from "./test/funbox/funbox-validation";
|
||||
import { reloadAfter } from "./utils/misc";
|
||||
import * as ConfigTypes from "@monkeytype/shared-types/config";
|
||||
|
||||
export let localStorageConfig: SharedTypes.Config;
|
||||
export let localStorageConfig: ConfigTypes.Config;
|
||||
|
||||
let loadDone: (value?: unknown) => void;
|
||||
|
||||
|
@ -25,7 +26,7 @@ const config = {
|
|||
...DefaultConfig,
|
||||
};
|
||||
|
||||
let configToSend = {} as SharedTypes.Config;
|
||||
let configToSend = {} as ConfigTypes.Config;
|
||||
const saveToDatabase = debounce(1000, () => {
|
||||
if (Object.keys(configToSend).length > 0) {
|
||||
AccountButton.loading(true);
|
||||
|
@ -33,11 +34,11 @@ const saveToDatabase = debounce(1000, () => {
|
|||
AccountButton.loading(false);
|
||||
});
|
||||
}
|
||||
configToSend = {} as SharedTypes.Config;
|
||||
configToSend = {} as ConfigTypes.Config;
|
||||
});
|
||||
|
||||
function saveToLocalStorage(
|
||||
key: keyof SharedTypes.Config,
|
||||
key: keyof ConfigTypes.Config,
|
||||
nosave = false,
|
||||
noDbCheck = false
|
||||
): void {
|
||||
|
@ -103,10 +104,7 @@ export function setPunctuation(punc: boolean, nosave?: boolean): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function setMode(
|
||||
mode: SharedTypes.Config.Mode,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
export function setMode(mode: ConfigTypes.Mode, nosave?: boolean): boolean {
|
||||
if (
|
||||
!isConfigValueValid("mode", mode, [
|
||||
["time", "words", "quote", "zen", "custom"],
|
||||
|
@ -139,7 +137,7 @@ export function setMode(
|
|||
}
|
||||
|
||||
export function setPlaySoundOnError(
|
||||
val: SharedTypes.Config.PlaySoundOnError,
|
||||
val: ConfigTypes.PlaySoundOnError,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -158,7 +156,7 @@ export function setPlaySoundOnError(
|
|||
}
|
||||
|
||||
export function setPlaySoundOnClick(
|
||||
val: SharedTypes.Config.PlaySoundOnClick,
|
||||
val: ConfigTypes.PlaySoundOnClick,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -194,7 +192,7 @@ export function setPlaySoundOnClick(
|
|||
}
|
||||
|
||||
export function setSoundVolume(
|
||||
val: SharedTypes.Config.SoundVolume,
|
||||
val: ConfigTypes.SoundVolume,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("sound volume", val, [["0.1", "0.5", "1.0"]])) {
|
||||
|
@ -210,7 +208,7 @@ export function setSoundVolume(
|
|||
|
||||
//difficulty
|
||||
export function setDifficulty(
|
||||
diff: SharedTypes.Config.Difficulty,
|
||||
diff: ConfigTypes.Difficulty,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -299,7 +297,7 @@ export function setBlindMode(blind: boolean, nosave?: boolean): boolean {
|
|||
}
|
||||
|
||||
function setAccountChart(
|
||||
array: SharedTypes.Config.AccountChart,
|
||||
array: ConfigTypes.AccountChart,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -380,7 +378,7 @@ export function setAccountChartAvg100(
|
|||
}
|
||||
|
||||
export function setStopOnError(
|
||||
soe: SharedTypes.Config.StopOnError,
|
||||
soe: ConfigTypes.StopOnError,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("stop on error", soe, [["off", "word", "letter"]])) {
|
||||
|
@ -417,7 +415,7 @@ export function setAlwaysShowDecimalPlaces(
|
|||
}
|
||||
|
||||
export function setTypingSpeedUnit(
|
||||
val: SharedTypes.Config.TypingSpeedUnit,
|
||||
val: ConfigTypes.TypingSpeedUnit,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -454,7 +452,7 @@ export function setShowOutOfFocusWarning(
|
|||
|
||||
//pace caret
|
||||
export function setPaceCaret(
|
||||
val: SharedTypes.Config.PaceCaret,
|
||||
val: ConfigTypes.PaceCaret,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -509,7 +507,7 @@ export function setRepeatedPace(pace: boolean, nosave?: boolean): boolean {
|
|||
|
||||
//min wpm
|
||||
export function setMinWpm(
|
||||
minwpm: SharedTypes.Config.MinimumWordsPerMinute,
|
||||
minwpm: ConfigTypes.MinimumWordsPerMinute,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("min speed", minwpm, [["off", "custom"]])) {
|
||||
|
@ -537,7 +535,7 @@ export function setMinWpmCustomSpeed(val: number, nosave?: boolean): boolean {
|
|||
|
||||
//min acc
|
||||
export function setMinAcc(
|
||||
min: SharedTypes.Config.MinimumAccuracy,
|
||||
min: ConfigTypes.MinimumAccuracy,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("min acc", min, [["off", "custom"]])) return false;
|
||||
|
@ -562,7 +560,7 @@ export function setMinAccCustom(val: number, nosave?: boolean): boolean {
|
|||
|
||||
//min burst
|
||||
export function setMinBurst(
|
||||
min: SharedTypes.Config.MinimumBurst,
|
||||
min: ConfigTypes.MinimumBurst,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("min burst", min, [["off", "fixed", "flex"]])) {
|
||||
|
@ -606,7 +604,7 @@ export function setAlwaysShowWordsHistory(
|
|||
|
||||
//single list command line
|
||||
export function setSingleListCommandLine(
|
||||
option: SharedTypes.Config.SingleListCommandLine,
|
||||
option: ConfigTypes.SingleListCommandLine,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -658,7 +656,7 @@ export function setQuickEnd(qe: boolean, nosave?: boolean): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function setAds(val: SharedTypes.Config.Ads, nosave?: boolean): boolean {
|
||||
export function setAds(val: ConfigTypes.Ads, nosave?: boolean): boolean {
|
||||
if (!isConfigValueValid("ads", val, [["off", "result", "on", "sellout"]])) {
|
||||
return false;
|
||||
}
|
||||
|
@ -675,7 +673,7 @@ export function setAds(val: SharedTypes.Config.Ads, nosave?: boolean): boolean {
|
|||
}
|
||||
|
||||
export function setRepeatQuotes(
|
||||
val: SharedTypes.Config.RepeatQuotes,
|
||||
val: ConfigTypes.RepeatQuotes,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("repeat quotes", val, [["off", "typing"]])) {
|
||||
|
@ -724,7 +722,7 @@ export function setStrictSpace(val: boolean, nosave?: boolean): boolean {
|
|||
|
||||
//opposite shift space
|
||||
export function setOppositeShiftMode(
|
||||
val: SharedTypes.Config.OppositeShiftMode,
|
||||
val: ConfigTypes.OppositeShiftMode,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -741,7 +739,7 @@ export function setOppositeShiftMode(
|
|||
}
|
||||
|
||||
export function setCaretStyle(
|
||||
caretStyle: SharedTypes.Config.CaretStyle,
|
||||
caretStyle: ConfigTypes.CaretStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -783,7 +781,7 @@ export function setCaretStyle(
|
|||
}
|
||||
|
||||
export function setPaceCaretStyle(
|
||||
caretStyle: SharedTypes.Config.CaretStyle,
|
||||
caretStyle: ConfigTypes.CaretStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -823,7 +821,7 @@ export function setPaceCaretStyle(
|
|||
}
|
||||
|
||||
export function setShowAverage(
|
||||
value: SharedTypes.Config.ShowAverage,
|
||||
value: ConfigTypes.ShowAverage,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -842,7 +840,7 @@ export function setShowAverage(
|
|||
}
|
||||
|
||||
export function setHighlightMode(
|
||||
mode: SharedTypes.Config.HighlightMode,
|
||||
mode: ConfigTypes.HighlightMode,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -872,7 +870,7 @@ export function setHighlightMode(
|
|||
}
|
||||
|
||||
export function setTapeMode(
|
||||
mode: SharedTypes.Config.TapeMode,
|
||||
mode: ConfigTypes.TapeMode,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("tape mode", mode, [["off", "letter", "word"]])) {
|
||||
|
@ -901,7 +899,7 @@ export function setHideExtraLetters(val: boolean, nosave?: boolean): boolean {
|
|||
}
|
||||
|
||||
export function setTimerStyle(
|
||||
style: SharedTypes.Config.TimerStyle,
|
||||
style: ConfigTypes.TimerStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -918,7 +916,7 @@ export function setTimerStyle(
|
|||
}
|
||||
|
||||
export function setLiveSpeedStyle(
|
||||
style: SharedTypes.Config.LiveSpeedAccBurstStyle,
|
||||
style: ConfigTypes.LiveSpeedAccBurstStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -935,7 +933,7 @@ export function setLiveSpeedStyle(
|
|||
}
|
||||
|
||||
export function setLiveAccStyle(
|
||||
style: SharedTypes.Config.LiveSpeedAccBurstStyle,
|
||||
style: ConfigTypes.LiveSpeedAccBurstStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("live acc style", style, [["off", "text", "mini"]])) {
|
||||
|
@ -950,7 +948,7 @@ export function setLiveAccStyle(
|
|||
}
|
||||
|
||||
export function setLiveBurstStyle(
|
||||
style: SharedTypes.Config.LiveSpeedAccBurstStyle,
|
||||
style: ConfigTypes.LiveSpeedAccBurstStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -967,7 +965,7 @@ export function setLiveBurstStyle(
|
|||
}
|
||||
|
||||
export function setTimerColor(
|
||||
color: SharedTypes.Config.TimerColor,
|
||||
color: ConfigTypes.TimerColor,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -986,7 +984,7 @@ export function setTimerColor(
|
|||
return true;
|
||||
}
|
||||
export function setTimerOpacity(
|
||||
opacity: SharedTypes.Config.TimerOpacity,
|
||||
opacity: ConfigTypes.TimerOpacity,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1040,7 +1038,7 @@ export function setTimeConfig(time: number, nosave?: boolean): boolean {
|
|||
|
||||
//quote length
|
||||
export function setQuoteLength(
|
||||
len: SharedTypes.Config.QuoteLength[] | SharedTypes.Config.QuoteLength,
|
||||
len: ConfigTypes.QuoteLength[] | ConfigTypes.QuoteLength,
|
||||
nosave?: boolean,
|
||||
multipleMode?: boolean
|
||||
): boolean {
|
||||
|
@ -1062,7 +1060,7 @@ export function setQuoteLength(
|
|||
if (len === null || isNaN(len) || len < -3 || len > 3) {
|
||||
len = 1;
|
||||
}
|
||||
len = parseInt(len.toString()) as SharedTypes.Config.QuoteLength;
|
||||
len = parseInt(len.toString()) as ConfigTypes.QuoteLength;
|
||||
|
||||
if (len === -1) {
|
||||
config.quoteLength = [0, 1, 2, 3];
|
||||
|
@ -1105,7 +1103,7 @@ export function setWordCount(wordCount: number, nosave?: boolean): boolean {
|
|||
|
||||
//caret
|
||||
export function setSmoothCaret(
|
||||
mode: SharedTypes.Config["smoothCaret"],
|
||||
mode: ConfigTypes.Config["smoothCaret"],
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1223,7 +1221,7 @@ export function setFreedomMode(freedom: boolean, nosave?: boolean): boolean {
|
|||
}
|
||||
|
||||
export function setConfidenceMode(
|
||||
cm: SharedTypes.Config.ConfidenceMode,
|
||||
cm: ConfigTypes.ConfidenceMode,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("confidence mode", cm, [["off", "on", "max"]])) {
|
||||
|
@ -1244,7 +1242,7 @@ export function setConfidenceMode(
|
|||
}
|
||||
|
||||
export function setIndicateTypos(
|
||||
value: SharedTypes.Config.IndicateTypos,
|
||||
value: ConfigTypes.IndicateTypos,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1351,7 +1349,7 @@ function setThemes(
|
|||
}
|
||||
|
||||
export function setRandomTheme(
|
||||
val: SharedTypes.Config.RandomTheme,
|
||||
val: ConfigTypes.RandomTheme,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1461,7 +1459,7 @@ export function setMonkey(monkey: boolean, nosave?: boolean): boolean {
|
|||
}
|
||||
|
||||
export function setKeymapMode(
|
||||
mode: SharedTypes.Config.KeymapMode,
|
||||
mode: ConfigTypes.KeymapMode,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1482,7 +1480,7 @@ export function setKeymapMode(
|
|||
}
|
||||
|
||||
export function setKeymapLegendStyle(
|
||||
style: SharedTypes.Config.KeymapLegendStyle,
|
||||
style: ConfigTypes.KeymapLegendStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1524,7 +1522,7 @@ export function setKeymapLegendStyle(
|
|||
}
|
||||
|
||||
export function setKeymapStyle(
|
||||
style: SharedTypes.Config.KeymapStyle,
|
||||
style: ConfigTypes.KeymapStyle,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1562,7 +1560,7 @@ export function setKeymapLayout(layout: string, nosave?: boolean): boolean {
|
|||
}
|
||||
|
||||
export function setKeymapShowTopRow(
|
||||
show: SharedTypes.Config.KeymapShowTopRow,
|
||||
show: ConfigTypes.KeymapShowTopRow,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1709,7 +1707,7 @@ export async function setCustomLayoutfluid(
|
|||
const customLayoutfluid = trimmed.replace(
|
||||
/ /g,
|
||||
"#"
|
||||
) as SharedTypes.Config.CustomLayoutFluid;
|
||||
) as ConfigTypes.CustomLayoutFluid;
|
||||
|
||||
config.customLayoutfluid = customLayoutfluid;
|
||||
saveToLocalStorage("customLayoutfluid", nosave);
|
||||
|
@ -1719,7 +1717,7 @@ export async function setCustomLayoutfluid(
|
|||
}
|
||||
|
||||
export function setCustomBackgroundSize(
|
||||
value: SharedTypes.Config.CustomBackgroundSize,
|
||||
value: ConfigTypes.CustomBackgroundSize,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1738,7 +1736,7 @@ export function setCustomBackgroundSize(
|
|||
}
|
||||
|
||||
export function setCustomBackgroundFilter(
|
||||
array: SharedTypes.Config.CustomBackgroundFilter,
|
||||
array: ConfigTypes.CustomBackgroundFilter,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (!isConfigValueValid("custom background filter", array, ["numberArray"])) {
|
||||
|
@ -1753,7 +1751,7 @@ export function setCustomBackgroundFilter(
|
|||
}
|
||||
|
||||
export function setMonkeyPowerLevel(
|
||||
level: SharedTypes.Config.MonkeyPowerLevel,
|
||||
level: ConfigTypes.MonkeyPowerLevel,
|
||||
nosave?: boolean
|
||||
): boolean {
|
||||
if (
|
||||
|
@ -1786,7 +1784,7 @@ export function setBurstHeatmap(value: boolean, nosave?: boolean): boolean {
|
|||
}
|
||||
|
||||
export async function apply(
|
||||
configToApply: SharedTypes.Config | MonkeyTypes.ConfigChanges
|
||||
configToApply: ConfigTypes.Config | MonkeyTypes.ConfigChanges
|
||||
): Promise<void> {
|
||||
if (configToApply === undefined) return;
|
||||
|
||||
|
@ -1794,8 +1792,8 @@ export async function apply(
|
|||
|
||||
configToApply = replaceLegacyValues(configToApply);
|
||||
|
||||
const configObj = configToApply as SharedTypes.Config;
|
||||
(Object.keys(DefaultConfig) as (keyof SharedTypes.Config)[]).forEach(
|
||||
const configObj = configToApply as ConfigTypes.Config;
|
||||
(Object.keys(DefaultConfig) as (keyof ConfigTypes.Config)[]).forEach(
|
||||
(configKey) => {
|
||||
if (configObj[configKey] === undefined) {
|
||||
const newValue = DefaultConfig[configKey];
|
||||
|
@ -1911,7 +1909,7 @@ export async function reset(): Promise<void> {
|
|||
export async function loadFromLocalStorage(): Promise<void> {
|
||||
console.log("loading localStorage config");
|
||||
const newConfigString = window.localStorage.getItem("config");
|
||||
let newConfig: SharedTypes.Config;
|
||||
let newConfig: ConfigTypes.Config;
|
||||
if (
|
||||
newConfigString !== undefined &&
|
||||
newConfigString !== null &&
|
||||
|
@ -1920,7 +1918,7 @@ export async function loadFromLocalStorage(): Promise<void> {
|
|||
try {
|
||||
newConfig = JSON.parse(newConfigString);
|
||||
} catch (e) {
|
||||
newConfig = {} as SharedTypes.Config;
|
||||
newConfig = {} as ConfigTypes.Config;
|
||||
}
|
||||
await apply(newConfig);
|
||||
localStorageConfig = newConfig;
|
||||
|
@ -1933,9 +1931,9 @@ export async function loadFromLocalStorage(): Promise<void> {
|
|||
}
|
||||
|
||||
function replaceLegacyValues(
|
||||
configToApply: SharedTypes.Config | MonkeyTypes.ConfigChanges
|
||||
): SharedTypes.Config | MonkeyTypes.ConfigChanges {
|
||||
const configObj = configToApply as SharedTypes.Config;
|
||||
configToApply: ConfigTypes.Config | MonkeyTypes.ConfigChanges
|
||||
): ConfigTypes.Config | MonkeyTypes.ConfigChanges {
|
||||
const configObj = configToApply as ConfigTypes.Config;
|
||||
|
||||
//@ts-expect-error
|
||||
if (configObj.quickTab === true) {
|
||||
|
@ -1972,7 +1970,7 @@ function replaceLegacyValues(
|
|||
|
||||
//@ts-expect-error
|
||||
if (configObj.showLiveWpm === true) {
|
||||
let val: SharedTypes.Config.LiveSpeedAccBurstStyle = "mini";
|
||||
let val: ConfigTypes.LiveSpeedAccBurstStyle = "mini";
|
||||
if (configObj.timerStyle !== "bar" && configObj.timerStyle !== "off") {
|
||||
val = configObj.timerStyle;
|
||||
}
|
||||
|
@ -1981,7 +1979,7 @@ function replaceLegacyValues(
|
|||
|
||||
//@ts-expect-error
|
||||
if (configObj.showLiveBurst === true) {
|
||||
let val: SharedTypes.Config.LiveSpeedAccBurstStyle = "mini";
|
||||
let val: ConfigTypes.LiveSpeedAccBurstStyle = "mini";
|
||||
if (configObj.timerStyle !== "bar" && configObj.timerStyle !== "off") {
|
||||
val = configObj.timerStyle;
|
||||
}
|
||||
|
@ -1990,7 +1988,7 @@ function replaceLegacyValues(
|
|||
|
||||
//@ts-expect-error
|
||||
if (configObj.showLiveAcc === true) {
|
||||
let val: SharedTypes.Config.LiveSpeedAccBurstStyle = "mini";
|
||||
let val: ConfigTypes.LiveSpeedAccBurstStyle = "mini";
|
||||
if (configObj.timerStyle !== "bar" && configObj.timerStyle !== "off") {
|
||||
val = configObj.timerStyle;
|
||||
}
|
||||
|
@ -2002,7 +2000,7 @@ function replaceLegacyValues(
|
|||
|
||||
export function getConfigChanges(): MonkeyTypes.PresetConfig {
|
||||
const configChanges = {} as MonkeyTypes.PresetConfig;
|
||||
(Object.keys(config) as (keyof SharedTypes.Config)[])
|
||||
(Object.keys(config) as (keyof ConfigTypes.Config)[])
|
||||
.filter((key) => {
|
||||
return config[key] !== DefaultConfig[key];
|
||||
})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Config } from "@monkeytype/shared-types/config";
|
||||
|
||||
export default {
|
||||
theme: "serika_dark",
|
||||
themeLight: "serika",
|
||||
|
@ -94,4 +96,4 @@ export default {
|
|||
showAverage: "off",
|
||||
tapeMode: "off",
|
||||
maxLineWidth: 0,
|
||||
} as SharedTypes.Config;
|
||||
} as Config;
|
||||
|
|
|
@ -9,6 +9,16 @@ import * as TestUI from "../test/test-ui";
|
|||
import * as ConfigEvent from "../observables/config-event";
|
||||
import * as TestState from "../test/test-state";
|
||||
import * as Loader from "../elements/loader";
|
||||
import {
|
||||
CustomTextLimitMode,
|
||||
CustomTextMode,
|
||||
Result,
|
||||
} from "@monkeytype/shared-types";
|
||||
import {
|
||||
Config as ConfigType,
|
||||
Difficulty,
|
||||
Mode,
|
||||
} from "@monkeytype/shared-types/config";
|
||||
|
||||
let challengeLoading = false;
|
||||
|
||||
|
@ -23,9 +33,7 @@ export function clearActive(): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function verify(
|
||||
result: SharedTypes.Result<SharedTypes.Config.Mode>
|
||||
): string | null {
|
||||
export function verify(result: Result<Mode>): string | null {
|
||||
try {
|
||||
if (TestState.activeChallenge) {
|
||||
const afk = (result.afkDuration / result.testDuration) * 100;
|
||||
|
@ -156,9 +164,7 @@ export function verify(
|
|||
} else if (requirementType === "config") {
|
||||
for (const configKey in requirementValue) {
|
||||
const configValue = requirementValue[configKey];
|
||||
if (
|
||||
Config[configKey as keyof SharedTypes.Config] !== configValue
|
||||
) {
|
||||
if (Config[configKey as keyof ConfigType] !== configValue) {
|
||||
requirementsMet = false;
|
||||
failReasons.push(`${configKey} not set to ${configValue}`);
|
||||
}
|
||||
|
@ -251,11 +257,9 @@ export async function setup(challengeName: string): Promise<boolean> {
|
|||
UpdateConfig.setDifficulty("normal", true);
|
||||
} else if (challenge.type === "customText") {
|
||||
CustomText.setText((challenge.parameters[0] as string).split(" "));
|
||||
CustomText.setMode(challenge.parameters[1] as SharedTypes.CustomTextMode);
|
||||
CustomText.setMode(challenge.parameters[1] as CustomTextMode);
|
||||
CustomText.setLimitValue(challenge.parameters[2] as number);
|
||||
CustomText.setLimitMode(
|
||||
challenge.parameters[3] as SharedTypes.CustomTextLimitMode
|
||||
);
|
||||
CustomText.setLimitMode(challenge.parameters[3] as CustomTextLimitMode);
|
||||
CustomText.setPipeDelimiter(challenge.parameters[4] as boolean);
|
||||
UpdateConfig.setMode("custom", true);
|
||||
UpdateConfig.setDifficulty("normal", true);
|
||||
|
@ -294,15 +298,9 @@ export async function setup(challengeName: string): Promise<boolean> {
|
|||
} else if (challenge.parameters[1] === "time") {
|
||||
UpdateConfig.setTimeConfig(challenge.parameters[2] as number, true);
|
||||
}
|
||||
UpdateConfig.setMode(
|
||||
challenge.parameters[1] as SharedTypes.Config.Mode,
|
||||
true
|
||||
);
|
||||
UpdateConfig.setMode(challenge.parameters[1] as Mode, true);
|
||||
if (challenge.parameters[3] !== undefined) {
|
||||
UpdateConfig.setDifficulty(
|
||||
challenge.parameters[3] as SharedTypes.Config.Difficulty,
|
||||
true
|
||||
);
|
||||
UpdateConfig.setDifficulty(challenge.parameters[3] as Difficulty, true);
|
||||
}
|
||||
} else if (challenge.type === "special") {
|
||||
if (challenge.name === "semimak") {
|
||||
|
|
|
@ -88,7 +88,7 @@ export async function change(
|
|||
await previousPage?.afterHide();
|
||||
await nextPage?.beforeShow({
|
||||
params: options.params,
|
||||
// @ts-expect-error
|
||||
//@ts-expect-error
|
||||
data: options.data,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { capsState } from "../test/caps-warning";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
|
||||
import type { Howl } from "howler";
|
||||
import { PlaySoundOnClick } from "@monkeytype/shared-types/config";
|
||||
|
||||
async function gethowler(): Promise<typeof import("howler")> {
|
||||
return await import("howler");
|
||||
|
@ -478,10 +479,7 @@ const codeToNote: Record<string, GetNoteFrequencyCallback> = {
|
|||
BracketRight: bindToNote(notes.G, 2),
|
||||
};
|
||||
|
||||
type DynamicClickSounds = Extract<
|
||||
SharedTypes.Config.PlaySoundOnClick,
|
||||
"8" | "9" | "10" | "11"
|
||||
>;
|
||||
type DynamicClickSounds = Extract<PlaySoundOnClick, "8" | "9" | "10" | "11">;
|
||||
type SupportedOscillatorTypes = Exclude<OscillatorType, "custom">;
|
||||
|
||||
const clickSoundIdsToOscillatorType: Record<
|
||||
|
@ -551,7 +549,7 @@ const defaultScaleData: ScaleData = {
|
|||
};
|
||||
|
||||
export const scaleConfigurations: Record<
|
||||
Extract<SharedTypes.Config.PlaySoundOnClick, "12" | "13">,
|
||||
Extract<PlaySoundOnClick, "12" | "13">,
|
||||
ScaleMeta
|
||||
> = {
|
||||
"12": {
|
||||
|
|
|
@ -14,6 +14,14 @@ import {
|
|||
ModifiableTestActivityCalendar,
|
||||
} from "./elements/test-activity-calendar";
|
||||
import * as Loader from "./elements/loader";
|
||||
import { PersonalBest, PersonalBests } from "@monkeytype/shared-types/user";
|
||||
import { Badge, DBResult, Result } from "@monkeytype/shared-types";
|
||||
import {
|
||||
Config,
|
||||
Difficulty,
|
||||
Mode,
|
||||
Mode2,
|
||||
} from "@monkeytype/shared-types/config";
|
||||
|
||||
let dbSnapshot: MonkeyTypes.Snapshot | undefined;
|
||||
|
||||
|
@ -112,7 +120,7 @@ export async function initSnapshot(): Promise<
|
|||
};
|
||||
|
||||
for (const mode of ["time", "words", "quote", "zen", "custom"]) {
|
||||
snap.personalBests[mode as keyof SharedTypes.PersonalBests] ??= {};
|
||||
snap.personalBests[mode as keyof PersonalBests] ??= {};
|
||||
}
|
||||
|
||||
snap.banned = userData.banned;
|
||||
|
@ -188,7 +196,7 @@ export async function initSnapshot(): Promise<
|
|||
// };
|
||||
|
||||
// for (const mode of ["time", "words", "quote", "zen", "custom"]) {
|
||||
// tag.personalBests[mode as keyof SharedTypes.PersonalBests] ??= {};
|
||||
// tag.personalBests[mode as keyof PersonalBests] ??= {};
|
||||
// }
|
||||
// });
|
||||
|
||||
|
@ -272,8 +280,7 @@ export async function getUserResults(offset?: number): Promise<boolean> {
|
|||
return false;
|
||||
}
|
||||
|
||||
const results =
|
||||
response.data as SharedTypes.DBResult<SharedTypes.Config.Mode>[];
|
||||
const results = response.data as DBResult<Mode>[];
|
||||
results?.sort((a, b) => b.timestamp - a.timestamp);
|
||||
results.forEach((result) => {
|
||||
if (result.bailedOut === undefined) result.bailedOut = false;
|
||||
|
@ -311,11 +318,10 @@ export async function getUserResults(offset?: number): Promise<boolean> {
|
|||
(it) => it.timestamp < oldestTimestamp
|
||||
);
|
||||
dbSnapshot.results.push(
|
||||
...(resultsWithoutDuplicates as unknown as SharedTypes.Result<SharedTypes.Config.Mode>[])
|
||||
...(resultsWithoutDuplicates as unknown as Result<Mode>[])
|
||||
);
|
||||
} else {
|
||||
dbSnapshot.results =
|
||||
results as unknown as SharedTypes.Result<SharedTypes.Config.Mode>[];
|
||||
dbSnapshot.results = results as unknown as Result<Mode>[];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -417,13 +423,13 @@ export async function deleteCustomTheme(themeId: string): Promise<boolean> {
|
|||
return true;
|
||||
}
|
||||
|
||||
export async function getUserAverage10<M extends SharedTypes.Config.Mode>(
|
||||
export async function getUserAverage10<M extends Mode>(
|
||||
mode: M,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
punctuation: boolean,
|
||||
numbers: boolean,
|
||||
language: string,
|
||||
difficulty: SharedTypes.Config.Difficulty,
|
||||
difficulty: Difficulty,
|
||||
lazyMode: boolean
|
||||
): Promise<[number, number]> {
|
||||
const snapshot = getSnapshot();
|
||||
|
@ -502,13 +508,13 @@ export async function getUserAverage10<M extends SharedTypes.Config.Mode>(
|
|||
return retval;
|
||||
}
|
||||
|
||||
export async function getUserDailyBest<M extends SharedTypes.Config.Mode>(
|
||||
export async function getUserDailyBest<M extends Mode>(
|
||||
mode: M,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
punctuation: boolean,
|
||||
numbers: boolean,
|
||||
language: string,
|
||||
difficulty: SharedTypes.Config.Difficulty,
|
||||
difficulty: Difficulty,
|
||||
lazyMode: boolean
|
||||
): Promise<number> {
|
||||
const snapshot = getSnapshot();
|
||||
|
@ -567,13 +573,13 @@ export async function getUserDailyBest<M extends SharedTypes.Config.Mode>(
|
|||
return retval;
|
||||
}
|
||||
|
||||
export async function getLocalPB<M extends SharedTypes.Config.Mode>(
|
||||
export async function getLocalPB<M extends Mode>(
|
||||
mode: M,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
punctuation: boolean,
|
||||
numbers: boolean,
|
||||
language: string,
|
||||
difficulty: SharedTypes.Config.Difficulty,
|
||||
difficulty: Difficulty,
|
||||
lazyMode: boolean,
|
||||
funbox: string
|
||||
): Promise<number> {
|
||||
|
@ -586,9 +592,7 @@ export async function getLocalPB<M extends SharedTypes.Config.Mode>(
|
|||
}
|
||||
if (dbSnapshot === null || dbSnapshot?.personalBests === null) return 0;
|
||||
|
||||
const bestsByMode = dbSnapshot?.personalBests[mode][
|
||||
mode2
|
||||
] as SharedTypes.PersonalBest[];
|
||||
const bestsByMode = dbSnapshot?.personalBests[mode][mode2] as PersonalBest[];
|
||||
|
||||
if (bestsByMode === undefined) return 0;
|
||||
|
||||
|
@ -604,13 +608,13 @@ export async function getLocalPB<M extends SharedTypes.Config.Mode>(
|
|||
);
|
||||
}
|
||||
|
||||
export async function saveLocalPB<M extends SharedTypes.Config.Mode>(
|
||||
export async function saveLocalPB<M extends Mode>(
|
||||
mode: M,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
punctuation: boolean,
|
||||
numbers: boolean,
|
||||
language: string,
|
||||
difficulty: SharedTypes.Config.Difficulty,
|
||||
difficulty: Difficulty,
|
||||
lazyMode: boolean,
|
||||
wpm: number,
|
||||
acc: number,
|
||||
|
@ -636,12 +640,10 @@ export async function saveLocalPB<M extends SharedTypes.Config.Mode>(
|
|||
};
|
||||
|
||||
dbSnapshot.personalBests[mode][mode2] ??=
|
||||
[] as unknown as SharedTypes.PersonalBests[M][SharedTypes.Config.Mode2<M>];
|
||||
[] as unknown as PersonalBests[M][Mode2<M>];
|
||||
|
||||
(
|
||||
dbSnapshot.personalBests[mode][
|
||||
mode2
|
||||
] as unknown as SharedTypes.PersonalBest[]
|
||||
dbSnapshot.personalBests[mode][mode2] as unknown as PersonalBest[]
|
||||
).forEach((pb) => {
|
||||
if (
|
||||
(pb.punctuation ?? false) === punctuation &&
|
||||
|
@ -661,22 +663,20 @@ export async function saveLocalPB<M extends SharedTypes.Config.Mode>(
|
|||
});
|
||||
if (!found) {
|
||||
//nothing found
|
||||
(
|
||||
dbSnapshot.personalBests[mode][
|
||||
mode2
|
||||
] as unknown as SharedTypes.PersonalBest[]
|
||||
).push({
|
||||
language,
|
||||
difficulty,
|
||||
lazyMode,
|
||||
punctuation,
|
||||
numbers,
|
||||
wpm,
|
||||
acc,
|
||||
raw,
|
||||
timestamp: Date.now(),
|
||||
consistency,
|
||||
});
|
||||
(dbSnapshot.personalBests[mode][mode2] as unknown as PersonalBest[]).push(
|
||||
{
|
||||
language,
|
||||
difficulty,
|
||||
lazyMode,
|
||||
punctuation,
|
||||
numbers,
|
||||
wpm,
|
||||
acc,
|
||||
raw,
|
||||
timestamp: Date.now(),
|
||||
consistency,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -685,14 +685,14 @@ export async function saveLocalPB<M extends SharedTypes.Config.Mode>(
|
|||
}
|
||||
}
|
||||
|
||||
export async function getLocalTagPB<M extends SharedTypes.Config.Mode>(
|
||||
export async function getLocalTagPB<M extends Mode>(
|
||||
tagId: string,
|
||||
mode: M,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
punctuation: boolean,
|
||||
numbers: boolean,
|
||||
language: string,
|
||||
difficulty: SharedTypes.Config.Difficulty,
|
||||
difficulty: Difficulty,
|
||||
lazyMode: boolean
|
||||
): Promise<number> {
|
||||
if (dbSnapshot === null) return 0;
|
||||
|
@ -718,10 +718,10 @@ export async function getLocalTagPB<M extends SharedTypes.Config.Mode>(
|
|||
};
|
||||
|
||||
filteredtag.personalBests[mode][mode2] ??=
|
||||
[] as unknown as SharedTypes.PersonalBests[M][SharedTypes.Config.Mode2<M>];
|
||||
[] as unknown as PersonalBests[M][Mode2<M>];
|
||||
|
||||
const personalBests = (filteredtag.personalBests[mode][mode2] ??
|
||||
[]) as SharedTypes.PersonalBest[];
|
||||
[]) as PersonalBest[];
|
||||
|
||||
ret =
|
||||
personalBests.find(
|
||||
|
@ -736,14 +736,14 @@ export async function getLocalTagPB<M extends SharedTypes.Config.Mode>(
|
|||
return ret;
|
||||
}
|
||||
|
||||
export async function saveLocalTagPB<M extends SharedTypes.Config.Mode>(
|
||||
export async function saveLocalTagPB<M extends Mode>(
|
||||
tagId: string,
|
||||
mode: M,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
punctuation: boolean,
|
||||
numbers: boolean,
|
||||
language: string,
|
||||
difficulty: SharedTypes.Config.Difficulty,
|
||||
difficulty: Difficulty,
|
||||
lazyMode: boolean,
|
||||
wpm: number,
|
||||
acc: number,
|
||||
|
@ -770,15 +770,13 @@ export async function saveLocalTagPB<M extends SharedTypes.Config.Mode>(
|
|||
};
|
||||
|
||||
filteredtag.personalBests[mode][mode2] ??=
|
||||
[] as unknown as SharedTypes.PersonalBests[M][SharedTypes.Config.Mode2<M>];
|
||||
[] as unknown as PersonalBests[M][Mode2<M>];
|
||||
|
||||
try {
|
||||
let found = false;
|
||||
|
||||
(
|
||||
filteredtag.personalBests[mode][
|
||||
mode2
|
||||
] as unknown as SharedTypes.PersonalBest[]
|
||||
filteredtag.personalBests[mode][mode2] as unknown as PersonalBest[]
|
||||
).forEach((pb) => {
|
||||
if (
|
||||
(pb.punctuation ?? false) === punctuation &&
|
||||
|
@ -799,9 +797,7 @@ export async function saveLocalTagPB<M extends SharedTypes.Config.Mode>(
|
|||
if (!found) {
|
||||
//nothing found
|
||||
(
|
||||
filteredtag.personalBests[mode][
|
||||
mode2
|
||||
] as unknown as SharedTypes.PersonalBest[]
|
||||
filteredtag.personalBests[mode][mode2] as unknown as PersonalBest[]
|
||||
).push({
|
||||
language,
|
||||
difficulty,
|
||||
|
@ -837,7 +833,7 @@ export async function saveLocalTagPB<M extends SharedTypes.Config.Mode>(
|
|||
timestamp: Date.now(),
|
||||
consistency: consistency,
|
||||
},
|
||||
] as unknown as SharedTypes.PersonalBests[M][SharedTypes.Config.Mode2<M>];
|
||||
] as unknown as PersonalBests[M][Mode2<M>];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -848,9 +844,9 @@ export async function saveLocalTagPB<M extends SharedTypes.Config.Mode>(
|
|||
return;
|
||||
}
|
||||
|
||||
export async function updateLbMemory<M extends SharedTypes.Config.Mode>(
|
||||
export async function updateLbMemory<M extends Mode>(
|
||||
mode: M,
|
||||
mode2: SharedTypes.Config.Mode2<M>,
|
||||
mode2: Mode2<M>,
|
||||
language: string,
|
||||
rank: number,
|
||||
api = false
|
||||
|
@ -890,7 +886,7 @@ export async function updateLbMemory<M extends SharedTypes.Config.Mode>(
|
|||
}
|
||||
}
|
||||
|
||||
export async function saveConfig(config: SharedTypes.Config): Promise<void> {
|
||||
export async function saveConfig(config: Config): Promise<void> {
|
||||
if (isAuthenticated()) {
|
||||
const response = await Ape.configs.save(config);
|
||||
if (response.status !== 200) {
|
||||
|
@ -899,9 +895,7 @@ export async function saveConfig(config: SharedTypes.Config): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
export function saveLocalResult(
|
||||
result: SharedTypes.Result<SharedTypes.Config.Mode>
|
||||
): void {
|
||||
export function saveLocalResult(result: Result<Mode>): void {
|
||||
const snapshot = getSnapshot();
|
||||
if (!snapshot) return;
|
||||
|
||||
|
@ -946,7 +940,7 @@ export function addXp(xp: number): void {
|
|||
setSnapshot(snapshot);
|
||||
}
|
||||
|
||||
export function addBadge(badge: SharedTypes.Badge): void {
|
||||
export function addBadge(badge: Badge): void {
|
||||
const snapshot = getSnapshot();
|
||||
if (!snapshot) return;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue