full
This commit is contained in:
521
controllers/application/profile/accountController.js
Normal file
521
controllers/application/profile/accountController.js
Normal file
@@ -0,0 +1,521 @@
|
||||
/* eslint-disable camelcase */
|
||||
const mongoose = require('mongoose')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment-jalaali')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const PostModel = require('../../../models/PostModel')
|
||||
const ExploreInteractionModel = require('../../../models/ExploreInteractionModel')
|
||||
const UserFollowModel = require('../../../models/UserFollowModel')
|
||||
const LikeModel = require('../../../models/LikeModel')
|
||||
const CommentModel = require('../../../models/CommentModel')
|
||||
const {
|
||||
DEACTIVATION_LOCK_DAYS,
|
||||
DEACTIVATION_PURGE_MONTHS,
|
||||
MS_DAY,
|
||||
isDeactivated,
|
||||
createDeletionTombstone,
|
||||
purgeUserData
|
||||
} = require('../../../utils/accountLifecycle')
|
||||
const { PUBLIC_POST_STATUSES } = require('../../../utils/moderationStatus')
|
||||
const { APP_LANGUAGE_CODES } = require('../../../utils/appLanguages')
|
||||
|
||||
// middlewares/auth.js فقط توکن رو معتبرسنجی میکنه و next() صدا میزنه — req.user رو ست نمیکنه،
|
||||
// پس باید توکن رو مستقیم دیکد کنیم (همون الگویی که notificationController.js استفاده میکنه)
|
||||
function getUserIdFromToken (req) {
|
||||
const authHeader = req.header('Authorization')
|
||||
const token = authHeader?.split(' ')[1]
|
||||
if (!token) return null
|
||||
try {
|
||||
const decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
return decodedToken.id
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const deactivateAccount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const { user_name } = req.body
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر یافت نشد' })
|
||||
}
|
||||
if (String(user.user_name).toLowerCase() !== String(user_name || '').trim().toLowerCase()) {
|
||||
return res.status(422).json({ message: 'نام کاربری صحیح نیست' })
|
||||
}
|
||||
if (isDeactivated(user)) {
|
||||
return res.status(400).json({ message: 'حساب کاربری قبلاً غیرفعال شده است' })
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
user.account_status = 'deactivated'
|
||||
user.deactivated_at = now
|
||||
user.reactivation_locked_until = new Date(now.getTime() + DEACTIVATION_LOCK_DAYS * MS_DAY)
|
||||
user.scheduled_purge_at = new Date(now.getTime() + DEACTIVATION_PURGE_MONTHS * 30 * MS_DAY)
|
||||
await user.save()
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'حساب کاربری غیرفعال شد',
|
||||
reactivation_locked_until: user.reactivation_locked_until,
|
||||
scheduled_purge_at: user.scheduled_purge_at
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('deactivateAccount:', err)
|
||||
return res.status(500).json({ message: 'خطا در غیرفعالسازی حساب' })
|
||||
}
|
||||
}
|
||||
|
||||
const reactivateAccount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر یافت نشد' })
|
||||
}
|
||||
if (!isDeactivated(user)) {
|
||||
return res.status(400).json({ message: 'حساب کاربری فعال است' })
|
||||
}
|
||||
if (user.reactivation_locked_until && new Date() < new Date(user.reactivation_locked_until)) {
|
||||
return res.status(403).json({
|
||||
message: 'تا پایان دوره ۷ روزه امکان فعالسازی مجدد وجود ندارد',
|
||||
reactivation_locked_until: user.reactivation_locked_until
|
||||
})
|
||||
}
|
||||
|
||||
user.account_status = 'active'
|
||||
user.deactivated_at = null
|
||||
user.reactivation_locked_until = null
|
||||
user.scheduled_purge_at = null
|
||||
await user.save()
|
||||
|
||||
return res.status(200).json({ message: 'حساب کاربری فعال شد' })
|
||||
} catch (err) {
|
||||
console.error('reactivateAccount:', err)
|
||||
return res.status(500).json({ message: 'خطا در فعالسازی حساب' })
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAccount = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const { user_name } = req.body
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر یافت نشد' })
|
||||
}
|
||||
if (String(user.user_name).toLowerCase() !== String(user_name || '').trim().toLowerCase()) {
|
||||
return res.status(422).json({ message: 'نام کاربری صحیح نیست' })
|
||||
}
|
||||
|
||||
const { mobile, user_name: uname } = user
|
||||
await createDeletionTombstone({ mobile, userName: uname })
|
||||
await purgeUserData(userId)
|
||||
await UserModel.deleteOne({ _id: userId })
|
||||
|
||||
return res.status(200).json({ message: 'حساب کاربری حذف شد' })
|
||||
} catch (err) {
|
||||
console.error('deleteAccount:', err)
|
||||
return res.status(500).json({ message: 'خطا در حذف حساب' })
|
||||
}
|
||||
}
|
||||
|
||||
const getAccountStatus = async (req, res) => {
|
||||
try {
|
||||
const user = await UserModel.findById(req.user._id)
|
||||
.select('account_status deactivated_at reactivation_locked_until scheduled_purge_at allow_save_posts ghost_mode show_location post_location_enabled is_private gift_enabled offer_payment_enabled preferred_language user_name blocked_by')
|
||||
.lean()
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر یافت نشد' })
|
||||
}
|
||||
const { blocked_by, ...userWithoutBlockedBy } = user
|
||||
return res.status(200).json({
|
||||
account: {
|
||||
...userWithoutBlockedBy,
|
||||
allow_save_posts: user.allow_save_posts !== false,
|
||||
ghost_mode: Boolean(user.ghost_mode),
|
||||
show_location: user.show_location === true,
|
||||
post_location_enabled: user.post_location_enabled === true,
|
||||
is_private: user.is_private === true,
|
||||
gift_enabled: user.gift_enabled !== false,
|
||||
offer_payment_enabled: user.offer_payment_enabled !== false,
|
||||
preferred_language: user.preferred_language || 'fa',
|
||||
// «کاربرانی که شما را مسدود کردهاند» — blocked_by یه فیلد denormalized روی خودِ
|
||||
// کاربره (هر بار block/unblock سینک میشه)، پس فقط طولش کافیه، نیازی به کوئری
|
||||
// جدا روی کل کالکشن کاربرها نیست
|
||||
blocked_by_count: Array.isArray(blocked_by) ? blocked_by.length : 0,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'خطا در دریافت وضعیت حساب' })
|
||||
}
|
||||
}
|
||||
|
||||
const updatePrivacySettings = async (req, res) => {
|
||||
try {
|
||||
const user = await UserModel.findById(req.user._id)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر یافت نشد' })
|
||||
}
|
||||
if (typeof req.body.allow_save_posts === 'boolean') {
|
||||
user.allow_save_posts = req.body.allow_save_posts
|
||||
}
|
||||
if (typeof req.body.ghost_mode === 'boolean') {
|
||||
user.ghost_mode = req.body.ghost_mode
|
||||
}
|
||||
if (typeof req.body.show_location === 'boolean') {
|
||||
user.show_location = req.body.show_location
|
||||
}
|
||||
if (typeof req.body.post_location_enabled === 'boolean') {
|
||||
user.post_location_enabled = req.body.post_location_enabled
|
||||
}
|
||||
if (typeof req.body.is_private === 'boolean') {
|
||||
user.is_private = req.body.is_private
|
||||
}
|
||||
if (typeof req.body.gift_enabled === 'boolean') {
|
||||
user.gift_enabled = req.body.gift_enabled
|
||||
}
|
||||
if (typeof req.body.offer_payment_enabled === 'boolean') {
|
||||
user.offer_payment_enabled = req.body.offer_payment_enabled
|
||||
}
|
||||
if (APP_LANGUAGE_CODES.includes(req.body.preferred_language)) {
|
||||
user.preferred_language = req.body.preferred_language
|
||||
}
|
||||
await user.save()
|
||||
return res.status(200).json({
|
||||
allow_save_posts: user.allow_save_posts !== false,
|
||||
ghost_mode: Boolean(user.ghost_mode),
|
||||
show_location: user.show_location === true,
|
||||
post_location_enabled: user.post_location_enabled === true,
|
||||
is_private: user.is_private === true,
|
||||
gift_enabled: user.gift_enabled !== false,
|
||||
offer_payment_enabled: user.offer_payment_enabled !== false,
|
||||
preferred_language: user.preferred_language || 'fa',
|
||||
})
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'خطا در ذخیره تنظیمات' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* «ریست الگوریتم» — تمام سیگنالهای رفتاریِ ذخیرهشدهی این کاربر که فید شخصیسازیشده
|
||||
* (utils/exploreAlgorithm.js) رو میسازن پاک میکنه: لاگ کامل تعاملهاش (بازدید/دویل/اسکیپ/
|
||||
* بازدید پروفایل و...) که buildUserExploreSignals/buildUnifiedInterestScore ازش تغذیه
|
||||
* میکنن، بهعلاوهی ترتیب رتبهبندیشدهی کششدهی درونحافظهش (تا فید بعدی از صفر
|
||||
* بازمحاسبه بشه، نه ادامهی همون ترتیب قبلی). لایک/کامنت/دنبالکردن دستنخورده میمونن —
|
||||
* اونها محتوای صریح خودِ کاربرن، نه «حالت الگوریتم»
|
||||
*/
|
||||
const resetAlgorithm = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
await ExploreInteractionModel.deleteMany({ viewerId: userId })
|
||||
const { resetUserFeedCache } = require('../../../utils/exploreAlgorithm')
|
||||
resetUserFeedCache(String(userId))
|
||||
return res.status(200).json({ message: 'الگوریتم بازنشانی شد' })
|
||||
} catch (err) {
|
||||
console.error('resetAlgorithm:', err)
|
||||
return res.status(500).json({ message: 'خطا در بازنشانی الگوریتم' })
|
||||
}
|
||||
}
|
||||
|
||||
/** Caller's own country/province/city selection (the "Region" picker) — a denormalized
|
||||
* snapshot, same pattern as the existing province/city fields, not a live FK join. */
|
||||
const updateUserRegion = async (req, res) => {
|
||||
try {
|
||||
const { CountryModel, ProvinceModel, CityModel } = require('../../../models/StateCity')
|
||||
const user = await UserModel.findById(req.user._id)
|
||||
if (!user) return res.status(404).json({ message: 'کاربر یافت نشد' })
|
||||
|
||||
const { country_id, province_id, city_id } = req.body
|
||||
|
||||
const [country, province, city] = await Promise.all([
|
||||
country_id ? CountryModel.findOne({ id: Number(country_id) }).lean() : null,
|
||||
province_id ? ProvinceModel.findOne({ id: Number(province_id) }).lean() : null,
|
||||
city_id ? CityModel.findOne({ id: Number(city_id) }).lean() : null
|
||||
])
|
||||
|
||||
if (country_id && !country) return res.status(422).json({ message: 'کشور نامعتبر است' })
|
||||
if (province_id && !province) return res.status(422).json({ message: 'استان نامعتبر است' })
|
||||
if (city_id && !city) return res.status(422).json({ message: 'شهر نامعتبر است' })
|
||||
|
||||
user.country = country ? { id: country.id, name: country.name_fa, iso2: country.iso2, slug: country.slug } : null
|
||||
user.province = province ? { id: province.id, name: province.name, slug: province.slug } : null
|
||||
user.city = city ? { id: city.id, name: city.name, slug: city.slug } : null
|
||||
await user.save()
|
||||
|
||||
return res.status(200).json({
|
||||
country: user.country,
|
||||
province: user.province,
|
||||
city: user.city
|
||||
})
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'خطا در ذخیره منطقه' })
|
||||
}
|
||||
}
|
||||
|
||||
const pinPost = async (req, res) => {
|
||||
try {
|
||||
const userId = getUserIdFromToken(req)
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: 'دسترسی غیرمجاز' })
|
||||
}
|
||||
const { postId } = req.body
|
||||
if (!postId) {
|
||||
return res.status(400).json({ message: 'شناسه پست الزامی است' })
|
||||
}
|
||||
|
||||
const post = await PostModel.findById(postId).select('user_id status').lean()
|
||||
if (
|
||||
!post ||
|
||||
String(post.user_id) !== String(userId) ||
|
||||
!PUBLIC_POST_STATUSES.includes(post.status)
|
||||
) {
|
||||
return res.status(404).json({ message: 'پست یافت نشد' })
|
||||
}
|
||||
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر یافت نشد' })
|
||||
}
|
||||
|
||||
const pinned = (user.pinned_post_ids || []).map(String)
|
||||
// اگر قبلاً پین شده، آنپین کن (تاگل)
|
||||
if (pinned.includes(String(postId))) {
|
||||
user.pinned_post_ids = (user.pinned_post_ids || []).filter(
|
||||
(id) => String(id) !== String(postId)
|
||||
)
|
||||
await user.save()
|
||||
return res.status(200).json({
|
||||
message: 'پین پست برداشته شد',
|
||||
pinned_post_ids: user.pinned_post_ids,
|
||||
unpinned: true,
|
||||
})
|
||||
}
|
||||
if (pinned.length >= 3) {
|
||||
return res.status(400).json({ message: 'حداکثر ۳ پست را میتوانید پین کنید' })
|
||||
}
|
||||
|
||||
user.pinned_post_ids = [postId, ...(user.pinned_post_ids || [])]
|
||||
await user.save()
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'پست با موفقیت پین شد',
|
||||
pinned_post_ids: user.pinned_post_ids,
|
||||
unpinned: false,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('pinPost:', err)
|
||||
return res.status(500).json({ message: 'خطا در پین کردن پست' })
|
||||
}
|
||||
}
|
||||
|
||||
const toggleArchivePost = async (req, res) => {
|
||||
try {
|
||||
const userId = getUserIdFromToken(req)
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: 'دسترسی غیرمجاز' })
|
||||
}
|
||||
const { postId } = req.body
|
||||
if (!postId) {
|
||||
return res.status(400).json({ message: 'شناسه پست الزامی است' })
|
||||
}
|
||||
|
||||
const post = await PostModel.findById(postId)
|
||||
if (!post || String(post.user_id) !== String(userId)) {
|
||||
return res.status(404).json({ message: 'پست یافت نشد' })
|
||||
}
|
||||
|
||||
post.is_archived = !post.is_archived
|
||||
post.archived_at = post.is_archived ? new Date() : null
|
||||
await post.save()
|
||||
|
||||
return res.status(200).json({
|
||||
message: post.is_archived ? 'پست بایگانی شد' : 'پست دوباره منتشر شد',
|
||||
is_archived: post.is_archived,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('toggleArchivePost:', err)
|
||||
return res.status(500).json({ message: 'خطا در بایگانی پست' })
|
||||
}
|
||||
}
|
||||
|
||||
const getArchivedPosts = async (req, res) => {
|
||||
try {
|
||||
const userId = getUserIdFromToken(req)
|
||||
if (!userId) {
|
||||
return res.status(401).json({ message: 'دسترسی غیرمجاز' })
|
||||
}
|
||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1)
|
||||
const limit = Math.min(30, Math.max(1, parseInt(req.query.limit, 10) || 12))
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
const filter = { user_id: userId, is_archived: true }
|
||||
const [posts, total] = await Promise.all([
|
||||
PostModel.find(filter).sort({ archived_at: -1 }).skip(skip).limit(limit).lean(),
|
||||
PostModel.countDocuments(filter),
|
||||
])
|
||||
|
||||
return res.status(200).json({
|
||||
posts,
|
||||
total,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit) || 1,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('getArchivedPosts:', err)
|
||||
return res.status(500).json({ message: 'خطا در دریافت پستهای بایگانیشده' })
|
||||
}
|
||||
}
|
||||
|
||||
const ANALYTICS_RANGE_DAYS = { '7d': 7, '30d': 30, '90d': 90, all: null }
|
||||
|
||||
const getAccountAnalytics = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const range = ANALYTICS_RANGE_DAYS[req.query.range] !== undefined ? req.query.range : '30d'
|
||||
const days = ANALYTICS_RANGE_DAYS[range]
|
||||
const since = days ? new Date(Date.now() - days * MS_DAY) : new Date(0)
|
||||
const dayFormat = '%Y-%m-%d'
|
||||
|
||||
const myPostIds = await PostModel.find({ user_id: userId }).distinct('_id')
|
||||
|
||||
const [postsSeries, followersSeries, likesSeries, commentsSeries, totalPosts, totalFollowers, totalLikes, totalComments] = await Promise.all([
|
||||
PostModel.aggregate([
|
||||
{ $match: { user_id: new mongoose.Types.ObjectId(userId), createdAt: { $gte: since } } },
|
||||
{ $group: { _id: { $dateToString: { format: dayFormat, date: '$createdAt' } }, count: { $sum: 1 } } },
|
||||
{ $sort: { _id: 1 } },
|
||||
]),
|
||||
UserFollowModel.aggregate([
|
||||
{ $match: { followingId: new mongoose.Types.ObjectId(userId), createdAt: { $gte: since } } },
|
||||
{ $group: { _id: { $dateToString: { format: dayFormat, date: '$createdAt' } }, count: { $sum: 1 } } },
|
||||
{ $sort: { _id: 1 } },
|
||||
]),
|
||||
myPostIds.length
|
||||
? LikeModel.aggregate([
|
||||
{ $match: { postId: { $in: myPostIds }, createdAt: { $gte: since } } },
|
||||
{ $group: { _id: { $dateToString: { format: dayFormat, date: '$createdAt' } }, count: { $sum: 1 } } },
|
||||
{ $sort: { _id: 1 } },
|
||||
])
|
||||
: [],
|
||||
myPostIds.length
|
||||
? CommentModel.aggregate([
|
||||
{ $match: { post: { $in: myPostIds }, comment_for: 'post', createdAt: { $gte: since } } },
|
||||
{ $group: { _id: { $dateToString: { format: dayFormat, date: '$createdAt' } }, count: { $sum: 1 } } },
|
||||
{ $sort: { _id: 1 } },
|
||||
])
|
||||
: [],
|
||||
PostModel.countDocuments({ user_id: userId }),
|
||||
UserFollowModel.countDocuments({ followingId: userId }),
|
||||
myPostIds.length ? LikeModel.countDocuments({ postId: { $in: myPostIds } }) : 0,
|
||||
myPostIds.length ? CommentModel.countDocuments({ post: { $in: myPostIds }, comment_for: 'post' }) : 0,
|
||||
])
|
||||
|
||||
const mergeMap = new Map()
|
||||
const bump = (arr, key) => {
|
||||
arr.forEach((item) => {
|
||||
const entry = mergeMap.get(item._id) || { date: item._id, posts: 0, followers: 0, likes: 0, comments: 0 }
|
||||
entry[key] = item.count
|
||||
mergeMap.set(item._id, entry)
|
||||
})
|
||||
}
|
||||
bump(postsSeries, 'posts')
|
||||
bump(followersSeries, 'followers')
|
||||
bump(likesSeries, 'likes')
|
||||
bump(commentsSeries, 'comments')
|
||||
|
||||
const series = [...mergeMap.values()].sort((a, b) => (a.date < b.date ? -1 : 1))
|
||||
|
||||
return res.status(200).json({
|
||||
range,
|
||||
series,
|
||||
totals: {
|
||||
posts: totalPosts,
|
||||
followers: totalFollowers,
|
||||
likes: totalLikes,
|
||||
comments: totalComments,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('getAccountAnalytics:', err)
|
||||
return res.status(500).json({ message: 'خطا در دریافت آنالیتیکس' })
|
||||
}
|
||||
}
|
||||
|
||||
const getProfileVisitors = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user._id
|
||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1)
|
||||
const limit = Math.min(50, Math.max(1, parseInt(req.query.limit, 10) || 20))
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
const filter = {
|
||||
targetType: 'profile',
|
||||
action: 'profile_visit',
|
||||
targetId: userId,
|
||||
viewerId: { $ne: userId },
|
||||
}
|
||||
|
||||
const resolveVisitDate = (visit) => {
|
||||
if (visit.createdAt) return new Date(visit.createdAt)
|
||||
if (visit.created_at) return new Date(visit.created_at)
|
||||
if (visit._id && mongoose.Types.ObjectId.isValid(visit._id)) {
|
||||
return new mongoose.Types.ObjectId(String(visit._id)).getTimestamp()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const visits = await ExploreInteractionModel.find(filter)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.lean()
|
||||
|
||||
const viewerIds = [...new Set(visits.map((v) => String(v.viewerId)))]
|
||||
const viewers = await UserModel.find({ _id: { $in: viewerIds } })
|
||||
.select('_id user_name first_name last_name profile_image account_status ghost_mode')
|
||||
.lean()
|
||||
const byId = Object.fromEntries(viewers.map((v) => [String(v._id), v]))
|
||||
|
||||
const list = visits
|
||||
.map((v) => {
|
||||
const u = byId[String(v.viewerId)] || {}
|
||||
// حالت روح: بازدیدکنندهای که الان ghost_mode داره نباید توی لیست بازدیدکنندگان
|
||||
// صاحب پروفایل دیده بشه — قبلاً این فیلد اصلاً select نمیشد، پس این چک هیچوقت
|
||||
// اجرا نمیشد و بازدیدهای کاربرهای روححالت هم عادی نمایش داده میشدن
|
||||
if (!u.user_name || u.account_status === 'deleted' || u.ghost_mode) return null
|
||||
const visitDate = resolveVisitDate(v)
|
||||
return {
|
||||
_id: String(v.viewerId),
|
||||
user_name: u.user_name || '',
|
||||
first_name: u.first_name || '',
|
||||
last_name: u.last_name || '',
|
||||
profile_image: u.profile_image || '',
|
||||
visited_at: visitDate ? visitDate.toISOString() : null,
|
||||
visited_at_time: visitDate ? moment(visitDate).format('HH:mm') : null,
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
const total = await ExploreInteractionModel.countDocuments(filter)
|
||||
|
||||
return res.status(200).json({ visitors: list, total, page, totalPages: Math.ceil(total / limit) || 1 })
|
||||
} catch (err) {
|
||||
console.error('getProfileVisitors:', err)
|
||||
return res.status(500).json({ message: 'خطا در دریافت بازدیدکنندگان' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
deactivateAccount,
|
||||
reactivateAccount,
|
||||
deleteAccount,
|
||||
getAccountStatus,
|
||||
updatePrivacySettings,
|
||||
resetAlgorithm,
|
||||
updateUserRegion,
|
||||
getProfileVisitors,
|
||||
pinPost,
|
||||
toggleArchivePost,
|
||||
getArchivedPosts,
|
||||
getAccountAnalytics,
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const PostModel = require('../../../models/PostModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { getFollowCounts } = require('../../../utils/followHelpers')
|
||||
|
||||
|
||||
// GET user by ID from URL
|
||||
@@ -26,6 +28,7 @@ const getUserById = async (req, res, next) => {
|
||||
// isRegister = false;
|
||||
// }
|
||||
|
||||
// پروفایل عمومی دیگران — بدون داده حساس / بدون سند خام
|
||||
const response = {
|
||||
_id: user._id,
|
||||
user_type: user.user_type,
|
||||
@@ -34,7 +37,10 @@ const getUserById = async (req, res, next) => {
|
||||
last_name: user.last_name,
|
||||
user_name: user.user_name,
|
||||
show_location: user.show_location,
|
||||
post_location_enabled: user.post_location_enabled === true,
|
||||
is_private: user.is_private === true,
|
||||
is_verified: user.is_verified,
|
||||
verify_badge: user.verify_badge,
|
||||
is_Register: user.is_Register,
|
||||
user_score: user.user_score,
|
||||
rate: user.rate,
|
||||
@@ -45,21 +51,22 @@ const getUserById = async (req, res, next) => {
|
||||
eye_color: user.eye_color,
|
||||
hair_color: user.hair_color,
|
||||
profile_image: user.profile_image,
|
||||
lat: user.lat,
|
||||
lng: user.lng,
|
||||
address: user.address,
|
||||
lat: user.show_location ? user.lat : null,
|
||||
lng: user.show_location ? user.lng : null,
|
||||
address: user.show_location ? user.address : null,
|
||||
province: user.province,
|
||||
city: user.city,
|
||||
shaba: user.shaba,
|
||||
national_code: user.national_code,
|
||||
mobile: user.mobile,
|
||||
cooperation_abroad: user.cooperation_abroad,
|
||||
cooperation_type: user.cooperation_type,
|
||||
conversation_projects: user.conversation_projects,
|
||||
expertise: user.expertise,
|
||||
sub_expertise: user.sub_expertise,
|
||||
display_sub_expertise: user.display_sub_expertise,
|
||||
auth_provider: ['google', 'instagram'].includes(user.auth_provider)
|
||||
? user.auth_provider
|
||||
: 'mobile',
|
||||
e2ee_public_key: user.e2ee_public_key || null,
|
||||
isRegister,
|
||||
user,
|
||||
services: service === "1" ? user.services : null
|
||||
};
|
||||
|
||||
@@ -71,7 +78,9 @@ const getUserById = async (req, res, next) => {
|
||||
|
||||
const getProfile = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
const authHeader = req.header('Authorization')
|
||||
if (!authHeader) return res.status(401).send('Access Denied')
|
||||
const token = authHeader.split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
const service = req.query.services
|
||||
|
||||
@@ -82,10 +91,9 @@ const getProfile = async (req, res, next) => {
|
||||
let isRegister = true
|
||||
if (!user.user_name ||
|
||||
!user.first_name ||
|
||||
!user.last_name ||
|
||||
!user.user_type ||
|
||||
!user.national_card_image ||
|
||||
!user.password ||
|
||||
!user.national_card_image ||
|
||||
!user.national_code
|
||||
) {
|
||||
isRegister = false
|
||||
@@ -93,15 +101,27 @@ const getProfile = async (req, res, next) => {
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'کاربر مورد نظر یافت نشد' })
|
||||
}
|
||||
|
||||
const postsCount = await PostModel.countDocuments({
|
||||
user_id: user._id,
|
||||
status: { $in: ['pending', 'accept'] }
|
||||
})
|
||||
const { followersCount, followingCount } = await getFollowCounts(user._id)
|
||||
const blockedCount = user.blocked_users?.length || 0
|
||||
|
||||
const response = {
|
||||
_id: user._id,
|
||||
is_self: true,
|
||||
user_type: user.user_type,
|
||||
user_level: user.user_level,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
user_name: user.user_name,
|
||||
show_location: user.show_location,
|
||||
post_location_enabled: user.post_location_enabled === true,
|
||||
is_private: user.is_private === true,
|
||||
is_verified: user.is_verified,
|
||||
verify_badge: user.verify_badge,
|
||||
is_Register: user.is_Register,
|
||||
user_score: user.user_score,
|
||||
rate: user.rate,
|
||||
@@ -111,22 +131,47 @@ const getProfile = async (req, res, next) => {
|
||||
size: user.size,
|
||||
eye_color: user.eye_color,
|
||||
hair_color: user.hair_color,
|
||||
marital_status: user.marital_status,
|
||||
residence_type: user.residence_type,
|
||||
residence_area: user.residence_area,
|
||||
vehicle_type: user.vehicle_type,
|
||||
vehicle_details: user.vehicle_details,
|
||||
personal_style: user.personal_style,
|
||||
job: user.job,
|
||||
income_range: user.income_range,
|
||||
moral_traits: user.moral_traits,
|
||||
personal_details_public: user.personal_details_public === true,
|
||||
profile_image: user.profile_image,
|
||||
lat: user.lat,
|
||||
lng: user.lng,
|
||||
address: user.address,
|
||||
country: user.country,
|
||||
province: user.province,
|
||||
city: user.city,
|
||||
shaba: user.shaba,
|
||||
national_code: user.national_code,
|
||||
birthday: user.birthday,
|
||||
mobile: user.mobile,
|
||||
email: user.email,
|
||||
auth_provider: user.auth_provider || 'mobile',
|
||||
cooperation_abroad: user.cooperation_abroad,
|
||||
cooperation_type: user.cooperation_type,
|
||||
conversation_projects: user.conversation_projects,
|
||||
expertise: user.expertise,
|
||||
sub_expertise: user.sub_expertise,
|
||||
display_sub_expertise: user.display_sub_expertise,
|
||||
isRegister,
|
||||
services: service === '1' ? user.services : null
|
||||
services: service === '1' ? user.services : null,
|
||||
account_status: user.account_status || 'active',
|
||||
deactivated_at: user.deactivated_at,
|
||||
reactivation_locked_until: user.reactivation_locked_until,
|
||||
scheduled_purge_at: user.scheduled_purge_at,
|
||||
allow_save_posts: user.allow_save_posts !== false,
|
||||
ghost_mode: Boolean(user.ghost_mode),
|
||||
postsCount,
|
||||
followersCount,
|
||||
followingCount,
|
||||
blockedCount
|
||||
}
|
||||
return res.status(200).json({ user: response })
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user