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 commit 52ffac47b1.

* 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 commit d715198829.

* fix missing imports, remove last .js

* remove test package

---------

Co-authored-by: Christian Fehmer <cfe@sexy-developer.com>
This commit is contained in:
Jack 2024-07-22 15:08:11 +02:00 committed by GitHub
parent c96185e90a
commit 3e88ac2f12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
164 changed files with 26988 additions and 35701 deletions

View file

@ -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
View file

@ -14,5 +14,8 @@ docs:
frontend:
- any: ["frontend/**/*"]
packages:
- any: ["packages/**/*"]
local dev:
- any: ["**/gulpfile.js", "**/tsconfig.json"]

View file

@ -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

View file

@ -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
View file

@ -126,3 +126,5 @@ copyAnticheatToDev.sh
# ignore generated fonts
frontend/src/webfonts-generated
frontend/static/webfonts-preview
.turbo

2
.nvmrc
View file

@ -1 +1 @@
18.19.1
18.20.4

View file

@ -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

View file

@ -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",

View file

@ -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",

View file

@ -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

View file

@ -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
View 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",
},
};

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"
}
}

View file

@ -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");
}

View file

@ -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,
},
}

View file

@ -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

View file

@ -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));

View file

@ -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";

View file

@ -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,

View file

@ -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>
},
},
},
};
},
};

View file

@ -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";

View file

@ -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");

View file

@ -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 });
}

View file

@ -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: {

View file

@ -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

View file

@ -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();
}

View file

@ -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(

View file

@ -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");

View file

@ -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,

View file

@ -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);

View file

@ -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";

View file

@ -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 = {

View file

@ -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";

View file

@ -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(

View file

@ -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";

View file

@ -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) {

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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.

View file

@ -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;

View file

@ -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";

View file

@ -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;

View file

@ -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";

View file

@ -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}`;

View file

@ -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";

View file

@ -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";

View file

@ -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;

View file

@ -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 = {

View file

@ -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,

View file

@ -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;

View file

@ -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,
};

View file

@ -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 ?? "",

View file

@ -1,4 +1,4 @@
import { Response } from "express";
import { type Response } from "express";
import { isCustomCode } from "../constants/monkey-status-codes";
//TODO FIX ANYS

View file

@ -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 {

View file

@ -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,

View file

@ -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 {

View file

@ -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") {

View file

@ -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";

View file

@ -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) => {

View file

@ -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",

View file

@ -1,7 +0,0 @@
call npm ci
cd .\frontend
call npm ci
cd ..\backend
call npm ci
cd ..\
PAUSE

View file

@ -1,4 +0,0 @@
npm ci &
cd ./frontend && npm ci &
cd ./backend && npm ci &
wait

8
docker/BUILD.md Normal file
View 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
```

View file

@ -13,4 +13,4 @@
"enabled": false
}
}
}
}

View file

@ -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" ]

View file

@ -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:

View file

@ -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

View file

@ -1,2 +0,0 @@
*.d.ts
node_modules

24
frontend/.eslintrc.cjs Normal file
View 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",
],
};

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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);
}

View file

@ -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)

View file

@ -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"]
}

View file

@ -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";

View file

@ -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";

View file

@ -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,

View file

@ -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 } });
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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`);
}
}

View file

@ -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 },

View file

@ -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`);
}
}

View file

@ -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;
}

View file

@ -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;
};
}

View file

@ -4,7 +4,7 @@ declare namespace Ape.Configs {
type GetConfig = {
_id: string;
uid: string;
config: Partial<SharedTypes.Config>;
config: Partial<Config>;
};
type PostConfig = null;
}

View file

@ -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;
};
}

View file

@ -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;
};

View file

@ -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[];
};

View file

@ -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;
};
}

View file

@ -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];
})

View file

@ -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;

View file

@ -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") {

View file

@ -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,
});
}

View file

@ -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": {

View file

@ -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