Skip to content

Commit 8370de1

Browse files
committed
impr: add function to clearstreakhouroffset
!nuf
1 parent 98f2b9c commit 8370de1

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed

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__/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/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> {

backend/src/api/routes/admin.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export default s.router(adminContract, {
1313
toggleBan: {
1414
handler: async (r) => callController(AdminController.toggleBan)(r),
1515
},
16+
clearStreakHourOffset: {
17+
handler: async (r) =>
18+
callController(AdminController.clearStreakHourOffset)(r),
19+
},
1620
acceptReports: {
1721
handler: async (r) => callController(AdminController.acceptReports)(r),
1822
},

backend/src/dal/user.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,17 @@ export async function setBanned(uid: string, banned: boolean): Promise<void> {
11461146
}
11471147
}
11481148

1149+
export async function clearStreakHourOffset(uid: string): Promise<void> {
1150+
await getUsersCollection().updateOne(
1151+
{ uid },
1152+
{
1153+
$unset: {
1154+
"streak.hourOffset": "",
1155+
},
1156+
}
1157+
);
1158+
}
1159+
11491160
export async function checkIfUserIsPremium(
11501161
uid: string,
11511162
userInfoOverride?: Pick<DBUser, "premium">

packages/contracts/src/admin.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ export const ToggleBanRequestSchema = z
1515
.strict();
1616
export type ToggleBanRequest = z.infer<typeof ToggleBanRequestSchema>;
1717

18+
export const ClearStreakHourOffsetRequestSchema = z
19+
.object({
20+
uid: IdSchema,
21+
})
22+
.strict();
23+
export type ClearStreakHourOffsetRequest = z.infer<
24+
typeof ClearStreakHourOffsetRequestSchema
25+
>;
26+
1827
export const ToggleBanResponseSchema = responseWithData(
1928
z.object({
2029
banned: z.boolean(),
@@ -73,6 +82,16 @@ export const adminContract = c.router(
7382
200: ToggleBanResponseSchema,
7483
},
7584
},
85+
clearStreakHourOffset: {
86+
summary: "clear streak hour offset",
87+
description: "Clear the streak hour offset for a user",
88+
method: "POST",
89+
path: "/clearStreakHourOffset",
90+
body: ClearStreakHourOffsetRequestSchema,
91+
responses: {
92+
200: MonkeyResponseSchema,
93+
},
94+
},
7695
acceptReports: {
7796
summary: "accept reports",
7897
description: "Accept one or many reports",

0 commit comments

Comments
 (0)