Initial commit

This commit is contained in:
payacom
2026-07-07 17:50:26 +03:30
parent 525565d685
commit 64eb8c288f
25 changed files with 801 additions and 231 deletions

View File

@@ -6,11 +6,22 @@ const jwt = require("jsonwebtoken");
const moment = require("moment-jalaali");
const NotificationModel = require("../../../models/NotificationModel");
const CommentModel = require("../../../models/CommentModel");
const {
hasCreatorRatedUser,
resolveRatingForComment,
recalculateUserRating,
} = require("../../../utils/commentRating");
const { createCommentNotification } = require("../../../utils/commentNotification");
const { ProvinceModel, CityModel } = require("../../../models/StateCity");
const OfferModel = require("../../../models/OfferModel");
const PostModel = require("../../../models/PostModel");
const ProjectModel = require("../../../models/ProjectModel");
const LicenseModel = require("../../../models/license")
const {
viewerIsBlockedBy,
blockedProfilePayload,
getBlockerUserIds,
} = require("../../../utils/blockVisibility")
const getAllLicenses = async (req, res, next) => {
@@ -775,9 +786,19 @@ const getPostsWeb = async (req, res, next) => {
.lean();
const userIds = [...new Set(explorePosts.map((p) => String(p.user_id)))];
const usersMap = await UserModel.find({ _id: { $in: userIds } })
.select('_id user_name first_name last_name expertise province city user_level')
.select('_id user_name first_name last_name expertise province city user_level show_location blocked_users')
.lean();
const usersById = Object.fromEntries(usersMap.map((u) => [String(u._id), u]));
if (decodedToken?.id) {
const blockerIds = new Set(
(await getBlockerUserIds(UserModel, decodedToken.id)).map(String)
);
explorePosts = explorePosts.filter(
(post) => !blockerIds.has(String(post.user_id))
);
}
explorePosts = explorePosts.map((post) => {
const user = usersById[String(post.user_id)] || {};
return {
@@ -786,8 +807,9 @@ const getPostsWeb = async (req, res, next) => {
first_name: user.first_name || '',
last_name: user.last_name || '',
expertise: user.expertise || '',
province: user.province || {},
city: user.city || {},
province: user.show_location ? user.province || {} : {},
city: user.show_location ? user.city || {} : {},
show_location: user.show_location,
user_level: user.user_level || '',
likesCount: post.likes ? post.likes.length : 0,
is_liked: false,
@@ -817,8 +839,15 @@ const getPostsWeb = async (req, res, next) => {
if (userLevel) userFilter.user_level = userLevel;
if (_id) userFilter._id = _id;
if (_id && decodedToken?.id) {
const profileOwner = await UserModel.findById(_id).select('blocked_users');
if (viewerIsBlockedBy(profileOwner, decodedToken.id)) {
return res.status(200).json({ posts: [], totalPages: 0, totalItems: 0 });
}
}
const users = await UserModel.find(userFilter)
.select("_id user_name first_name last_name expertise province city user_level")
.select("_id user_name first_name last_name expertise province city user_level show_location")
.lean();
if (!users || users.length === 0) {
@@ -836,6 +865,16 @@ const getPostsWeb = async (req, res, next) => {
return res.status(200).json({ posts: [], totalPages: 0, totalItems: 0 });
}
if (decodedToken?.id) {
const blockerIds = new Set(
(await getBlockerUserIds(UserModel, decodedToken.id)).map(String)
);
posts = posts.filter((post) => !blockerIds.has(String(post.user_id)));
if (!posts.length) {
return res.status(200).json({ posts: [], totalPages: 0, totalItems: 0 });
}
}
let likeFilter = {};
if (decodedToken) likeFilter.userId = decodedToken.id;
const likes = decodedToken
@@ -860,8 +899,9 @@ const getPostsWeb = async (req, res, next) => {
first_name: user?.first_name || "",
last_name: user?.last_name || "",
expertise: user?.expertise || "",
province: user?.province || {},
city: user?.city || {},
province: user?.show_location ? user?.province || {} : {},
city: user?.show_location ? user?.city || {} : {},
show_location: user?.show_location,
user_level: user?.user_level || "",
likesCount: post.likes ? post.likes.length : 0,
is_liked: !!likesMap[post._id.toString()],
@@ -918,6 +958,10 @@ const getSingleUser = async (req, res, next) => {
const blockedByCurrentUser = user.blocked_by.includes(userReqId);
const blockedYou = user.blocked_users.includes(userReqId);
if (blockedYou) {
return res.status(200).json({ user: blockedProfilePayload(user) });
}
// Allow chat
const offerExists = await OfferModel.exists({
$or: [
@@ -937,6 +981,7 @@ const getSingleUser = async (req, res, next) => {
user_name: user.user_name,
show_location: user.show_location,
is_verified: user.is_verified,
is_Register: user.is_Register,
user_score: user.user_score,
rate: user.rate,
bio: user.bio,
@@ -948,8 +993,8 @@ const getSingleUser = async (req, res, next) => {
profile_image: user.profile_image,
lat: user.show_location ? user.lat : null,
lng: user.show_location ? user.lng : null,
province: user.province,
city: user.city,
province: user.show_location ? user.province : null,
city: user.show_location ? user.city : null,
address: user.show_location ? user.address : null,
is_blocked: blockedByCurrentUser,
blocked_you: blockedYou,
@@ -1016,6 +1061,11 @@ const getSingleUserWeb = async (req, res, next) => {
receiver: user._id,
});
}
if (blockedYou && userReqId) {
return res.status(200).json({ user: blockedProfilePayload(user) });
}
// دریافت تعداد پست‌ها و پروژه‌ها
const postsCount = await PostModel.countDocuments({
user_id: user._id,
@@ -1043,6 +1093,7 @@ const getSingleUserWeb = async (req, res, next) => {
user_name: user.user_name,
show_location: user.show_location,
is_verified: user.is_verified,
is_Register: user.is_Register,
user_score: user.user_score,
rate: user.rate,
bio: user.bio,
@@ -1054,8 +1105,8 @@ const getSingleUserWeb = async (req, res, next) => {
profile_image: user.profile_image,
lat: user.show_location ? user.lat : null,
lng: user.show_location ? user.lng : null,
province: user.province,
city: user.city,
province: user.show_location ? user.province : null,
city: user.show_location ? user.city : null,
address: user.show_location ? user.address : null,
is_blocked: blockedByCurrentUser,
blocked_you: blockedYou,
@@ -1099,6 +1150,11 @@ const getUserContactInfo = async (req, res, next) => {
next(error);
}
};
const {
applyAutoBlockSuspension,
clearAutoBlockSuspension
} = require('../../../utils/blockSuspension')
const blockUser = async (req, res) => {
try {
const { user_to_block } = req.body;
@@ -1107,17 +1163,30 @@ const blockUser = async (req, res) => {
const decodedToken = jwt.verify(token, process.env.APP_SECRET);
const userReqId = decodedToken.id;
const currentUser = await UserModel.findById(userReqId); // Req
const userToBlock = await UserModel.findById(user_to_block); // To Block
userToBlock.blocked_by.push(currentUser);
currentUser.blocked_users.push(userToBlock);
// بررسی تعداد بلاک ها و تنظیم block_status
if (userToBlock.blocked_by.length >= 3) {
userToBlock.block_status = true;
if (userReqId === user_to_block) {
return res.status(400).json({ message: "امکان بلاک خودتان وجود ندارد" });
}
const currentUser = await UserModel.findById(userReqId);
const userToBlock = await UserModel.findById(user_to_block);
if (!currentUser || !userToBlock) {
return res.status(404).json({ message: "کاربر یافت نشد" });
}
const alreadyBlocked = userToBlock.blocked_by.some((id) =>
id.equals(currentUser._id)
);
if (alreadyBlocked) {
return res.status(400).json({ message: "این کاربر قبلاً بلاک شده است" });
}
userToBlock.blocked_by.push(currentUser._id);
currentUser.blocked_users.push(userToBlock._id);
applyAutoBlockSuspension(userToBlock);
await currentUser.save();
await userToBlock.save();
@@ -1136,18 +1205,17 @@ const unblockUser = async (req, res) => {
const decodedToken = jwt.verify(token, process.env.APP_SECRET);
const userReqId = decodedToken.id;
const currentUser = await UserModel.findById(userReqId); // Req
const userToUnblock = await UserModel.findById(user_to_unblock); // To Unblock
const currentUser = await UserModel.findById(userReqId);
const userToUnblock = await UserModel.findById(user_to_unblock);
if (!currentUser || !userToUnblock) {
return res.status(404).json({ message: "کاربر یافت نشد" });
}
// حذف کاربر از لیست blocked_by کاربر فعلی
currentUser.blocked_users.pull(userToUnblock._id);
// حذف کاربر از لیست blocked_users کاربر مورد نظر
userToUnblock.blocked_by.pull(currentUser._id);
// بررسی تعداد بلاک ها و تنظیم block_status
if (userToUnblock.blocked_by.length < 3) {
userToUnblock.block_status = null;
}
clearAutoBlockSuspension(userToUnblock);
await currentUser.save();
await userToUnblock.save();
@@ -1163,6 +1231,7 @@ const getComments = async (req, res, next) => {
try {
const token = req.header("Authorization").split(" ")[1];
if (!token) return res.status(401).send("Access Denied");
const decodedToken = jwt.verify(token, process.env.APP_SECRET);
const user_id = req.query.user_id;
const post_id = req.query.post_id;
@@ -1192,27 +1261,53 @@ const getComments = async (req, res, next) => {
const comments = await CommentModel.paginate(filter, options);
const formattedComments = comments.docs.map((comment) => ({
_id: comment._id,
comment: comment.comment,
rating: comment.rating,
createdAt: moment(comment.createdAt).format("jYYYY-jMM-jDD HH:mm"),
status: comment.status,
user: {
profile_image: comment.creator.profile_image,
user_name: comment.creator.user_name,
is_verified: comment.creator.is_verified,
_id: comment.creator._id,
},
project: comment.project ? comment.project.project_name : null,
post_id: comment.post ? comment.post.toString() : null,
}));
const earliestRatedComments = await CommentModel.find({
user: user_id,
rating: { $ne: null, $gt: 0 },
})
.select("creator _id createdAt")
.sort({ createdAt: 1 });
const ratedCreatorCommentIds = new Map();
for (const ratedComment of earliestRatedComments) {
const creatorId = ratedComment.creator.toString();
if (!ratedCreatorCommentIds.has(creatorId)) {
ratedCreatorCommentIds.set(creatorId, ratedComment._id.toString());
}
}
const formattedComments = comments.docs.map((comment) => {
const creatorId = comment.creator._id.toString();
const isPrimaryRatingComment =
comment.rating != null &&
comment.rating > 0 &&
ratedCreatorCommentIds.get(creatorId) === comment._id.toString();
return {
_id: comment._id,
comment: comment.comment,
rating: isPrimaryRatingComment ? comment.rating : null,
createdAt: moment(comment.createdAt).format("jYYYY-jMM-jDD HH:mm"),
status: comment.status,
user: {
profile_image: comment.creator.profile_image,
user_name: comment.creator.user_name,
is_verified: comment.creator.is_verified,
_id: comment.creator._id,
},
project: comment.project ? comment.project.project_name : null,
post_id: comment.post ? comment.post.toString() : null,
};
});
const hasRated = await hasCreatorRatedUser(decodedToken.id, user_id);
res.status(200).json({
comments: formattedComments,
totalPages: comments.totalPages,
totalItems: comments.totalDocs,
currentPage: comments.page,
has_rated: hasRated,
});
} catch (error) {
next(error);
@@ -1231,20 +1326,17 @@ const createUserComment = async (req, res, next) => {
return res.status(400).send({ message: "All fields are required" });
}
const existingRating = await CommentModel.findOne({
user: user_id,
creator: creatorId,
comment_for: post_id ? "post" : "user",
rating: { $exists: true, $ne: null }
});
const { ratingValue, isNewRating, requiresRating } =
await resolveRatingForComment({
creatorId,
targetUserId: user_id,
rate,
});
let ratingValue = rate;
if (existingRating) {
ratingValue = existingRating.rating;
} else {
if (!rate || rate < 1 || rate > 5) {
return res.status(400).send({ message: "Rating must be between 1 and 5" });
}
if (requiresRating) {
return res
.status(400)
.send({ message: "Rating must be between 1 and 5" });
}
const newComment = new CommentModel({
@@ -1258,50 +1350,16 @@ const createUserComment = async (req, res, next) => {
});
await newComment.save();
const user = await UserModel.findById(user_id);
// محاسبه امتیاز جدید کاربر
const comments = await CommentModel.find({
user: user_id,
comment_for: "user",
});
const totalRating = comments.reduce(
(acc, comment) => acc + comment.rating,
0
);
const totalComments = comments.length;
const averageRating = totalRating / totalComments;
// تعیین سطح کاربر
let userLevel;
if (user.expertise === "مدل") {
if (totalRating <= 50) userLevel = "تازه وارد";
else if (totalRating <= 100) userLevel = "استاندارد";
else if (totalRating <= 300) userLevel = "حرفه‌ای";
else userLevel = "استاد";
} else if (["زیبایی", "عکاس"].includes(user.expertise)) {
if (totalRating <= 50) userLevel = "تازه وارد";
else if (totalRating <= 100) userLevel = "استاندارد";
else if (totalRating <= 300) userLevel = "حرفه‌ای";
else userLevel = "استاد";
if (isNewRating) {
await recalculateUserRating(user_id);
}
// به‌روزرسانی اطلاعات کاربر
user.user_score = totalRating;
user.user_level = userLevel;
user.rate = averageRating.toFixed(1);
await user.save();
// ارسال نوتیفیکیشن
const notification = new NotificationModel({
user_id: user._id,
type: "user_comment",
title: "نظر جدید",
description: `یک کاربر جدید برای شما نظر ثبت کرد:
${comment}
امتیاز: ${rate} از 5`,
await createCommentNotification({
ownerId: user_id,
commenterId: creatorId,
entityId: post_id || user_id,
type: post_id ? "post_comment" : "profile_comment",
});
await notification.save();
res.status(201).json({
message: "نظر با موفقیت ثبت شد و بعد از تایید، منتشر میشود.",