mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-20 07:16:17 +08:00
refactor: enable no-unsafe-assignment rule (@miodec) (#5874)
Co-authored-by: Nad Alaba <37968805+NadAlaba@users.noreply.github.com> Co-authored-by: Christian Fehmer <fehmer@users.noreply.github.com> Co-authored-by: Igor Bedesqui <igorbedesqui@gmail.com> Co-authored-by: amarnathsama <63007641+amarnathsama@users.noreply.github.com>
This commit is contained in:
parent
e19b3e3e8b
commit
955eeae2a7
|
@ -57,10 +57,15 @@ describe("Pb Utils", () => {
|
|||
mode2: "15",
|
||||
} as unknown as Result<Mode>;
|
||||
|
||||
const run = pb.checkAndUpdatePb(userPbs, undefined, result);
|
||||
const run = pb.checkAndUpdatePb(
|
||||
userPbs,
|
||||
{} as MonkeyTypes.LbPersonalBests,
|
||||
result
|
||||
);
|
||||
|
||||
expect(run.isPb).toBe(true);
|
||||
expect(run.personalBests?.["time"]?.["15"]?.[0]).not.toBe(undefined);
|
||||
expect(run.lbPersonalBests).not.toBe({});
|
||||
});
|
||||
it("should not override default pb when saving numbers test", () => {
|
||||
const userPbs: PersonalBests = {
|
||||
|
@ -111,4 +116,93 @@ describe("Pb Utils", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
describe("updateLeaderboardPersonalBests", () => {
|
||||
const userPbs: PersonalBests = {
|
||||
time: {
|
||||
"15": [
|
||||
{
|
||||
acc: 100,
|
||||
consistency: 100,
|
||||
difficulty: "normal",
|
||||
lazyMode: false,
|
||||
language: "english",
|
||||
numbers: false,
|
||||
punctuation: false,
|
||||
raw: 100,
|
||||
timestamp: 0,
|
||||
wpm: 100,
|
||||
},
|
||||
{
|
||||
acc: 100,
|
||||
consistency: 100,
|
||||
difficulty: "normal",
|
||||
lazyMode: false,
|
||||
language: "spanish",
|
||||
numbers: false,
|
||||
punctuation: false,
|
||||
raw: 100,
|
||||
timestamp: 0,
|
||||
wpm: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
words: {},
|
||||
custom: {},
|
||||
quote: {},
|
||||
zen: {},
|
||||
};
|
||||
it("should update leaderboard personal bests if they dont exist or the structure is incomplete", () => {
|
||||
const lbpbstartingvalues = [
|
||||
undefined,
|
||||
{},
|
||||
{ time: {} },
|
||||
{ time: { "15": {} } },
|
||||
{ time: { "15": { english: {} } } },
|
||||
];
|
||||
|
||||
const result15 = {
|
||||
mode: "time",
|
||||
mode2: "15",
|
||||
} as unknown as Result<Mode>;
|
||||
|
||||
for (const lbPb of lbpbstartingvalues) {
|
||||
const lbPbPb = pb.updateLeaderboardPersonalBests(
|
||||
userPbs,
|
||||
_.cloneDeep(lbPb) as MonkeyTypes.LbPersonalBests,
|
||||
result15
|
||||
);
|
||||
|
||||
expect(lbPbPb).toEqual({
|
||||
time: {
|
||||
"15": {
|
||||
english: {
|
||||
acc: 100,
|
||||
consistency: 100,
|
||||
difficulty: "normal",
|
||||
lazyMode: false,
|
||||
language: "english",
|
||||
numbers: false,
|
||||
punctuation: false,
|
||||
raw: 100,
|
||||
timestamp: 0,
|
||||
wpm: 100,
|
||||
},
|
||||
spanish: {
|
||||
acc: 100,
|
||||
consistency: 100,
|
||||
difficulty: "normal",
|
||||
lazyMode: false,
|
||||
language: "spanish",
|
||||
numbers: false,
|
||||
punctuation: false,
|
||||
raw: 100,
|
||||
timestamp: 0,
|
||||
wpm: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -198,7 +198,7 @@ export async function sendVerificationEmail(
|
|||
JSON.stringify({
|
||||
decodedTokenEmail: email,
|
||||
userInfoEmail: userInfo.email,
|
||||
stack: e.stack,
|
||||
stack: e.stack as unknown,
|
||||
}),
|
||||
userInfo.uid
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@ function addSwaggerMiddlewares(app: Application): void {
|
|||
const openApiSpec = __dirname + "/../../static/api/openapi.json";
|
||||
let spec = {};
|
||||
try {
|
||||
spec = JSON.parse(readFileSync(openApiSpec, "utf8"));
|
||||
spec = JSON.parse(readFileSync(openApiSpec, "utf8")) as string;
|
||||
} catch (err) {
|
||||
Logger.warning(
|
||||
`Cannot read openApi specification from ${openApiSpec}. Swagger stats will not fully work.`
|
||||
|
|
|
@ -22,7 +22,7 @@ export function callController<
|
|||
query: all.query as TQuery,
|
||||
params: all.params as TParams,
|
||||
raw: all.req,
|
||||
ctx: all.req["ctx"],
|
||||
ctx: all.req["ctx"] as MonkeyTypes.Context,
|
||||
};
|
||||
|
||||
const result = await handler(req);
|
||||
|
|
|
@ -8,6 +8,20 @@ import MonkeyError from "../utils/error";
|
|||
import { compareTwoStrings } from "string-similarity";
|
||||
import { ApproveQuote, Quote } from "@monkeytype/contracts/schemas/quotes";
|
||||
|
||||
type JsonQuote = {
|
||||
text: string;
|
||||
britishText?: string;
|
||||
source: string;
|
||||
length: number;
|
||||
id: number;
|
||||
};
|
||||
|
||||
type QuoteData = {
|
||||
language: string;
|
||||
quotes: JsonQuote[];
|
||||
groups: [number, number][];
|
||||
};
|
||||
|
||||
const PATH_TO_REPO = "../../../../monkeytype-new-quotes";
|
||||
|
||||
let git;
|
||||
|
@ -71,11 +85,11 @@ export async function add(
|
|||
let similarityScore = -1;
|
||||
if (existsSync(fileDir)) {
|
||||
const quoteFile = await readFile(fileDir);
|
||||
const quoteFileJSON = JSON.parse(quoteFile.toString());
|
||||
const quoteFileJSON = JSON.parse(quoteFile.toString()) as QuoteData;
|
||||
quoteFileJSON.quotes.every((old) => {
|
||||
if (compareTwoStrings(old.text as string, quote.text) > 0.9) {
|
||||
if (compareTwoStrings(old.text, quote.text) > 0.9) {
|
||||
duplicateId = old.id;
|
||||
similarityScore = compareTwoStrings(old.text as string, quote.text);
|
||||
similarityScore = compareTwoStrings(old.text, quote.text);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -155,9 +169,9 @@ export async function approve(
|
|||
await git.pull("upstream", "master");
|
||||
if (existsSync(fileDir)) {
|
||||
const quoteFile = await readFile(fileDir);
|
||||
const quoteObject = JSON.parse(quoteFile.toString());
|
||||
const quoteObject = JSON.parse(quoteFile.toString()) as QuoteData;
|
||||
quoteObject.quotes.every((old) => {
|
||||
if (compareTwoStrings(old.text as string, quote.text) > 0.8) {
|
||||
if (compareTwoStrings(old.text, quote.text) > 0.8) {
|
||||
throw new MonkeyError(409, "Duplicate quote");
|
||||
}
|
||||
});
|
||||
|
@ -168,7 +182,7 @@ export async function approve(
|
|||
}
|
||||
});
|
||||
quote.id = maxid + 1;
|
||||
quoteObject.quotes.push(quote);
|
||||
quoteObject.quotes.push(quote as JsonQuote);
|
||||
writeFileSync(fileDir, JSON.stringify(quoteObject, null, 2));
|
||||
message = `Added quote to ${language}.json.`;
|
||||
} else {
|
||||
|
|
|
@ -87,7 +87,7 @@ export async function getLastResult(
|
|||
|
||||
export async function getResultByTimestamp(
|
||||
uid: string,
|
||||
timestamp
|
||||
timestamp: number
|
||||
): Promise<MonkeyTypes.DBResult | null> {
|
||||
return await getResultCollection().findOne({ uid, timestamp });
|
||||
}
|
||||
|
|
|
@ -25,14 +25,14 @@ function mergeConfigurations(
|
|||
const commonKeys = _.intersection(_.keys(base), _.keys(source));
|
||||
|
||||
commonKeys.forEach((key) => {
|
||||
const baseValue = base[key];
|
||||
const sourceValue = source[key];
|
||||
const baseValue = base[key] as object;
|
||||
const sourceValue = source[key] as object;
|
||||
|
||||
const isBaseValueObject = _.isPlainObject(baseValue);
|
||||
const isSourceValueObject = _.isPlainObject(sourceValue);
|
||||
|
||||
if (isBaseValueObject && isSourceValueObject) {
|
||||
merge(baseValue as object, sourceValue as object);
|
||||
merge(baseValue, sourceValue);
|
||||
} else if (identity(baseValue) === identity(sourceValue)) {
|
||||
base[key] = sourceValue;
|
||||
}
|
||||
|
|
|
@ -103,14 +103,16 @@ export async function sendEmail(
|
|||
html: template,
|
||||
};
|
||||
|
||||
let result;
|
||||
type Result = { response: string; accepted: string[] };
|
||||
|
||||
let result: Result;
|
||||
try {
|
||||
result = await transporter.sendMail(mailOptions);
|
||||
result = (await transporter.sendMail(mailOptions)) as Result;
|
||||
} catch (e) {
|
||||
recordEmail(templateName, "fail");
|
||||
return {
|
||||
success: false,
|
||||
message: e.message,
|
||||
message: e.message as string,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -29,11 +29,9 @@ export function init(): void {
|
|||
encoding: "utf8",
|
||||
flag: "r",
|
||||
})
|
||||
);
|
||||
) as ServiceAccount;
|
||||
admin.initializeApp({
|
||||
credential: admin.credential.cert(
|
||||
serviceAccount as unknown as ServiceAccount
|
||||
),
|
||||
credential: admin.credential.cert(serviceAccount),
|
||||
});
|
||||
Logger.success("Firebase app initialized");
|
||||
}
|
||||
|
|
|
@ -188,33 +188,27 @@ async function authenticateWithBearerToken(
|
|||
email: decodedToken.email ?? "",
|
||||
};
|
||||
} catch (error) {
|
||||
const errorCode = error?.errorInfo?.code;
|
||||
const errorCode = error?.errorInfo?.code as string | undefined;
|
||||
|
||||
if (errorCode?.includes("auth/id-token-expired") as boolean | undefined) {
|
||||
if (errorCode?.includes("auth/id-token-expired")) {
|
||||
throw new MonkeyError(
|
||||
401,
|
||||
"Token expired - please login again",
|
||||
"authenticateWithBearerToken"
|
||||
);
|
||||
} else if (
|
||||
errorCode?.includes("auth/id-token-revoked") as boolean | undefined
|
||||
) {
|
||||
} else if (errorCode?.includes("auth/id-token-revoked")) {
|
||||
throw new MonkeyError(
|
||||
401,
|
||||
"Token revoked - please login again",
|
||||
"authenticateWithBearerToken"
|
||||
);
|
||||
} else if (
|
||||
errorCode?.includes("auth/user-not-found") as boolean | undefined
|
||||
) {
|
||||
} else if (errorCode?.includes("auth/user-not-found")) {
|
||||
throw new MonkeyError(
|
||||
404,
|
||||
"User not found",
|
||||
"authenticateWithBearerToken"
|
||||
);
|
||||
} else if (
|
||||
errorCode?.includes("auth/argument-error") as boolean | undefined
|
||||
) {
|
||||
} else if (errorCode?.includes("auth/argument-error")) {
|
||||
throw new MonkeyError(
|
||||
400,
|
||||
"Incorrect Bearer token format",
|
||||
|
|
|
@ -53,11 +53,12 @@ function getValue(
|
|||
path: ConfigurationPath
|
||||
): boolean {
|
||||
const keys = (path as string).split(".");
|
||||
let result = configuration;
|
||||
let result: unknown = configuration;
|
||||
|
||||
for (const key of keys) {
|
||||
if (result === undefined || result === null)
|
||||
if (result === undefined || result === null) {
|
||||
throw new MonkeyError(500, `Invalid configuration path: "${path}"`);
|
||||
}
|
||||
result = result[key];
|
||||
}
|
||||
|
||||
|
|
|
@ -129,14 +129,14 @@ export class WeeklyXpLeaderboard {
|
|||
this.getThisWeeksXpLeaderboardKeys();
|
||||
|
||||
// @ts-expect-error
|
||||
const [results, scores]: string[][] = await connection.getResults(
|
||||
const [results, scores] = (await connection.getResults(
|
||||
2, // How many of the arguments are redis keys (https://redis.io/docs/manual/programmability/lua-api/)
|
||||
weeklyXpLeaderboardScoresKey,
|
||||
weeklyXpLeaderboardResultsKey,
|
||||
minRank,
|
||||
maxRank,
|
||||
"true"
|
||||
);
|
||||
)) as string[][];
|
||||
|
||||
if (results === undefined) {
|
||||
throw new Error(
|
||||
|
@ -183,13 +183,17 @@ export class WeeklyXpLeaderboard {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
||||
// @ts-ignore
|
||||
const [[, rank], [, totalXp], [, count], [, result]] = await connection
|
||||
const [[, rank], [, totalXp], [, count], [, result]] = (await connection
|
||||
.multi()
|
||||
.zrevrank(weeklyXpLeaderboardScoresKey, uid)
|
||||
.zscore(weeklyXpLeaderboardScoresKey, uid)
|
||||
.zcard(weeklyXpLeaderboardScoresKey)
|
||||
.hget(weeklyXpLeaderboardResultsKey, uid)
|
||||
.exec();
|
||||
.exec()) as [
|
||||
[null, number | null],
|
||||
[null, string | null],
|
||||
[null, number | null]
|
||||
];
|
||||
|
||||
if (rank === null) {
|
||||
return null;
|
||||
|
|
|
@ -121,14 +121,14 @@ export class DailyLeaderboard {
|
|||
this.getTodaysLeaderboardKeys();
|
||||
|
||||
// @ts-expect-error
|
||||
const [results]: string[][] = await connection.getResults(
|
||||
const [results] = (await connection.getResults(
|
||||
2,
|
||||
leaderboardScoresKey,
|
||||
leaderboardResultsKey,
|
||||
minRank,
|
||||
maxRank,
|
||||
"false"
|
||||
);
|
||||
)) as string[][];
|
||||
|
||||
if (results === undefined) {
|
||||
throw new Error(
|
||||
|
@ -198,7 +198,7 @@ export class DailyLeaderboard {
|
|||
count: count ?? 0,
|
||||
rank: rank + 1,
|
||||
entry: {
|
||||
...JSON.parse(result ?? "null"),
|
||||
...(JSON.parse(result ?? "null") as LeaderboardEntry),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -285,7 +285,7 @@ export function formatSeconds(
|
|||
}
|
||||
|
||||
export function intersect<T>(a: T[], b: T[], removeDuplicates = false): T[] {
|
||||
let t;
|
||||
let t: T[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
if (b.length > a.length) (t = b), (b = a), (a = t); // indexOf to loop over shorter
|
||||
const filtered = a.filter(function (e) {
|
||||
|
|
|
@ -59,7 +59,14 @@ export function checkAndUpdatePb(
|
|||
}
|
||||
|
||||
if (!_.isNil(lbPersonalBests)) {
|
||||
updateLeaderboardPersonalBests(userPb, lbPersonalBests, result);
|
||||
const newLbPb = updateLeaderboardPersonalBests(
|
||||
userPb,
|
||||
lbPersonalBests,
|
||||
result
|
||||
);
|
||||
if (newLbPb !== null) {
|
||||
lbPersonalBests = newLbPb;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -165,26 +172,20 @@ function buildPersonalBest(result: Result): PersonalBest {
|
|||
};
|
||||
}
|
||||
|
||||
function updateLeaderboardPersonalBests(
|
||||
export function updateLeaderboardPersonalBests(
|
||||
userPersonalBests: PersonalBests,
|
||||
lbPersonalBests: MonkeyTypes.LbPersonalBests,
|
||||
result: Result
|
||||
): void {
|
||||
): MonkeyTypes.LbPersonalBests | null {
|
||||
if (!shouldUpdateLeaderboardPersonalBests(result)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const mode = result.mode;
|
||||
const mode2 = result.mode2;
|
||||
|
||||
lbPersonalBests[mode] = lbPersonalBests[mode] ?? {};
|
||||
const lbMode2 = lbPersonalBests[mode][mode2] as MonkeyTypes.LbPersonalBests;
|
||||
if (lbMode2 === undefined || Array.isArray(lbMode2)) {
|
||||
lbPersonalBests[mode][mode2] = {};
|
||||
}
|
||||
|
||||
const lbPb = lbPersonalBests ?? {};
|
||||
lbPb[mode] ??= {};
|
||||
lbPb[mode][mode2] ??= {};
|
||||
const bestForEveryLanguage = {};
|
||||
|
||||
userPersonalBests[mode][mode2].forEach((pb: PersonalBest) => {
|
||||
const language = pb.language;
|
||||
if (
|
||||
|
@ -194,18 +195,18 @@ function updateLeaderboardPersonalBests(
|
|||
bestForEveryLanguage[language] = pb;
|
||||
}
|
||||
});
|
||||
|
||||
_.each(bestForEveryLanguage, (pb: PersonalBest, language: string) => {
|
||||
const languageDoesNotExist =
|
||||
lbPersonalBests[mode][mode2][language] === undefined;
|
||||
|
||||
const languageDoesNotExist = lbPb[mode][mode2][language] === undefined;
|
||||
const languageIsEmpty = _.isEmpty(lbPb[mode][mode2][language]);
|
||||
if (
|
||||
languageDoesNotExist ||
|
||||
lbPersonalBests[mode][mode2][language].wpm < pb.wpm
|
||||
languageIsEmpty ||
|
||||
lbPb[mode][mode2][language].wpm < pb.wpm
|
||||
) {
|
||||
lbPersonalBests[mode][mode2][language] = pb;
|
||||
lbPb[mode][mode2][language] = pb;
|
||||
}
|
||||
});
|
||||
return lbPb;
|
||||
}
|
||||
|
||||
function shouldUpdateLeaderboardPersonalBests(result: Result): boolean {
|
||||
|
|
|
@ -2,18 +2,15 @@ import _ from "lodash";
|
|||
import IORedis from "ioredis";
|
||||
import { Worker, Job, type ConnectionOptions } from "bullmq";
|
||||
import Logger from "../utils/logger";
|
||||
import EmailQueue, {
|
||||
type EmailTaskContexts,
|
||||
type EmailType,
|
||||
} from "../queues/email-queue";
|
||||
import EmailQueue, { EmailTask, type EmailType } from "../queues/email-queue";
|
||||
import { sendEmail } from "../init/email-client";
|
||||
import { recordTimeToCompleteJob } from "../utils/prometheus";
|
||||
import { addLog } from "../dal/logs";
|
||||
|
||||
async function jobHandler(job: Job): Promise<void> {
|
||||
const type: EmailType = job.data.type;
|
||||
const email: string = job.data.email;
|
||||
const ctx: EmailTaskContexts[typeof type] = job.data.ctx;
|
||||
async function jobHandler(job: Job<EmailTask<EmailType>>): Promise<void> {
|
||||
const type = job.data.type;
|
||||
const email = job.data.email;
|
||||
const ctx = job.data.ctx;
|
||||
|
||||
Logger.info(`Starting job: ${type}`);
|
||||
|
||||
|
|
|
@ -180,8 +180,8 @@ async function handleWeeklyXpLeaderboardResults(
|
|||
await addToInboxBulk(mailEntries, inboxConfig);
|
||||
}
|
||||
|
||||
async function jobHandler(job: Job): Promise<void> {
|
||||
const { taskName, ctx }: LaterTask<LaterTaskType> = job.data;
|
||||
async function jobHandler(job: Job<LaterTask<LaterTaskType>>): Promise<void> {
|
||||
const { taskName, ctx } = job.data;
|
||||
|
||||
Logger.info(`Starting job: ${taskName}`);
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ function buildApi(timeout: number): (args: ApiFetcherArgs) => Promise<{
|
|||
: AbortSignal.timeout(timeout),
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as object;
|
||||
if (response.status >= 400) {
|
||||
console.error(`${request.method} ${request.path} failed`, {
|
||||
status: response.status,
|
||||
|
|
|
@ -9,6 +9,11 @@ import { createErrorMessage } from "../utils/misc";
|
|||
|
||||
let analytics: AnalyticsType;
|
||||
|
||||
type AcceptedCookies = {
|
||||
security: boolean;
|
||||
analytics: boolean;
|
||||
};
|
||||
|
||||
export async function log(
|
||||
eventName: string,
|
||||
params?: Record<string, string>
|
||||
|
@ -21,12 +26,9 @@ export async function log(
|
|||
}
|
||||
|
||||
const lsString = localStorage.getItem("acceptedCookies");
|
||||
let acceptedCookies: {
|
||||
security: boolean;
|
||||
analytics: boolean;
|
||||
} | null;
|
||||
let acceptedCookies;
|
||||
if (lsString !== undefined && lsString !== null && lsString !== "") {
|
||||
acceptedCookies = JSON.parse(lsString);
|
||||
acceptedCookies = JSON.parse(lsString) as AcceptedCookies;
|
||||
} else {
|
||||
acceptedCookies = null;
|
||||
}
|
||||
|
|
|
@ -13,12 +13,13 @@ export function render(
|
|||
}
|
||||
|
||||
//@ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const widgetId = grecaptcha.render(element, {
|
||||
sitekey: siteKey,
|
||||
callback,
|
||||
});
|
||||
|
||||
captchas[id] = widgetId;
|
||||
captchas[id] = widgetId as number;
|
||||
}
|
||||
|
||||
export function reset(id: string): void {
|
||||
|
|
|
@ -143,8 +143,9 @@ export function init(): void {
|
|||
headOfDocument.appendChild(rampScript);
|
||||
|
||||
window._pwGA4PageviewId = "".concat(Date.now());
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/no-unsafe-assignment
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
window.gtag =
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
window.gtag ||
|
||||
|
@ -156,6 +157,7 @@ export function init(): void {
|
|||
gtag("config", "G-KETCPNHRJF", { send_page_view: false });
|
||||
gtag("event", "ramp_js", {
|
||||
send_to: "G-KETCPNHRJF",
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
pageview_id: window._pwGA4PageviewId,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -350,10 +350,12 @@ export function updateActive(): void {
|
|||
}
|
||||
|
||||
for (const [id, select] of Object.entries(groupSelects)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
const ss = select;
|
||||
const group = getGroup(id as ResultFiltersGroup);
|
||||
const everythingSelected = Object.values(group).every((v) => v === true);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
const newData = ss.store.getData();
|
||||
|
||||
const allOption = $(
|
||||
|
@ -682,8 +684,10 @@ function adjustScrollposition(
|
|||
group: ResultFiltersGroup,
|
||||
topItem: number = 0
|
||||
): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
const slimSelect = groupSelects[group];
|
||||
if (slimSelect === undefined) return;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
const listElement = slimSelect.render.content.list;
|
||||
const topListItem = listElement.children.item(topItem) as HTMLElement;
|
||||
|
||||
|
@ -779,6 +783,7 @@ export async function appendButtons(
|
|||
);
|
||||
if (el) {
|
||||
el.innerHTML = html;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
groupSelects["language"] = new SlimSelect({
|
||||
select: el.querySelector(".languageSelect") as HTMLSelectElement,
|
||||
settings: {
|
||||
|
@ -840,6 +845,7 @@ export async function appendButtons(
|
|||
);
|
||||
if (el) {
|
||||
el.innerHTML = html;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
groupSelects["funbox"] = new SlimSelect({
|
||||
select: el.querySelector(".funboxSelect") as HTMLSelectElement,
|
||||
settings: {
|
||||
|
@ -897,6 +903,7 @@ export async function appendButtons(
|
|||
);
|
||||
if (el) {
|
||||
el.innerHTML = html;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
groupSelects["tags"] = new SlimSelect({
|
||||
select: el.querySelector(".tagsSelect") as HTMLSelectElement,
|
||||
settings: {
|
||||
|
|
|
@ -712,6 +712,7 @@ export function show(): void {
|
|||
Config.typingSpeedUnit + '<br><div class="sub">accuracy</div>'
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
languageSelector = new SlimSelect({
|
||||
select:
|
||||
"#leaderboardsWrapper #leaderboards .leaderboardsTop .buttonGroup.timeRange .languageSelect",
|
||||
|
|
|
@ -120,7 +120,7 @@ export default class SettingsGroup<T extends ConfigValue> {
|
|||
select.value = this.configValue as string;
|
||||
|
||||
//@ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unnecessary-type-assertion -- TODO: update slim-select
|
||||
const ss = select.slim as SlimSelect | undefined;
|
||||
ss?.store.setSelectedBy("value", [this.configValue as string]);
|
||||
ss?.render.renderValues();
|
||||
|
|
|
@ -57,9 +57,9 @@ export class TestActivityCalendar implements MonkeyTypes.TestActivityCalendar {
|
|||
lastDay: Date
|
||||
): (number | null | undefined)[] {
|
||||
//fill calendar with enough values
|
||||
const values: (number | null | undefined)[] = new Array(
|
||||
Math.max(0, 386 - data.length)
|
||||
).fill(undefined);
|
||||
const values = new Array(Math.max(0, 386 - data.length)).fill(
|
||||
undefined
|
||||
) as (number | null | undefined)[];
|
||||
values.push(...data);
|
||||
|
||||
//discard values outside the calendar range
|
||||
|
|
|
@ -18,6 +18,7 @@ export function init(
|
|||
}
|
||||
$("#testActivity").removeClass("hidden");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
yearSelector = getYearSelector();
|
||||
initYearSelector("current", userSignUpDate?.getFullYear() || 2022);
|
||||
update(calendar);
|
||||
|
@ -84,6 +85,7 @@ export function initYearSelector(
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
const yearSelect = getYearSelector();
|
||||
yearSelect.setData(years);
|
||||
years.length > 1 ? yearSelect.enable() : yearSelect.disable();
|
||||
|
@ -102,6 +104,7 @@ function updateMonths(months: MonkeyTypes.TestActivityMonth[]): void {
|
|||
|
||||
function getYearSelector(): SlimSelect {
|
||||
if (yearSelector !== undefined) return yearSelector;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
yearSelector = new SlimSelect({
|
||||
select: "#testActivity .yearSelect",
|
||||
settings: {
|
||||
|
|
|
@ -46,7 +46,7 @@ window.onerror = function (message, url, line, column, error): void {
|
|||
|
||||
window.onunhandledrejection = function (e): void {
|
||||
if (Misc.isDevEnvironment()) {
|
||||
const message = e.reason.message ?? e.reason;
|
||||
const message = (e.reason.message ?? e.reason) as string;
|
||||
Notifications.add(`${message}`, -1, {
|
||||
customTitle: "DEV: Unhandled rejection",
|
||||
duration: 5,
|
||||
|
@ -54,6 +54,6 @@ window.onunhandledrejection = function (e): void {
|
|||
console.error(e);
|
||||
}
|
||||
void log("error", {
|
||||
error: e.reason.stack ?? "",
|
||||
error: (e.reason.stack ?? "") as string,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -139,7 +139,9 @@ function addCheckboxListeners(): void {
|
|||
`#editPresetModal .modal .changePresetToCurrentCheckbox input`
|
||||
);
|
||||
presetToCurrentCheckbox.on("change", async () => {
|
||||
state.setPresetToCurrent = presetToCurrentCheckbox.prop("checked");
|
||||
state.setPresetToCurrent = presetToCurrentCheckbox.prop(
|
||||
"checked"
|
||||
) as boolean;
|
||||
await updateEditPresetUI();
|
||||
});
|
||||
}
|
||||
|
@ -212,9 +214,9 @@ async function apply(): Promise<void> {
|
|||
"data-preset-id"
|
||||
) as string;
|
||||
|
||||
const updateConfig: boolean = $("#editPresetModal .modal label input").prop(
|
||||
const updateConfig = $("#editPresetModal .modal label input").prop(
|
||||
"checked"
|
||||
);
|
||||
) as boolean;
|
||||
|
||||
const snapshotPresets = DB.getSnapshot()?.presets ?? [];
|
||||
|
||||
|
@ -474,14 +476,13 @@ function getPartialConfigChanges(
|
|||
state.checkboxes.get(getSettingGroup(settingName)) === true
|
||||
)
|
||||
.forEach((settingName) => {
|
||||
//@ts-expect-error this is fine
|
||||
activeConfigChanges[settingName] =
|
||||
//@ts-expect-error this is fine
|
||||
configChanges[settingName] !== undefined
|
||||
? //@ts-expect-error this is fine
|
||||
configChanges[settingName]
|
||||
: //@ts-expect-error this is fine
|
||||
defaultConfig[settingName];
|
||||
const safeSettingName = settingName as keyof MonkeyTypes.ConfigChanges;
|
||||
const newValue =
|
||||
configChanges[safeSettingName] !== undefined
|
||||
? configChanges[safeSettingName]
|
||||
: defaultConfig[safeSettingName];
|
||||
// @ts-expect-error cant figure this one out, but it works
|
||||
activeConfigChanges[safeSettingName] = newValue;
|
||||
});
|
||||
return activeConfigChanges;
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ export async function show(
|
|||
$("#quoteReportModal .reason").val("Grammatical error");
|
||||
$("#quoteReportModal .comment").val("");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
state.reasonSelect = new SlimSelect({
|
||||
select: "#quoteReportModal .reason",
|
||||
settings: {
|
||||
|
|
|
@ -273,6 +273,7 @@ export async function show(showOptions?: ShowOptions): Promise<void> {
|
|||
$("#quoteSearchModal .goToQuoteApprove").addClass("hidden");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
lengthSelect = new SlimSelect({
|
||||
select: "#quoteSearchModal .quoteLengthFilter",
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ export async function show(showOptions: ShowOptions): Promise<void> {
|
|||
);
|
||||
await initDropdown();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
select = new SlimSelect({
|
||||
select: "#quoteSubmitModal .newQuoteLanguage",
|
||||
});
|
||||
|
|
|
@ -51,6 +51,7 @@ export async function show(options: ShowOptions): Promise<void> {
|
|||
"Inappropriate name";
|
||||
(modalEl.querySelector(".comment") as HTMLTextAreaElement).value = "";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
select = new SlimSelect({
|
||||
select: modalEl.querySelector(".reason") as HTMLElement,
|
||||
settings: {
|
||||
|
|
|
@ -159,18 +159,21 @@ export async function show(showOptions?: ShowOptions): Promise<void> {
|
|||
void modal.show({
|
||||
...showOptions,
|
||||
beforeAnimation: async (modalEl) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
languageSelect = new SlimSelect({
|
||||
select: "#wordFilterModal .languageInput",
|
||||
settings: {
|
||||
contentLocation: modalEl,
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
layoutSelect = new SlimSelect({
|
||||
select: "#wordFilterModal .layoutInput",
|
||||
settings: {
|
||||
contentLocation: modal.getModal(),
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- TODO: update slim-select
|
||||
presetSelect = new SlimSelect({
|
||||
select: "#wordFilterModal .presetInput",
|
||||
settings: {
|
||||
|
|
|
@ -183,7 +183,7 @@ export function updateUI(): void {
|
|||
}
|
||||
|
||||
$(".page.pageAccountSettings").on("click", ".tabs button", (event) => {
|
||||
state.activeTab = $(event.target).data("tab");
|
||||
state.activeTab = $(event.target).data("tab") as State["activeTab"];
|
||||
updateTabs();
|
||||
});
|
||||
|
||||
|
|
|
@ -1062,7 +1062,7 @@ $(".pageSettings .section.tags").on(
|
|||
"click",
|
||||
".tagsList .tag .tagButton",
|
||||
(e) => {
|
||||
const target = e.currentTarget;
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const tagid = $(target).parent(".tag").attr("data-id") as string;
|
||||
TagController.toggle(tagid);
|
||||
$(target).toggleClass("active");
|
||||
|
@ -1073,7 +1073,7 @@ $(".pageSettings .section.presets").on(
|
|||
"click",
|
||||
".presetsList .preset .presetButton",
|
||||
async (e) => {
|
||||
const target = e.currentTarget;
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const presetid = $(target).parent(".preset").attr("data-id") as string;
|
||||
await PresetController.apply(presetid);
|
||||
void update();
|
||||
|
|
|
@ -49,8 +49,12 @@ export async function getPoem(): Promise<Section | false> {
|
|||
|
||||
try {
|
||||
const response = await fetch(apiURL);
|
||||
const data = await response.json();
|
||||
const poemObj: PoemObject = data[0];
|
||||
const data = (await response.json()) as PoemObject[];
|
||||
const poemObj = data[0];
|
||||
|
||||
if (!poemObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const words: string[] = [];
|
||||
|
||||
|
|
|
@ -1182,9 +1182,9 @@ async function saveResult(
|
|||
}
|
||||
|
||||
if (data.insertedId !== undefined) {
|
||||
const result: MonkeyTypes.FullResult<Mode> = JSON.parse(
|
||||
const result = JSON.parse(
|
||||
JSON.stringify(completedEvent)
|
||||
);
|
||||
) as MonkeyTypes.FullResult<Mode>;
|
||||
result._id = data.insertedId;
|
||||
if (data.isPb !== undefined && data.isPb) {
|
||||
result.isPb = true;
|
||||
|
|
|
@ -268,7 +268,7 @@ export async function getSection(language: string): Promise<Section> {
|
|||
let pageid = 0;
|
||||
|
||||
if (randomPostReq.status === 200) {
|
||||
const postObj: Post = await randomPostReq.json();
|
||||
const postObj = (await randomPostReq.json()) as Post;
|
||||
sectionObj.title = postObj.title;
|
||||
sectionObj.author = postObj.author;
|
||||
pageid = postObj.pageid;
|
||||
|
@ -286,8 +286,9 @@ export async function getSection(language: string): Promise<Section> {
|
|||
sectionReq.onload = (): void => {
|
||||
if (sectionReq.readyState === 4) {
|
||||
if (sectionReq.status === 200) {
|
||||
let sectionText: string = JSON.parse(sectionReq.responseText).query
|
||||
.pages[pageid.toString()].extract;
|
||||
let sectionText = JSON.parse(sectionReq.responseText).query.pages[
|
||||
pageid.toString()
|
||||
].extract as string;
|
||||
|
||||
// Converting to one paragraph
|
||||
sectionText = sectionText.replace(/<\/p><p>+/g, " ");
|
||||
|
|
|
@ -25,7 +25,7 @@ export class LocalStorageWithSchema<T> {
|
|||
return this.fallback;
|
||||
}
|
||||
|
||||
let jsonParsed;
|
||||
let jsonParsed: unknown;
|
||||
try {
|
||||
jsonParsed = JSON.parse(value);
|
||||
} catch (e) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
Mode2,
|
||||
PersonalBests,
|
||||
} from "@monkeytype/contracts/schemas/shared";
|
||||
import { ZodError, ZodSchema } from "zod";
|
||||
|
||||
export function kogasa(cov: number): number {
|
||||
return (
|
||||
|
@ -684,4 +685,24 @@ export function isObject(obj: unknown): obj is Record<string, unknown> {
|
|||
return typeof obj === "object" && !Array.isArray(obj) && obj !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JSON string into an object and validate it against a schema
|
||||
* @param input JSON string
|
||||
* @param schema Zod schema to validate the JSON against
|
||||
* @returns The parsed JSON object
|
||||
*/
|
||||
export function parseJsonWithSchema<T>(input: string, schema: ZodSchema<T>): T {
|
||||
try {
|
||||
const jsonParsed = JSON.parse(input) as unknown;
|
||||
const zodParsed = schema.parse(jsonParsed);
|
||||
return zodParsed;
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError) {
|
||||
throw new Error(error.errors.map((err) => err.message).join("\n"));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DO NOT ALTER GLOBAL OBJECTSONSTRUCTOR, IT WILL BREAK RESULT HASHES
|
||||
|
|
|
@ -19,9 +19,9 @@ export async function syncNotSignedInLastResult(uid: string): Promise<void> {
|
|||
return;
|
||||
}
|
||||
|
||||
const result: MonkeyTypes.FullResult<Mode> = JSON.parse(
|
||||
const result = JSON.parse(
|
||||
JSON.stringify(notSignedInLastResult)
|
||||
);
|
||||
) as MonkeyTypes.FullResult<Mode>;
|
||||
result._id = response.body.data.insertedId;
|
||||
if (response.body.data.isPb) {
|
||||
result.isPb = true;
|
||||
|
|
|
@ -65,37 +65,32 @@ export async function linkDiscord(hashOverride: string): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
const customThemeUrlDataSchema = z.object({
|
||||
c: CustomThemeColorsSchema,
|
||||
i: z.string().optional(),
|
||||
s: CustomBackgroundSizeSchema.optional(),
|
||||
f: CustomBackgroundFilterSchema.optional(),
|
||||
});
|
||||
|
||||
export function loadCustomThemeFromUrl(getOverride?: string): void {
|
||||
const getValue = Misc.findGetParameter("customTheme", getOverride);
|
||||
if (getValue === null) return;
|
||||
|
||||
let decoded = null;
|
||||
let decoded: z.infer<typeof customThemeUrlDataSchema>;
|
||||
try {
|
||||
decoded = JSON.parse(atob(getValue));
|
||||
decoded = Misc.parseJsonWithSchema(
|
||||
atob(getValue),
|
||||
customThemeUrlDataSchema
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("Custom theme URL decoding failed", e);
|
||||
Notifications.add(
|
||||
"Failed to load theme from URL: could not decode theme",
|
||||
"Failed to load theme from URL: " + (e as Error).message,
|
||||
0
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const decodedSchema = z.object({
|
||||
c: CustomThemeColorsSchema,
|
||||
i: z.string().optional(),
|
||||
s: CustomBackgroundSizeSchema.optional(),
|
||||
f: CustomBackgroundFilterSchema.optional(),
|
||||
});
|
||||
|
||||
const parsed = decodedSchema.safeParse(decoded);
|
||||
if (!parsed.success) {
|
||||
Notifications.add("Failed to load theme from URL: invalid data schema", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
decoded = parsed.data;
|
||||
|
||||
let colorArray: CustomThemeColors | undefined;
|
||||
let image: string | undefined;
|
||||
let size: CustomBackgroundSize | undefined;
|
||||
|
@ -151,7 +146,9 @@ export function loadTestSettingsFromUrl(getOverride?: string): void {
|
|||
const getValue = Misc.findGetParameter("testSettings", getOverride);
|
||||
if (getValue === null) return;
|
||||
|
||||
const de: SharedTestSettings = JSON.parse(decompressFromURI(getValue) ?? "");
|
||||
const de = JSON.parse(
|
||||
decompressFromURI(getValue) ?? ""
|
||||
) as SharedTestSettings;
|
||||
|
||||
const applied: Record<string, string> = {};
|
||||
|
||||
|
|
|
@ -80,12 +80,12 @@ module.exports = {
|
|||
"@typescript-eslint/restrict-plus-operands": "off",
|
||||
|
||||
// TODO: enable at some point
|
||||
"@typescript-eslint/no-unsafe-assignment": "off", //~63
|
||||
"@typescript-eslint/no-unsafe-call": "off", //~76
|
||||
"@typescript-eslint/no-unsafe-member-access": "off", //~105
|
||||
//
|
||||
|
||||
"@typescript-eslint/no-unsafe-argument": "error",
|
||||
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-confusing-void-expression": [
|
||||
"error",
|
||||
|
|
Loading…
Reference in a new issue