mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-06-22 04:11:32 +10:00
feat: separate library and metadata pages, notification acls
This commit is contained in:
@@ -1,82 +1,141 @@
|
|||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- import games button -->
|
<div
|
||||||
<NuxtLink
|
v-if="game && unimportedVersions !== undefined"
|
||||||
v-if="unimportedVersions !== undefined"
|
class="grow flex flex-col gap-y-8"
|
||||||
:href="
|
|
||||||
unimportedVersions.length > 0 ? `/admin/library/${game.id}/import` : ''
|
|
||||||
"
|
|
||||||
type="button"
|
|
||||||
:class="[
|
|
||||||
unimportedVersions.length > 0
|
|
||||||
? 'bg-blue-600 hover:bg-blue-700'
|
|
||||||
: 'bg-blue-800/50',
|
|
||||||
'inline-flex w-fit items-center gap-x-2 rounded-md px-3 py-1 text-sm font-semibold font-display text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
{{
|
<div
|
||||||
unimportedVersions.length > 0
|
class="grow w-full h-full lg:pr-[30vw] px-6 py-4 flex flex-col"
|
||||||
? "Import version"
|
></div>
|
||||||
: "No versions to import"
|
<div
|
||||||
}}
|
class="lg:overflow-y-auto lg:border-l lg:border-zinc-800 lg:fixed lg:inset-y-0 lg:z-50 lg:w-[30vw] flex flex-col lg:right-0 gap-y-8 px-6 py-4"
|
||||||
</NuxtLink>
|
|
||||||
|
|
||||||
<!-- version priority -->
|
|
||||||
<div>
|
|
||||||
<div class="border-b border-zinc-800 pb-3">
|
|
||||||
<div class="flex flex-wrap items-center justify-between sm:flex-nowrap">
|
|
||||||
<h3
|
|
||||||
class="text-base font-semibold font-display leading-6 text-zinc-100"
|
|
||||||
>
|
|
||||||
Version priority
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4 text-center w-full text-sm text-zinc-600">lowest</div>
|
|
||||||
<draggable
|
|
||||||
:list="game.versions"
|
|
||||||
handle=".handle"
|
|
||||||
class="mt-2 space-y-4"
|
|
||||||
@update="() => updateVersionOrder()"
|
|
||||||
>
|
>
|
||||||
<template #item="{ element: item }: { element: GameVersion }">
|
<!-- toolbar -->
|
||||||
<div
|
<div class="inline-flex justify-end items-stretch gap-x-4">
|
||||||
class="w-full inline-flex items-center px-4 py-2 bg-zinc-800 rounded justify-between"
|
<!-- open in library button -->
|
||||||
|
<NuxtLink
|
||||||
|
:href="`/admin/metadata/games/${game.id}`"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||||
>
|
>
|
||||||
<div class="text-zinc-100 font-semibold">
|
Open in Metadata
|
||||||
{{ item.versionName }}
|
<ArrowTopRightOnSquareIcon
|
||||||
|
class="-mr-0.5 h-7 w-7 p-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</NuxtLink>
|
||||||
|
<!-- open in store button -->
|
||||||
|
<NuxtLink
|
||||||
|
:href="`/store/${game.id}`"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||||
|
>
|
||||||
|
Open in Store
|
||||||
|
<ArrowTopRightOnSquareIcon
|
||||||
|
class="-mr-0.5 h-7 w-7 p-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- version manager -->
|
||||||
|
<div>
|
||||||
|
<!-- version priority -->
|
||||||
|
<div>
|
||||||
|
<div class="border-b border-zinc-800 pb-3">
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap items-center justify-between sm:flex-nowrap"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="text-base font-semibold font-display leading-6 text-zinc-100"
|
||||||
|
>
|
||||||
|
Version priority
|
||||||
|
|
||||||
|
<!-- import games button -->
|
||||||
|
|
||||||
|
<NuxtLink
|
||||||
|
v-if="unimportedVersions !== undefined"
|
||||||
|
:href="
|
||||||
|
unimportedVersions.length > 0
|
||||||
|
? `/admin/library/${game.id}/import`
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
unimportedVersions.length > 0
|
||||||
|
? 'bg-blue-600 hover:bg-blue-700'
|
||||||
|
: 'bg-blue-800/50',
|
||||||
|
'inline-flex w-fit items-center gap-x-2 rounded-md px-3 py-1 text-sm font-semibold font-display text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
unimportedVersions.length > 0
|
||||||
|
? "Import version"
|
||||||
|
: "No versions to import"
|
||||||
|
}}
|
||||||
|
</NuxtLink>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-zinc-400">
|
|
||||||
{{ item.delta ? "Upgrade mode" : "" }}
|
<div class="mt-4 text-center w-full text-sm text-zinc-600">
|
||||||
|
lowest
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-flex items-center gap-x-2">
|
<draggable
|
||||||
<component
|
:list="game.versions"
|
||||||
:is="PLATFORM_ICONS[item.platform]"
|
handle=".handle"
|
||||||
class="size-6 text-blue-600"
|
class="mt-2 space-y-4"
|
||||||
/>
|
@update="() => updateVersionOrder()"
|
||||||
<Bars3Icon class="cursor-move w-6 h-6 text-zinc-400 handle" />
|
>
|
||||||
<button @click="() => deleteVersion(item.versionName)">
|
<template #item="{ element: item }: { element: GameVersion }">
|
||||||
<TrashIcon class="w-5 h-5 text-red-600" />
|
<div
|
||||||
</button>
|
class="w-full inline-flex items-center px-4 py-2 bg-zinc-800 rounded justify-between"
|
||||||
|
>
|
||||||
|
<div class="text-zinc-100 font-semibold">
|
||||||
|
{{ item.versionName }}
|
||||||
|
</div>
|
||||||
|
<div class="text-zinc-400">
|
||||||
|
{{ item.delta ? "Upgrade mode" : "" }}
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex items-center gap-x-2">
|
||||||
|
<component
|
||||||
|
:is="PLATFORM_ICONS[item.platform]"
|
||||||
|
class="size-6 text-blue-600"
|
||||||
|
/>
|
||||||
|
<Bars3Icon
|
||||||
|
class="cursor-move w-6 h-6 text-zinc-400 handle"
|
||||||
|
/>
|
||||||
|
<button @click="() => deleteVersion(item.versionName)">
|
||||||
|
<TrashIcon class="w-5 h-5 text-red-600" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
<div
|
||||||
|
v-if="game.versions.length == 0"
|
||||||
|
class="text-center font-bold text-zinc-400 my-3"
|
||||||
|
>
|
||||||
|
no versions added
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-center w-full text-sm text-zinc-600">
|
||||||
|
highest
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</draggable>
|
|
||||||
<div
|
|
||||||
v-if="game.versions.length == 0"
|
|
||||||
class="text-center font-bold text-zinc-400 my-3"
|
|
||||||
>
|
|
||||||
no versions added
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-center w-full text-sm text-zinc-600">highest</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Bars3Icon, TrashIcon } from "@heroicons/vue/16/solid";
|
|
||||||
import type { GameVersion } from "~/prisma/client";
|
import type { GameVersion } from "~/prisma/client";
|
||||||
|
import {
|
||||||
|
ArrowTopRightOnSquareIcon,
|
||||||
|
Bars3Icon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@heroicons/vue/24/solid";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:href="`/admin/metadata/games/${game.id}`"
|
:href="`/admin/metadata/games/${game.id}`"
|
||||||
class="w-fit rounded-md bg-zinc-800 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
class="w-fit rounded-md bg-zinc-800 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-zinc-700u focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||||
>
|
>
|
||||||
Open with Metadata →
|
Open with Metadata →
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||||
>
|
>
|
||||||
Open in Library →
|
Open in Library
|
||||||
<ArrowTopRightOnSquareIcon
|
<ArrowTopRightOnSquareIcon
|
||||||
class="-mr-0.5 h-7 w-7 p-1"
|
class="-mr-0.5 h-7 w-7 p-1"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|||||||
@@ -20,15 +20,15 @@
|
|||||||
to="/admin/metadata/games"
|
to="/admin/metadata/games"
|
||||||
class="transition group aspect-[3/2] flex flex-col justify-center items-center rounded-lg bg-zinc-950 hover:bg-zinc-950/50 shadow"
|
class="transition group aspect-[3/2] flex flex-col justify-center items-center rounded-lg bg-zinc-950 hover:bg-zinc-950/50 shadow"
|
||||||
>
|
>
|
||||||
<span class="transition-all text-4xl font-bold text-zinc-300 group-hover:text-zinc-100 uppercase tracking-widest"
|
<span class="transition-all text-4xl font-bold text-zinc-400 group-hover:text-zinc-100 uppercase tracking-widest"
|
||||||
>GAMES</span
|
>Games</span
|
||||||
>
|
>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/admin/metadata/companies"
|
to="/admin/metadata/companies"
|
||||||
class="transition group aspect-[3/2] flex flex-col justify-center items-center rounded-lg bg-zinc-950 hover:bg-zinc-950/50 shadow"
|
class="transition group aspect-[3/2] flex flex-col justify-center items-center rounded-lg bg-zinc-950 hover:bg-zinc-950/50 shadow"
|
||||||
>
|
>
|
||||||
<span class="transition-all text-4xl font-bold text-zinc-300 group-hover:text-zinc-100 uppercase tracking-widest"
|
<span class="transition-all text-4xl font-bold text-zinc-400 group-hover:text-zinc-100 uppercase tracking-widest"
|
||||||
>Companies</span
|
>Companies</span
|
||||||
>
|
>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Notification" ADD COLUMN "acls" TEXT[];
|
||||||
@@ -27,7 +27,8 @@ model Notification {
|
|||||||
nonce String? @unique
|
nonce String? @unique
|
||||||
|
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
acls String[]
|
||||||
|
|
||||||
created DateTime @default(now())
|
created DateTime @default(now())
|
||||||
title String
|
title String
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default defineClientEventHandler(
|
|||||||
title: `"${client.name}" can now access ${capability}`,
|
title: `"${client.name}" can now access ${capability}`,
|
||||||
description: `A device called "${client.name}" now has access to your ${capability}.`,
|
description: `A device called "${client.name}" now has access to your ${capability}.`,
|
||||||
actions: ["Review|/account/devices"],
|
actions: ["Review|/account/devices"],
|
||||||
|
acls: ["user:clients:read"]
|
||||||
});
|
});
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ export default defineEventHandler(async (h3) => {
|
|||||||
const userId = await aclManager.getUserIdACL(h3, ["notifications:read"]);
|
const userId = await aclManager.getUserIdACL(h3, ["notifications:read"]);
|
||||||
if (!userId) throw createError({ statusCode: 403 });
|
if (!userId) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
const userIds = [userId];
|
const acls = await aclManager.fetchAllACLs(h3);
|
||||||
const hasSystemPerms = await aclManager.allowSystemACL(h3, [
|
if (!acls)
|
||||||
"notifications:mark",
|
throw createError({
|
||||||
]);
|
statusCode: 500,
|
||||||
if (hasSystemPerms) {
|
statusMessage: "Got userId but no ACLs - what?",
|
||||||
userIds.push("system");
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const notifications = await prisma.notification.findMany({
|
const notifications = await prisma.notification.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: { in: userIds },
|
userId,
|
||||||
|
acls: {
|
||||||
|
hasSome: acls,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
created: "desc", // Newest first
|
created: "desc", // Newest first
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ export default defineEventHandler(async (h3) => {
|
|||||||
const userId = await aclManager.getUserIdACL(h3, ["notifications:mark"]);
|
const userId = await aclManager.getUserIdACL(h3, ["notifications:mark"]);
|
||||||
if (!userId) throw createError({ statusCode: 403 });
|
if (!userId) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
const userIds = [userId];
|
const acls = await aclManager.fetchAllACLs(h3);
|
||||||
const hasSystemPerms = await aclManager.allowSystemACL(h3, [
|
if (!acls)
|
||||||
"notifications:mark",
|
throw createError({
|
||||||
]);
|
statusCode: 500,
|
||||||
if (hasSystemPerms) {
|
statusMessage: "Got userId but no ACLs - what?",
|
||||||
userIds.push("system");
|
});
|
||||||
}
|
|
||||||
|
|
||||||
await prisma.notification.updateMany({
|
await prisma.notification.updateMany({
|
||||||
where: {
|
where: {
|
||||||
userId: { in: userIds },
|
userId,
|
||||||
|
acls: {
|
||||||
|
hasSome: acls,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
read: true,
|
read: true,
|
||||||
|
|||||||
@@ -14,22 +14,17 @@ export default defineWebSocketHandler({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userIds = [userId];
|
const acls = await aclManager.fetchAllACLs(h3);
|
||||||
|
if (!acls) {
|
||||||
const hasSystemPerms = await aclManager.allowSystemACL(h3, [
|
peer.send("unauthenticated");
|
||||||
"notifications:listen",
|
return;
|
||||||
]);
|
|
||||||
if (hasSystemPerms) {
|
|
||||||
userIds.push("system");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socketSessions.set(peer.id, userId);
|
socketSessions.set(peer.id, userId);
|
||||||
|
|
||||||
for (const listenUserId of userIds) {
|
notificationSystem.listen(userId, acls, peer.id, (notification) => {
|
||||||
notificationSystem.listen(listenUserId, peer.id, (notification) => {
|
peer.send(JSON.stringify(notification));
|
||||||
peer.send(JSON.stringify(notification));
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async close(peer, _details) {
|
async close(peer, _details) {
|
||||||
const userId = socketSessions.get(peer.id);
|
const userId = socketSessions.get(peer.id);
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ const systemACLPrefix = "system:";
|
|||||||
|
|
||||||
export type SystemACL = Array<(typeof systemACLs)[number]>;
|
export type SystemACL = Array<(typeof systemACLs)[number]>;
|
||||||
|
|
||||||
|
export type GlobalACL = `${typeof systemACLPrefix}${(typeof systemACLs)[number]}` | `${typeof userACLPrefix}${(typeof userACLs)[number]}`;
|
||||||
|
|
||||||
class ACLManager {
|
class ACLManager {
|
||||||
private getAuthorizationToken(request: MinimumRequestObject) {
|
private getAuthorizationToken(request: MinimumRequestObject) {
|
||||||
const [type, token] =
|
const [type, token] =
|
||||||
@@ -173,6 +175,36 @@ class ACLManager {
|
|||||||
|
|
||||||
return true;
|
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();
|
export const aclManager = new ACLManager();
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ class LibraryManager {
|
|||||||
title: `'${game.mName}' ('${versionName}') finished importing.`,
|
title: `'${game.mName}' ('${versionName}') finished importing.`,
|
||||||
description: `Drop finished importing version ${versionName} for ${game.mName}.`,
|
description: `Drop finished importing version ${versionName} for ${game.mName}.`,
|
||||||
actions: [`View|/admin/library/${gameId}`],
|
actions: [`View|/admin/library/${gameId}`],
|
||||||
|
acls: ["system:import:version:read"]
|
||||||
});
|
});
|
||||||
|
|
||||||
progress(100);
|
progress(100);
|
||||||
|
|||||||
@@ -8,25 +8,32 @@ Design goals:
|
|||||||
|
|
||||||
import type { Notification } from "~/prisma/client";
|
import type { Notification } from "~/prisma/client";
|
||||||
import prisma from "../db/database";
|
import prisma from "../db/database";
|
||||||
|
import type { GlobalACL } from "../acls";
|
||||||
|
|
||||||
export type NotificationCreateArgs = Pick<
|
export type NotificationCreateArgs = Pick<
|
||||||
Notification,
|
Notification,
|
||||||
"title" | "description" | "actions" | "nonce"
|
"title" | "description" | "actions" | "nonce"
|
||||||
>;
|
> & { acls: Array<GlobalACL> };
|
||||||
|
|
||||||
class NotificationSystem {
|
class NotificationSystem {
|
||||||
|
// userId to acl to listenerId
|
||||||
private listeners = new Map<
|
private listeners = new Map<
|
||||||
string,
|
string,
|
||||||
Map<string, (notification: Notification) => void>
|
Map<
|
||||||
|
string,
|
||||||
|
{ callback: (notification: Notification) => void; acls: GlobalACL[] }
|
||||||
|
>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
listen(
|
listen(
|
||||||
userId: string,
|
userId: string,
|
||||||
|
acls: Array<GlobalACL>,
|
||||||
id: string,
|
id: string,
|
||||||
callback: (notification: Notification) => void,
|
callback: (notification: Notification) => void,
|
||||||
) {
|
) {
|
||||||
this.listeners.set(userId, new Map());
|
if (!this.listeners.has(userId)) this.listeners.set(userId, new Map());
|
||||||
this.listeners.get(userId)?.set(id, callback);
|
// eslint-disable-next-line @typescript-eslint/no-extra-non-null-assertion
|
||||||
|
this.listeners.get(userId)!!.set(id, { callback, acls });
|
||||||
|
|
||||||
this.catchupListener(userId, id);
|
this.catchupListener(userId, id);
|
||||||
}
|
}
|
||||||
@@ -36,23 +43,23 @@ class NotificationSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async catchupListener(userId: string, id: string) {
|
private async catchupListener(userId: string, id: string) {
|
||||||
const callback = this.listeners.get(userId)?.get(id);
|
const listener = this.listeners.get(userId)?.get(id);
|
||||||
if (!callback)
|
if (!listener)
|
||||||
throw new Error("Failed to catch-up listener: callback does not exist");
|
throw new Error("Failed to catch-up listener: callback does not exist");
|
||||||
const notifications = await prisma.notification.findMany({
|
const notifications = await prisma.notification.findMany({
|
||||||
where: { userId: userId },
|
where: { userId: userId, acls: { hasSome: listener.acls } },
|
||||||
orderBy: {
|
orderBy: {
|
||||||
created: "asc", // Oldest first, because they arrive in reverse order
|
created: "asc", // Oldest first, because they arrive in reverse order
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
for (const notification of notifications) {
|
for (const notification of notifications) {
|
||||||
await callback(notification);
|
await listener.callback(notification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pushNotification(userId: string, notification: Notification) {
|
private async pushNotification(userId: string, notification: Notification) {
|
||||||
for (const listener of this.listeners.get(userId) ?? []) {
|
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) {
|
async systemPush(notificationCreateArgs: NotificationCreateArgs) {
|
||||||
return await this.push("system", notificationCreateArgs);
|
return await this.pushAll(notificationCreateArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user