add an REST API to trigger the prune trash job

This commit is contained in:
Hongbo Wu
2024-06-13 14:48:14 +08:00
parent 30adbb735f
commit 9ef91ff1ca
6 changed files with 50 additions and 69 deletions

View File

@ -1,5 +1,14 @@
import { pruneTrash } from '../services/library_item'
import { appDataSource } from '../data_source'
export const PRUNE_TRASH_JOB = 'prune_trash'
export const pruneTrashJob = async () => pruneTrash()
interface PruneTrashJobData {
numDays: number
}
export const pruneTrashJob = async (jobData: PruneTrashJobData) => {
// call the stored procedure to delete trash items older than {numDays} days
await appDataSource.query(
`CALL omnivore.batch_delete_trash_items(${jobData.numDays});`
)
}

View File

@ -216,7 +216,7 @@ export const createWorker = (connection: ConnectionOptions) =>
case GENERATE_PREVIEW_CONTENT_JOB:
return generatePreviewContent(job.data)
case PRUNE_TRASH_JOB:
return pruneTrashJob()
return pruneTrashJob(job.data)
default:
logger.warning(`[queue-processor] unhandled job: ${job.name}`)
}
@ -251,18 +251,6 @@ const setupCronJobs = async () => {
},
}
)
await queue.add(
PRUNE_TRASH_JOB,
{},
{
priority: getJobPriority(PRUNE_TRASH_JOB),
repeat: {
// daily at 3am
pattern: '0 3 * * *',
},
}
)
}
const main = async () => {

View File

@ -2,12 +2,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import express from 'express'
import { LessThan } from 'typeorm'
import { LibraryItemState } from '../../entity/library_item'
import { readPushSubscription } from '../../pubsub'
import { userRepository } from '../../repository/user'
import { createPageSaveRequest } from '../../services/create_page_save_request'
import { deleteLibraryItemsByAdmin } from '../../services/library_item'
import { enqueuePruneTrashJob } from '../../utils/createTask'
import { logger } from '../../utils/logger'
interface CreateLinkRequestMessage {
@ -16,28 +14,7 @@ interface CreateLinkRequestMessage {
}
type PruneMessage = {
expireInDays: number
folder?: string
state?: LibraryItemState
}
const isPruneMessage = (obj: any): obj is PruneMessage => 'expireInDays' in obj
const getPruneMessage = (msgStr: string): PruneMessage => {
try {
const obj = JSON.parse(msgStr) as unknown
if (isPruneMessage(obj)) {
return obj
}
} catch (err) {
logger.error('error deserializing event: ', { msgStr, err })
}
// default to prune following folder items older than 30 days
return {
folder: 'following',
expireInDays: 30,
}
ttlInDays?: number
}
export function linkServiceRouter() {
@ -84,28 +61,28 @@ export function linkServiceRouter() {
}
})
router.post('/prune', async (req, res) => {
router.post('/pruneTrash', async (req, res) => {
const { message: msgStr, expired } = readPushSubscription(req)
if (!msgStr) {
return res.status(200).send('Bad Request')
}
if (expired) {
logger.info('discarding expired message')
return res.status(200).send('Expired')
}
const pruneMessage = getPruneMessage(msgStr)
const expireTime = pruneMessage.expireInDays * 1000 * 60 * 60 * 24 // convert days to milliseconds
// default to prune trash items older than 14 days
let ttlInDays = 14
if (msgStr) {
const pruneMessage = JSON.parse(msgStr) as PruneMessage
if (pruneMessage.ttlInDays) {
ttlInDays = pruneMessage.ttlInDays
}
}
try {
const result = await deleteLibraryItemsByAdmin({
folder: pruneMessage.folder,
state: pruneMessage.state,
updatedAt: LessThan(new Date(Date.now() - expireTime)),
})
logger.info('prune result', result)
const job = await enqueuePruneTrashJob(ttlInDays)
logger.info('enqueue prune trash job', { id: job?.id })
return res.sendStatus(200)
} catch (error) {

View File

@ -1354,17 +1354,6 @@ export const deleteLibraryItemsByUserId = async (userId: string) => {
)
}
export const deleteLibraryItemsByAdmin = async (
criteria: FindOptionsWhere<LibraryItem>
) => {
return authTrx(
async (tx) => tx.withRepository(libraryItemRepository).delete(criteria),
undefined,
undefined,
'admin'
)
}
export const batchDelete = async (criteria: FindOptionsWhere<LibraryItem>) => {
const batchSize = 1000
@ -1779,6 +1768,3 @@ export const downloadOriginalContent = async (
})
)
}
export const pruneTrash = async () =>
appDataSource.query(`CALL omnivore.batch_delete_trash_items();`)

View File

@ -1053,4 +1053,23 @@ export const enqueueGeneratePreviewContentJob = async (
)
}
export const enqueuePruneTrashJob = async (numDays: number) => {
const queue = await getBackendQueue()
if (!queue) {
return undefined
}
return queue.add(
PRUNE_TRASH_JOB,
{ numDays },
{
jobId: `${PRUNE_TRASH_JOB}_${numDays}_${JOB_VERSION}`,
removeOnComplete: true,
removeOnFail: true,
priority: getJobPriority(PRUNE_TRASH_JOB),
attempts: 3,
}
)
}
export default createHttpTaskWithToken

View File

@ -4,7 +4,9 @@
BEGIN;
CREATE OR REPLACE PROCEDURE omnivore.batch_delete_trash_items()
CREATE OR REPLACE PROCEDURE omnivore.batch_delete_trash_items(
num_days INT
)
LANGUAGE plpgsql AS $$
DECLARE
user_record RECORD;
@ -26,7 +28,7 @@ BEGIN
WHERE
user_id = user_record.id
AND state = 'DELETED'
AND deleted_at < NOW() - INTERVAL '14 days';
AND deleted_at < NOW() - INTERVAL '1 day' * num_days;
COMMIT;
END;