Skip to content

Commit 8474072

Browse files
authored
Merge branch 'master' into feature/reusable-backend-migrations
2 parents 72bbe06 + b9cff9e commit 8474072

File tree

478 files changed

+167921
-31694
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

478 files changed

+167921
-31694
lines changed

.github/ISSUE_TEMPLATE/bug_report.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ body:
8686
value: |
8787
# Issue details
8888
```
89-
Please provide a detailed decription of what's happening, and most importantly - solid steps to reproduce the issue. This will help us find it quicker.
89+
Please provide a detailed description of what's happening, and most importantly - solid steps to reproduce the issue. This will help us find it quicker.
9090
```
9191
9292
- type: textarea

.github/pull_request_template.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,21 @@
66

77
- [ ] Adding quotes?
88
- [ ] Make sure to include translations for the quotes in the description (or another comment) so we can verify their content.
9-
- [ ] Adding a language or a theme?
10-
- [ ] If is a language, did you edit `_list.json`, `_groups.json` and add `languages.json`?
11-
- [ ] If is a theme, did you add the theme.css?
12-
- Also please add a screenshot of the theme, it would be extra awesome if you do so!
9+
- [ ] Adding a language?
10+
- Make sure to follow the [languages documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LANGUAGES.md)
11+
- [ ] Add language to `packages/contracts/src/schemas/languages.ts`
12+
- [ ] Add language to exactly one group in `frontend/src/ts/constants/languages.ts`
13+
- [ ] Add language json file to `frontend/static/languages`
14+
- [ ] Adding a theme?
15+
- Make sure to follow the [themes documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/THEMES.md)
16+
- [ ] Add theme to `packages/contracts/src/schemas/themes.ts`
17+
- [ ] Add theme to `frontend/src/ts/constants/themes.ts`
18+
- [ ] Add theme css file to `frontend/static/themes`
19+
- Also please add a screenshot of the theme, it would be extra awesome if you do so!
20+
- [ ] Adding a layout?
21+
- [ ] Make sure to follow the [layouts documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LAYOUTS.md)
22+
- [ ] Add layout to `packages/contracts/src/schemas/layouts.ts`
23+
- [ ] Add layout json file to `frontend/static/layouts`
1324
- [ ] Check if any open issues are related to this PR; if so, be sure to tag them below.
1425
- [ ] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info)
1526
- [ ] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,4 @@ frontend/src/webfonts-generated
127127
frontend/static/webfonts-preview
128128

129129
.turbo
130+
frontend/.env.sentry-build-plugin

backend/.oxlintrc.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
"overrides": [
77
{
88
"files": ["__tests__/**"],
9-
"plugins": ["jest", "vitest"],
9+
"plugins": [
10+
"typescript",
11+
"unicorn",
12+
"oxc",
13+
"import",
14+
"node",
15+
"promise",
16+
"jest",
17+
"vitest"
18+
],
1019
"rules": {
1120
"no-explicit-any": "allow",
1221
"explicit-function-return-type": "off",

backend/__tests__/api/controllers/admin.spec.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,95 @@ describe("AdminController", () => {
194194
).toBeRateLimited({ max: 1, windowMs: 5000 });
195195
});
196196
});
197+
198+
describe("clear streak hour offset", () => {
199+
const clearStreakHourOffset = vi.spyOn(UserDal, "clearStreakHourOffset");
200+
201+
beforeEach(() => {
202+
[clearStreakHourOffset].forEach((it) => it.mockReset());
203+
});
204+
205+
it("should clear streak hour offset for user", async () => {
206+
//GIVEN
207+
const victimUid = new ObjectId().toHexString();
208+
209+
//WHEN
210+
const { body } = await mockApp
211+
.post("/admin/clearStreakHourOffset")
212+
.send({ uid: victimUid })
213+
.set("Authorization", `Bearer ${uid}`)
214+
.expect(200);
215+
216+
//THEN
217+
expect(body).toEqual({
218+
message: "Streak hour offset cleared",
219+
data: null,
220+
});
221+
expect(clearStreakHourOffset).toHaveBeenCalledWith(victimUid);
222+
});
223+
it("should fail without mandatory properties", async () => {
224+
//GIVEN
225+
226+
//WHEN
227+
const { body } = await mockApp
228+
.post("/admin/clearStreakHourOffset")
229+
.send({})
230+
.set("Authorization", `Bearer ${uid}`)
231+
.expect(422);
232+
233+
//THEN
234+
expect(body).toEqual({
235+
message: "Invalid request data schema",
236+
validationErrors: ['"uid" Required'],
237+
});
238+
});
239+
it("should fail with unknown properties", async () => {
240+
//GIVEN
241+
242+
//WHEN
243+
const { body } = await mockApp
244+
.post("/admin/clearStreakHourOffset")
245+
.send({ uid: new ObjectId().toHexString(), extra: "value" })
246+
.set("Authorization", `Bearer ${uid}`)
247+
.expect(422);
248+
249+
//THEN
250+
expect(body).toEqual({
251+
message: "Invalid request data schema",
252+
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
253+
});
254+
});
255+
it("should fail if user is no admin", async () => {
256+
await expectFailForNonAdmin(
257+
mockApp
258+
.post("/admin/clearStreakHourOffset")
259+
.send({ uid: new ObjectId().toHexString() })
260+
.set("Authorization", `Bearer ${uid}`)
261+
);
262+
});
263+
it("should fail if admin endpoints are disabled", async () => {
264+
//GIVEN
265+
await expectFailForDisabledEndpoint(
266+
mockApp
267+
.post("/admin/clearStreakHourOffset")
268+
.send({ uid: new ObjectId().toHexString() })
269+
.set("Authorization", `Bearer ${uid}`)
270+
);
271+
});
272+
it("should be rate limited", async () => {
273+
//GIVEN
274+
const victimUid = new ObjectId().toHexString();
275+
276+
//WHEN
277+
await expect(
278+
mockApp
279+
.post("/admin/clearStreakHourOffset")
280+
.send({ uid: victimUid })
281+
.set("Authorization", `Bearer ${uid}`)
282+
).toBeRateLimited({ max: 1, windowMs: 5000 });
283+
});
284+
});
285+
197286
describe("accept reports", () => {
198287
const getReportsMock = vi.spyOn(ReportDal, "getReports");
199288
const deleteReportsMock = vi.spyOn(ReportDal, "deleteReports");

backend/__tests__/api/controllers/leaderboard.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ describe("Loaderboard Controller", () => {
188188
expect(body).toEqual({
189189
message: "Invalid query schema",
190190
validationErrors: [
191-
'"language" Can only contain letters [a-zA-Z0-9_+]',
191+
'"language" Invalid enum value. Must be a supported language',
192192
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
193193
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
194194
'"page" Number must be greater than or equal to 0',
@@ -344,7 +344,7 @@ describe("Loaderboard Controller", () => {
344344
expect(body).toEqual({
345345
message: "Invalid query schema",
346346
validationErrors: [
347-
'"language" Can only contain letters [a-zA-Z0-9_+]',
347+
'"language" Invalid enum value. Must be a supported language',
348348
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
349349
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
350350
],
@@ -652,7 +652,7 @@ describe("Loaderboard Controller", () => {
652652
expect(body).toEqual({
653653
message: "Invalid query schema",
654654
validationErrors: [
655-
'"language" Can only contain letters [a-zA-Z0-9_+]',
655+
'"language" Invalid enum value. Must be a supported language',
656656
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
657657
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
658658
],
@@ -830,7 +830,7 @@ describe("Loaderboard Controller", () => {
830830
expect(body).toEqual({
831831
message: "Invalid query schema",
832832
validationErrors: [
833-
'"language" Can only contain letters [a-zA-Z0-9_+]',
833+
'"language" Invalid enum value. Must be a supported language',
834834
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
835835
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
836836
],

backend/__tests__/api/controllers/public.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ describe("PublicController", () => {
8888
expect(body).toEqual({
8989
message: "Invalid query schema",
9090
validationErrors: [
91-
'"language" Can only contain letters [a-zA-Z0-9_+]',
91+
'"language" Invalid enum value. Must be a supported language',
9292
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
9393
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
9494
],

backend/__tests__/dal/user.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,4 +2011,23 @@ describe("UserDal", () => {
20112011
});
20122012
});
20132013
});
2014+
2015+
describe("clearStreakHourOffset", () => {
2016+
it("should clear streak hour offset", async () => {
2017+
// given
2018+
const { uid } = await UserTestData.createUser({
2019+
//@ts-expect-error
2020+
streak: {
2021+
hourOffset: 1,
2022+
},
2023+
});
2024+
2025+
// when
2026+
await UserDAL.clearStreakHourOffset(uid);
2027+
2028+
//then
2029+
const read = await UserDAL.getUser(uid, "read");
2030+
expect(read.streak?.hourOffset).toBeUndefined();
2031+
});
2032+
});
20142033
});

backend/package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,19 @@
2828
"@monkeytype/contracts": "workspace:*",
2929
"@monkeytype/funbox": "workspace:*",
3030
"@monkeytype/util": "workspace:*",
31-
"@ts-rest/core": "3.51.0",
32-
"@ts-rest/express": "3.51.0",
33-
"@ts-rest/open-api": "3.51.0",
31+
"@ts-rest/core": "3.52.1",
32+
"@ts-rest/express": "3.52.1",
33+
"@ts-rest/open-api": "3.52.1",
3434
"bcrypt": "5.1.1",
3535
"bullmq": "1.91.1",
3636
"chalk": "4.1.2",
3737
"cors": "2.8.5",
3838
"cron": "2.3.0",
3939
"date-fns": "3.6.0",
4040
"dotenv": "16.4.5",
41-
"express": "4.21.1",
42-
"express-rate-limit": "7.4.0",
41+
"etag": "1.8.1",
42+
"express": "5.1.0",
43+
"express-rate-limit": "7.5.1",
4344
"firebase-admin": "12.0.0",
4445
"helmet": "4.6.0",
4546
"ioredis": "4.28.5",
@@ -71,7 +72,7 @@
7172
"@types/bcrypt": "5.0.2",
7273
"@types/cors": "2.8.12",
7374
"@types/cron": "1.7.3",
74-
"@types/express": "4.17.21",
75+
"@types/express": "5.0.3",
7576
"@types/ioredis": "4.28.10",
7677
"@types/lodash": "4.14.178",
7778
"@types/mjml": "4.7.4",
@@ -92,7 +93,7 @@
9293
"eslint-watch": "8.0.0",
9394
"ioredis-mock": "7.4.0",
9495
"openapi3-ts": "2.0.2",
95-
"oxlint": "0.16.7",
96+
"oxlint": "1.1.0",
9697
"readline-sync": "1.4.10",
9798
"supertest": "6.2.3",
9899
"tsx": "4.16.2",

backend/src/api/controllers/admin.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import GeorgeQueue from "../../queues/george-queue";
66
import { sendForgotPasswordEmail as authSendForgotPasswordEmail } from "../../utils/auth";
77
import {
88
AcceptReportsRequest,
9+
ClearStreakHourOffsetRequest,
910
RejectReportsRequest,
1011
SendForgotPasswordEmailRequest,
1112
ToggleBanRequest,
@@ -42,6 +43,17 @@ export async function toggleBan(
4243
});
4344
}
4445

46+
export async function clearStreakHourOffset(
47+
req: MonkeyRequest<undefined, ClearStreakHourOffsetRequest>
48+
): Promise<MonkeyResponse> {
49+
const { uid } = req.body;
50+
51+
await UserDAL.clearStreakHourOffset(uid);
52+
void addImportantLog("admin_streak_hour_offset_cleared_by", {}, uid);
53+
54+
return new MonkeyResponse("Streak hour offset cleared", null);
55+
}
56+
4557
export async function acceptReports(
4658
req: MonkeyRequest<undefined, AcceptReportsRequest>
4759
): Promise<MonkeyResponse> {

0 commit comments

Comments
 (0)