refactor: remove leftover code from ts-rest migration (@fehmer) (#5875)

This commit is contained in:
Christian Fehmer 2024-09-13 12:27:35 +02:00 committed by GitHub
parent 8a6c81669e
commit e19b3e3e8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 383 additions and 972 deletions

View file

@ -37,7 +37,7 @@ const mockApeKey = {
vi.spyOn(ApeKeys, "getApeKey").mockResolvedValue(mockApeKey);
vi.spyOn(ApeKeys, "updateLastUsedOn").mockResolvedValue();
const isDevModeMock = vi.spyOn(Misc, "isDevEnvironment");
let mockRequest: Partial<MonkeyTypes.Request>;
let mockRequest: Partial<Auth.TsRestRequestWithCtx>;
let mockResponse: Partial<Response>;
let nextFunction: NextFunction;
@ -79,184 +79,6 @@ describe("middlewares/auth", () => {
isDevModeMock.mockReset();
});
describe("authenticateRequest", () => {
it("should fail if token is not fresh", async () => {
Date.now = vi.fn(() => 60001);
const authenticateRequest = Auth.authenticateRequest({
requireFreshToken: true,
});
expect(() =>
authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
)
).rejects.toThrowError(
"Unauthorized\nStack: This endpoint requires a fresh token"
);
});
it("should allow the request if token is fresh", async () => {
Date.now = vi.fn(() => 10000);
const authenticateRequest = Auth.authenticateRequest({
requireFreshToken: true,
});
await authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
);
const decodedToken = mockRequest?.ctx?.decodedToken;
expect(decodedToken?.type).toBe("Bearer");
expect(decodedToken?.email).toBe(mockDecodedToken.email);
expect(decodedToken?.uid).toBe(mockDecodedToken.uid);
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow the request if apeKey is supported", async () => {
mockRequest.headers = {
authorization: "ApeKey aWQua2V5",
};
const authenticateRequest = Auth.authenticateRequest({
acceptApeKeys: true,
});
await authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
);
const decodedToken = mockRequest?.ctx?.decodedToken;
expect(decodedToken?.type).toBe("ApeKey");
expect(decodedToken?.email).toBe("");
expect(decodedToken?.uid).toBe("123");
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow the request with authentation on public endpoint", async () => {
const authenticateRequest = Auth.authenticateRequest({
isPublic: true,
});
await authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
);
const decodedToken = mockRequest?.ctx?.decodedToken;
expect(decodedToken?.type).toBe("Bearer");
expect(decodedToken?.email).toBe(mockDecodedToken.email);
expect(decodedToken?.uid).toBe(mockDecodedToken.uid);
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow the request without authentication on public endpoint", async () => {
mockRequest.headers = {};
const authenticateRequest = Auth.authenticateRequest({
isPublic: true,
});
await authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
);
const decodedToken = mockRequest?.ctx?.decodedToken;
expect(decodedToken?.type).toBe("None");
expect(decodedToken?.email).toBe("");
expect(decodedToken?.uid).toBe("");
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow the request with apeKey on public endpoint", async () => {
mockRequest.headers = {
authorization: "ApeKey aWQua2V5",
};
const authenticateRequest = Auth.authenticateRequest({
isPublic: true,
});
await authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
);
const decodedToken = mockRequest?.ctx?.decodedToken;
expect(decodedToken?.type).toBe("ApeKey");
expect(decodedToken?.email).toBe("");
expect(decodedToken?.uid).toBe("123");
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow request with Uid on dev", async () => {
mockRequest.headers = {
authorization: "Uid 123",
};
const authenticateRequest = Auth.authenticateRequest({});
await authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
);
const decodedToken = mockRequest?.ctx?.decodedToken;
expect(decodedToken?.type).toBe("Bearer");
expect(decodedToken?.email).toBe("");
expect(decodedToken?.uid).toBe("123");
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow request with Uid and email on dev", async () => {
mockRequest.headers = {
authorization: "Uid 123|test@example.com",
};
const authenticateRequest = Auth.authenticateRequest({});
await authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
);
const decodedToken = mockRequest?.ctx?.decodedToken;
expect(decodedToken?.type).toBe("Bearer");
expect(decodedToken?.email).toBe("test@example.com");
expect(decodedToken?.uid).toBe("123");
expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should fail request with Uid on non-dev", async () => {
isDevModeMock.mockReturnValue(false);
mockRequest.headers = {
authorization: "Uid 123",
};
const authenticateRequest = Auth.authenticateRequest({});
await expect(() =>
authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
)
).rejects.toThrow(
new MonkeyError(401, "Baerer type uid is not supported")
);
});
});
describe("authenticateTsRestRequest", () => {
const prometheusRecordAuthTimeMock = vi.spyOn(Prometheus, "recordAuthTime");
const prometheusIncrementAuthMock = vi.spyOn(Prometheus, "incrementAuth");

View file

@ -1,124 +1,6 @@
import * as Validation from "../../src/utils/validation";
import { isTagPresetNameValid } from "../../src/utils/validation";
describe("Validation", () => {
it("isTagPresetNameValid", () => {
const testCases = [
{
name: "valid_name",
expected: true,
},
{
name: "validname",
expected: true,
},
{
name: "valid-name",
expected: true,
},
{
name: "thistagnameistoolong",
expected: false,
},
{
name: "",
expected: false,
},
{
name: "invalid name",
expected: false,
},
{
name: "invalid=name",
expected: false,
},
];
testCases.forEach((testCase) => {
expect(Validation.isTagPresetNameValid(testCase.name)).toBe(
testCase.expected
);
});
});
it("inRange", () => {
const testCases = [
{
value: 1,
min: 1,
max: 2,
expected: true,
},
{
value: 1,
min: 2,
max: 2,
expected: false,
},
{
value: 1,
min: 1,
max: 1,
expected: true,
},
{
value: 53,
min: -100,
max: 100,
expected: true,
},
{
value: 153,
min: -100,
max: 100,
expected: false,
},
];
testCases.forEach((testCase) => {
expect(
Validation.inRange(testCase.value, testCase.min, testCase.max)
).toBe(testCase.expected);
});
});
it("isUsernameValid", () => {
const testCases = [
{
name: "Bruce",
expected: true,
},
{
name: "Rizwan_123",
expected: true,
},
{
name: "Fe-rotiq_123_",
expected: true,
},
{
name: " ",
expected: false,
},
{
name: "",
expected: false,
},
{
name: "superduperlongnamethatshouldbeinvalid",
expected: false,
},
{
name: ".period",
expected: false,
},
];
testCases.forEach((testCase) => {
expect(Validation.isUsernameValid(testCase.name)).toBe(testCase.expected);
});
});
it("isTestTooShort", () => {
const testCases = [
{
@ -164,8 +46,7 @@ describe("Validation", () => {
];
testCases.forEach((testCase) => {
//@ts-ignore
expect(Validation.isTestTooShort(testCase.result)).toBe(
expect(Validation.isTestTooShort(testCase.result as any)).toBe(
testCase.expected
);
});

View file

@ -39,7 +39,6 @@
"firebase-admin": "12.0.0",
"helmet": "4.6.0",
"ioredis": "4.28.5",
"joi": "17.6.0",
"lodash": "4.17.21",
"lru-cache": "7.10.1",
"mjml": "4.15.0",

View file

@ -1,4 +1,4 @@
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import { buildMonkeyMail } from "../../utils/monkey-mail";
import * as UserDAL from "../../dal/user";
import * as ReportDAL from "../../dal/report";
@ -15,14 +15,12 @@ import MonkeyError from "../../utils/error";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
import { addImportantLog } from "../../dal/logs";
export async function test(
_req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
return new MonkeyResponse2("OK", null);
export async function test(_req: MonkeyTypes.Request): Promise<MonkeyResponse> {
return new MonkeyResponse("OK", null);
}
export async function toggleBan(
req: MonkeyTypes.Request2<undefined, ToggleBanRequest>
req: MonkeyTypes.Request<undefined, ToggleBanRequest>
): Promise<ToggleBanResponse> {
const { uid } = req.body;
@ -38,31 +36,31 @@ export async function toggleBan(
void addImportantLog("user_ban_toggled", { banned: !user.banned }, uid);
return new MonkeyResponse2(`Ban toggled`, {
return new MonkeyResponse(`Ban toggled`, {
banned: !user.banned,
});
}
export async function acceptReports(
req: MonkeyTypes.Request2<undefined, AcceptReportsRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, AcceptReportsRequest>
): Promise<MonkeyResponse> {
await handleReports(
req.body.reports.map((it) => ({ ...it })),
true,
req.ctx.configuration.users.inbox
);
return new MonkeyResponse2("Reports removed and users notified.", null);
return new MonkeyResponse("Reports removed and users notified.", null);
}
export async function rejectReports(
req: MonkeyTypes.Request2<undefined, RejectReportsRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, RejectReportsRequest>
): Promise<MonkeyResponse> {
await handleReports(
req.body.reports.map((it) => ({ ...it })),
false,
req.ctx.configuration.users.inbox
);
return new MonkeyResponse2("Reports removed and users notified.", null);
return new MonkeyResponse("Reports removed and users notified.", null);
}
export async function handleReports(
@ -126,9 +124,9 @@ export async function handleReports(
}
export async function sendForgotPasswordEmail(
req: MonkeyTypes.Request2<undefined, SendForgotPasswordEmailRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, SendForgotPasswordEmailRequest>
): Promise<MonkeyResponse> {
const { email } = req.body;
await authSendForgotPasswordEmail(email);
return new MonkeyResponse2("Password reset request email sent.", null);
return new MonkeyResponse("Password reset request email sent.", null);
}

View file

@ -3,7 +3,7 @@ import { randomBytes } from "crypto";
import { hash } from "bcrypt";
import * as ApeKeysDAL from "../../dal/ape-keys";
import MonkeyError from "../../utils/error";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import { base64UrlEncode } from "../../utils/misc";
import { ObjectId } from "mongodb";
@ -21,18 +21,18 @@ function cleanApeKey(apeKey: MonkeyTypes.ApeKeyDB): ApeKey {
}
export async function getApeKeys(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetApeKeyResponse> {
const { uid } = req.ctx.decodedToken;
const apeKeys = await ApeKeysDAL.getApeKeys(uid);
const cleanedKeys = _(apeKeys).keyBy("_id").mapValues(cleanApeKey).value();
return new MonkeyResponse2("ApeKeys retrieved", cleanedKeys);
return new MonkeyResponse("ApeKeys retrieved", cleanedKeys);
}
export async function generateApeKey(
req: MonkeyTypes.Request2<undefined, AddApeKeyRequest>
req: MonkeyTypes.Request<undefined, AddApeKeyRequest>
): Promise<AddApeKeyResponse> {
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
@ -62,7 +62,7 @@ export async function generateApeKey(
const apeKeyId = await ApeKeysDAL.addApeKey(apeKey);
return new MonkeyResponse2("ApeKey generated", {
return new MonkeyResponse("ApeKey generated", {
apeKey: base64UrlEncode(`${apeKeyId}.${apiKey}`),
apeKeyId,
apeKeyDetails: cleanApeKey(apeKey),
@ -70,24 +70,24 @@ export async function generateApeKey(
}
export async function editApeKey(
req: MonkeyTypes.Request2<undefined, EditApeKeyRequest, ApeKeyParams>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, EditApeKeyRequest, ApeKeyParams>
): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
await ApeKeysDAL.editApeKey(uid, apeKeyId, name, enabled);
return new MonkeyResponse2("ApeKey updated", null);
return new MonkeyResponse("ApeKey updated", null);
}
export async function deleteApeKey(
req: MonkeyTypes.Request2<undefined, undefined, ApeKeyParams>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, undefined, ApeKeyParams>
): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { uid } = req.ctx.decodedToken;
await ApeKeysDAL.deleteApeKey(uid, apeKeyId);
return new MonkeyResponse2("ApeKey deleted", null);
return new MonkeyResponse("ApeKey deleted", null);
}

View file

@ -1,33 +1,33 @@
import { PartialConfig } from "@monkeytype/contracts/schemas/configs";
import * as ConfigDAL from "../../dal/config";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import { GetConfigResponse } from "@monkeytype/contracts/configs";
export async function getConfig(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetConfigResponse> {
const { uid } = req.ctx.decodedToken;
const data = (await ConfigDAL.getConfig(uid))?.config ?? null;
return new MonkeyResponse2("Configuration retrieved", data);
return new MonkeyResponse("Configuration retrieved", data);
}
export async function saveConfig(
req: MonkeyTypes.Request2<undefined, PartialConfig>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, PartialConfig>
): Promise<MonkeyResponse> {
const config = req.body;
const { uid } = req.ctx.decodedToken;
await ConfigDAL.saveConfig(uid, config);
return new MonkeyResponse2("Config updated", null);
return new MonkeyResponse("Config updated", null);
}
export async function deleteConfig(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await ConfigDAL.deleteConfig(uid);
return new MonkeyResponse2("Config deleted", null);
return new MonkeyResponse("Config deleted", null);
}

View file

@ -1,5 +1,5 @@
import * as Configuration from "../../init/configuration";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import { CONFIGURATION_FORM_SCHEMA } from "../../constants/base-configuration";
import {
ConfigurationSchemaResponse,
@ -9,24 +9,24 @@ import {
import MonkeyError from "../../utils/error";
export async function getConfiguration(
_req: MonkeyTypes.Request2
_req: MonkeyTypes.Request
): Promise<GetConfigurationResponse> {
const currentConfiguration = await Configuration.getLiveConfiguration();
return new MonkeyResponse2("Configuration retrieved", currentConfiguration);
return new MonkeyResponse("Configuration retrieved", currentConfiguration);
}
export async function getSchema(
_req: MonkeyTypes.Request2
_req: MonkeyTypes.Request
): Promise<ConfigurationSchemaResponse> {
return new MonkeyResponse2(
return new MonkeyResponse(
"Configuration schema retrieved",
CONFIGURATION_FORM_SCHEMA
);
}
export async function updateConfiguration(
req: MonkeyTypes.Request2<undefined, PatchConfigurationRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, PatchConfigurationRequest>
): Promise<MonkeyResponse> {
const { configuration } = req.body;
const success = await Configuration.patchConfiguration(configuration);
@ -34,5 +34,5 @@ export async function updateConfiguration(
throw new MonkeyError(500, "Configuration update failed");
}
return new MonkeyResponse2("Configuration updated", null);
return new MonkeyResponse("Configuration updated", null);
}

View file

@ -1,4 +1,4 @@
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import * as UserDal from "../../dal/user";
import FirebaseAdmin from "../../init/firebase-admin";
import Logger from "../../utils/logger";
@ -28,7 +28,7 @@ const CREATE_RESULT_DEFAULT_OPTIONS = {
};
export async function createTestData(
req: MonkeyTypes.Request2<undefined, GenerateDataRequest>
req: MonkeyTypes.Request<undefined, GenerateDataRequest>
): Promise<GenerateDataResponse> {
const { username, createUser } = req.body;
const user = await getOrCreateUser(username, "password", createUser);
@ -39,7 +39,7 @@ export async function createTestData(
await updateUser(uid);
await updateLeaderboard();
return new MonkeyResponse2("test data created", { uid, email });
return new MonkeyResponse("test data created", { uid, email });
}
async function getOrCreateUser(

View file

@ -4,7 +4,7 @@ import {
MILLISECONDS_IN_DAY,
getCurrentWeekTimestamp,
} from "../../utils/misc";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import * as LeaderboardsDAL from "../../dal/leaderboards";
import MonkeyError from "../../utils/error";
import * as DailyLeaderboards from "../../utils/daily-leaderboards";
@ -24,7 +24,7 @@ import {
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
export async function getLeaderboard(
req: MonkeyTypes.Request2<GetLeaderboardQuery>
req: MonkeyTypes.Request<GetLeaderboardQuery>
): Promise<GetLeaderboardResponse> {
const { language, mode, mode2, skip = 0, limit = 50 } = req.query;
@ -45,11 +45,11 @@ export async function getLeaderboard(
const normalizedLeaderboard = leaderboard.map((it) => _.omit(it, ["_id"]));
return new MonkeyResponse2("Leaderboard retrieved", normalizedLeaderboard);
return new MonkeyResponse("Leaderboard retrieved", normalizedLeaderboard);
}
export async function getRankFromLeaderboard(
req: MonkeyTypes.Request2<LanguageAndModeQuery>
req: MonkeyTypes.Request<LanguageAndModeQuery>
): Promise<GetLeaderboardRankResponse> {
const { language, mode, mode2 } = req.query;
const { uid } = req.ctx.decodedToken;
@ -62,7 +62,7 @@ export async function getRankFromLeaderboard(
);
}
return new MonkeyResponse2("Rank retrieved", data);
return new MonkeyResponse("Rank retrieved", data);
}
function getDailyLeaderboardWithError(
@ -89,7 +89,7 @@ function getDailyLeaderboardWithError(
}
export async function getDailyLeaderboard(
req: MonkeyTypes.Request2<GetDailyLeaderboardQuery>
req: MonkeyTypes.Request<GetDailyLeaderboardQuery>
): Promise<GetLeaderboardResponse> {
const { skip = 0, limit = 50 } = req.query;
@ -108,11 +108,11 @@ export async function getDailyLeaderboard(
req.ctx.configuration.users.premium.enabled
);
return new MonkeyResponse2("Daily leaderboard retrieved", topResults);
return new MonkeyResponse("Daily leaderboard retrieved", topResults);
}
export async function getDailyLeaderboardRank(
req: MonkeyTypes.Request2<GetDailyLeaderboardRankQuery>
req: MonkeyTypes.Request<GetDailyLeaderboardRankQuery>
): Promise<GetLeaderboardDailyRankResponse> {
const { uid } = req.ctx.decodedToken;
@ -126,7 +126,7 @@ export async function getDailyLeaderboardRank(
req.ctx.configuration.dailyLeaderboards
);
return new MonkeyResponse2("Daily leaderboard rank retrieved", rank);
return new MonkeyResponse("Daily leaderboard rank retrieved", rank);
}
function getWeeklyXpLeaderboardWithError(
@ -147,7 +147,7 @@ function getWeeklyXpLeaderboardWithError(
}
export async function getWeeklyXpLeaderboardResults(
req: MonkeyTypes.Request2<GetWeeklyXpLeaderboardQuery>
req: MonkeyTypes.Request<GetWeeklyXpLeaderboardQuery>
): Promise<GetWeeklyXpLeaderboardResponse> {
const { skip = 0, limit = 50 } = req.query;
@ -164,11 +164,11 @@ export async function getWeeklyXpLeaderboardResults(
req.ctx.configuration.leaderboards.weeklyXp
);
return new MonkeyResponse2("Weekly xp leaderboard retrieved", results);
return new MonkeyResponse("Weekly xp leaderboard retrieved", results);
}
export async function getWeeklyXpLeaderboardRank(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetWeeklyXpLeaderboardRankResponse> {
const { uid } = req.ctx.decodedToken;
@ -181,5 +181,5 @@ export async function getWeeklyXpLeaderboardRank(
req.ctx.configuration.leaderboards.weeklyXp
);
return new MonkeyResponse2("Weekly xp leaderboard rank retrieved", rankEntry);
return new MonkeyResponse("Weekly xp leaderboard rank retrieved", rankEntry);
}

View file

@ -5,12 +5,12 @@ import {
GetPresetResponse,
} from "@monkeytype/contracts/presets";
import * as PresetDAL from "../../dal/preset";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import { replaceObjectId } from "../../utils/misc";
import { EditPresetRequest } from "@monkeytype/contracts/schemas/presets";
export async function getPresets(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetPresetResponse> {
const { uid } = req.ctx.decodedToken;
@ -21,36 +21,36 @@ export async function getPresets(
}))
.map((it) => replaceObjectId(it));
return new MonkeyResponse2("Presets retrieved", data);
return new MonkeyResponse("Presets retrieved", data);
}
export async function addPreset(
req: MonkeyTypes.Request2<undefined, AddPresetRequest>
req: MonkeyTypes.Request<undefined, AddPresetRequest>
): Promise<AddPresetResponse> {
const { uid } = req.ctx.decodedToken;
const data = await PresetDAL.addPreset(uid, req.body);
return new MonkeyResponse2("Preset created", data);
return new MonkeyResponse("Preset created", data);
}
export async function editPreset(
req: MonkeyTypes.Request2<undefined, EditPresetRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, EditPresetRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await PresetDAL.editPreset(uid, req.body);
return new MonkeyResponse2("Preset updated", null);
return new MonkeyResponse("Preset updated", null);
}
export async function removePreset(
req: MonkeyTypes.Request2<undefined, undefined, DeletePresetsParams>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, undefined, DeletePresetsParams>
): Promise<MonkeyResponse> {
const { presetId } = req.params;
const { uid } = req.ctx.decodedToken;
await PresetDAL.removePreset(uid, presetId);
return new MonkeyResponse2("Preset deleted", null);
return new MonkeyResponse("Preset deleted", null);
}

View file

@ -1,11 +1,11 @@
import { GetPsaResponse } from "@monkeytype/contracts/psas";
import * as PsaDAL from "../../dal/psa";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import { replaceObjectIds } from "../../utils/misc";
export async function getPsas(
_req: MonkeyTypes.Request2
_req: MonkeyTypes.Request
): Promise<GetPsaResponse> {
const data = await PsaDAL.get();
return new MonkeyResponse2("PSAs retrieved", replaceObjectIds(data));
return new MonkeyResponse("PSAs retrieved", replaceObjectIds(data));
}

View file

@ -4,19 +4,19 @@ import {
GetTypingStatsResponse,
} from "@monkeytype/contracts/public";
import * as PublicDAL from "../../dal/public";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
export async function getSpeedHistogram(
req: MonkeyTypes.Request2<GetSpeedHistogramQuery>
req: MonkeyTypes.Request<GetSpeedHistogramQuery>
): Promise<GetSpeedHistogramResponse> {
const { language, mode, mode2 } = req.query;
const data = await PublicDAL.getSpeedHistogram(language, mode, mode2);
return new MonkeyResponse2("Public speed histogram retrieved", data);
return new MonkeyResponse("Public speed histogram retrieved", data);
}
export async function getTypingStats(
_req: MonkeyTypes.Request2
_req: MonkeyTypes.Request
): Promise<GetTypingStatsResponse> {
const data = await PublicDAL.getTypingStats();
return new MonkeyResponse2("Public typing stats retrieved", data);
return new MonkeyResponse("Public typing stats retrieved", data);
}

View file

@ -6,7 +6,7 @@ import * as NewQuotesDAL from "../../dal/new-quotes";
import * as QuoteRatingsDAL from "../../dal/quote-ratings";
import MonkeyError from "../../utils/error";
import { verify } from "../../utils/captcha";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import { ObjectId } from "mongodb";
import { addLog } from "../../dal/logs";
import {
@ -30,7 +30,7 @@ async function verifyCaptcha(captcha: string): Promise<void> {
}
export async function getQuotes(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetQuotesResponse> {
const { uid } = req.ctx.decodedToken;
const quoteMod = (await getPartialUser(uid, "get quotes", ["quoteMod"]))
@ -38,36 +38,36 @@ export async function getQuotes(
const quoteModString = quoteMod === true ? "all" : (quoteMod as string);
const data = await NewQuotesDAL.get(quoteModString);
return new MonkeyResponse2(
return new MonkeyResponse(
"Quote submissions retrieved",
replaceObjectIds(data)
);
}
export async function isSubmissionEnabled(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<IsSubmissionEnabledResponse> {
const { submissionsEnabled } = req.ctx.configuration.quotes;
return new MonkeyResponse2(
return new MonkeyResponse(
"Quote submission " + (submissionsEnabled ? "enabled" : "disabled"),
{ isEnabled: submissionsEnabled }
);
}
export async function addQuote(
req: MonkeyTypes.Request2<undefined, AddQuoteRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, AddQuoteRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { text, source, language, captcha } = req.body;
await verifyCaptcha(captcha);
await NewQuotesDAL.add(text, source, language, uid);
return new MonkeyResponse2("Quote submission added", null);
return new MonkeyResponse("Quote submission added", null);
}
export async function approveQuote(
req: MonkeyTypes.Request2<undefined, ApproveQuoteRequest>
req: MonkeyTypes.Request<undefined, ApproveQuoteRequest>
): Promise<ApproveQuoteResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, editText, editSource } = req.body;
@ -81,31 +81,31 @@ export async function approveQuote(
const data = await NewQuotesDAL.approve(quoteId, editText, editSource, name);
void addLog("system_quote_approved", data, uid);
return new MonkeyResponse2(data.message, data.quote);
return new MonkeyResponse(data.message, data.quote);
}
export async function refuseQuote(
req: MonkeyTypes.Request2<undefined, RejectQuoteRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, RejectQuoteRequest>
): Promise<MonkeyResponse> {
const { quoteId } = req.body;
await NewQuotesDAL.refuse(quoteId);
return new MonkeyResponse2("Quote refused", null);
return new MonkeyResponse("Quote refused", null);
}
export async function getRating(
req: MonkeyTypes.Request2<GetQuoteRatingQuery>
req: MonkeyTypes.Request<GetQuoteRatingQuery>
): Promise<GetQuoteRatingResponse> {
const { quoteId, language } = req.query;
const data = await QuoteRatingsDAL.get(quoteId, language);
return new MonkeyResponse2("Rating retrieved", replaceObjectId(data));
return new MonkeyResponse("Rating retrieved", replaceObjectId(data));
}
export async function submitRating(
req: MonkeyTypes.Request2<undefined, AddQuoteRatingRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, AddQuoteRatingRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, rating, language } = req.body;
@ -131,12 +131,12 @@ export async function submitRating(
const responseMessage = `Rating ${
shouldUpdateRating ? "updated" : "submitted"
}`;
return new MonkeyResponse2(responseMessage, null);
return new MonkeyResponse(responseMessage, null);
}
export async function reportQuote(
req: MonkeyTypes.Request2<undefined, ReportQuoteRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, ReportQuoteRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {
reporting: { maxReports, contentReportLimit },
@ -159,5 +159,5 @@ export async function reportQuote(
await ReportDAL.createReport(newReport, maxReports, contentReportLimit);
return new MonkeyResponse2("Quote reported", null);
return new MonkeyResponse("Quote reported", null);
}

View file

@ -12,7 +12,7 @@ import {
import objectHash from "object-hash";
import Logger from "../../utils/logger";
import "dotenv/config";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import MonkeyError from "../../utils/error";
import { areFunboxesCompatible, isTestTooShort } from "../../utils/validation";
import {
@ -73,7 +73,7 @@ try {
}
export async function getResults(
req: MonkeyTypes.Request2<GetResultsQuery>
req: MonkeyTypes.Request<GetResultsQuery>
): Promise<GetResultsResponse> {
const { uid } = req.ctx.decodedToken;
const premiumFeaturesEnabled = req.ctx.configuration.users.premium.enabled;
@ -122,29 +122,29 @@ export async function getResults(
},
uid
);
return new MonkeyResponse2("Results retrieved", results.map(convertResult));
return new MonkeyResponse("Results retrieved", results.map(convertResult));
}
export async function getLastResult(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetLastResultResponse> {
const { uid } = req.ctx.decodedToken;
const results = await ResultDAL.getLastResult(uid);
return new MonkeyResponse2("Result retrieved", convertResult(results));
return new MonkeyResponse("Result retrieved", convertResult(results));
}
export async function deleteAll(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await ResultDAL.deleteAll(uid);
void addLog("user_results_deleted", "", uid);
return new MonkeyResponse2("All results deleted", null);
return new MonkeyResponse("All results deleted", null);
}
export async function updateTags(
req: MonkeyTypes.Request2<undefined, UpdateResultTagsRequest>
req: MonkeyTypes.Request<undefined, UpdateResultTagsRequest>
): Promise<UpdateResultTagsResponse> {
const { uid } = req.ctx.decodedToken;
const { tagIds, resultId } = req.body;
@ -173,13 +173,13 @@ export async function updateTags(
const user = await UserDAL.getPartialUser(uid, "update tags", ["tags"]);
const tagPbs = await UserDAL.checkIfTagPb(uid, user, result);
return new MonkeyResponse2("Result tags updated", {
return new MonkeyResponse("Result tags updated", {
tagPbs,
});
}
export async function addResult(
req: MonkeyTypes.Request2<undefined, AddResultRequest>
req: MonkeyTypes.Request<undefined, AddResultRequest>
): Promise<AddResultResponse> {
const { uid } = req.ctx.decodedToken;
@ -630,7 +630,7 @@ export async function addResult(
incrementResult(completedEvent, dbresult.isPb);
return new MonkeyResponse2("Result saved", data);
return new MonkeyResponse("Result saved", data);
}
type XpResult = {

View file

@ -1,7 +1,7 @@
import _ from "lodash";
import * as UserDAL from "../../dal/user";
import MonkeyError from "../../utils/error";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import * as DiscordUtils from "../../utils/discord";
import {
MILLISECONDS_IN_DAY,
@ -102,8 +102,8 @@ async function verifyCaptcha(captcha: string): Promise<void> {
}
export async function createNewUser(
req: MonkeyTypes.Request2<undefined, CreateUserRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, CreateUserRequest>
): Promise<MonkeyResponse> {
const { name, captcha } = req.body;
const { email, uid } = req.ctx.decodedToken;
@ -127,7 +127,7 @@ export async function createNewUser(
await UserDAL.addUser(name, email, uid);
void addImportantLog("user_created", `${name} ${email}`, uid);
return new MonkeyResponse2("User created", null);
return new MonkeyResponse("User created", null);
} catch (e) {
//user was created in firebase from the frontend, remove it
await firebaseDeleteUserIgnoreError(uid);
@ -136,8 +136,8 @@ export async function createNewUser(
}
export async function sendVerificationEmail(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { email, uid } = req.ctx.decodedToken;
const isVerified = (
await FirebaseAdmin()
@ -212,23 +212,23 @@ export async function sendVerificationEmail(
}
await emailQueue.sendVerificationEmail(email, userInfo.name, link);
return new MonkeyResponse2("Email sent", null);
return new MonkeyResponse("Email sent", null);
}
export async function sendForgotPasswordEmail(
req: MonkeyTypes.Request2<undefined, ForgotPasswordEmailRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, ForgotPasswordEmailRequest>
): Promise<MonkeyResponse> {
const { email } = req.body;
await authSendForgotPasswordEmail(email);
return new MonkeyResponse2(
return new MonkeyResponse(
"Password reset request received. If the email is valid, you will receive an email shortly.",
null
);
}
export async function deleteUser(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const userInfo = await UserDAL.getPartialUser(uid, "delete user", [
@ -265,12 +265,12 @@ export async function deleteUser(
uid
);
return new MonkeyResponse2("User deleted", null);
return new MonkeyResponse("User deleted", null);
}
export async function resetUser(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const userInfo = await UserDAL.getPartialUser(uid, "reset user", [
@ -301,12 +301,12 @@ export async function resetUser(
await Promise.all(promises);
void addImportantLog("user_reset", `${userInfo.email} ${userInfo.name}`, uid);
return new MonkeyResponse2("User reset", null);
return new MonkeyResponse("User reset", null);
}
export async function updateName(
req: MonkeyTypes.Request2<undefined, UpdateUserNameRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, UpdateUserNameRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { name } = req.body;
@ -335,12 +335,12 @@ export async function updateName(
uid
);
return new MonkeyResponse2("User's name updated", null);
return new MonkeyResponse("User's name updated", null);
}
export async function clearPb(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await UserDAL.clearPb(uid);
@ -350,12 +350,12 @@ export async function clearPb(
);
void addImportantLog("user_cleared_pbs", "", uid);
return new MonkeyResponse2("User's PB cleared", null);
return new MonkeyResponse("User's PB cleared", null);
}
export async function optOutOfLeaderboards(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await UserDAL.optOutOfLeaderboards(uid);
@ -365,12 +365,12 @@ export async function optOutOfLeaderboards(
);
void addImportantLog("user_opted_out_of_leaderboards", "", uid);
return new MonkeyResponse2("User opted out of leaderboards", null);
return new MonkeyResponse("User opted out of leaderboards", null);
}
export async function checkName(
req: MonkeyTypes.Request2<undefined, undefined, CheckNamePathParameters>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, undefined, CheckNamePathParameters>
): Promise<MonkeyResponse> {
const { name } = req.params;
const { uid } = req.ctx.decodedToken;
@ -379,12 +379,12 @@ export async function checkName(
throw new MonkeyError(409, "Username unavailable");
}
return new MonkeyResponse2("Username available", null);
return new MonkeyResponse("Username available", null);
}
export async function updateEmail(
req: MonkeyTypes.Request2<undefined, UpdateEmailRequestSchema>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, UpdateEmailRequestSchema>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
let { newEmail } = req.body;
@ -423,18 +423,18 @@ export async function updateEmail(
uid
);
return new MonkeyResponse2("Email updated", null);
return new MonkeyResponse("Email updated", null);
}
export async function updatePassword(
req: MonkeyTypes.Request2<undefined, UpdatePasswordRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, UpdatePasswordRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { newPassword } = req.body;
await AuthUtil.updateUserPassword(uid, newPassword);
return new MonkeyResponse2("Password updated", null);
return new MonkeyResponse("Password updated", null);
}
type RelevantUserInfo = Omit<
@ -467,7 +467,7 @@ function getRelevantUserInfo(user: MonkeyTypes.DBUser): RelevantUserInfo {
}
export async function getUser(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetUserResponse> {
const { uid } = req.ctx.decodedToken;
@ -555,14 +555,14 @@ export async function getUser(
testActivity,
};
return new MonkeyResponse2("User data retrieved", {
return new MonkeyResponse("User data retrieved", {
...userData,
inboxUnreadSize: inboxUnreadSize,
});
}
export async function getOauthLink(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetDiscordOauthLinkResponse> {
const { uid } = req.ctx.decodedToken;
@ -570,13 +570,13 @@ export async function getOauthLink(
const url = await DiscordUtils.getOauthLink(uid);
//return
return new MonkeyResponse2("Discord oauth link generated", {
return new MonkeyResponse("Discord oauth link generated", {
url: url,
});
}
export async function linkDiscord(
req: MonkeyTypes.Request2<undefined, LinkDiscordRequest>
req: MonkeyTypes.Request<undefined, LinkDiscordRequest>
): Promise<LinkDiscordResponse> {
const { uid } = req.ctx.decodedToken;
const { tokenType, accessToken, state } = req.body;
@ -598,7 +598,7 @@ export async function linkDiscord(
if (userInfo.discordId !== undefined && userInfo.discordId !== "") {
await UserDAL.linkDiscord(uid, userInfo.discordId, discordAvatar);
return new MonkeyResponse2("Discord avatar updated", {
return new MonkeyResponse("Discord avatar updated", {
discordId,
discordAvatar,
});
@ -629,15 +629,15 @@ export async function linkDiscord(
await GeorgeQueue.linkDiscord(discordId, uid);
void addImportantLog("user_discord_link", `linked to ${discordId}`, uid);
return new MonkeyResponse2("Discord account linked", {
return new MonkeyResponse("Discord account linked", {
discordId,
discordAvatar,
});
}
export async function unlinkDiscord(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const userInfo = await UserDAL.getPartialUser(uid, "unlink discord", [
@ -658,11 +658,11 @@ export async function unlinkDiscord(
await UserDAL.unlinkDiscord(uid);
void addImportantLog("user_discord_unlinked", discordId, uid);
return new MonkeyResponse2("Discord account unlinked", null);
return new MonkeyResponse("Discord account unlinked", null);
}
export async function addResultFilterPreset(
req: MonkeyTypes.Request2<undefined, AddResultFilterPresetRequest>
req: MonkeyTypes.Request<undefined, AddResultFilterPresetRequest>
): Promise<AddResultFilterPresetResponse> {
const { uid } = req.ctx.decodedToken;
const filter = req.body;
@ -673,158 +673,158 @@ export async function addResultFilterPreset(
filter,
maxPresetsPerUser
);
return new MonkeyResponse2(
return new MonkeyResponse(
"Result filter preset created",
createdId.toHexString()
);
}
export async function removeResultFilterPreset(
req: MonkeyTypes.Request2<
req: MonkeyTypes.Request<
undefined,
undefined,
RemoveResultFilterPresetPathParams
>
): Promise<MonkeyResponse2> {
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { presetId } = req.params;
await UserDAL.removeResultFilterPreset(uid, presetId);
return new MonkeyResponse2("Result filter preset deleted", null);
return new MonkeyResponse("Result filter preset deleted", null);
}
export async function addTag(
req: MonkeyTypes.Request2<undefined, AddTagRequest>
req: MonkeyTypes.Request<undefined, AddTagRequest>
): Promise<AddTagResponse> {
const { uid } = req.ctx.decodedToken;
const { tagName } = req.body;
const tag = await UserDAL.addTag(uid, tagName);
return new MonkeyResponse2("Tag updated", replaceObjectId(tag));
return new MonkeyResponse("Tag updated", replaceObjectId(tag));
}
export async function clearTagPb(
req: MonkeyTypes.Request2<undefined, undefined, TagIdPathParams>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, undefined, TagIdPathParams>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagId } = req.params;
await UserDAL.removeTagPb(uid, tagId);
return new MonkeyResponse2("Tag PB cleared", null);
return new MonkeyResponse("Tag PB cleared", null);
}
export async function editTag(
req: MonkeyTypes.Request2<undefined, EditTagRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, EditTagRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagId, newName } = req.body;
await UserDAL.editTag(uid, tagId, newName);
return new MonkeyResponse2("Tag updated", null);
return new MonkeyResponse("Tag updated", null);
}
export async function removeTag(
req: MonkeyTypes.Request2<undefined, undefined, TagIdPathParams>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, undefined, TagIdPathParams>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagId } = req.params;
await UserDAL.removeTag(uid, tagId);
return new MonkeyResponse2("Tag deleted", null);
return new MonkeyResponse("Tag deleted", null);
}
export async function getTags(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetTagsResponse> {
const { uid } = req.ctx.decodedToken;
const tags = await UserDAL.getTags(uid);
return new MonkeyResponse2("Tags retrieved", replaceObjectIds(tags));
return new MonkeyResponse("Tags retrieved", replaceObjectIds(tags));
}
export async function updateLbMemory(
req: MonkeyTypes.Request2<undefined, UpdateLeaderboardMemoryRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, UpdateLeaderboardMemoryRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, language, rank } = req.body;
const mode2 = req.body.mode2;
await UserDAL.updateLbMemory(uid, mode, mode2, language, rank);
return new MonkeyResponse2("Leaderboard memory updated", null);
return new MonkeyResponse("Leaderboard memory updated", null);
}
export async function getCustomThemes(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetCustomThemesResponse> {
const { uid } = req.ctx.decodedToken;
const customThemes = await UserDAL.getThemes(uid);
return new MonkeyResponse2(
return new MonkeyResponse(
"Custom themes retrieved",
replaceObjectIds(customThemes)
);
}
export async function addCustomTheme(
req: MonkeyTypes.Request2<undefined, AddCustomThemeRequest>
req: MonkeyTypes.Request<undefined, AddCustomThemeRequest>
): Promise<AddCustomThemeResponse> {
const { uid } = req.ctx.decodedToken;
const { name, colors } = req.body;
const addedTheme = await UserDAL.addTheme(uid, { name, colors });
return new MonkeyResponse2("Custom theme added", replaceObjectId(addedTheme));
return new MonkeyResponse("Custom theme added", replaceObjectId(addedTheme));
}
export async function removeCustomTheme(
req: MonkeyTypes.Request2<undefined, DeleteCustomThemeRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, DeleteCustomThemeRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId } = req.body;
await UserDAL.removeTheme(uid, themeId);
return new MonkeyResponse2("Custom theme removed", null);
return new MonkeyResponse("Custom theme removed", null);
}
export async function editCustomTheme(
req: MonkeyTypes.Request2<undefined, EditCustomThemeRequst>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, EditCustomThemeRequst>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId, theme } = req.body;
await UserDAL.editTheme(uid, themeId, theme);
return new MonkeyResponse2("Custom theme updated", null);
return new MonkeyResponse("Custom theme updated", null);
}
export async function getPersonalBests(
req: MonkeyTypes.Request2<GetPersonalBestsQuery>
req: MonkeyTypes.Request<GetPersonalBestsQuery>
): Promise<GetPersonalBestsResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, mode2 } = req.query;
const data = (await UserDAL.getPersonalBests(uid, mode, mode2)) ?? null;
return new MonkeyResponse2("Personal bests retrieved", data);
return new MonkeyResponse("Personal bests retrieved", data);
}
export async function getStats(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetStatsResponse> {
const { uid } = req.ctx.decodedToken;
const data = (await UserDAL.getStats(uid)) ?? null;
return new MonkeyResponse2("Personal stats retrieved", data);
return new MonkeyResponse("Personal stats retrieved", data);
}
export async function getFavoriteQuotes(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetFavoriteQuotesResponse> {
const { uid } = req.ctx.decodedToken;
const quotes = await UserDAL.getFavoriteQuotes(uid);
return new MonkeyResponse2("Favorite quotes retrieved", quotes);
return new MonkeyResponse("Favorite quotes retrieved", quotes);
}
export async function addFavoriteQuote(
req: MonkeyTypes.Request2<undefined, AddFavoriteQuoteRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, AddFavoriteQuoteRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { language, quoteId } = req.body;
@ -836,22 +836,22 @@ export async function addFavoriteQuote(
req.ctx.configuration.quotes.maxFavorites
);
return new MonkeyResponse2("Quote added to favorites", null);
return new MonkeyResponse("Quote added to favorites", null);
}
export async function removeFavoriteQuote(
req: MonkeyTypes.Request2<undefined, RemoveFavoriteQuoteRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, RemoveFavoriteQuoteRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, language } = req.body;
await UserDAL.removeFavoriteQuote(uid, language, quoteId);
return new MonkeyResponse2("Quote removed from favorites", null);
return new MonkeyResponse("Quote removed from favorites", null);
}
export async function getProfile(
req: MonkeyTypes.Request2<GetProfileQuery, undefined, GetProfilePathParams>
req: MonkeyTypes.Request<GetProfileQuery, undefined, GetProfilePathParams>
): Promise<GetProfileResponse> {
const { uidOrName } = req.params;
@ -906,7 +906,7 @@ export async function getProfile(
};
if (banned) {
return new MonkeyResponse2("Profile retrived: banned user", baseProfile);
return new MonkeyResponse("Profile retrived: banned user", baseProfile);
}
const allTimeLbs = await getAllTimeLbs(user.uid);
@ -919,11 +919,11 @@ export async function getProfile(
uid: user.uid,
} as UserProfile;
return new MonkeyResponse2("Profile retrieved", profileData);
return new MonkeyResponse("Profile retrieved", profileData);
}
export async function updateProfile(
req: MonkeyTypes.Request2<undefined, UpdateUserProfileRequest>
req: MonkeyTypes.Request<undefined, UpdateUserProfileRequest>
): Promise<UpdateUserProfileResponse> {
const { uid } = req.ctx.decodedToken;
const { bio, keyboard, socialProfiles, selectedBadgeId } = req.body;
@ -956,25 +956,25 @@ export async function updateProfile(
await UserDAL.updateProfile(uid, profileDetailsUpdates, user.inventory);
return new MonkeyResponse2("Profile updated", profileDetailsUpdates);
return new MonkeyResponse("Profile updated", profileDetailsUpdates);
}
export async function getInbox(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetUserInboxResponse> {
const { uid } = req.ctx.decodedToken;
const inbox = await UserDAL.getInbox(uid);
return new MonkeyResponse2("Inbox retrieved", {
return new MonkeyResponse("Inbox retrieved", {
inbox,
maxMail: req.ctx.configuration.users.inbox.maxMail,
});
}
export async function updateInbox(
req: MonkeyTypes.Request2<undefined, UpdateUserInboxRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, UpdateUserInboxRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mailIdsToMarkRead, mailIdsToDelete } = req.body;
@ -984,12 +984,12 @@ export async function updateInbox(
mailIdsToDelete ?? []
);
return new MonkeyResponse2("Inbox updated", null);
return new MonkeyResponse("Inbox updated", null);
}
export async function reportUser(
req: MonkeyTypes.Request2<undefined, ReportUserRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, ReportUserRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {
reporting: { maxReports, contentReportLimit },
@ -1012,12 +1012,12 @@ export async function reportUser(
await ReportDAL.createReport(newReport, maxReports, contentReportLimit);
return new MonkeyResponse2("User reported", null);
return new MonkeyResponse("User reported", null);
}
export async function setStreakHourOffset(
req: MonkeyTypes.Request2<undefined, SetStreakHourOffsetRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, SetStreakHourOffsetRequest>
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { hourOffset } = req.body;
@ -1036,16 +1036,16 @@ export async function setStreakHourOffset(
void addImportantLog("user_streak_hour_offset_set", { hourOffset }, uid);
return new MonkeyResponse2("Streak hour offset set", null);
return new MonkeyResponse("Streak hour offset set", null);
}
export async function revokeAllTokens(
req: MonkeyTypes.Request2
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await AuthUtil.revokeTokensByUid(uid);
void addImportantLog("user_tokens_revoked", "", uid);
return new MonkeyResponse2("All tokens revoked", null);
return new MonkeyResponse("All tokens revoked", null);
}
async function getAllTimeLbs(uid: string): Promise<AllTimeLbs> {
@ -1128,7 +1128,7 @@ export function generateCurrentTestActivity(
}
export async function getTestActivity(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetTestActivityResponse> {
const { uid } = req.ctx.decodedToken;
const premiumFeaturesEnabled = req.ctx.configuration.users.premium.enabled;
@ -1146,7 +1146,7 @@ export async function getTestActivity(
throw new MonkeyError(503, "User does not have premium");
}
return new MonkeyResponse2(
return new MonkeyResponse(
"Test activity data retrieved",
user.testActivity ?? null
);
@ -1161,7 +1161,7 @@ async function firebaseDeleteUserIgnoreError(uid: string): Promise<void> {
}
export async function getCurrentTestActivity(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetCurrentTestActivityResponse> {
const { uid } = req.ctx.decodedToken;
@ -1169,18 +1169,18 @@ export async function getCurrentTestActivity(
"testActivity",
]);
const data = generateCurrentTestActivity(user.testActivity);
return new MonkeyResponse2(
return new MonkeyResponse(
"Current test activity data retrieved",
data ?? null
);
}
export async function getStreak(
req: MonkeyTypes.Request2
req: MonkeyTypes.Request
): Promise<GetStreakResponseSchema> {
const { uid } = req.ctx.decodedToken;
const user = await UserDAL.getPartialUser(uid, "streak", ["streak"]);
return new MonkeyResponse2("Streak data retrieved", user.streak ?? null);
return new MonkeyResponse("Streak data retrieved", user.streak ?? null);
}

View file

@ -1,11 +1,11 @@
import { PostGithubReleaseRequest } from "@monkeytype/contracts/webhooks";
import GeorgeQueue from "../../queues/george-queue";
import { MonkeyResponse2 } from "../../utils/monkey-response";
import { MonkeyResponse } from "../../utils/monkey-response";
import MonkeyError from "../../utils/error";
export async function githubRelease(
req: MonkeyTypes.Request2<undefined, PostGithubReleaseRequest>
): Promise<MonkeyResponse2> {
req: MonkeyTypes.Request<undefined, PostGithubReleaseRequest>
): Promise<MonkeyResponse> {
const action = req.body.action;
if (action === "published") {
@ -14,10 +14,7 @@ export async function githubRelease(
throw new MonkeyError(422, 'Missing property "release.id".');
await GeorgeQueue.sendReleaseAnnouncement(releaseId);
return new MonkeyResponse2(
"Added release announcement task to queue",
null
);
return new MonkeyResponse("Added release announcement task to queue", null);
}
return new MonkeyResponse2("No action taken", null);
return new MonkeyResponse("No action taken", null);
}

View file

@ -17,7 +17,6 @@ import configuration from "./configuration";
import { version } from "../../version";
import leaderboards from "./leaderboards";
import addSwaggerMiddlewares from "./swagger";
import { asyncHandler } from "../../middlewares/utility";
import { MonkeyResponse } from "../../utils/monkey-response";
import {
Application,
@ -68,15 +67,16 @@ export function addApiRoutes(app: Application): void {
applyApiRoutes(app);
applyTsRestApiRoutes(app);
app.use(
asyncHandler(async (req, _res) => {
return new MonkeyResponse(
`Unknown request URL (${req.method}: ${req.path})`,
null,
404
app.use((req, res) => {
res
.status(404)
.json(
new MonkeyResponse(
`Unknown request URL (${req.method}: ${req.path})`,
null
)
);
})
);
});
}
function applyTsRestApiRoutes(app: IRouter): void {
@ -155,7 +155,11 @@ function applyApiRoutes(app: Application): void {
addSwaggerMiddlewares(app);
app.use(
(req: MonkeyTypes.Request, res: Response, next: NextFunction): void => {
(
req: MonkeyTypes.ExpressRequestWithContext,
res: Response,
next: NextFunction
): void => {
if (req.path.startsWith("/configuration")) {
next();
return;
@ -174,25 +178,13 @@ function applyApiRoutes(app: Application): void {
}
);
app.get(
"/",
asyncHandler(async (_req, _res) => {
return new MonkeyResponse("ok", {
app.get("/", (_req, res) => {
res.status(200).json(
new MonkeyResponse("ok", {
uptime: Date.now() - APP_START_TIME,
version,
});
})
);
//legacy route
app.get("/psa", (_req, res) => {
res.json([
{
message:
"It seems like your client version is very out of date as you're requesting an API endpoint that no longer exists. This will likely cause most of the website to not function correctly. Please clear your cache, or contact support if this message persists.",
sticky: true,
},
]);
})
);
});
_.each(API_ROUTE_MAP, (router: Router, route) => {

View file

@ -1,90 +0,0 @@
import joi from "joi";
const FILTER_SCHEMA = {
_id: joi.string().required(),
name: joi
.string()
.required()
.regex(/^[0-9a-zA-Z_.-]+$/)
.max(16)
.messages({
"string.pattern.base":
"Filter name invalid. Name cannot contain special characters or more than 16 characters. Can include _ . and -",
"string.max": "Filter name exceeds maximum of 16 characters",
}),
pb: joi.object({
no: joi.bool().required(),
yes: joi.bool().required(),
}),
difficulty: joi
.object({
normal: joi.bool().required(),
expert: joi.bool().required(),
master: joi.bool().required(),
})
.required(),
mode: joi
.object({
words: joi.bool().required(),
time: joi.bool().required(),
quote: joi.bool().required(),
zen: joi.bool().required(),
custom: joi.bool().required(),
})
.required(),
words: joi
.object({
10: joi.bool().required(),
25: joi.bool().required(),
50: joi.bool().required(),
100: joi.bool().required(),
custom: joi.bool().required(),
})
.required(),
time: joi
.object({
15: joi.bool().required(),
30: joi.bool().required(),
60: joi.bool().required(),
120: joi.bool().required(),
custom: joi.bool().required(),
})
.required(),
quoteLength: joi
.object({
short: joi.bool().required(),
medium: joi.bool().required(),
long: joi.bool().required(),
thicc: joi.bool().required(),
})
.required(),
punctuation: joi
.object({
on: joi.bool().required(),
off: joi.bool().required(),
})
.required(),
numbers: joi
.object({
on: joi.bool().required(),
off: joi.bool().required(),
})
.required(),
date: joi
.object({
last_day: joi.bool().required(),
last_week: joi.bool().required(),
last_month: joi.bool().required(),
last_3months: joi.bool().required(),
all: joi.bool().required(),
})
.required(),
tags: joi.object().pattern(joi.string().token(), joi.bool()).required(),
language: joi
.object()
.pattern(joi.string().pattern(/^[a-zA-Z0-9_+]+$/), joi.bool())
.required(),
funbox: joi.object().pattern(/\w+/, joi.bool()).required(),
};
export default FILTER_SCHEMA;

View file

@ -1,6 +1,6 @@
import { AppRoute, AppRouter } from "@ts-rest/core";
import { TsRestRequest } from "@ts-rest/express";
import { MonkeyResponse2 } from "../utils/monkey-response";
import { MonkeyResponse } from "../utils/monkey-response";
export function callController<
TRoute extends AppRoute | AppRouter,
TQuery,
@ -17,7 +17,7 @@ export function callController<
body: { message: string; data: TResponse };
}> {
return async (all) => {
const req: MonkeyTypes.Request2<TQuery, TBody, TParams> = {
const req: MonkeyTypes.Request<TQuery, TBody, TParams> = {
body: all.body as TBody,
query: all.query as TQuery,
params: all.params as TParams,
@ -60,8 +60,8 @@ type WithoutParams = {
};
type Handler<TQuery, TBody, TParams, TResponse> = (
req: MonkeyTypes.Request2<TQuery, TBody, TParams>
) => Promise<MonkeyResponse2<TResponse>>;
req: MonkeyTypes.Request<TQuery, TBody, TParams>
) => Promise<MonkeyResponse<TResponse>>;
type RequestType2<
TRoute extends AppRoute | AppRouter,

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 { NextFunction, Response } from "express";
import statuses from "../constants/monkey-status-codes";
import {
incrementAuth,
@ -51,102 +51,79 @@ export function authenticateTsRestRequest<
...((req.tsRestRoute["metadata"]?.["authenticationOptions"] ??
{}) as EndpointMetadata),
};
return _authenticateRequestInternal(req, _res, next, options);
};
}
export function authenticateRequest(authOptions = DEFAULT_OPTIONS): Handler {
const options = {
...DEFAULT_OPTIONS,
...authOptions,
};
const startTime = performance.now();
let token: MonkeyTypes.DecodedToken;
let authType = "None";
return async (
req: MonkeyTypes.Request,
_res: Response,
next: NextFunction
): Promise<void> => {
return _authenticateRequestInternal(req, _res, next, options);
};
}
const isPublic =
options.isPublic || (options.isPublicOnDev && isDevEnvironment());
async function _authenticateRequestInternal(
req: MonkeyTypes.Request | TsRestRequestWithCtx,
_res: Response,
next: NextFunction,
options: RequestAuthenticationOptions
): Promise<void> {
const startTime = performance.now();
let token: MonkeyTypes.DecodedToken;
let authType = "None";
const {
authorization: authHeader,
"x-hub-signature-256": githubWebhookHeader,
} = req.headers;
const isPublic =
options.isPublic || (options.isPublicOnDev && isDevEnvironment());
try {
if (options.isGithubWebhook) {
token = authenticateGithubWebhook(req, githubWebhookHeader);
} else if (authHeader !== undefined && authHeader !== "") {
token = await authenticateWithAuthHeader(
authHeader,
req.ctx.configuration,
options
);
} else if (isPublic === true) {
token = {
type: "None",
uid: "",
email: "",
};
} else {
throw new MonkeyError(
401,
"Unauthorized",
`endpoint: ${req.baseUrl} no authorization header found`
);
}
const {
authorization: authHeader,
"x-hub-signature-256": githubWebhookHeader,
} = req.headers;
incrementAuth(token.type);
try {
if (options.isGithubWebhook) {
token = authenticateGithubWebhook(req, githubWebhookHeader);
} else if (authHeader !== undefined && authHeader !== "") {
token = await authenticateWithAuthHeader(
authHeader,
req.ctx.configuration,
options
);
} else if (isPublic === true) {
token = {
type: "None",
uid: "",
email: "",
req.ctx = {
...req.ctx,
decodedToken: token,
};
} else {
throw new MonkeyError(
401,
"Unauthorized",
`endpoint: ${req.baseUrl} no authorization header found`
} catch (error) {
authType = authHeader?.split(" ")[0] ?? "None";
recordAuthTime(
authType,
"failure",
Math.round(performance.now() - startTime),
req
);
next(error);
return;
}
incrementAuth(token.type);
req.ctx = {
...req.ctx,
decodedToken: token,
};
} catch (error) {
authType = authHeader?.split(" ")[0] ?? "None";
recordAuthTime(
authType,
"failure",
token.type,
"success",
Math.round(performance.now() - startTime),
req
);
next(error);
return;
}
recordAuthTime(
token.type,
"success",
Math.round(performance.now() - startTime),
req
);
const country = req.headers["cf-ipcountry"] as string;
if (country) {
recordRequestCountry(country, req);
}
const country = req.headers["cf-ipcountry"] as string;
if (country) {
recordRequestCountry(country, req);
}
// if (req.method !== "OPTIONS" && req?.ctx?.decodedToken?.uid) {
// recordRequestForUid(req.ctx.decodedToken.uid);
// }
// if (req.method !== "OPTIONS" && req?.ctx?.decodedToken?.uid) {
// recordRequestForUid(req.ctx.decodedToken.uid);
// }
next();
next();
};
}
async function authenticateWithAuthHeader(
@ -333,7 +310,7 @@ async function authenticateWithUid(
}
export function authenticateGithubWebhook(
req: MonkeyTypes.Request,
req: TsRestRequest,
authHeader: string | string[] | undefined
): MonkeyTypes.DecodedToken {
try {

View file

@ -1,8 +1,14 @@
import { getCachedConfiguration } from "../init/configuration";
import type { Response, NextFunction } from "express";
/**
* Add the context to the request
* @param req
* @param _res
* @param next
*/
async function contextMiddleware(
req: MonkeyTypes.Request,
req: MonkeyTypes.ExpressRequestWithContext,
_res: Response,
next: NextFunction
): Promise<void> {

View file

@ -4,7 +4,8 @@ import Logger from "../utils/logger";
import MonkeyError from "../utils/error";
import { incrementBadAuth } from "./rate-limit";
import type { NextFunction, Response } from "express";
import { MonkeyResponse, handleMonkeyResponse } from "../utils/monkey-response";
import { isCustomCode } from "../constants/monkey-status-codes";
import {
recordClientErrorByVersion,
recordServerErrorByVersion,
@ -26,49 +27,48 @@ type DBError = {
url: string;
};
type ErrorData = {
errorId?: string;
uid: string;
};
async function errorHandlingMiddleware(
error: Error,
req: MonkeyTypes.Request,
req: MonkeyTypes.ExpressRequestWithContext,
res: Response,
_next: NextFunction
): Promise<void> {
try {
const monkeyError = error as MonkeyError;
const monkeyResponse = new MonkeyResponse();
monkeyResponse.status = 500;
monkeyResponse.data = {
let status = 500;
const data: { errorId?: string; uid: string } = {
errorId: monkeyError.errorId ?? uuidv4(),
uid: monkeyError.uid ?? req.ctx?.decodedToken?.uid,
};
let message = "Unknown error";
if (/ECONNREFUSED.*27017/i.test(error.message)) {
monkeyResponse.message =
"Could not connect to the database. It may be down.";
message = "Could not connect to the database. It may be down.";
} else if (error instanceof URIError || error instanceof SyntaxError) {
monkeyResponse.status = 400;
monkeyResponse.message = "Unprocessable request";
status = 400;
message = "Unprocessable request";
} else if (error instanceof MonkeyError) {
monkeyResponse.message = error.message;
monkeyResponse.status = error.status;
message = error.message;
status = error.status;
} else {
monkeyResponse.message = `Oops! Our monkeys dropped their bananas. Please try again later. - ${monkeyResponse.data.errorId}`;
message = `Oops! Our monkeys dropped their bananas. Please try again later. - ${data.errorId}`;
}
await incrementBadAuth(req, res, monkeyResponse.status);
await incrementBadAuth(req, res, status);
if (monkeyResponse.status >= 400 && monkeyResponse.status < 500) {
if (status >= 400 && status < 500) {
recordClientErrorByVersion(req.headers["x-client-version"] as string);
}
if (
!isDevEnvironment() &&
monkeyResponse.status >= 500 &&
monkeyResponse.status !== 503
) {
if (!isDevEnvironment() && status >= 500 && status !== 503) {
recordServerErrorByVersion(version);
const { uid, errorId } = monkeyResponse.data as {
const { uid, errorId } = data as {
uid: string;
errorId: string;
};
@ -76,13 +76,13 @@ async function errorHandlingMiddleware(
try {
await addLog(
"system_error",
`${monkeyResponse.status} ${errorId} ${error.message} ${error.stack}`,
`${status} ${errorId} ${error.message} ${error.stack}`,
uid
);
await db.collection<DBError>("errors").insertOne({
_id: new ObjectId(errorId),
timestamp: Date.now(),
status: monkeyResponse.status,
status: status,
uid,
message: error.message,
stack: error.stack,
@ -99,11 +99,11 @@ async function errorHandlingMiddleware(
Logger.error(`Error: ${error.message} Stack: ${error.stack}`);
}
if (monkeyResponse.status < 500) {
delete monkeyResponse.data.errorId;
if (status < 500) {
delete data.errorId;
}
handleMonkeyResponse(monkeyResponse, res);
handleErrorResponse(res, status, message, data);
return;
} catch (e) {
Logger.error("Error handling middleware failed.");
@ -111,14 +111,28 @@ async function errorHandlingMiddleware(
console.error(e);
}
handleMonkeyResponse(
new MonkeyResponse(
"Something went really wrong, please contact support.",
undefined,
500
),
res
handleErrorResponse(
res,
500,
"Something went really wrong, please contact support."
);
}
function handleErrorResponse(
res: Response,
status: number,
message: string,
data?: ErrorData
): void {
res.status(status);
if (isCustomCode(status)) {
res.statusMessage = message;
}
//@ts-expect-error ignored so that we can see message in swagger stats
res.monkeyMessage = message;
res.json({ message, data: data ?? null });
}
export default errorHandlingMiddleware;

View file

@ -1,6 +1,6 @@
import _ from "lodash";
import MonkeyError from "../utils/error";
import type { Response, NextFunction } from "express";
import type { Response, NextFunction, Request } from "express";
import { RateLimiterMemory } from "rate-limiter-flexible";
import {
rateLimit,
@ -36,7 +36,7 @@ export const customHandler = (
throw new MonkeyError(429, "Request limit reached, please try again later.");
};
const getKey = (req: MonkeyTypes.Request, _res: Response): string => {
const getKey = (req: Request, _res: Response): string => {
return (
(req.headers["cf-connecting-ip"] as string) ||
(req.headers["x-forwarded-for"] as string) ||
@ -45,7 +45,10 @@ const getKey = (req: MonkeyTypes.Request, _res: Response): string => {
);
};
const getKeyWithUid = (req: MonkeyTypes.Request, _res: Response): string => {
const getKeyWithUid = (
req: MonkeyTypes.ExpressRequestWithContext,
_res: Response
): string => {
const uid = req?.ctx?.decodedToken?.uid;
const useUid = uid !== undefined && uid !== "";
@ -148,7 +151,7 @@ const badAuthRateLimiter = new RateLimiterMemory({
});
export async function badAuthRateLimiterHandler(
req: MonkeyTypes.Request,
req: MonkeyTypes.ExpressRequestWithContext,
res: Response,
next: NextFunction
): Promise<void> {
@ -178,7 +181,7 @@ export async function badAuthRateLimiterHandler(
}
export async function incrementBadAuth(
req: MonkeyTypes.Request,
req: MonkeyTypes.ExpressRequestWithContext,
res: Response,
status: number
): Promise<void> {

View file

@ -1,43 +1,10 @@
import _ from "lodash";
import type { Request, Response, NextFunction, RequestHandler } from "express";
import { handleMonkeyResponse, MonkeyResponse } from "../utils/monkey-response";
import { recordClientVersion as prometheusRecordClientVersion } from "../utils/prometheus";
import { isDevEnvironment } from "../utils/misc";
import MonkeyError from "../utils/error";
import { TsRestRequestWithCtx } from "./auth";
export const emptyMiddleware = (
_req: MonkeyTypes.Request,
_res: Response,
next: NextFunction
): void => next();
type AsyncHandler = (
req: MonkeyTypes.Request,
res?: Response
) => Promise<MonkeyResponse>;
/**
* This utility serves as an alternative to wrapping express handlers with try/catch statements.
* Any routes that use an async handler function should wrap the handler with this function.
* Without this, any errors thrown will not be caught by the error handling middleware, and
* the app will hang!
*/
export function asyncHandler(handler: AsyncHandler): RequestHandler {
return async (
req: MonkeyTypes.Request,
res: Response,
next: NextFunction
) => {
try {
const handlerData = await handler(req, res);
handleMonkeyResponse(handlerData, res);
} catch (error) {
next(error);
}
};
}
/**
* record the client version from the `x-client-version` or ` client-version` header to prometheus
*/
@ -53,6 +20,7 @@ export function recordClientVersion(): RequestHandler {
};
}
/** Endpoint is only available in dev environment, else return 503. */
export function onlyAvailableOnDev(): MonkeyTypes.RequestHandler {
return (_req: TsRestRequestWithCtx, _res: Response, next: NextFunction) => {
if (!isDevEnvironment()) {

View file

@ -1,69 +0,0 @@
import _ from "lodash";
import joi from "joi";
import MonkeyError from "../utils/error";
import type { Response, NextFunction, RequestHandler } from "express";
type ValidationSchema = {
body?: object;
query?: object;
params?: object;
headers?: object;
};
type ValidationSchemaOption = {
allowUnknown?: boolean;
};
type ValidationHandlingOptions = {
validationErrorMessage?: string;
};
type ValidationSchemaOptions = {
[_schema in keyof ValidationSchema]?: ValidationSchemaOption;
} & ValidationHandlingOptions;
const VALIDATION_SCHEMA_DEFAULT_OPTIONS: ValidationSchemaOptions = {
body: { allowUnknown: false },
headers: { allowUnknown: true },
params: { allowUnknown: false },
query: { allowUnknown: false },
};
export function validateRequest(
validationSchema: ValidationSchema,
validationOptions: ValidationSchemaOptions = VALIDATION_SCHEMA_DEFAULT_OPTIONS
): RequestHandler {
const options = {
...VALIDATION_SCHEMA_DEFAULT_OPTIONS,
...validationOptions,
};
const { validationErrorMessage } = options;
const normalizedValidationSchema: ValidationSchema = _.omit(
validationSchema,
"validationErrorMessage"
);
return (req: MonkeyTypes.Request, _res: Response, next: NextFunction) => {
_.each(
normalizedValidationSchema,
(schema: object, key: keyof ValidationSchema) => {
const joiSchema = joi
.object()
.keys(schema)
.unknown(options[key]?.allowUnknown);
const { error } = joiSchema.validate(req[key] ?? {});
if (error) {
const errorMessage = error.details[0]?.message;
throw new MonkeyError(
422,
validationErrorMessage ??
`${errorMessage} (${error.details[0]?.context?.value})`
);
}
}
);
next();
};
}

View file

@ -18,15 +18,11 @@ declare namespace MonkeyTypes {
decodedToken: DecodedToken;
};
type Request = {
ctx: Readonly<Context>;
} & ExpressRequest;
type ExpressRequestWithContext = {
ctx: Readonly<Context>;
} & ExpressRequest;
type Request2<TQuery = undefined, TBody = undefined, TParams = undefined> = {
type Request<TQuery = undefined, TBody = undefined, TParams = undefined> = {
query: Readonly<TQuery>;
body: Readonly<TBody>;
params: Readonly<TParams>;

View file

@ -1,49 +1,10 @@
import { type Response } from "express";
import { isCustomCode } from "../constants/monkey-status-codes";
import { MonkeyResponseType } from "@monkeytype/contracts/schemas/api";
export type MonkeyDataAware<T> = {
data: T | null;
};
//TODO FIX ANYS
export class MonkeyResponse {
message: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any;
status: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(message?: string, data?: any, status = 200) {
this.message = message ?? "ok";
this.data = data ?? null;
this.status = status;
}
}
export function handleMonkeyResponse(
monkeyResponse: MonkeyResponse,
res: Response
): void {
const { message, data, status } = monkeyResponse;
res.status(status);
if (isCustomCode(status)) {
res.statusMessage = message;
}
//@ts-expect-error ignored so that we can see message in swagger stats
res.monkeyMessage = message;
if ([301, 302].includes(status)) {
// todo add stronger types here, maybe a MonkeyRedirectResponse
res.redirect(data as string);
return;
}
res.json({ message, data });
}
export class MonkeyResponse2<T = null>
export class MonkeyResponse<T = null>
implements MonkeyResponseType, MonkeyDataAware<T>
{
public message: string;

View file

@ -1,7 +1,7 @@
import "dotenv/config";
import { Counter, Histogram, Gauge } from "prom-client";
import { TsRestRequestWithCtx } from "../middlewares/auth";
import { CompletedEvent } from "@monkeytype/contracts/schemas/results";
import { Request } from "express";
const auth = new Counter({
name: "api_request_auth_total",
@ -213,7 +213,7 @@ export function recordAuthTime(
type: string,
status: "success" | "failure",
time: number,
req: MonkeyTypes.Request | TsRestRequestWithCtx
req: Request
): void {
const reqPath = req.baseUrl + req.route.path;
@ -233,10 +233,7 @@ const requestCountry = new Counter({
labelNames: ["path", "country"],
});
export function recordRequestCountry(
country: string,
req: MonkeyTypes.Request | TsRestRequestWithCtx
): void {
export function recordRequestCountry(country: string, req: Request): void {
const reqPath = req.baseUrl + req.route.path;
let normalizedPath = "/";

View file

@ -3,28 +3,6 @@ import { intersect } from "./misc";
import { default as FunboxList } from "../constants/funbox-list";
import { CompletedEvent } from "@monkeytype/contracts/schemas/results";
export function inRange(value: number, min: number, max: number): boolean {
return value >= min && value <= max;
}
const VALID_NAME_PATTERN = /^[\da-zA-Z_-]+$/;
export function isUsernameValid(name: string): boolean {
if (_.isNil(name) || !inRange(name.length, 1, 16)) {
return false;
}
return VALID_NAME_PATTERN.test(name);
}
export function isTagPresetNameValid(name: string): boolean {
if (_.isNil(name) || !inRange(name.length, 1, 16)) {
return false;
}
return VALID_NAME_PATTERN.test(name);
}
export function isTestTooShort(result: CompletedEvent): boolean {
const { mode, mode2, customText, testDuration, bailedOut } = result;

View file

@ -71,7 +71,6 @@
"@date-fns/utc": "1.2.0",
"@monkeytype/contracts": "workspace:*",
"@ts-rest/core": "3.51.0",
"axios": "1.7.4",
"canvas-confetti": "1.5.1",
"chart.js": "3.7.1",
"chartjs-adapter-date-fns": "3.0.0",

View file

@ -1,5 +1,4 @@
import { AppRouter, initClient, type ApiFetcherArgs } from "@ts-rest/core";
import { Method } from "axios";
import { getIdToken } from "firebase/auth";
import { envConfig } from "../../constants/env-config";
import { getAuthenticatedUser, isAuthenticated } from "../../firebase";
@ -28,7 +27,7 @@ function buildApi(timeout: number): (args: ApiFetcherArgs) => Promise<{
}
const fetchOptions: RequestInit = {
method: request.method as Method,
method: request.method,
headers,
body: request.body,
};

View file

@ -1,4 +1,3 @@
import axios from "axios";
import { Section } from "../utils/misc";
const bannedChars = ["—", "_", " "];
@ -49,8 +48,9 @@ export async function getPoem(): Promise<Section | false> {
console.log("Getting poem");
try {
const response = await axios.get(apiURL);
const poemObj: PoemObject = response.data[0];
const response = await fetch(apiURL);
const data = await response.json();
const poemObj: PoemObject = data[0];
const words: string[] = [];

View file

@ -98,9 +98,6 @@ importers:
ioredis:
specifier: 4.28.5
version: 4.28.5
joi:
specifier: 17.6.0
version: 17.6.0
lodash:
specifier: 4.17.21
version: 4.17.21
@ -270,9 +267,6 @@ importers:
'@ts-rest/core':
specifier: 3.51.0
version: 3.51.0(@types/node@20.14.11)(zod@3.23.8)
axios:
specifier: 1.7.4
version: 1.7.4(debug@4.3.6)
canvas-confetti:
specifier: 1.5.1
version: 1.5.1
@ -5814,9 +5808,6 @@ packages:
joi@17.13.3:
resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
joi@17.6.0:
resolution: {integrity: sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==}
join-path@1.1.1:
resolution: {integrity: sha512-jnt9OC34sLXMLJ6YfPQ2ZEKrR9mB5ZbSnQb4LPaOx1c5rTzxpR33L18jjp0r75mGGTJmsil3qwN1B5IBeTnSSA==}
@ -15857,14 +15848,6 @@ snapshots:
'@sideway/formula': 3.0.1
'@sideway/pinpoint': 2.0.0
joi@17.6.0:
dependencies:
'@hapi/hoek': 9.3.0
'@hapi/topo': 5.1.0
'@sideway/address': 4.1.5
'@sideway/formula': 3.0.1
'@sideway/pinpoint': 2.0.0
join-path@1.1.1:
dependencies:
as-array: 2.0.0