Added remaining elements to alert dialog.

This commit is contained in:
2024-05-19 05:07:35 +02:00
parent 715e5ac58a
commit 014758241c
22 changed files with 1210 additions and 30 deletions

158
src/alerts.js Executable file
View File

@@ -0,0 +1,158 @@
import { knexOwner as knex } from './knex.js';
import promiseProps from '../utils/promise-props.js';
import { HttpError } from './errors.js';
function curateAlert(alert, context = {}) {
return {
id: alert.id,
notify: alert.notify,
email: alert.email,
createdAt: alert.created_at,
and: {
fields: alert.all,
actors: alert.all_actors,
tags: alert.all_tags,
entities: alert.all_entities,
matches: alert.all_tags,
},
actors: context.actors?.map((actor) => ({
id: actor.actor_id,
name: actor.actor_name,
slug: actor.actor_slug,
})) || [],
tags: context.tags?.map((tag) => ({
id: tag.tag_id,
name: tag.tag_name,
slug: tag.tag_slug,
})) || [],
entities: context.entities?.map((entity) => ({
id: entity.entity_id,
name: entity.entity_name,
slug: entity.entity_slug,
type: entity.type,
})) || [],
matches: context.matches?.map((match) => ({
id: match.id,
property: match.property,
expression: match.expression,
})) || [],
};
}
export async function fetchAlerts(user) {
const {
alerts,
actors,
tags,
entities,
matches,
} = await promiseProps({
alerts: knex('alerts')
.where('user_id', user.id),
actors: knex('alerts_actors')
.select('alerts_actors.*', 'actors.name as actor_name', 'actors.slug as actor_slug')
.leftJoin('alerts', 'alerts.id', 'alerts_actors.alert_id')
.leftJoin('actors', 'actors.id', 'alerts_actors.actor_id')
.where('alerts.user_id', user.id),
tags: knex('alerts_tags')
.select('alerts_tags.*', 'tags.name as tag_name', 'tags.slug as tag_slug')
.leftJoin('alerts', 'alerts.id', 'alerts_tags.alert_id')
.leftJoin('tags', 'tags.id', 'alerts_tags.tag_id')
.where('alerts.user_id', user.id),
entities: knex('alerts_entities')
.select('alerts_entities.*', 'entities.name as entity_name', 'entities.slug as entity_slug', 'entities.type as entity_type')
.leftJoin('alerts', 'alerts.id', 'alerts_entities.alert_id')
.leftJoin('entities', 'entities.id', 'alerts_entities.entity_id')
.where('alerts.user_id', user.id),
matches: knex('alerts_matches')
.select('alerts_matches.*')
.leftJoin('alerts', 'alerts.id', 'alerts_matches.alert_id')
.where('alerts.user_id', user.id),
});
const curatedAlerts = alerts.map((alert) => curateAlert(alert, {
actors: actors.filter((actor) => actor.alert_id === alert.id),
tags: tags.filter((tag) => tag.alert_id === alert.id),
entities: entities.filter((entity) => entity.alert_id === alert.id),
matches: matches.filter((match) => match.alert_id === alert.id),
}));
return curatedAlerts;
}
export async function createAlert(alert, reqUser) {
if (!reqUser) {
throw new HttpError('You are not authenthicated', 401);
}
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)) {
throw new HttpError('Alert must contain at least one actor, tag or entity', 400);
}
if (alert.matches?.some((match) => !match.property || !match.expression)) {
throw new HttpError('Match must define a property and an expression', 400);
}
const [{ id: alertId }] = await knex('alerts')
.insert({
user_id: reqUser.id,
notify: alert.notify,
email: alert.email,
all: alert.all,
all_actors: alert.allActors,
all_entities: alert.allEntities,
all_tags: alert.allTags,
all_matches: alert.allMatches,
})
.returning('id');
await Promise.all([
alert.actors?.length > 0 && knex('alerts_actors').insert(alert.actors.map((actorId) => ({
alert_id: alertId,
actor_id: actorId,
}))),
alert.tags?.length > 0 && knex('alerts_tags').insert(alert.tags.map((tagId) => ({
alert_id: alertId,
tag_id: tagId,
}))),
alert.matches?.length > 0 && knex('alerts_matches').insert(alert.matches.map((match) => ({
alert_id: alertId,
property: match.property,
expression: match.expression,
}))),
alert.stashes?.length > 0 && knex('alerts_stashes').insert(alert.stashes.map((stashId) => ({
alert_id: alertId,
stash_id: stashId,
}))),
alert.entities?.length > 0 && knex('alerts_entities').insert(alert.entities.map((entityId) => ({
alert_id: alertId,
entity_id: entityId,
})).slice(0, alert.allEntities ? 1 : Infinity)), // one scene can never match multiple entities in AND mode
]);
return alertId;
}
export async function removeAlert(alertId, reqUser) {
await knex('alerts')
.where('id', alertId)
.where('user_id', reqUser.id)
.delete();
}
export async function updateNotification(notificationId, updatedNotification, reqUser) {
await knex('notifications')
.where('id', notificationId)
.where('user_id', reqUser.id)
.update({
seen: updatedNotification.seen,
});
}
export async function updateNotifications(updatedNotification, reqUser) {
await knex('notifications')
.where('user_id', reqUser.id)
.update({
seen: updatedNotification.seen,
});
}

View File

@@ -20,13 +20,24 @@ function getQuery(data) {
}
function showFeedback(isSuccess, options = {}, errorMessage) {
if (!isSuccess && typeof options.errorFeedback) {
if (!isSuccess && (typeof options.errorFeedback === 'string' || options.appendErrorMessage)) {
events.emit('feedback', {
type: 'error',
message: options.appendErrorMessage && errorMessage
? `${options.errorFeedback}: ${errorMessage}`
? `${options.errorFeedback ? `${options.errorFeedback}: ` : ''}${errorMessage}`
: options.errorFeedback,
});
return;
}
if (!isSuccess) {
events.emit('feedback', {
type: 'error',
message: 'Error, please try again',
});
return;
}
if (isSuccess && options.successFeedback) {
@@ -34,6 +45,8 @@ function showFeedback(isSuccess, options = {}, errorMessage) {
type: 'success',
message: options.successFeedback,
});
return;
}
if (isSuccess && options.undoFeedback) {
@@ -82,6 +95,8 @@ export async function post(path, data, options = {}) {
return body;
}
console.log(body.statusMessage);
showFeedback(false, options, body.statusMessage);
throw new Error(body.statusMessage);
} catch (error) {

View File

@@ -32,11 +32,17 @@ function curateTag(tag, context) {
export async function fetchTags(options = {}) {
const [tags, posters] = await Promise.all([
knex('tags')
.select('tags.*', 'tags_posters.media_id as poster_id')
.select('tags.*')
.modify((builder) => {
if (!options.includeAliases) {
builder.whereNull('alias_for');
}
if (options.query) {
builder
.where('name', 'like', `%${options.query}%`)
.orWhere('slug', 'like', `%${options.query}%`);
}
}),
knex('tags_posters')
.select('tags_posters.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent'))

37
src/web/alerts.js Executable file
View File

@@ -0,0 +1,37 @@
import {
fetchAlerts,
createAlert,
removeAlert,
updateNotifications,
updateNotification,
} from '../alerts.js';
export async function fetchAlertsApi(req, res) {
const alerts = await fetchAlerts(req.user);
res.send(alerts);
}
export async function createAlertApi(req, res) {
const alertId = await createAlert(req.body, req.user);
res.send({ id: alertId });
}
export async function removeAlertApi(req, res) {
await removeAlert(req.params.alertId, req.user);
res.status(204).send();
}
export async function updateNotificationsApi(req, res) {
await updateNotifications(req.body, req.user);
res.status(204).send();
}
export async function updateNotificationApi(req, res) {
await updateNotification(req.params.notificationId, req.body, req.user);
res.status(204).send();
}

View File

@@ -1,7 +1,7 @@
import { fetchEntities } from '../entities.js';
export async function fetchEntitiesApi(req, res) {
const entities = await fetchEntities(req.body);
const entities = await fetchEntities(req.query);
res.send(entities);
}

View File

@@ -18,6 +18,7 @@ import { fetchScenesApi } from './scenes.js';
import { fetchActorsApi } from './actors.js';
import { fetchMoviesApi } from './movies.js';
import { fetchEntitiesApi } from './entities.js';
import { fetchTagsApi } from './tags.js';
import {
setUserApi,
@@ -42,6 +43,14 @@ import {
updateStashApi,
} from './stashes.js';
import {
fetchAlertsApi,
createAlertApi,
removeAlertApi,
updateNotificationApi,
updateNotificationsApi,
} from './alerts.js';
import initLogger from '../logger.js';
const logger = initLogger();
@@ -118,6 +127,9 @@ export default async function initServer() {
router.get('/api/users/:userId', fetchUserApi);
router.post('/api/users', signupApi);
router.patch('/api/users/:userId/notifications', updateNotificationsApi);
router.patch('/api/users/:userId/notifications/:notificationId', updateNotificationApi);
// STASHES
router.post('/api/stashes', createStashApi);
router.patch('/api/stashes/:stashId', updateStashApi);
@@ -131,6 +143,11 @@ export default async function initServer() {
router.delete('/api/stashes/:stashId/scenes/:sceneId', unstashSceneApi);
router.delete('/api/stashes/:stashId/movies/:movieId', unstashMovieApi);
// ALERTS
router.get('/api/alerts', fetchAlertsApi);
router.post('/api/alerts', createAlertApi);
router.delete('/api/alerts/:alertId', removeAlertApi);
// SCENES
router.get('/api/scenes', fetchScenesApi);
@@ -143,6 +160,9 @@ export default async function initServer() {
// ENTITIES
router.get('/api/entities', fetchEntitiesApi);
// TAGS
router.get('/api/tags', fetchTagsApi);
router.get('*', async (req, res, next) => {
const pageContextInit = {
urlOriginal: req.originalUrl,

9
src/web/tags.js Normal file
View File

@@ -0,0 +1,9 @@
import { fetchTags } from '../tags.js';
export async function fetchTagsApi(req, res) {
const tags = await fetchTags({
query: req.query.query,
});
res.send(tags);
}