Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.

Commit 40eca53

Browse files
✨ Recent events & GDPR export
1 parent 838a8b5 commit 40eca53

File tree

8 files changed

+70
-9
lines changed

8 files changed

+70
-9
lines changed

src/crud/event.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ export const createEvent = async (event: Event, locals?: Locals) => {
1111
event.ipAddress = locals.ipAddress;
1212
event.userAgent = locals.userAgent;
1313
}
14-
if (event.userId)
14+
if (event.userId) {
1515
deleteItemFromCache(CacheCategories.USER_EVENT, event.userId);
16+
deleteItemFromCache(CacheCategories.USER_RECENT_EVENTS, event.userId);
17+
}
1618
await query(`INSERT INTO events ${tableValues(event)}`, Object.values(event));
1719
};
1820

@@ -26,3 +28,14 @@ export const getUserEvents = async (userId: number) => {
2628
)
2729
);
2830
};
31+
32+
export const getUserRecentEvents = async (userId: number) => {
33+
return <Event[]>(
34+
await cachedQuery(
35+
CacheCategories.USER_RECENT_EVENTS,
36+
userId,
37+
`SELECT * FROM events WHERE userId = ? ORDER BY id DESC LIMIT 10`,
38+
[userId]
39+
)
40+
);
41+
};

src/helpers/authorization.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ const canUserUser = async (
3131
if (
3232
userOrganizationId === targetOrganizationId &&
3333
user.role == UserRole.RESELLER &&
34-
action != Authorizations.IMPERSONATE
34+
[
35+
Authorizations.READ,
36+
Authorizations.UPDATE,
37+
Authorizations.DELETE
38+
].includes(action)
3539
)
3640
return true;
3741

src/helpers/mysql.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Membership } from "../interfaces/tables/memberships";
1313
import { Organization } from "../interfaces/tables/organization";
1414
import { Event } from "../interfaces/tables/events";
1515
import { KeyValue } from "../interfaces/general";
16-
import { boolValues, dateValues, readOnlyValues } from "./utils";
16+
import { boolValues, jsonValues, dateValues, readOnlyValues } from "./utils";
1717

1818
export const pool = createPool({
1919
host: DB_HOST,
@@ -55,6 +55,11 @@ export const uncleanValues = (
5555
if (typeof data.map === "function") {
5656
data.map((item: KeyValue) => {
5757
Object.keys(item).forEach(key => {
58+
try {
59+
if (jsonValues.includes(key)) item[key] = JSON.parse(item[key]);
60+
} catch (error) {
61+
item[key] = {};
62+
}
5863
if (boolValues.includes(key)) item[key] = !!item[key];
5964
if (dateValues.includes(key))
6065
item[key] = new Date(item[key]).toISOString();

src/helpers/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@ export const boolValues = [
4242

4343
export const dateValues = ["createdAt", "updatedAt"];
4444

45+
export const jsonValues = ["data"];
46+
4547
export const readOnlyValues = ["createdAt", "id"];

src/interfaces/enum.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export enum CacheCategories {
7676
USER_VERIFIED_EMAILS = "user-verified-emails",
7777
EMAIL = "email",
7878
USER_EVENT = "user-event",
79+
USER_RECENT_EVENTS = "user-recent-events",
7980
ORGANIZATION_MEMBERSHIPS = "memberships",
8081
MEMBERSHIP = "membership",
8182
ORGANIZATION = "organization",
@@ -86,6 +87,7 @@ export enum CacheCategories {
8687
export enum Authorizations {
8788
CREATE = "create",
8889
READ = "read",
90+
READ_SECURE = "read-secure",
8991
UPDATE = "update",
9092
DELETE = "delete",
9193
INVITE_MEMBER = "invite-member",

src/rest/user.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from "../crud/membership";
77
import { User } from "../interfaces/tables/user";
88
import { Locals } from "../interfaces/general";
9-
import { createEvent, getUserEvents } from "../crud/event";
9+
import { createEvent, getUserEvents, getUserRecentEvents } from "../crud/event";
1010
import { getUserEmails } from "../crud/email";
1111
import { can } from "../helpers/authorization";
1212

@@ -37,7 +37,21 @@ export const updateUserForUser = async (
3737
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
3838
};
3939

40-
export const getAllDataForUser = async (userId: number) => {
40+
export const getRecentEventsForUser = async (
41+
tokenUserId: number,
42+
dataUserId: number
43+
) => {
44+
if (await can(tokenUserId, Authorizations.READ_SECURE, "user", dataUserId))
45+
return await getUserRecentEvents(dataUserId);
46+
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
47+
};
48+
49+
export const getAllDataForUser = async (
50+
tokenUserId: number,
51+
userId: number
52+
) => {
53+
if (!(await can(tokenUserId, Authorizations.READ_SECURE, "user", userId)))
54+
throw new Error(ErrorCode.INSUFFICIENT_PERMISSION);
4155
const user = await getUser(userId);
4256
const organization = await getUserOrganization(userId);
4357
const membership = await getUserMembershipObject(userId);

src/routes/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Application } from "express";
22
import asyncHandler from "express-async-handler";
3-
import { routeUserId, routeUserUpdate, routeUserAllData } from "./users";
3+
import {
4+
routeUserId,
5+
routeUserUpdate,
6+
routeUserAllData,
7+
routeUserRecentEvents
8+
} from "./users";
49
import {
510
routeEmailVerify,
611
routeEmailAdd,
@@ -70,7 +75,12 @@ const routesUser = (app: Application) => {
7075
app.put("/users", asyncHandler(routeAuthRegister));
7176
app.get("/users/:id", authHandler, asyncHandler(routeUserId));
7277
app.patch("/users/:id", authHandler, asyncHandler(routeUserUpdate));
73-
app.get("/gdpr/json", authHandler, asyncHandler(routeUserAllData));
78+
app.get(
79+
"/users/:id/events",
80+
authHandler,
81+
asyncHandler(routeUserRecentEvents)
82+
);
83+
app.get("/users/:id/data", authHandler, asyncHandler(routeUserAllData));
7484
};
7585

7686
const routesEmail = (app: Application) => {

src/routes/users.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { Request, Response } from "express";
22
import {
33
getUserFromId,
44
updateUserForUser,
5-
getAllDataForUser
5+
getAllDataForUser,
6+
getRecentEventsForUser
67
} from "../rest/user";
78
import { ErrorCode } from "../interfaces/enum";
89

@@ -21,6 +22,16 @@ export const routeUserUpdate = async (req: Request, res: Response) => {
2122
res.json({ success: true });
2223
};
2324

25+
export const routeUserRecentEvents = async (req: Request, res: Response) => {
26+
let id = req.params.id;
27+
if (id === "me") id = res.locals.token.id;
28+
if (!id) throw new Error(ErrorCode.MISSING_FIELD);
29+
res.json(await getRecentEventsForUser(res.locals.token.id, id));
30+
};
31+
2432
export const routeUserAllData = async (req: Request, res: Response) => {
25-
res.json(await getAllDataForUser(res.locals.token.id));
33+
let id = req.params.id;
34+
if (id === "me") id = res.locals.token.id;
35+
if (!id) throw new Error(ErrorCode.MISSING_FIELD);
36+
res.json(await getAllDataForUser(res.locals.token.id, id));
2637
};

0 commit comments

Comments
 (0)