From 61ed171c9d7035bc866422a0b9b268a5b18d4f7c Mon Sep 17 00:00:00 2001 From: DebaucheryLibrarian Date: Sun, 9 Mar 2025 04:12:02 +0100 Subject: [PATCH] Added quick alert button to actor bio. --- components/alerts/create.vue | 13 ++- components/stashes/heart.vue | 115 ++++++++++++++++++++++++++- pages/actors/@actorId/edit/+Page.vue | 2 - src/actors.js | 13 ++- src/alerts.js | 30 +++++++ src/web/alerts.js | 2 +- 6 files changed, 166 insertions(+), 9 deletions(-) diff --git a/components/alerts/create.vue b/components/alerts/create.vue index 377f34e..3a288f1 100644 --- a/components/alerts/create.vue +++ b/components/alerts/create.vue @@ -418,6 +418,13 @@ const email = ref(false); const stashes = ref([]); async function createAlert() { + const alertLabel = [ + ...actors.value.map((actor) => actor.name), + ...tags.value.map((tag) => tag.name), + ...entities.value.map((entity) => entity.name), + ...matches.value.map((match) => match.expression), + ].filter(Boolean).join(', '); + await post('/alerts', { all: fieldsAnd.value, allActors: actorAnd.value, @@ -430,7 +437,11 @@ async function createAlert() { notify: notify.value, email: email.value, stashes: stashes.value.map((stash) => stash.id), - }, { appendErrorMessage: true }); + }, { + successFeedback: `Alert for '${alertLabel}' set`, + errorFeedback: `Failed to set alert for '${alertLabel}'`, + appendErrorMessage: true, + }); emit('close', true); } diff --git a/components/stashes/heart.vue b/components/stashes/heart.vue index 9b8b356..4c31f14 100644 --- a/components/stashes/heart.vue +++ b/components/stashes/heart.vue @@ -3,6 +3,34 @@ v-if="user" class="bookmarks" > +
+ + + + + +
+ 0); const hasSecondaryStash = computed(() => itemStashes.value.some((itemStash) => !itemStash.isPrimary)); +if (props.domain === 'actors') { + console.log(itemAlerts.value); +} + const done = ref(true); const showStashes = ref(false); const showStashDialog = ref(false); @@ -154,9 +190,9 @@ async function stashItem(stash) { emit('stashed', stash); } catch (error) { itemStashes.value = stashState; + } finally { + done.value = true; } - - done.value = true; } async function unstashItem(stash) { @@ -178,9 +214,68 @@ async function unstashItem(stash) { emit('unstashed', stash); } catch (error) { itemStashes.value = stashState; + } finally { + done.value = true; + } +} + +async function alertItem() { + if (!done.value) { + return; } - done.value = true; + const alertLabel = props.item.title || props.item.name; + + done.value = false; + itemAlerted.value = true; + + try { + const newAlert = await post('/alerts', { + ...(props.domain === 'actors' && { actors: [props.item.id] }), + ...(props.domain === 'tags' && { tags: [props.item.id] }), + ...(props.domain === 'entities' && { entities: [props.item.id] }), + notify: true, + email: false, + preset: true, + }, { + successFeedback: `Alert set for '${alertLabel}'`, + errorFeedback: `Failed to set alert for '${alertLabel}'`, + appendErrorMessage: true, + }); + + itemAlerts.value.only = itemAlerts.value.only.concat(newAlert.id); + } catch (error) { + itemAlerted.value = false; + } finally { + done.value = true; + } +} + +async function removeAlert() { + if (done.value === false) { + return; + } + + const alertLabel = props.item.title || props.item.name; + const alertIds = itemAlerts.value.only; + + done.value = false; + + itemAlerted.value = false; + itemAlerts.value.only = itemAlerts.value.only.filter((alertId) => !alertIds.includes(alertId)); + + try { + await del(`/alerts/${alertIds.join(',')}`, { + undoFeedback: `Removed alert for '${alertLabel}'`, + errorFeedback: `Failed to remove alert for '${alertLabel}'`, + appendErrorMessage: true, + }); + } catch (error) { + itemAlerted.value = true; + itemAlerts.value.only = itemAlerts.value.only.concat(alertIds); + } finally { + done.value = true; + } } function toggleShowStashes(state) { @@ -229,10 +324,22 @@ async function reloadStashes(newStash) { } } + .icon.alert { + width: 1.3rem; + padding: .7rem .5rem; + } + .icon.heart:hover, - .icon.heart.favorited { + .icon.alert:hover, + .icon.heart.favorited, + .icon.alert.active { cursor: pointer; fill: var(--primary); } + + .icon.alert.partial { + cursor: pointer; + fill: var(--text-light); + } } diff --git a/pages/actors/@actorId/edit/+Page.vue b/pages/actors/@actorId/edit/+Page.vue index 07075f7..c21a47c 100644 --- a/pages/actors/@actorId/edit/+Page.vue +++ b/pages/actors/@actorId/edit/+Page.vue @@ -357,8 +357,6 @@ const pageContext = inject('pageContext'); const user = pageContext.user; const actor = ref(pageContext.pageProps.actor); -// console.log(actor.value); - const fields = computed(() => [ ...(actor.value.photos.length > 0 ? [{ key: 'avatar', diff --git a/src/actors.js b/src/actors.js index 9b29e55..437b82c 100644 --- a/src/actors.js +++ b/src/actors.js @@ -166,6 +166,10 @@ export function curateActor(actor, context = {}) { scenes: actor.scenes, likes: actor.stashed, stashes: context.stashes?.map((stash) => curateStash(stash)) || [], + alerts: { + only: context.alerts?.filter((alert) => alert.is_only).flatMap((alert) => alert.alert_ids) || [], + multi: context.alerts?.filter((alert) => !alert.is_only).flatMap((alert) => alert.alert_ids) || [], + }, ...context.append?.[actor.id], }; } @@ -198,7 +202,7 @@ export function sortActorsByGender(actors, context = {}) { } export async function fetchActorsById(actorIds, options = {}, reqUser) { - const [actors, profiles, photos, socials, stashes] = await Promise.all([ + const [actors, profiles, photos, socials, stashes, alerts] = await Promise.all([ knex('actors') .select( 'actors.*', @@ -253,11 +257,17 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) { .where('stashes.user_id', reqUser.id) .whereIn('stashes_actors.actor_id', actorIds) : [], + reqUser + ? knex('alerts_users_actors') + .where('user_id', reqUser.id) + .whereIn('actor_id', actorIds) + : [], ]); if (options.order) { return actors.map((actorEntry) => curateActor(actorEntry, { stashes: stashes.filter((stash) => stash.actor_id === actorEntry.id), + alerts: alerts.filter((alert) => alert.actor_id === actorEntry.id), append: options.append, })); } @@ -272,6 +282,7 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) { return curateActor(actor, { stashes: stashes.filter((stash) => stash.actor_id === actor.id), + alerts: alerts.filter((alert) => alert.actor_id === actor.id), profiles: profiles.filter((profile) => profile.actor_id === actor.id), photos: photos.filter((photo) => photo.actor_id === actor.id), socials: socials.filter((social) => social.actor_id === actor.id), diff --git a/src/alerts.js b/src/alerts.js index 1aeb49b..75f1f5a 100755 --- a/src/alerts.js +++ b/src/alerts.js @@ -120,6 +120,7 @@ export async function createAlert(alert, reqUser) { all_entities: alert.allEntities, all_tags: alert.allTags, all_matches: alert.allMatches, + from_preset: alert.preset, }) .returning('id'); @@ -149,14 +150,43 @@ export async function createAlert(alert, reqUser) { })).slice(0, alert.allEntities ? 1 : Infinity)), // one scene can never match multiple entities in AND mode ]); + await Promise.all([ + alert.actors?.length > 0 && knex.schema.refreshMaterializedView('alerts_users_actors'), + alert.tags?.length > 0 && knex.schema.refreshMaterializedView('alerts_users_tags'), + alert.entities?.length > 0 && knex.schema.refreshMaterializedView('alerts_users_entities'), + ]); + return alertId; } export async function removeAlert(alertId, reqUser) { + const alert = await knex('alerts') + .where('alerts.id', alertId) + .select( + '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_entities.entity_id) filter (where alerts_entities.entity_id is not null), \'{}\') as entity_ids'), + knex.raw('coalesce(array_agg(distinct alerts_tags.tag_id) filter (where alerts_tags.tag_id is not null), \'{}\') as tag_ids'), + ) + .leftJoin('alerts_actors', 'alerts_actors.alert_id', 'alerts.id') + .leftJoin('alerts_entities', 'alerts_entities.alert_id', 'alerts.id') + .leftJoin('alerts_tags', 'alerts_tags.alert_id', 'alerts.id') + .leftJoin('alerts_matches', 'alerts_matches.alert_id', 'alerts.id') + .groupBy('alerts.id') + .first(); + + console.log(alertId, alert); + await knex('alerts') .where('id', alertId) .where('user_id', reqUser.id) .delete(); + + await Promise.all([ + alert.actor_ids.length > 0 && knex.schema.refreshMaterializedView('alerts_users_actors'), + alert.tag_ids.length > 0 && knex.schema.refreshMaterializedView('alerts_users_tags'), + alert.entity_ids?.length > 0 && knex.schema.refreshMaterializedView('alerts_users_entities'), + ]); } export async function fetchUnseenNotificationsCount(reqUser) { diff --git a/src/web/alerts.js b/src/web/alerts.js index 9b8773f..d113693 100755 --- a/src/web/alerts.js +++ b/src/web/alerts.js @@ -20,7 +20,7 @@ export async function createAlertApi(req, res) { } export async function removeAlertApi(req, res) { - await removeAlert(req.params.alertId, req.user); + await Promise.all(req.params.alertId.split(',').map(async (alertId) => removeAlert(alertId, req.user))); res.status(204).send(); }