Implemented notifications, simplified alerts overview.
This commit is contained in:
parent
342ba6191e
commit
9e18fb4455
|
@ -0,0 +1,4 @@
|
|||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M5 1v1.155l-2.619 0.368 0.17 1.211-2.551 0.732 3.308 11.535 10.189-2.921 0.558-0.079h1.945v-12h-11zM3.929 14.879l-2.808-9.793 1.558-0.447 1.373 9.766 2.997-0.421-3.119 0.894zM4.822 13.382l-1.418-10.088 1.595-0.224v9.93h2.543l-2.721 0.382zM6 12v-10h9v2.5l-0.707-0.707-4.293 3.793-2.293-1.793-0.914 0.914 3.207 3.707 5-5.48v7.066h-9z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 488 B |
|
@ -86,21 +86,90 @@
|
|||
<VDropdown
|
||||
:triggers="['click']"
|
||||
:prevent-overflow="true"
|
||||
class="notifs-trigger"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="notifs-button"
|
||||
:class="{ unseen: unseen > 0 }"
|
||||
>
|
||||
<Icon
|
||||
icon="bell2"
|
||||
class="notifs-bell"
|
||||
:class="{ unread: notifications.some((notif) => !notif.isSeen) }"
|
||||
/>
|
||||
|
||||
<span
|
||||
v-if="unseen > 0"
|
||||
class="notifs-unseen"
|
||||
>{{ unseen }}</span>
|
||||
</button>
|
||||
|
||||
<template #popper>
|
||||
<div class="menu">
|
||||
<div class="notifs-header">
|
||||
<h4 class="notifs-heading">Notifications</h4>
|
||||
|
||||
<div
|
||||
class="notifs-actions"
|
||||
>
|
||||
<Icon
|
||||
icon="stack-check"
|
||||
@click="markAllSeen"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-close-popper
|
||||
icon="plus3"
|
||||
@click="showAlertDialog = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="notifs nolist">
|
||||
<li
|
||||
v-for="notif in notifications"
|
||||
:key="notif.id"
|
||||
class="notif"
|
||||
>{{ notif.scene.title }}</li>
|
||||
:class="{ unseen: !notif.isSeen }"
|
||||
@click="markSeen(notif)"
|
||||
>
|
||||
<a
|
||||
:href="`/scene/${notif.scene.id}/${notif.scene.slug}`"
|
||||
target="_blank"
|
||||
class="notif-body notif-link nolink"
|
||||
>
|
||||
<span class="notif-details">
|
||||
New
|
||||
|
||||
<span
|
||||
v-if="notif.matchedTags.length > 0"
|
||||
class="notif-tags"
|
||||
>{{ notif.matchedTags.map((tag) => tag.name).join(', ') }}</span>
|
||||
|
||||
scene
|
||||
|
||||
<span
|
||||
v-if="notif.matchedActors.length > 0"
|
||||
class="notif-actors"
|
||||
>with {{ notif.matchedActors.map((actor) => actor.name).join(', ') }}</span>
|
||||
|
||||
<span
|
||||
v-if="notif.matchedEntity"
|
||||
class="notif-entities"
|
||||
>for {{ notif.matchedEntity.name }}</span>
|
||||
|
||||
<span
|
||||
v-if="notif.matchedExpressions.length > 0"
|
||||
class="notif-entities"
|
||||
>matching {{ notif.matchedExpressions.map((match) => match.expression).join(', ') }}</span>
|
||||
</span>
|
||||
|
||||
<span class="notif-scene">
|
||||
<span class="notif-date">{{ formatDate(notif.scene.effectiveDate, 'MMM d') }}</span>
|
||||
<span class="notif-title ellipsis">{{ notif.scene.title }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -184,6 +253,11 @@
|
|||
v-if="showSettings"
|
||||
@close="showSettings = false"
|
||||
/>
|
||||
|
||||
<AlertDialog
|
||||
v-if="showAlertDialog"
|
||||
@close="showAlertDialog = false"
|
||||
/>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
|
@ -195,22 +269,26 @@ import {
|
|||
} from 'vue';
|
||||
|
||||
import navigate from '#/src/navigate.js';
|
||||
import { del } from '#/src/api.js';
|
||||
import { get, patch, del } from '#/src/api.js';
|
||||
import { formatDate } from '#/utils/format.js';
|
||||
// import getPath from '#/src/get-path.js';
|
||||
|
||||
import Settings from '#/components/settings/settings.vue';
|
||||
import AlertDialog from '#/components/alerts/create.vue';
|
||||
|
||||
import logo from '../../assets/img/logo.svg?raw'; // eslint-disable-line import/no-unresolved
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
|
||||
const user = pageContext.user;
|
||||
const notifications = pageContext.notifications;
|
||||
const notifications = ref(pageContext.notifications.notifications);
|
||||
const unseen = ref(pageContext.notifications.unseen);
|
||||
const done = ref(true);
|
||||
const query = ref(pageContext.urlParsed.search.q || '');
|
||||
const allowLogin = pageContext.env.allowLogin;
|
||||
const searchFocused = ref(false);
|
||||
const showSettings = ref(false);
|
||||
|
||||
console.log(notifications);
|
||||
const showAlertDialog = ref(false);
|
||||
|
||||
const activePage = computed(() => pageContext.urlParsed.pathname.split('/')[1]);
|
||||
const currentPath = `${pageContext.urlParsed.pathnameOriginal}${pageContext.urlParsed.searchOriginal || ''}`;
|
||||
|
@ -219,6 +297,41 @@ function search() {
|
|||
navigate('/search', { q: query.value }, { redirect: true });
|
||||
}
|
||||
|
||||
async function fetchNotifications() {
|
||||
const res = await get(`/users/${user?.id}/notifications`);
|
||||
|
||||
notifications.value = res.notifications;
|
||||
unseen.value = res.unseen;
|
||||
}
|
||||
|
||||
async function markSeen(notif) {
|
||||
if (notif.isSeen || !done.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
done.value = false;
|
||||
|
||||
await patch(`/users/${user?.id}/notifications/${notif.id}`, {
|
||||
seen: true,
|
||||
});
|
||||
|
||||
await fetchNotifications();
|
||||
|
||||
done.value = true;
|
||||
}
|
||||
|
||||
async function markAllSeen() {
|
||||
done.value = false;
|
||||
|
||||
await patch(`/users/${user?.id}/notifications`, {
|
||||
seen: true,
|
||||
});
|
||||
|
||||
await fetchNotifications();
|
||||
|
||||
done.value = true;
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await del('/session');
|
||||
navigate('/login', null, { redirect: true });
|
||||
|
@ -346,6 +459,10 @@ function blurSearch(event) {
|
|||
font-size: 0;
|
||||
cursor: pointer;
|
||||
|
||||
.button-submit {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
&:hover .avatar {
|
||||
box-shadow: 0 0 3px var(--shadow-weak-10);
|
||||
}
|
||||
|
@ -358,29 +475,156 @@ function blurSearch(event) {
|
|||
object-fit: cover;
|
||||
}
|
||||
|
||||
.notifs-bell {
|
||||
padding: 0 1.25rem;
|
||||
.notifs-trigger {
|
||||
height: 100%;
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
.notifs-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 0 1.25rem;
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
&:hover,
|
||||
&.unread {
|
||||
&.unseen {
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notifs-bell {
|
||||
margin-bottom: .1rem;
|
||||
}
|
||||
|
||||
.notifs-unseen {
|
||||
bottom: 0;
|
||||
font-size: .65rem;
|
||||
font-weight: bold;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.notifs-bell {
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
.notifs {
|
||||
overflow: auto;
|
||||
width: 30rem;
|
||||
height: 20rem;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.notifs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
box-shadow: inset 0 0 3px var(--shadow-weak-30);
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow-strong-10);
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notifs-heading {
|
||||
font-size: 1rem;
|
||||
padding: .75rem 1rem;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.notifs-actions {
|
||||
align-items: stretch;
|
||||
|
||||
.icon {
|
||||
height: 100%;
|
||||
padding: 0 .75rem;
|
||||
|
||||
&:last-child {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notif {
|
||||
display: flex;
|
||||
padding: .5rem 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: solid 1px var(--shadow-weak-30);
|
||||
border-bottom: solid 1px var(--shadow-weak-40);
|
||||
}
|
||||
|
||||
&:before {
|
||||
width: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
content: '•';
|
||||
color: var(--shadow-weak-20);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.unseen:before {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
&.unseen:hover:before {
|
||||
content: '✓';
|
||||
}
|
||||
}
|
||||
|
||||
.notif-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.notif-details {
|
||||
padding: .5rem 0 .15rem 0;
|
||||
box-sizing: border-box;
|
||||
color: var(--shadow-strong);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notif-link {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notif-scene {
|
||||
display: flex;
|
||||
padding: .15rem 0 .5rem 0;
|
||||
}
|
||||
|
||||
.notif-date {
|
||||
flex-shrink: 0;
|
||||
color: var(--shadow-strong-10);
|
||||
|
||||
&:after {
|
||||
content: '•';
|
||||
padding: 0 .5rem;
|
||||
color: var(--shadow-weak-20);
|
||||
}
|
||||
}
|
||||
|
||||
.notif-thumb {
|
||||
width: 5rem;
|
||||
height: 3rem;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.login {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"cross-env": "^7.0.3",
|
||||
"date-fns": "^3.0.0",
|
||||
"error-stack-parser": "^2.1.4",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"express": "^4.18.2",
|
||||
"express-promise-router": "^4.1.1",
|
||||
"express-query-boolean": "^2.0.0",
|
||||
|
@ -4379,6 +4380,15 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
|
@ -5065,12 +5075,14 @@
|
|||
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
|
@ -13355,6 +13367,14 @@
|
|||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
|
@ -13881,10 +13901,9 @@
|
|||
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.56.0",
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"cross-env": "^7.0.3",
|
||||
"date-fns": "^3.0.0",
|
||||
"error-stack-parser": "^2.1.4",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"express": "^4.18.2",
|
||||
"express-promise-router": "^4.1.1",
|
||||
"express-query-boolean": "^2.0.0",
|
||||
|
|
|
@ -83,33 +83,57 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<table class="alerts">
|
||||
<tbody>
|
||||
<tr class="alerts-headers">
|
||||
<th class="alerts-header">Actors</th>
|
||||
<th class="alerts-header">Tags</th>
|
||||
<th class="alerts-header">Entities</th>
|
||||
<th class="alerts-header">Matches</th>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
<ul class="alerts nolist">
|
||||
<li
|
||||
v-for="alert in alerts"
|
||||
:key="`alert-${alert.id}`"
|
||||
class="alert"
|
||||
>
|
||||
<td class="alert-field">{{ alert.actors.map((actor) => actor.name).join(', ') }}</td>
|
||||
<td class="alert-field">{{ alert.tags.map((tag) => tag.name).join(alert.and.tags ? ' + ' : ' | ') }}</td>
|
||||
<td class="alert-field">{{ alert.entities.map((entity) => entity.name).join(', ') }}</td>
|
||||
<td class="alert-field">{{ alert.matches.map((match) => match.expression).join(', ') }}</td>
|
||||
<td class="alert-actions noselect">
|
||||
<div class="alert-details">
|
||||
<span
|
||||
v-if="alert.tags.length > 0"
|
||||
class="alert-tags"
|
||||
><a
|
||||
v-for="tag in alert.tags"
|
||||
:key="`tag-${alert.id}-${tag.id}`"
|
||||
:href="`/tag/${tag.slug}`"
|
||||
class="link"
|
||||
>{{ tag.name }}</a> </span>
|
||||
|
||||
<span
|
||||
v-if="alert.actors.length > 0"
|
||||
class="alert-actors"
|
||||
>with <a
|
||||
v-for="actor in alert.actors"
|
||||
:key="`actor-${alert.id}-${actor.id}`"
|
||||
:href="`/actor/${actor.id}/${actor.slug}`"
|
||||
class="link"
|
||||
>{{ actor.name }}</a> </span>
|
||||
|
||||
<span
|
||||
v-if="alert.entities.length > 0"
|
||||
class="alert-entities"
|
||||
>for <a
|
||||
v-for="entity in alert.entities"
|
||||
:key="`entity-${alert.id}-${entity.id}`"
|
||||
:href="`/${entity.type}/${entity.slug}`"
|
||||
class="link"
|
||||
>{{ entity.name }}</a> </span>
|
||||
|
||||
<span
|
||||
v-if="alert.matches.length > 0"
|
||||
class="alert-entities"
|
||||
>matching {{ alert.matches.map((match) => match.expression).join(', ') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="alert-actions">
|
||||
<Icon
|
||||
icon="bin"
|
||||
@click="removeAlert(alert)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<AlertDialog
|
||||
v-if="showAlertDialog"
|
||||
|
@ -283,34 +307,35 @@ async function removeAlert(alert) {
|
|||
|
||||
.alerts {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.alert {
|
||||
&:nth-child(odd) {
|
||||
background: var(--shadow-weak-40);
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: solid 1px var(--shadow-weak-40);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-weak-30);
|
||||
background: var(--shadow-weak-40);
|
||||
}
|
||||
}
|
||||
|
||||
.alerts-header {
|
||||
text-align: left;
|
||||
}
|
||||
.alert-details {
|
||||
padding: .5rem 1rem;
|
||||
flex-grow: 1;
|
||||
line-height: 1.5;
|
||||
|
||||
.alerts-header,
|
||||
.alert-field {
|
||||
padding: .25rem .5rem;
|
||||
.link {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-actions {
|
||||
height: 100%;
|
||||
|
||||
.icon {
|
||||
height: 100%;
|
||||
padding: 0 .25rem;
|
||||
padding: 0 .5rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -11,8 +11,6 @@ export async function onBeforeRender(pageContext) {
|
|||
: [],
|
||||
]);
|
||||
|
||||
console.log(pageContext.notifications);
|
||||
|
||||
if (!profile) {
|
||||
throw render(404, `Cannot find user '${pageContext.routeParams.username}'.`);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import escapeRegexp from 'escape-string-regexp';
|
||||
|
||||
import { knexOwner as knex } from './knex.js';
|
||||
import { fetchScenesById } from './scenes.js';
|
||||
import promiseProps from '../utils/promise-props.js';
|
||||
import { HttpError } from './errors.js';
|
||||
|
||||
|
@ -29,7 +32,7 @@ function curateAlert(alert, context = {}) {
|
|||
id: entity.entity_id,
|
||||
name: entity.entity_name,
|
||||
slug: entity.entity_slug,
|
||||
type: entity.type,
|
||||
type: entity.entity_type,
|
||||
})) || [],
|
||||
matches: context.matches?.map((match) => ({
|
||||
id: match.id,
|
||||
|
@ -118,7 +121,9 @@ export async function createAlert(alert, reqUser) {
|
|||
alert.matches?.length > 0 && knex('alerts_matches').insert(alert.matches.map((match) => ({
|
||||
alert_id: alertId,
|
||||
property: match.property,
|
||||
expression: match.expression,
|
||||
expression: /\/.*\//.test(match.expression)
|
||||
? match.expression.slice(1, -1)
|
||||
: escapeRegexp(match.expression),
|
||||
}))),
|
||||
alert.stashes?.length > 0 && knex('alerts_stashes').insert(alert.stashes.map((stashId) => ({
|
||||
alert_id: alertId,
|
||||
|
@ -141,25 +146,60 @@ export async function removeAlert(alertId, reqUser) {
|
|||
}
|
||||
|
||||
export async function fetchNotifications(reqUser, options = {}) {
|
||||
if (!reqUser) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const notifications = await knex('notifications')
|
||||
.select('notifications.*', 'alerts.id as alert_id', 'scenes.title as scene_title')
|
||||
.leftJoin('releases as scenes', 'scenes.id', 'notifications.scene_id')
|
||||
.select(
|
||||
'notifications.*',
|
||||
'alerts.id as alert_id',
|
||||
knex.raw('coalesce(array_agg(alerts_actors.actor_id) filter (where alerts_actors.id is not null), \'{}\') as alert_actors'),
|
||||
knex.raw('coalesce(array_agg(alerts_tags.tag_id) filter (where alerts_tags.id is not null), \'{}\') as alert_tags'),
|
||||
knex.raw('coalesce(array_agg(alerts_entities.entity_id) filter (where alerts_entities.id is not null), \'{}\') as alert_entities'),
|
||||
knex.raw('coalesce(json_agg(alerts_matches) filter (where alerts_matches.id is not null), \'[]\') as alert_matches'),
|
||||
)
|
||||
.leftJoin('alerts', 'alerts.id', 'notifications.alert_id')
|
||||
.leftJoin('alerts_actors', 'alerts_actors.alert_id', 'alerts.id')
|
||||
.leftJoin('alerts_tags', 'alerts_tags.alert_id', 'alerts.id')
|
||||
.leftJoin('alerts_entities', 'alerts_entities.alert_id', 'alerts.id')
|
||||
.leftJoin('alerts_matches', 'alerts_matches.alert_id', 'alerts.id')
|
||||
.where('notifications.user_id', reqUser.id)
|
||||
.limit(options.limit || 10)
|
||||
.groupBy('notifications.id', 'alerts.id')
|
||||
.orderBy('created_at', 'desc');
|
||||
|
||||
return notifications.map((notification) => ({
|
||||
const scenes = await fetchScenesById(notifications.map((notification) => notification.scene_id));
|
||||
const unseen = notifications.filter((notification) => !notification.seen).length;
|
||||
|
||||
const curatedNotifications = notifications
|
||||
.slice(0, options.limit || 10)
|
||||
.map((notification) => {
|
||||
const scene = scenes.find((sceneX) => sceneX.id === notification.scene_id);
|
||||
|
||||
return {
|
||||
id: notification.id,
|
||||
sceneId: notification.scene_id,
|
||||
scene: {
|
||||
id: notification.scene_id,
|
||||
title: notification.scene_title,
|
||||
},
|
||||
scene,
|
||||
alertId: notification.alert_id,
|
||||
matchedActors: scene.actors.filter((actor) => notification.alert_actors.includes(actor.id)),
|
||||
matchedTags: scene.tags.filter((tag) => notification.alert_tags.includes(tag.id)),
|
||||
matchedEntity: [scene.channel, scene.network].find((entity) => notification.alert_entities.includes(entity?.id)) || null,
|
||||
matchedExpressions: notification.alert_matches
|
||||
.filter((match) => new RegExp(match.expression, 'ui').test(scene[match.property]))
|
||||
.map((match) => ({
|
||||
id: match.id,
|
||||
property: match.property,
|
||||
expression: match.expression,
|
||||
})),
|
||||
isSeen: notification.seen,
|
||||
createdAt: notification.created_at,
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
notifications: curatedNotifications,
|
||||
unseen,
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateNotification(notificationId, updatedNotification, reqUser) {
|
||||
|
|
|
@ -47,6 +47,7 @@ import {
|
|||
fetchAlertsApi,
|
||||
createAlertApi,
|
||||
removeAlertApi,
|
||||
fetchNotificationsApi,
|
||||
updateNotificationApi,
|
||||
updateNotificationsApi,
|
||||
} from './alerts.js';
|
||||
|
@ -129,6 +130,7 @@ export default async function initServer() {
|
|||
router.get('/api/users/:userId', fetchUserApi);
|
||||
router.post('/api/users', signupApi);
|
||||
|
||||
router.get('/api/users/:userId/notifications', fetchNotificationsApi);
|
||||
router.patch('/api/users/:userId/notifications', updateNotificationsApi);
|
||||
router.patch('/api/users/:userId/notifications/:notificationId', updateNotificationApi);
|
||||
|
||||
|
|
Loading…
Reference in New Issue