Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ephemeral items #570

Merged
merged 1 commit into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions api/resolvers/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { parse } from 'tldts'
import uu from 'url-unshort'
import { advSchema, amountSchema, bountySchema, commentSchema, discussionSchema, jobSchema, linkSchema, pollSchema, ssValidate } from '../../lib/validate'
import { sendUserNotification } from '../webPush'
import { defaultCommentSort, isJob } from '../../lib/item'
import { defaultCommentSort, isJob, deleteItemByAuthor, getDeleteCommand, hasDeleteCommand } from '../../lib/item'
import { notifyItemParents, notifyUserSubscribers, notifyZapped } from '../../lib/push-notifications'
import { datePivot } from '../../lib/time'

Expand Down Expand Up @@ -654,21 +654,7 @@ export default {
throw new GraphQLError('item does not belong to you', { extensions: { code: 'FORBIDDEN' } })
}

const data = { deletedAt: new Date() }
if (old.text) {
data.text = '*deleted by author*'
}
if (old.title) {
data.title = 'deleted by author'
}
if (old.url) {
data.url = null
}
if (old.pollCost) {
data.pollCost = null
}

return await models.item.update({ where: { id: Number(id) }, data })
return await deleteItemByAuthor({ models, id, item: old })
},
upsertLink: async (parent, { id, hash, hmac, ...item }, { me, models, lnd }) => {
await ssValidate(linkSchema, item, { models, me })
Expand Down Expand Up @@ -1109,6 +1095,12 @@ export const updateItem = async (parent, { sub: subName, forward, options, ...it

await createMentions(item, models)

if (hasDeleteCommand(old.text)) {
// delete any deletion jobs that were created from a prior version of the item
await clearDeletionJobs(item, models)
}
await enqueueDeletionJob(item, models)

item.comments = []
return item
}
Expand Down Expand Up @@ -1138,12 +1130,27 @@ export const createItem = async (parent, { forward, options, ...item }, { me, mo

await createMentions(item, models)

await enqueueDeletionJob(item, models)

notifyUserSubscribers({ models, item })

item.comments = []
return item
}

const clearDeletionJobs = async (item, models) => {
await models.$queryRawUnsafe(`DELETE FROM pgboss.job WHERE name = 'deleteItem' AND data->>'id' = '${item.id}';`)
}

const enqueueDeletionJob = async (item, models) => {
const deleteCommand = getDeleteCommand(item.text)
if (deleteCommand) {
await models.$queryRawUnsafe(`
INSERT INTO pgboss.job (name, data, startafter)
VALUES ('deleteItem', jsonb_build_object('id', ${item.id}), now() + interval '${deleteCommand.number} ${deleteCommand.unit}s');`)
}
}

const getForwardUsers = async (models, forward) => {
const fwdUsers = []
if (forward) {
Expand Down
35 changes: 35 additions & 0 deletions lib/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,38 @@ export const defaultCommentSort = (pinned, bio, createdAt) => {
}

export const isJob = item => typeof item.maxBid !== 'undefined'

const deletePattern = /\B@delete\s+in\s+(\d+)\s+(second|minute|hour|day|week|month|year)s?/gi

export const getDeleteCommand = (text = '') => {
const matches = [...text.matchAll(deletePattern)]
const commands = matches?.map(match => ({ number: match[1], unit: match[2] }))
return commands.length ? commands[commands.length - 1] : undefined
}

export const hasDeleteCommand = (text) => !!getDeleteCommand(text)

export const deleteItemByAuthor = async ({ models, id, item }) => {
if (!item) {
item = await models.item.findUnique({ where: { id: Number(id) } })
}
if (!item) {
console.log('attempted to delete an item that does not exist', id)
return
}
const updateData = { deletedAt: new Date() }
if (item.text) {
updateData.text = '*deleted by author*'
}
if (item.title) {
updateData.title = 'deleted by author'
}
if (item.url) {
updateData.url = null
}
if (item.pollCost) {
updateData.pollCost = null
}

return await models.item.update({ where: { id: Number(id) }, data: updateData })
}
13 changes: 13 additions & 0 deletions worker/ephemeralItems.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { deleteItemByAuthor } from '../lib/item.js'

export function deleteItem ({ models }) {
return async function ({ data: eventData }) {
console.log('deleteItem', eventData)
const { id } = eventData
try {
await deleteItemByAuthor({ models, id })
} catch (err) {
console.error('failed to delete item', err)
}
}
}
2 changes: 2 additions & 0 deletions worker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import fetch from 'cross-fetch'
import { authenticatedLndGrpc } from 'ln-service'
import { views, rankViews } from './views.js'
import { imgproxy } from './imgproxy.js'
import { deleteItem } from './ephemeralItems.js'

const { loadEnvConfig } = nextEnv
const { ApolloClient, HttpLink, InMemoryCache } = apolloClient
Expand Down Expand Up @@ -68,6 +69,7 @@ async function work () {
await boss.work('views', views(args))
await boss.work('rankViews', rankViews(args))
await boss.work('imgproxy', imgproxy(args))
await boss.work('deleteItem', deleteItem(args))

console.log('working jobs')
}
Expand Down