2021-04-04 19:52:19 +00:00
|
|
|
'use strict';
|
|
|
|
|
2023-11-24 00:29:22 +00:00
|
|
|
const escapeRegexp = require('escape-string-regexp');
|
|
|
|
|
2024-06-05 01:11:49 +00:00
|
|
|
const argv = require('./argv');
|
|
|
|
const logger = require('./logger')(__filename);
|
2021-04-04 19:52:19 +00:00
|
|
|
const knex = require('./knex');
|
2024-04-29 01:53:17 +00:00
|
|
|
const { indexApi } = require('./manticore');
|
2021-06-04 01:10:41 +00:00
|
|
|
const bulkInsert = require('./utils/bulk-insert');
|
2021-04-04 20:52:54 +00:00
|
|
|
const { HttpError } = require('./errors');
|
2021-04-04 19:52:19 +00:00
|
|
|
|
2021-04-04 20:52:54 +00:00
|
|
|
async function addAlert(alert, sessionUser) {
|
|
|
|
if (!sessionUser) {
|
|
|
|
throw new HttpError('You are not authenthicated', 401);
|
|
|
|
}
|
2021-04-04 19:52:19 +00:00
|
|
|
|
2024-03-28 00:19:13 +00:00
|
|
|
if ((!alert.actors || alert.actors.length === 0) && (!alert.tags || alert.tags.length === 0) && (!alert.entities || alert.entities.length === 0) && (!alert.matches || alert.matches.length === 0)) {
|
2021-04-04 20:52:54 +00:00
|
|
|
throw new HttpError('Alert must contain at least one actor, tag or entity', 400);
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:29:22 +00:00
|
|
|
if (alert.matches?.some((match) => !match.property || !match.expression)) {
|
|
|
|
throw new HttpError('Match must define a property and an expression', 400);
|
|
|
|
}
|
|
|
|
|
2023-11-30 02:12:47 +00:00
|
|
|
const [{ id: alertId }] = await knex('alerts')
|
2021-04-04 20:52:54 +00:00
|
|
|
.insert({
|
|
|
|
user_id: sessionUser.id,
|
|
|
|
notify: alert.notify,
|
|
|
|
email: alert.email,
|
2023-11-24 01:10:03 +00:00
|
|
|
all: alert.all,
|
2021-04-04 20:52:54 +00:00
|
|
|
})
|
|
|
|
.returning('id');
|
|
|
|
|
|
|
|
await Promise.all([
|
2021-11-20 22:59:15 +00:00
|
|
|
alert.actors?.length > 0 && bulkInsert('alerts_actors', alert.actors.map((actorId) => ({
|
2021-04-04 20:52:54 +00:00
|
|
|
alert_id: alertId,
|
|
|
|
actor_id: actorId,
|
2021-06-04 01:10:41 +00:00
|
|
|
})), false),
|
2021-11-20 22:59:15 +00:00
|
|
|
alert.tags?.length > 0 && bulkInsert('alerts_tags', alert.tags.map((tagId) => ({
|
2021-04-04 20:52:54 +00:00
|
|
|
alert_id: alertId,
|
|
|
|
tag_id: tagId,
|
2021-06-04 01:10:41 +00:00
|
|
|
})), false),
|
2023-11-24 00:29:22 +00:00
|
|
|
alert.matches?.length > 0 && bulkInsert('alerts_matches', alert.matches.map((match) => ({
|
|
|
|
alert_id: alertId,
|
|
|
|
property: match.property,
|
2024-05-29 21:57:38 +00:00
|
|
|
expression: /\/.*\//.test(match.expression)
|
|
|
|
? match.expression.slice(1, -1)
|
|
|
|
: escapeRegexp(match.expression),
|
2023-11-24 00:29:22 +00:00
|
|
|
})), false),
|
2021-11-20 22:59:15 +00:00
|
|
|
alert.stashes?.length > 0 && bulkInsert('alerts_stashes', alert.stashes.map((stashId) => ({
|
2021-04-04 20:52:54 +00:00
|
|
|
alert_id: alertId,
|
|
|
|
stash_id: stashId,
|
2021-06-04 01:10:41 +00:00
|
|
|
})), false),
|
2023-11-24 01:10:03 +00:00
|
|
|
alert.entities && bulkInsert('alerts_entities', alert.entities.map((entityId) => ({
|
2021-04-04 20:52:54 +00:00
|
|
|
alert_id: alertId,
|
2023-11-24 01:10:03 +00:00
|
|
|
entity_id: entityId,
|
|
|
|
})).slice(0, alert.all ? 1 : Infinity), false), // one scene can never match multiple entities in AND mode
|
2021-04-04 20:52:54 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
return alertId;
|
2021-04-04 19:52:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function removeAlert(alertId) {
|
|
|
|
await knex('alerts').where('id', alertId).delete();
|
|
|
|
}
|
|
|
|
|
2021-04-25 02:20:38 +00:00
|
|
|
async function notify(scenes) {
|
2024-06-05 01:11:49 +00:00
|
|
|
if (argv.showcased === false) {
|
|
|
|
logger.info(`Skipping notify for ${scenes.length} scenes because showcase flag is disabled.`);
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:29:22 +00:00
|
|
|
const sceneIds = scenes.map((scene) => scene.id);
|
|
|
|
|
|
|
|
const [
|
|
|
|
releasesActors,
|
|
|
|
releasesTags,
|
|
|
|
rawAlerts,
|
|
|
|
alertsActors,
|
|
|
|
alertsTags,
|
|
|
|
alertsEntities,
|
|
|
|
alertsMatches,
|
|
|
|
alertsStashes,
|
|
|
|
] = await Promise.all([
|
|
|
|
knex('releases_actors').whereIn('release_id', sceneIds),
|
|
|
|
knex('releases_tags').whereIn('release_id', sceneIds),
|
|
|
|
knex('alerts'),
|
|
|
|
knex('alerts_actors'),
|
|
|
|
knex('alerts_tags'),
|
|
|
|
knex('alerts_entities'),
|
|
|
|
knex('alerts_matches'),
|
|
|
|
knex('alerts_stashes'),
|
|
|
|
]);
|
|
|
|
|
|
|
|
const actorIdsByReleaseId = releasesActors.reduce((acc, releaseActor) => {
|
|
|
|
if (!acc[releaseActor.release_id]) {
|
|
|
|
acc[releaseActor.release_id] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
acc[releaseActor.release_id].push(releaseActor.actor_id);
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
const tagIdsByReleaseId = releasesTags.reduce((acc, releaseTag) => {
|
|
|
|
if (!acc[releaseTag.release_id]) {
|
|
|
|
acc[releaseTag.release_id] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
acc[releaseTag.release_id].push(releaseTag.tag_id);
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
const alertsActorsByAlertId = alertsActors.reduce((acc, alertActor) => { if (!acc[alertActor.alert_id]) { acc[alertActor.alert_id] = []; } acc[alertActor.alert_id].push(alertActor.actor_id); return acc; }, {});
|
|
|
|
const alertsTagsByAlertId = alertsTags.reduce((acc, alertTag) => { if (!acc[alertTag.alert_id]) { acc[alertTag.alert_id] = []; } acc[alertTag.alert_id].push(alertTag.tag_id); return acc; }, {});
|
|
|
|
const alertsEntitiesByAlertId = alertsEntities.reduce((acc, alertEntity) => { if (!acc[alertEntity.alert_id]) { acc[alertEntity.alert_id] = []; } acc[alertEntity.alert_id].push(alertEntity.entity_id); return acc; }, {});
|
|
|
|
const alertsStashesByAlertId = alertsStashes.reduce((acc, alertStash) => { if (!acc[alertStash.alert_id]) { acc[alertStash.alert_id] = []; } acc[alertStash.alert_id].push(alertStash.stash_id); return acc; }, {});
|
|
|
|
|
|
|
|
const alertsMatchesByAlertId = alertsMatches.reduce((acc, alertMatch) => {
|
|
|
|
if (!acc[alertMatch.alert_id]) {
|
|
|
|
acc[alertMatch.alert_id] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
acc[alertMatch.alert_id].push({
|
|
|
|
property: alertMatch.property,
|
2024-05-29 21:57:38 +00:00
|
|
|
expression: new RegExp(alertMatch.expression, 'ui'),
|
2023-11-24 00:29:22 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
const alerts = rawAlerts.map((alert) => ({
|
|
|
|
id: alert.id,
|
|
|
|
userId: alert.user_id,
|
|
|
|
notify: alert.notify,
|
|
|
|
email: alert.email,
|
|
|
|
all: alert.all,
|
2024-05-20 04:29:44 +00:00
|
|
|
allActors: alert.all_actors,
|
|
|
|
allEntities: alert.all_entities,
|
|
|
|
allTags: alert.all_tags,
|
|
|
|
allMatches: alert.all_matches,
|
2023-11-24 00:29:22 +00:00
|
|
|
actors: alertsActorsByAlertId[alert.id] || [],
|
|
|
|
tags: alertsTagsByAlertId[alert.id] || [],
|
|
|
|
entities: alertsEntitiesByAlertId[alert.id] || [],
|
|
|
|
matches: alertsMatchesByAlertId[alert.id] || [],
|
|
|
|
stashes: alertsStashesByAlertId[alert.id] || [],
|
|
|
|
}));
|
|
|
|
|
|
|
|
const curatedScenes = scenes.map((scene) => ({
|
|
|
|
id: scene.id,
|
|
|
|
title: scene.title,
|
|
|
|
description: scene.description,
|
|
|
|
actorIds: actorIdsByReleaseId[scene.id] || [],
|
|
|
|
tagIds: tagIdsByReleaseId[scene.id] || [],
|
|
|
|
entityId: scene.entity.id,
|
|
|
|
parentEntityId: scene.entity.parent?.id,
|
|
|
|
}));
|
|
|
|
|
|
|
|
const triggers = alerts.flatMap((alert) => {
|
|
|
|
const alertScenes = curatedScenes.filter((scene) => {
|
2023-11-24 01:10:03 +00:00
|
|
|
if (alert.all) {
|
2024-05-20 04:29:44 +00:00
|
|
|
if (alert.actors.length > 0 && !alert.actors[alert.allActors ? 'every' : 'some']((actorId) => scene.actorIds.includes(actorId))) {
|
2023-11-24 01:10:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-05-20 04:29:44 +00:00
|
|
|
if (alert.tags.length > 0 && !alert.tags[alert.allTags ? 'every' : 'some']((tagId) => scene.tagIds.includes(tagId))) {
|
2023-11-24 01:10:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// multiple entities can only be matched in OR mode
|
|
|
|
if (alert.entities.length > 0 && !alert.entities.some((alertEntityId) => alertEntityId === scene.entityId || alertEntityId === scene.parentEntityId)) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-11-24 00:29:22 +00:00
|
|
|
|
2024-05-20 04:29:44 +00:00
|
|
|
if (alert.matches.length > 0 && !alert.matches[alert.allMatches ? 'every' : 'some']((match) => match.expression.test(scene[match.property]))) {
|
2023-11-24 01:10:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2023-11-24 00:29:22 +00:00
|
|
|
}
|
|
|
|
|
2024-05-20 04:29:44 +00:00
|
|
|
if (alert.matches.length > 0 && alert.actors[alert.allActors ? 'every' : 'some']((actorId) => scene.actorIds.includes(actorId))) {
|
2023-11-24 01:10:03 +00:00
|
|
|
return true;
|
2023-11-24 00:29:22 +00:00
|
|
|
}
|
|
|
|
|
2024-05-20 04:29:44 +00:00
|
|
|
if (alert.tags.length > 0 && alert.tags[alert.allTags ? 'every' : 'some']((tagId) => scene.tagIds.includes(tagId))) {
|
2023-11-24 01:10:03 +00:00
|
|
|
return true;
|
2023-11-24 00:29:22 +00:00
|
|
|
}
|
|
|
|
|
2023-11-24 01:10:03 +00:00
|
|
|
// multiple entities can only be matched in OR mode
|
2024-05-20 04:29:44 +00:00
|
|
|
if (alert.entities.length > 0 && alert.entities.some((alertEntityId) => alertEntityId === scene.entityId || alertEntityId === scene.parentEntityId)) {
|
2023-11-24 01:10:03 +00:00
|
|
|
return true;
|
2023-11-24 00:29:22 +00:00
|
|
|
}
|
|
|
|
|
2024-05-20 04:29:44 +00:00
|
|
|
if (alert.matches.length > 0 && alert.matches[alert.allMatches ? 'every' : 'some']((match) => match.expression.test(scene[match.property]))) {
|
2023-11-24 01:10:03 +00:00
|
|
|
return true;
|
|
|
|
}
|
2023-11-24 00:29:22 +00:00
|
|
|
|
2023-11-24 01:10:03 +00:00
|
|
|
return false;
|
2023-11-24 00:29:22 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return alertScenes.map((scene) => ({
|
|
|
|
sceneId: scene.id,
|
|
|
|
alert,
|
|
|
|
}));
|
2021-04-25 01:08:50 +00:00
|
|
|
});
|
|
|
|
|
2023-11-24 00:29:22 +00:00
|
|
|
const notifications = Object.values(Object.fromEntries(triggers // prevent multiple notifications for the same scene
|
|
|
|
.filter((trigger) => trigger.alert.notify)
|
|
|
|
.map((trigger) => [`${trigger.alert.userId}:${trigger.sceneId}`, trigger])))
|
|
|
|
.map((trigger) => ({
|
|
|
|
user_id: trigger.alert.userId,
|
|
|
|
alert_id: trigger.alert.id,
|
|
|
|
scene_id: trigger.sceneId,
|
2021-04-28 23:45:01 +00:00
|
|
|
}));
|
|
|
|
|
2024-04-29 01:53:17 +00:00
|
|
|
const uniqueStashes = Object.values(Object.fromEntries(triggers.flatMap((trigger) => trigger.alert.stashes.map((stashId) => ({
|
|
|
|
stashId,
|
2024-05-04 03:18:00 +00:00
|
|
|
sceneId: trigger.sceneId,
|
2024-04-29 01:53:17 +00:00
|
|
|
userId: trigger.alert.userId,
|
2024-05-04 03:18:00 +00:00
|
|
|
}))).map((stash) => [`${stash.stashId}:${stash.sceneId}`, stash])));
|
2023-11-24 00:29:22 +00:00
|
|
|
|
2024-04-29 01:53:17 +00:00
|
|
|
const stashEntries = uniqueStashes.map((stash) => ({
|
|
|
|
scene_id: stash.sceneId,
|
|
|
|
stash_id: stash.stashId,
|
|
|
|
}));
|
|
|
|
|
|
|
|
const [stashed] = await Promise.all([
|
|
|
|
bulkInsert('stashes_scenes', stashEntries, false),
|
2021-06-04 01:22:40 +00:00
|
|
|
bulkInsert('notifications', notifications, false),
|
2021-04-28 23:45:01 +00:00
|
|
|
]);
|
|
|
|
|
2024-04-29 01:53:17 +00:00
|
|
|
// we need created_at from the databased, but user_id is not returned. it's easier to query it than to try and merge it with the input data
|
|
|
|
const stashedEntries = await knex('stashes_scenes')
|
|
|
|
.select('stashes_scenes.*', 'stashes.user_id')
|
|
|
|
.leftJoin('stashes', 'stashes.id', 'stashes_scenes.stash_id')
|
|
|
|
.whereIn('stashes_scenes.id', stashed.map((stash) => stash.id));
|
|
|
|
|
|
|
|
const docs = stashedEntries.map((stash) => ({
|
|
|
|
replace: {
|
|
|
|
index: 'scenes_stashed',
|
|
|
|
id: stash.id,
|
|
|
|
doc: {
|
|
|
|
scene_id: stash.scene_id,
|
|
|
|
user_id: stash.user_id,
|
|
|
|
stash_id: stash.stash_id,
|
|
|
|
created_at: Math.round(stash.created_at.getTime() / 1000),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (docs.length > 0) {
|
|
|
|
await indexApi.bulk(docs.map((doc) => JSON.stringify(doc)).join('\n'));
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:29:22 +00:00
|
|
|
return triggers;
|
2021-04-25 01:08:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function updateNotification(notificationId, notification, sessionUser) {
|
|
|
|
await knex('notifications')
|
|
|
|
.where('user_id', sessionUser.id)
|
|
|
|
.where('id', notificationId)
|
|
|
|
.update({
|
|
|
|
seen: notification.seen,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updateNotifications(notification, sessionUser) {
|
|
|
|
await knex('notifications')
|
|
|
|
.where('user_id', sessionUser.id)
|
|
|
|
.update({
|
|
|
|
seen: notification.seen,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-04 19:52:19 +00:00
|
|
|
module.exports = {
|
|
|
|
addAlert,
|
|
|
|
removeAlert,
|
2021-04-25 01:08:50 +00:00
|
|
|
notify,
|
|
|
|
updateNotification,
|
|
|
|
updateNotifications,
|
2021-04-04 19:52:19 +00:00
|
|
|
};
|