Added GraphQL queries for alerts and notifications.
This commit is contained in:
parent
2121c51ae6
commit
fe1a9ed26b
|
@ -195,6 +195,7 @@ export async function createAlert(alert, reqUser) {
|
||||||
export async function removeAlert(alertId, reqUser) {
|
export async function removeAlert(alertId, reqUser) {
|
||||||
const alert = await knex('alerts')
|
const alert = await knex('alerts')
|
||||||
.where('alerts.id', alertId)
|
.where('alerts.id', alertId)
|
||||||
|
.where('alerts.user_id', reqUser.id)
|
||||||
.select(
|
.select(
|
||||||
'alerts.id',
|
'alerts.id',
|
||||||
knex.raw('coalesce(array_agg(distinct alerts_actors.actor_id) filter (where alerts_actors.actor_id is not null), \'{}\') as actor_ids'),
|
knex.raw('coalesce(array_agg(distinct alerts_actors.actor_id) filter (where alerts_actors.actor_id is not null), \'{}\') as actor_ids'),
|
||||||
|
@ -208,16 +209,51 @@ export async function removeAlert(alertId, reqUser) {
|
||||||
.groupBy('alerts.id')
|
.groupBy('alerts.id')
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
await knex('alerts')
|
if (!alert) {
|
||||||
|
throw new HttpError(`Could not find alert ${alertId}`, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [removed] = await knex('alerts')
|
||||||
.where('id', alertId)
|
.where('id', alertId)
|
||||||
.where('user_id', reqUser.id)
|
.where('user_id', reqUser.id)
|
||||||
.delete();
|
.delete()
|
||||||
|
.returning('*');
|
||||||
|
|
||||||
await Promise.all([
|
if (!removed) {
|
||||||
|
throw new HttpError(`Could not remove alert ${alertId}`, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// slow not critical for response, don't await
|
||||||
|
Promise.all([
|
||||||
alert.actor_ids.length > 0 && knex.schema.refreshMaterializedView('alerts_users_actors'),
|
alert.actor_ids.length > 0 && knex.schema.refreshMaterializedView('alerts_users_actors'),
|
||||||
alert.tag_ids.length > 0 && knex.schema.refreshMaterializedView('alerts_users_tags'),
|
alert.tag_ids.length > 0 && knex.schema.refreshMaterializedView('alerts_users_tags'),
|
||||||
alert.entity_ids?.length > 0 && knex.schema.refreshMaterializedView('alerts_users_entities'),
|
alert.entity_ids?.length > 0 && knex.schema.refreshMaterializedView('alerts_users_entities'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
return curateAlert(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateNotification(notification, scenes) {
|
||||||
|
const scene = scenes.find((sceneX) => sceneX.id === notification.scene_id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: notification.id,
|
||||||
|
sceneId: notification.scene_id,
|
||||||
|
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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUnseenNotificationsCount(reqUser) {
|
export async function fetchUnseenNotificationsCount(reqUser) {
|
||||||
|
@ -262,29 +298,7 @@ export async function fetchNotifications(reqUser, options = {}) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const scenes = await fetchScenesById(notifications.map((notification) => notification.scene_id));
|
const scenes = await fetchScenesById(notifications.map((notification) => notification.scene_id));
|
||||||
|
const curatedNotifications = notifications.map((notification) => curateNotification(notification, scenes));
|
||||||
const curatedNotifications = notifications.map((notification) => {
|
|
||||||
const scene = scenes.find((sceneX) => sceneX.id === notification.scene_id);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: notification.id,
|
|
||||||
sceneId: notification.scene_id,
|
|
||||||
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 {
|
return {
|
||||||
notifications: curatedNotifications,
|
notifications: curatedNotifications,
|
||||||
|
@ -293,18 +307,27 @@ export async function fetchNotifications(reqUser, options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateNotification(notificationId, updatedNotification, reqUser) {
|
export async function updateNotification(notificationId, updatedNotification, reqUser) {
|
||||||
await knex('notifications')
|
const [updated] = await knex('notifications')
|
||||||
.where('id', notificationId)
|
.where('id', notificationId)
|
||||||
.where('user_id', reqUser.id)
|
.where('user_id', reqUser.id)
|
||||||
.update({
|
.update({
|
||||||
seen: updatedNotification.seen,
|
seen: updatedNotification.seen,
|
||||||
});
|
})
|
||||||
|
.returning('*');
|
||||||
|
|
||||||
|
if (!updated) {
|
||||||
|
throw new HttpError(`No notification ${notificationId} found to update`, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateNotifications(updatedNotification, reqUser) {
|
export async function updateNotifications(updatedNotification, reqUser) {
|
||||||
await knex('notifications')
|
const updatedCount = await knex('notifications')
|
||||||
.where('user_id', reqUser.id)
|
.where('user_id', reqUser.id)
|
||||||
.update({
|
.update({
|
||||||
seen: updatedNotification.seen,
|
seen: updatedNotification.seen,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return updatedCount;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@ export const alertsSchema = `
|
||||||
alert(
|
alert(
|
||||||
id: Int!
|
id: Int!
|
||||||
): Alert
|
): Alert
|
||||||
|
|
||||||
|
notifications(
|
||||||
|
limit: Int = 10
|
||||||
|
): Notifications
|
||||||
}
|
}
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
|
@ -32,6 +36,17 @@ export const alertsSchema = `
|
||||||
email: Boolean = false
|
email: Boolean = false
|
||||||
stashes: [Int!]
|
stashes: [Int!]
|
||||||
): Alert
|
): Alert
|
||||||
|
|
||||||
|
removeAlert(id: Int!): Alert
|
||||||
|
|
||||||
|
updateNotification(
|
||||||
|
id: Int!
|
||||||
|
seen: Boolean
|
||||||
|
): Int!
|
||||||
|
|
||||||
|
updateNotifications(
|
||||||
|
seen: Boolean
|
||||||
|
): Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertAnd {
|
type AlertAnd {
|
||||||
|
@ -100,6 +115,23 @@ export const alertsSchema = `
|
||||||
matches: [AlertMatch]
|
matches: [AlertMatch]
|
||||||
stashes: [AlertStash]
|
stashes: [AlertStash]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Notifications {
|
||||||
|
notifications: [Notification!]!
|
||||||
|
unseen: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification {
|
||||||
|
id: Int!
|
||||||
|
alertId: Int
|
||||||
|
sceneId: Int
|
||||||
|
scene: Release
|
||||||
|
isSeen: Boolean!
|
||||||
|
matchedActors: [Actor!]!
|
||||||
|
matchedTags: [Tag!]!
|
||||||
|
matchedEntity: Entity
|
||||||
|
createdAt: Date!
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export async function fetchAlertsApi(req, res) {
|
export async function fetchAlertsApi(req, res) {
|
||||||
|
@ -121,7 +153,6 @@ export async function createAlertApi(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAlertGraphql(query, req) {
|
export async function createAlertGraphql(query, req) {
|
||||||
console.log('CREATE ALERT', query);
|
|
||||||
const alert = await createAlert(query, req.user);
|
const alert = await createAlert(query, req.user);
|
||||||
|
|
||||||
return alert;
|
return alert;
|
||||||
|
@ -133,6 +164,12 @@ export async function removeAlertApi(req, res) {
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function removeAlertGraphql(query, req) {
|
||||||
|
const removedAlert = await removeAlert(query.id, req.user);
|
||||||
|
|
||||||
|
return removedAlert;
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchNotificationsApi(req, res) {
|
export async function fetchNotificationsApi(req, res) {
|
||||||
const notifications = await fetchNotifications(req.user, {
|
const notifications = await fetchNotifications(req.user, {
|
||||||
limit: req.query.limit || 10,
|
limit: req.query.limit || 10,
|
||||||
|
@ -141,18 +178,38 @@ export async function fetchNotificationsApi(req, res) {
|
||||||
res.send(notifications);
|
res.send(notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchNotificationsGraphql(query, req) {
|
||||||
|
const notifications = await fetchNotifications(req.user, {
|
||||||
|
limit: query.limit || 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
return notifications;
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateNotificationsApi(req, res) {
|
export async function updateNotificationsApi(req, res) {
|
||||||
await updateNotifications(req.body, req.user);
|
await updateNotifications(req.body, req.user);
|
||||||
|
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateNotificationsGraphql(query, req) {
|
||||||
|
const updatedCount = await updateNotifications(query, req.user);
|
||||||
|
|
||||||
|
return updatedCount;
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateNotificationApi(req, res) {
|
export async function updateNotificationApi(req, res) {
|
||||||
await updateNotification(req.params.notificationId, req.body, req.user);
|
await updateNotification(req.params.notificationId, req.body, req.user);
|
||||||
|
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateNotificationGraphql(query, req) {
|
||||||
|
const updatedNotification = await updateNotification(query.id, query, req.user);
|
||||||
|
|
||||||
|
return updatedNotification;
|
||||||
|
}
|
||||||
|
|
||||||
export const router = Router();
|
export const router = Router();
|
||||||
|
|
||||||
router.get('/api/alerts', fetchAlertsApi);
|
router.get('/api/alerts', fetchAlertsApi);
|
||||||
|
|
|
@ -50,6 +50,10 @@ import {
|
||||||
alertsSchema,
|
alertsSchema,
|
||||||
fetchAlertsGraphql,
|
fetchAlertsGraphql,
|
||||||
createAlertGraphql,
|
createAlertGraphql,
|
||||||
|
removeAlertGraphql,
|
||||||
|
fetchNotificationsGraphql,
|
||||||
|
updateNotificationGraphql,
|
||||||
|
updateNotificationsGraphql,
|
||||||
} from './alerts.js';
|
} from './alerts.js';
|
||||||
|
|
||||||
import { verifyKey } from '../auth.js';
|
import { verifyKey } from '../auth.js';
|
||||||
|
@ -133,6 +137,7 @@ export async function graphqlApi(req, res) {
|
||||||
stashes: async (query) => fetchUserStashesGraphql(query, req),
|
stashes: async (query) => fetchUserStashesGraphql(query, req),
|
||||||
stash: async (query) => fetchStashGraphql(query, req),
|
stash: async (query) => fetchStashGraphql(query, req),
|
||||||
alerts: async (query) => fetchAlertsGraphql(query, req),
|
alerts: async (query) => fetchAlertsGraphql(query, req),
|
||||||
|
notifications: async (query) => fetchNotificationsGraphql(query, req),
|
||||||
// stash mutation
|
// stash mutation
|
||||||
createStash: async (query) => createStashGraphql(query, req),
|
createStash: async (query) => createStashGraphql(query, req),
|
||||||
updateStash: async (query) => updateStashGraphql(query, req),
|
updateStash: async (query) => updateStashGraphql(query, req),
|
||||||
|
@ -145,6 +150,9 @@ export async function graphqlApi(req, res) {
|
||||||
unstashMovie: async (query) => unstashMovieGraphql(query, req),
|
unstashMovie: async (query) => unstashMovieGraphql(query, req),
|
||||||
// alert mutation
|
// alert mutation
|
||||||
createAlert: async (query) => createAlertGraphql(query, req),
|
createAlert: async (query) => createAlertGraphql(query, req),
|
||||||
|
removeAlert: async (query) => removeAlertGraphql(query, req),
|
||||||
|
updateNotification: async (query) => updateNotificationGraphql(query, req),
|
||||||
|
updateNotifications: async (query) => updateNotificationsGraphql(query, req),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,7 @@ export const scenesSchema = `
|
||||||
covers: [Media!]!
|
covers: [Media!]!
|
||||||
movies: [Release!]!
|
movies: [Release!]!
|
||||||
stashes: [Stash!]
|
stashes: [Stash!]
|
||||||
|
isStashed(stash: String!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tag {
|
type Tag {
|
||||||
|
@ -173,6 +174,19 @@ function getScope(query) {
|
||||||
return 'latest';
|
return 'latest';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function attachResolvers(scene) {
|
||||||
|
return {
|
||||||
|
...scene,
|
||||||
|
isStashed(args) {
|
||||||
|
if (!scene.stashes) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scene.stashes.some((stash) => stash.slug === args.stash) || false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchScenesGraphql(query, req) {
|
export async function fetchScenesGraphql(query, req) {
|
||||||
const mainEntity = query.entities?.find((entity) => entity.charAt(0) !== '!');
|
const mainEntity = query.entities?.find((entity) => entity.charAt(0) !== '!');
|
||||||
|
|
||||||
|
@ -223,7 +237,7 @@ export async function fetchScenesGraphql(query, req) {
|
||||||
}, req.user);
|
}, req.user);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodes: scenes,
|
nodes: scenes.map((scene) => attachResolvers(scene)),
|
||||||
total,
|
total,
|
||||||
/* restrict until deemed essential for 3rd party apps
|
/* restrict until deemed essential for 3rd party apps
|
||||||
aggregates: {
|
aggregates: {
|
||||||
|
@ -252,10 +266,10 @@ export async function fetchScenesByIdGraphql(query, req) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (query.ids) {
|
if (query.ids) {
|
||||||
return scenes;
|
return scenes.map((scene) => attachResolvers(scene));
|
||||||
}
|
}
|
||||||
|
|
||||||
return scenes[0];
|
return attachResolvers(scenes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchSceneRevisionsApi(req, res) {
|
async function fetchSceneRevisionsApi(req, res) {
|
||||||
|
|
Loading…
Reference in New Issue