add a pubsub service endpoint for cleanup soft deleted users

This commit is contained in:
Hongbo Wu
2023-10-25 13:02:59 +08:00
parent d3d3dd9458
commit 5f5423ddcb
3 changed files with 101 additions and 1 deletions

View File

@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import cors from 'cors'
import express from 'express'
import { LessThan } from 'typeorm'
import { StatusType } from '../../entity/user'
import { readPushSubscription } from '../../pubsub'
import { deleteUsers } from '../../services/user'
import { corsConfig } from '../../utils/corsConfig'
import { logger } from '../../utils/logger'
type CleanupMessage = {
subDays: number
}
const isCleanupMessage = (obj: any): obj is CleanupMessage =>
'subDays' in obj && !isNaN(obj.subDays)
const getCleanupMessage = (msgStr: string): CleanupMessage => {
try {
const obj = JSON.parse(msgStr) as unknown
if (isCleanupMessage(obj)) {
return obj
}
} catch (err) {
console.log('error deserializing event: ', { msgStr, err })
}
return {
subDays: 0, // default to 0
}
}
export function userServiceRouter() {
const router = express.Router()
router.post(
'/cleanup',
cors<express.Request>(corsConfig),
async (req, res) => {
logger.info('cleanup soft deleted users')
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 cleanupMessage = getCleanupMessage(msgStr)
const subTime = cleanupMessage.subDays * 1000 * 60 * 60 * 24 // convert days to milliseconds
try {
const result = await deleteUsers({
status: StatusType.Deleted,
updatedAt: LessThan(new Date(Date.now() - subTime)), // subDays ago
})
logger.info('cleanup result', result)
return res.sendStatus(200)
} catch (error) {
logger.error('error cleaning up users', error)
return res.sendStatus(500)
}
}
)
return router
}

View File

@ -31,6 +31,7 @@ import { newsletterServiceRouter } from './routers/svc/newsletters'
// import { remindersServiceRouter } from './routers/svc/reminders'
import { rssFeedRouter } from './routers/svc/rss_feed'
import { uploadServiceRouter } from './routers/svc/upload'
import { userServiceRouter } from './routers/svc/user'
import { webhooksServiceRouter } from './routers/svc/webhooks'
import { textToSpeechRouter } from './routers/text_to_speech'
import { userRouter } from './routers/user_router'
@ -121,6 +122,7 @@ export const createApp = (): {
app.use('/svc/pubsub/webhooks', webhooksServiceRouter())
app.use('/svc/pubsub/integrations', integrationsServiceRouter())
app.use('/svc/pubsub/rss-feed', rssFeedRouter())
app.use('/svc/pubsub/user', userServiceRouter())
// app.use('/svc/reminders', remindersServiceRouter())
app.use('/svc/email-attachment', emailAttachmentRouter())

View File

@ -1,6 +1,8 @@
import { DeepPartial, FindOptionsWhere, In } from 'typeorm'
import { StatusType, User } from '../entity/user'
import { authTrx } from '../repository'
import { userRepository } from '../repository/user'
import { SetClaimsRole } from '../utils/dictionary'
export const deleteUser = async (userId: string) => {
await authTrx(
@ -20,6 +22,28 @@ export const updateUser = async (userId: string, update: Partial<User>) => {
)
}
export const findUser = async (id: string): Promise<User | null> => {
export const findActiveUser = async (id: string): Promise<User | null> => {
return userRepository.findOneBy({ id, status: StatusType.Active })
}
export const findUsersById = async (ids: string[]): Promise<User[]> => {
return userRepository.findBy({ id: In(ids) })
}
export const deleteUsers = async (criteria: FindOptionsWhere<User>) => {
return authTrx(
async (t) => t.getRepository(User).delete(criteria),
undefined,
undefined,
SetClaimsRole.ADMIN
)
}
export const createUsers = async (users: DeepPartial<User>[]) => {
return authTrx(
async (t) => t.getRepository(User).save(users),
undefined,
undefined,
SetClaimsRole.ADMIN
)
}