Files
modstagram-back/utils/accountLifecycle.js
payacom 3f416a7a60 full
2026-08-31 20:45:41 +03:30

135 lines
3.9 KiB
JavaScript

const crypto = require('crypto')
const mongoose = require('mongoose')
const UserModel = require('../models/UserModel')
const DeletedAccountTombstoneModel = require('../models/DeletedAccountTombstoneModel')
const PostModel = require('../models/PostModel')
const MessageModel = require('../models/MessageModel')
const PostBookmarkModel = require('../models/PostBookmarkModel')
const StoryModel = require('../models/StoryModel')
const NotificationModel = require('../models/NotificationModel')
const ExploreInteractionModel = require('../models/ExploreInteractionModel')
const CourseModel = require('../models/CourseModel')
const AdvertisingModel = require('../models/AdvertisingModel')
const AcademyModel = require('../models/AcademyModel')
const DEACTIVATION_LOCK_DAYS = 7
const DEACTIVATION_PURGE_MONTHS = 6
const DELETION_TOMBSTONE_DAYS = 30
const MS_DAY = 24 * 60 * 60 * 1000
function hashValue (value) {
if (!value) return null
return crypto
.createHash('sha256')
.update(String(value).trim().toLowerCase() + (process.env.APP_SECRET || 'modstagram'))
.digest('hex')
}
function isDeactivated (user) {
return user?.account_status === 'deactivated'
}
function isHiddenFromPublic (user) {
return isDeactivated(user)
}
function formatChatUserLabel (user) {
const name =
`${user?.first_name || ''} ${user?.last_name || ''}`.trim() ||
user?.user_name ||
'کاربر'
if (isDeactivated(user)) {
return `${name} (کاربر غیرفعال)`
}
return name
}
async function getHiddenUserIds () {
return UserModel.find({ account_status: 'deactivated' }).distinct('_id')
}
async function isMobileBlockedByTombstone (mobile) {
const hash = hashValue(mobile)
if (!hash) return false
const hit = await DeletedAccountTombstoneModel.findOne({
mobile_hash: hash,
expires_at: { $gt: new Date() }
}).lean()
return Boolean(hit)
}
async function isUsernameBlockedByTombstone (userName) {
const hash = hashValue(userName)
if (!hash) return false
const hit = await DeletedAccountTombstoneModel.findOne({
user_name_hash: hash,
expires_at: { $gt: new Date() }
}).lean()
return Boolean(hit)
}
async function createDeletionTombstone ({ mobile, userName }) {
const expiresAt = new Date(Date.now() + DELETION_TOMBSTONE_DAYS * MS_DAY)
await DeletedAccountTombstoneModel.create({
mobile_hash: hashValue(mobile),
user_name_hash: hashValue(userName),
expires_at: expiresAt
})
}
async function purgeUserData (userId) {
const uid = new mongoose.Types.ObjectId(String(userId))
await Promise.all([
PostModel.deleteMany({ user_id: uid }),
MessageModel.deleteMany({ $or: [{ senderId: uid }, { receiverId: uid }] }),
PostBookmarkModel.deleteMany({ userId: uid }),
StoryModel.deleteMany({ user_id: uid }),
NotificationModel.deleteMany({ user_id: uid }),
ExploreInteractionModel.deleteMany({
$or: [{ viewerId: uid }, { authorId: uid }]
}),
CourseModel.deleteMany({ user_id: uid }),
AdvertisingModel.deleteMany({ userId: uid }),
AcademyModel.deleteMany({ userId: uid })
])
}
async function purgeExpiredDeactivatedAccounts () {
const now = new Date()
const due = await UserModel.find({
account_status: 'deactivated',
scheduled_purge_at: { $lte: now }
}).select('_id mobile user_name').lean()
for (const user of due) {
await purgeUserData(user._id)
await UserModel.deleteOne({ _id: user._id })
}
return due.length
}
async function purgeExpiredTombstones () {
await DeletedAccountTombstoneModel.deleteMany({
expires_at: { $lte: new Date() }
})
}
module.exports = {
DEACTIVATION_LOCK_DAYS,
DEACTIVATION_PURGE_MONTHS,
DELETION_TOMBSTONE_DAYS,
MS_DAY,
hashValue,
isDeactivated,
isHiddenFromPublic,
formatChatUserLabel,
getHiddenUserIds,
isMobileBlockedByTombstone,
isUsernameBlockedByTombstone,
createDeletionTombstone,
purgeUserData,
purgeExpiredDeactivatedAccounts,
purgeExpiredTombstones
}