feat: separate library and metadata pages, notification acls

This commit is contained in:
DecDuck
2025-05-15 14:55:05 +10:00
parent 086664adfd
commit 1dba112bce
13 changed files with 209 additions and 107 deletions
+32
View File
@@ -70,6 +70,8 @@ const systemACLPrefix = "system:";
export type SystemACL = Array<(typeof systemACLs)[number]>;
export type GlobalACL = `${typeof systemACLPrefix}${(typeof systemACLs)[number]}` | `${typeof userACLPrefix}${(typeof userACLs)[number]}`;
class ACLManager {
private getAuthorizationToken(request: MinimumRequestObject) {
const [type, token] =
@@ -173,6 +175,36 @@ class ACLManager {
return true;
}
async fetchAllACLs(request: MinimumRequestObject): Promise<GlobalACL[] | undefined> {
const userSession = await sessionHandler.getSession(request);
if (!userSession) {
const authorizationToken = this.getAuthorizationToken(request);
if (!authorizationToken) return undefined;
const token = await prisma.aPIToken.findUnique({
where: { token: authorizationToken },
});
if (!token) return undefined;
return token.acls as GlobalACL[];
}
const user = await prisma.user.findUnique({
where: { id: userSession.userId },
select: {
admin: true,
},
});
if (!user)
throw new Error("User session without user - did something break?");
const acls = userACLs.map((e) => `${userACLPrefix}${e}`);
if (user.admin) {
acls.push(...systemACLs.map((e) => `${systemACLPrefix}${e}`));
}
return acls as GlobalACL[];
}
}
export const aclManager = new ACLManager();
+1
View File
@@ -305,6 +305,7 @@ class LibraryManager {
title: `'${game.mName}' ('${versionName}') finished importing.`,
description: `Drop finished importing version ${versionName} for ${game.mName}.`,
actions: [`View|/admin/library/${gameId}`],
acls: ["system:import:version:read"]
});
progress(100);
+17 -10
View File
@@ -8,25 +8,32 @@ Design goals:
import type { Notification } from "~/prisma/client";
import prisma from "../db/database";
import type { GlobalACL } from "../acls";
export type NotificationCreateArgs = Pick<
Notification,
"title" | "description" | "actions" | "nonce"
>;
> & { acls: Array<GlobalACL> };
class NotificationSystem {
// userId to acl to listenerId
private listeners = new Map<
string,
Map<string, (notification: Notification) => void>
Map<
string,
{ callback: (notification: Notification) => void; acls: GlobalACL[] }
>
>();
listen(
userId: string,
acls: Array<GlobalACL>,
id: string,
callback: (notification: Notification) => void,
) {
this.listeners.set(userId, new Map());
this.listeners.get(userId)?.set(id, callback);
if (!this.listeners.has(userId)) this.listeners.set(userId, new Map());
// eslint-disable-next-line @typescript-eslint/no-extra-non-null-assertion
this.listeners.get(userId)!!.set(id, { callback, acls });
this.catchupListener(userId, id);
}
@@ -36,23 +43,23 @@ class NotificationSystem {
}
private async catchupListener(userId: string, id: string) {
const callback = this.listeners.get(userId)?.get(id);
if (!callback)
const listener = this.listeners.get(userId)?.get(id);
if (!listener)
throw new Error("Failed to catch-up listener: callback does not exist");
const notifications = await prisma.notification.findMany({
where: { userId: userId },
where: { userId: userId, acls: { hasSome: listener.acls } },
orderBy: {
created: "asc", // Oldest first, because they arrive in reverse order
},
});
for (const notification of notifications) {
await callback(notification);
await listener.callback(notification);
}
}
private async pushNotification(userId: string, notification: Notification) {
for (const listener of this.listeners.get(userId) ?? []) {
await listener[1](notification);
await listener[1].callback(notification);
}
}
@@ -90,7 +97,7 @@ class NotificationSystem {
}
async systemPush(notificationCreateArgs: NotificationCreateArgs) {
return await this.push("system", notificationCreateArgs);
return await this.pushAll(notificationCreateArgs);
}
}