full
This commit is contained in:
@@ -1,74 +0,0 @@
|
||||
const AcademyCategoryModel = require('../../../models/AcademyCategoryModel');
|
||||
// GET all categories
|
||||
exports.getAll = async (req, res) => {
|
||||
try {
|
||||
const categories = await AcademyCategoryModel.find({}).sort({ createdAt: -1 });
|
||||
res.status(200).json({ success: true, categories });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// CREATE category
|
||||
exports.create = async (req, res) => {
|
||||
try {
|
||||
const { title, status } = req.body;
|
||||
|
||||
if (!title) {
|
||||
return res.status(400).json({ success: false, message: "title is required" });
|
||||
}
|
||||
|
||||
const newCategory = new AcademyCategoryModel({
|
||||
title,
|
||||
status: status ?? true,
|
||||
});
|
||||
|
||||
await newCategory.save();
|
||||
|
||||
res.status(201).json({ success: true, category: newCategory });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// UPDATE category
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { title, status } = req.body;
|
||||
|
||||
const updated = await AcademyCategoryModel.findByIdAndUpdate(
|
||||
id,
|
||||
{ title, status },
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
|
||||
if (!updated) {
|
||||
return res.status(404).json({ success: false, message: "category not found" });
|
||||
}
|
||||
|
||||
res.status(200).json({ success: true, category: updated });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// DELETE
|
||||
exports.remove = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const deleted = await AcademyCategoryModel.findByIdAndDelete(id);
|
||||
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ success: false, message: "category not found" });
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'category deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
};
|
||||
@@ -15,13 +15,30 @@ const CourseComment = require("../../../models/CourseComentModel");
|
||||
const IsPaymentCourse = require("../../../models/IsPaymentCourseModel");
|
||||
const Tax = require("../../../models/TaxModel");
|
||||
const AcademyCategoryModel = require('../../../models/AcademyCategoryModel');
|
||||
const { buildUnifiedInterestScore, blendFreshIntoRanked: blendInterestIntoRanked } = require('../../../utils/exploreAlgorithm');
|
||||
const { resolveCommentModerationStatus } = require('../../../utils/contentModeration');
|
||||
const { saveCommentWithModerationStatus } = require('../../../utils/commentStatusSave');
|
||||
const {
|
||||
coursePublicStatusFilter,
|
||||
academyContentPublicStatusFilter,
|
||||
commentStatusFilter,
|
||||
PUBLIC_COMMENT_STATUSES,
|
||||
} = require('../../../utils/moderationStatus');
|
||||
const {
|
||||
createLikeNotification,
|
||||
resolveCourseOwnerId
|
||||
} = require('../../../utils/likeNotification');
|
||||
const {
|
||||
createCommentNotification
|
||||
createCommentNotification,
|
||||
createRatingNotification,
|
||||
} = require('../../../utils/commentNotification');
|
||||
const {
|
||||
createCoursePurchaseNotification,
|
||||
} = require('../../../utils/academyNotification');
|
||||
const AdvertisingProfileModel = require('../../../models/AdvertisingProfile');
|
||||
const WalletTransactionModel = require('../../../models/WalletTransactionModel');
|
||||
|
||||
const ACADEMY_COMMISSION_RATE = 0.15;
|
||||
|
||||
const onServerRestart = async () => {
|
||||
const CoursePayment = await CoursePaymentModel.findOne({
|
||||
@@ -81,8 +98,15 @@ const findAcademy = async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
const advertisingProfile = await AdvertisingProfileModel.findOne({
|
||||
user: userId,
|
||||
}).select("contactInfo");
|
||||
|
||||
res.status(200).json({
|
||||
academy: academy,
|
||||
academy: {
|
||||
...academy.toObject(),
|
||||
contactInfo: advertisingProfile?.contactInfo || null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// در صورت بروز خطا، ارسال پیام خطا به کاربر
|
||||
@@ -96,16 +120,53 @@ const findAcademyById = async (req, res, next) => {
|
||||
|
||||
const academy = await AcademyModel.findById(_id);
|
||||
if (!academy) {
|
||||
res.status(500).json({
|
||||
academy: "اکادمی موحود نیست",
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "اکادمی موجود نیست",
|
||||
});
|
||||
}
|
||||
|
||||
const owner = await UserModel.findById(academy.userId).select(
|
||||
"user_name first_name last_name is_verified verify_badge is_Register profile_image user_score rate"
|
||||
);
|
||||
|
||||
const packagesCount = await CourseModel.countDocuments({
|
||||
academyId: academy._id,
|
||||
status: coursePublicStatusFilter(),
|
||||
});
|
||||
|
||||
const academyCourses = await CourseModel.find({
|
||||
academyId: academy._id,
|
||||
status: coursePublicStatusFilter(),
|
||||
}).select("_id number_of_course_content");
|
||||
|
||||
const courseIds = academyCourses.map((course) => course._id);
|
||||
const soldCount = courseIds.length
|
||||
? await IsPaymentCourse.countDocuments({ course_id: { $in: courseIds } })
|
||||
: 0;
|
||||
|
||||
const totalVideos = academyCourses.reduce(
|
||||
(sum, course) => sum + (parseInt(course.number_of_course_content, 10) || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const advertisingProfile = await AdvertisingProfileModel.findOne({
|
||||
user: academy.userId,
|
||||
}).select("contactInfo");
|
||||
|
||||
res.status(200).json({
|
||||
academy: academy,
|
||||
academy: {
|
||||
...academy.toObject(),
|
||||
stats: {
|
||||
packagesCount,
|
||||
soldCount,
|
||||
totalVideos,
|
||||
},
|
||||
owner,
|
||||
contactInfo: advertisingProfile?.contactInfo || null,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// در صورت بروز خطا، ارسال پیام خطا به کاربر
|
||||
console.error("Error:", error);
|
||||
res.status(500).json({ message: "err in creat" });
|
||||
}
|
||||
@@ -141,6 +202,20 @@ const academyProfile = async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
let shebaDigits = String(sheba || "")
|
||||
.replace(/\s/g, "")
|
||||
.toUpperCase();
|
||||
if (shebaDigits.startsWith("IR")) {
|
||||
shebaDigits = shebaDigits.slice(2);
|
||||
}
|
||||
shebaDigits = shebaDigits.replace(/\D/g, "");
|
||||
if (!/^\d{24}$/.test(shebaDigits)) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: "شماره شبا باید ۲۴ رقم باشد",
|
||||
});
|
||||
}
|
||||
|
||||
const uploadDir = path.join(__dirname, "../../../../storage/profiles");
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
@@ -155,11 +230,40 @@ const academyProfile = async (req, res, next) => {
|
||||
|
||||
academy.academy_image = `/profiles/${uniqueFileName}`;
|
||||
academy.academy_name = name;
|
||||
academy.sheba = sheba;
|
||||
academy.sheba = `IR${shebaDigits}`;
|
||||
academy.bio = bio;
|
||||
academy.tag = tag;
|
||||
await academy.save();
|
||||
|
||||
const contactPayloadPresent =
|
||||
req.body.contact_mobile !== undefined ||
|
||||
req.body.contact_telegram !== undefined ||
|
||||
req.body.contact_whatsapp !== undefined ||
|
||||
req.body.contact_instagram !== undefined;
|
||||
|
||||
if (contactPayloadPresent) {
|
||||
const contactInfo =
|
||||
parseCourseContactInfo(req.body) ?? {
|
||||
mobile: null,
|
||||
telegramLink: null,
|
||||
whatsappNumber: null,
|
||||
instagramLink: null,
|
||||
};
|
||||
let advertisingProfile = await AdvertisingProfileModel.findOne({
|
||||
user: userId,
|
||||
});
|
||||
if (!advertisingProfile) {
|
||||
advertisingProfile = new AdvertisingProfileModel({
|
||||
user: userId,
|
||||
contactInfo,
|
||||
});
|
||||
await advertisingProfile.save();
|
||||
} else {
|
||||
advertisingProfile.contactInfo = contactInfo;
|
||||
await advertisingProfile.save();
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: "ثبت شد",
|
||||
});
|
||||
@@ -170,6 +274,28 @@ const academyProfile = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
const parseCourseContactInfo = (body) => {
|
||||
const mobile = body.contact_mobile || body["contactInfo[mobile]"] || "";
|
||||
const telegramLink =
|
||||
body.contact_telegram || body["contactInfo[telegramLink]"] || "";
|
||||
const whatsappNumber =
|
||||
body.contact_whatsapp || body["contactInfo[whatsappNumber]"] || "";
|
||||
const instagramLink =
|
||||
body.contact_instagram || body["contactInfo[instagramLink]"] || "";
|
||||
|
||||
const hasAny = [mobile, telegramLink, whatsappNumber, instagramLink].some(
|
||||
Boolean
|
||||
);
|
||||
if (!hasAny) return undefined;
|
||||
|
||||
return {
|
||||
mobile: mobile || null,
|
||||
telegramLink: telegramLink || null,
|
||||
whatsappNumber: whatsappNumber || null,
|
||||
instagramLink: instagramLink || null,
|
||||
};
|
||||
};
|
||||
|
||||
const creatCourse = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header("Authorization").split(" ")[1];
|
||||
@@ -199,10 +325,13 @@ const creatCourse = async (req, res, next) => {
|
||||
caption,
|
||||
course_time,
|
||||
number_of_course_content,
|
||||
teacher_number,
|
||||
teacher_name,
|
||||
is_free,
|
||||
} = req.body;
|
||||
|
||||
const isFreePackage = is_free === "true" || is_free === true;
|
||||
const contactInfo = parseCourseContactInfo(req.body);
|
||||
|
||||
const { course_image } = req.files;
|
||||
|
||||
if (!course_image) {
|
||||
@@ -227,16 +356,18 @@ const creatCourse = async (req, res, next) => {
|
||||
// ✅ ایجاد رکورد جدید
|
||||
const newAcademy = new CourseModel({
|
||||
course_image: `/profiles/${uniqueFileName}`,
|
||||
price,
|
||||
price: isFreePackage ? "0" : price,
|
||||
cuorse_name,
|
||||
category,
|
||||
offer,
|
||||
offer: offer || "0",
|
||||
teacher_name,
|
||||
academyId: academy._id,
|
||||
caption,
|
||||
course_time,
|
||||
number_of_course_content,
|
||||
teacher_number,
|
||||
is_free: isFreePackage,
|
||||
...(contactInfo ? { contactInfo } : {}),
|
||||
status: 'temp_accept',
|
||||
});
|
||||
|
||||
await newAcademy.save();
|
||||
@@ -269,9 +400,16 @@ const updateCourse = async (req, res, next) => {
|
||||
caption,
|
||||
course_time,
|
||||
number_of_course_content,
|
||||
teacher_number,
|
||||
is_free,
|
||||
} = req.body;
|
||||
|
||||
const isFreePackage =
|
||||
is_free === "true" || is_free === true
|
||||
? true
|
||||
: is_free === "false" || is_free === false
|
||||
? false
|
||||
: undefined;
|
||||
|
||||
// اعتبارسنجی courseId
|
||||
if (!courseId) {
|
||||
return res.status(422).json({
|
||||
@@ -317,16 +455,29 @@ const updateCourse = async (req, res, next) => {
|
||||
}
|
||||
|
||||
// ========== به روز رسانی فیلدها (فقط فیلدهایی که ارسال شده) ==========
|
||||
if (price !== undefined) course.price = price;
|
||||
if (price !== undefined) course.price = isFreePackage ? "0" : price;
|
||||
if (cuorse_name !== undefined) course.cuorse_name = cuorse_name;
|
||||
if (category !== undefined) course.category = category;
|
||||
if (offer !== undefined) course.offer = offer;
|
||||
if (offer !== undefined) course.offer = offer || "0";
|
||||
if (caption !== undefined) course.caption = caption;
|
||||
if (course_time !== undefined) course.course_time = course_time;
|
||||
if (number_of_course_content !== undefined)
|
||||
course.number_of_course_content = number_of_course_content;
|
||||
if (teacher_number !== undefined) course.teacher_number = teacher_number;
|
||||
if (teacher_name !== undefined) course.teacher_name = teacher_name;
|
||||
if (isFreePackage !== undefined) course.is_free = isFreePackage;
|
||||
if (
|
||||
req.body.contact_mobile !== undefined ||
|
||||
req.body.contact_telegram !== undefined ||
|
||||
req.body.contact_whatsapp !== undefined ||
|
||||
req.body.contact_instagram !== undefined
|
||||
) {
|
||||
course.contactInfo = {
|
||||
mobile: req.body.contact_mobile || null,
|
||||
telegramLink: req.body.contact_telegram || null,
|
||||
whatsappNumber: req.body.contact_whatsapp || null,
|
||||
instagramLink: req.body.contact_instagram || null,
|
||||
};
|
||||
}
|
||||
|
||||
await course.save();
|
||||
|
||||
@@ -413,7 +564,7 @@ const getCourse = async (req, res, next) => {
|
||||
// ✅ شرط جستجو - فقط دورههای تایید شده
|
||||
const query = {
|
||||
academyId: academy._id,
|
||||
status: "accept",
|
||||
status: coursePublicStatusFilter(),
|
||||
};
|
||||
|
||||
// دریافت تعداد کل دورههای تایید شده
|
||||
@@ -495,6 +646,7 @@ const getCourses = async (req, res, next) => {
|
||||
const hasOffer = req.query.hasOffer === "true"; // تخفیف دار
|
||||
const teacherName = req.query.teacherName || "";
|
||||
const type = req.query.type || ""; // normal, pro, legend
|
||||
const isFree = req.query.isFree === "true";
|
||||
const status = req.query.status || "all"; // pending, accept, reject
|
||||
const sortBy = req.query.sortBy || "createdAt";
|
||||
const sortOrder = req.query.sortOrder === "asc" ? 1 : -1;
|
||||
@@ -541,9 +693,17 @@ const getCourses = async (req, res, next) => {
|
||||
query.type = type;
|
||||
}
|
||||
|
||||
// فیلتر بر اساس وضعیت
|
||||
if (status !== "all") {
|
||||
if (isFree) {
|
||||
query.is_free = true;
|
||||
}
|
||||
|
||||
// فیلتر بر اساس وضعیت — در اپ، temp_accept هم نمایش داده میشود
|
||||
if (status === "accept") {
|
||||
query.status = coursePublicStatusFilter();
|
||||
} else if (status !== "all") {
|
||||
query.status = status;
|
||||
} else {
|
||||
query.status = coursePublicStatusFilter();
|
||||
}
|
||||
|
||||
// ========== 6. دریافت آمار و اطلاعات ==========
|
||||
@@ -584,6 +744,7 @@ const getCourses = async (req, res, next) => {
|
||||
// ========== 7. دریافت پکیجها با صفحهبندی ==========
|
||||
const courses = await CourseModel.find(query)
|
||||
.populate("user_id", "name email") // اطلاعات کاربر سازنده
|
||||
.populate("academyId", "academy_name academy_image") // برای نمایش عکس/نام آموزشگاه توی لیست
|
||||
.populate("likes", "name") // اطلاعات لایکها
|
||||
.sort({ [sortBy]: sortOrder })
|
||||
.skip(skip)
|
||||
@@ -591,16 +752,88 @@ const getCourses = async (req, res, next) => {
|
||||
.lean();
|
||||
|
||||
// پردازش دادهها برای خروجی
|
||||
const processedCourses = courses.map((course) => ({
|
||||
let processedCourses = courses.map((course) => ({
|
||||
...course,
|
||||
// academy_name/academy_image رو صاف روی خود کورس میذاریم (نه فقط زیر academyId
|
||||
// populate-شده) چون اپ موبایل/وب این دو فیلد رو مستقیم روی course انتظار دارن
|
||||
academy_name: course.academyId?.academy_name || null,
|
||||
academy_image: course.academyId?.academy_image || null,
|
||||
// academyId رو بعد از populate به همون شناسهی رشتهای برمیگردونیم — وگرنه بهجای
|
||||
// ObjectId، کل سابداکیومنت populate-شده (academy_name/academy_image) توی جواب میره؛
|
||||
// موبایل/وب همین academyId رو مستقیم به /academy/findAcademyById میفرستن، پس یه
|
||||
// object بهجای رشته باعث خطای کست ObjectId میشه (نتیجه: پروفایل آموزشگاه لود نمیشه)
|
||||
academyId: course.academyId?._id
|
||||
? String(course.academyId._id)
|
||||
: course.academyId,
|
||||
priceNumber: parseInt(course.price) || 0,
|
||||
offerNumber: parseInt(course.offer) || 0,
|
||||
finalPrice: course.offer
|
||||
? (parseInt(course.price) * (100 - parseInt(course.offer))) / 100
|
||||
: parseInt(course.price),
|
||||
finalPrice: course.is_free
|
||||
? 0
|
||||
: course.offer
|
||||
? (parseInt(course.price) * (100 - parseInt(course.offer))) / 100
|
||||
: parseInt(course.price),
|
||||
likesCount: course.likes?.length || 0,
|
||||
}));
|
||||
|
||||
// بلند کردن دورههای مرتبط با علایق کاربر داخل نتایج پیشفرض (بدون مرتبسازی دستی کاربر)
|
||||
const isDefaultBrowsePath = !req.query.sortBy && !category && !search && page === 1;
|
||||
if (isDefaultBrowsePath) {
|
||||
try {
|
||||
const token = req.header("Authorization")?.split(" ")[1];
|
||||
const viewerId = token ? jwt.verify(token, process.env.APP_SECRET)?.id : null;
|
||||
if (viewerId) {
|
||||
const interestWeights = await buildUnifiedInterestScore(viewerId);
|
||||
const tags = Object.keys(interestWeights).filter((tag) => interestWeights[tag] > 0);
|
||||
if (tags.length) {
|
||||
const interestCategories = await AcademyCategoryModel.find({
|
||||
relatedExpertise: { $in: tags },
|
||||
})
|
||||
.select("title")
|
||||
.lean();
|
||||
const categoryTitles = interestCategories.map((c) => c.title);
|
||||
if (categoryTitles.length) {
|
||||
const existingIds = processedCourses.map((c) => c._id);
|
||||
const interestCourses = await CourseModel.find({
|
||||
...query,
|
||||
category: { $in: categoryTitles },
|
||||
_id: { $nin: existingIds },
|
||||
})
|
||||
.populate("user_id", "name email")
|
||||
.populate("academyId", "academy_name academy_image")
|
||||
.populate("likes", "name")
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(5)
|
||||
.lean();
|
||||
const processedInterestCourses = interestCourses.map((course) => ({
|
||||
...course,
|
||||
academy_name: course.academyId?.academy_name || null,
|
||||
academy_image: course.academyId?.academy_image || null,
|
||||
// عین بلوک اصلی بالاتر — academyId بعد از populate باید رشته بمونه
|
||||
academyId: course.academyId?._id
|
||||
? String(course.academyId._id)
|
||||
: course.academyId,
|
||||
priceNumber: parseInt(course.price) || 0,
|
||||
offerNumber: parseInt(course.offer) || 0,
|
||||
finalPrice: course.is_free
|
||||
? 0
|
||||
: course.offer
|
||||
? (parseInt(course.price) * (100 - parseInt(course.offer))) / 100
|
||||
: parseInt(course.price),
|
||||
likesCount: course.likes?.length || 0,
|
||||
}));
|
||||
processedCourses = blendInterestIntoRanked(
|
||||
processedCourses,
|
||||
[...processedCourses, ...processedInterestCourses],
|
||||
0.25
|
||||
).slice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* non-blocking: personalization is a bonus, never break the listing */
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 8. محاسبات صفحهبندی ==========
|
||||
const totalPages = Math.ceil(totalCourses / limit);
|
||||
const hasNextPage = page < totalPages;
|
||||
@@ -786,7 +1019,7 @@ const handleCoursePaymentCallback = async (req, res) => {
|
||||
const { Authority, Status, userId, courseId } = req.query;
|
||||
|
||||
if (Status !== "OK") {
|
||||
return res.redirect(`${process.env.APP_SITE}/offer/payment/failed`);
|
||||
return res.redirect(`${process.env.APP_SITE}/settings/academy/course?payment=failed`);
|
||||
}
|
||||
|
||||
// ========== 1. پیدا کردن قیمت ==========
|
||||
@@ -796,7 +1029,7 @@ const handleCoursePaymentCallback = async (req, res) => {
|
||||
|
||||
if (!coursePayment) {
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?message=price_not_found`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&message=price_not_found`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -859,12 +1092,12 @@ const handleCoursePaymentCallback = async (req, res) => {
|
||||
|
||||
// پرداخت ناموفق
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?code=${data.code}`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&code=${data.code}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Error in handleCoursePaymentCallback:", err);
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?message=verification_error`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&message=verification_error`
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -913,7 +1146,7 @@ const creatCourseVideo = async (req, res, next) => {
|
||||
|
||||
// ایجاد رکورد جدید در دیتابیس
|
||||
const newAcademyContent = new AcademyContentModel({
|
||||
course_video: `/storage/posts/videos/${safeFileName}`,
|
||||
course_video: `/storage/courses/videos/${safeFileName}`,
|
||||
type: "video",
|
||||
is_free: is_free === "true" || is_free === true,
|
||||
courseId,
|
||||
@@ -1070,10 +1303,10 @@ const getCourseContent = async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ شرط جستجو - فقط دورههای تایید شده
|
||||
// ✅ شرط جستجو - محتوای تایید موقت و تایید کامل
|
||||
const query = {
|
||||
courseId: academy._id,
|
||||
status: "accept",
|
||||
status: academyContentPublicStatusFilter(),
|
||||
};
|
||||
|
||||
// دریافت تعداد کل دورههای تایید شده
|
||||
@@ -1186,7 +1419,12 @@ const likeCourseContent = async (req, res, next) => {
|
||||
});
|
||||
|
||||
const courseOwnerId = await resolveCourseOwnerId(course);
|
||||
if (!courseOwnerId) {
|
||||
console.warn("likeCourseContent: course owner not found for", courseId);
|
||||
}
|
||||
|
||||
await createLikeNotification({
|
||||
io: req.app.get('io'),
|
||||
ownerId: courseOwnerId,
|
||||
likerId: userId,
|
||||
entityId: course._id,
|
||||
@@ -1412,27 +1650,50 @@ const createComment = async (req, res) => {
|
||||
const finalRate = existingRatedComment ? 0 : parsedRate;
|
||||
|
||||
// ایجاد کامنت جدید
|
||||
const newComment = await CourseComment.create({
|
||||
user_id: userId,
|
||||
course_id: course_id,
|
||||
comment: comment,
|
||||
rate: finalRate,
|
||||
status: "accepted", // با توجه به مدل شما که default: 'accepted' است
|
||||
});
|
||||
const newComment = await saveCommentWithModerationStatus(
|
||||
CourseComment,
|
||||
{
|
||||
user_id: userId,
|
||||
course_id: course_id,
|
||||
comment: comment,
|
||||
rate: finalRate,
|
||||
},
|
||||
'comment'
|
||||
);
|
||||
|
||||
// populate کردن اطلاعات کاربر
|
||||
const populatedComment = await CourseComment.findById(
|
||||
newComment._id
|
||||
).populate("user_id", "name username");
|
||||
const commentDoc = await CourseComment.findById(newComment._id).lean();
|
||||
const commentUser = await UserModel.findById(userId)
|
||||
.select("first_name last_name user_name profile_image is_verified verify_badge")
|
||||
.lean();
|
||||
const populatedComment = {
|
||||
...commentDoc,
|
||||
user_id: commentUser,
|
||||
};
|
||||
|
||||
const courseOwnerId = await resolveCourseOwnerId(course);
|
||||
if (!courseOwnerId) {
|
||||
console.warn("createComment: course owner not found for", course_id);
|
||||
}
|
||||
|
||||
await createCommentNotification({
|
||||
io: req.app.get('io'),
|
||||
ownerId: courseOwnerId,
|
||||
commenterId: userId,
|
||||
entityId: course._id,
|
||||
type: 'academy_comment'
|
||||
});
|
||||
|
||||
if (finalRate > 0) {
|
||||
await createRatingNotification({
|
||||
io: req.app.get('io'),
|
||||
ownerId: courseOwnerId,
|
||||
raterId: userId,
|
||||
entityId: course._id,
|
||||
type: 'academy_rating',
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: "کامنت با موفقیت ثبت شد",
|
||||
@@ -1440,6 +1701,12 @@ const createComment = async (req, res) => {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in createComment:", error);
|
||||
if (error?.name === "ValidationError") {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "خطا در ثبت کامنت. مدل کامنت روی سرور بهروز نیست.",
|
||||
});
|
||||
}
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "خطا در ثبت کامنت",
|
||||
@@ -1504,8 +1771,12 @@ const getCourseComments = async (req, res) => {
|
||||
const query = { course_id: courseId };
|
||||
|
||||
// فیلتر بر اساس وضعیت
|
||||
if (status !== "all") {
|
||||
if (status === "accepted") {
|
||||
query.status = commentStatusFilter();
|
||||
} else if (status !== "all") {
|
||||
query.status = status;
|
||||
} else {
|
||||
query.status = commentStatusFilter();
|
||||
}
|
||||
|
||||
// فیلتر بر اساس امتیاز
|
||||
@@ -1806,6 +2077,28 @@ const coursePayment = async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (course.is_free) {
|
||||
await IsPaymentCourse.create({
|
||||
user_id: userId,
|
||||
course_id: course._id,
|
||||
price_paid: 0,
|
||||
purchased_at: new Date(),
|
||||
});
|
||||
|
||||
const courseOwnerId = await resolveCourseOwnerId(course);
|
||||
await createCoursePurchaseNotification({
|
||||
ownerId: courseOwnerId,
|
||||
buyerId: userId,
|
||||
courseId: course._id,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
free: true,
|
||||
message: "پکیج رایگان با موفقیت ثبت شد",
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 3. دریافت نرخ مالیات ==========
|
||||
let taxRate = 9; // پیشفرض 9 درصد
|
||||
const tax = await Tax.findOne({ type: "course" });
|
||||
@@ -1922,7 +2215,7 @@ const CoursePaymentCallback = async (req, res) => {
|
||||
// ========== 1. بررسی وضعیت پرداخت ==========
|
||||
if (Status !== "OK") {
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?message=payment_canceled`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&message=payment_canceled`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1930,7 +2223,7 @@ const CoursePaymentCallback = async (req, res) => {
|
||||
const course = await CourseModel.findById(courseId);
|
||||
if (!course) {
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?message=course_not_found`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&message=course_not_found`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1943,7 +2236,7 @@ const CoursePaymentCallback = async (req, res) => {
|
||||
|
||||
if (!paymentRecord) {
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?message=payment_not_found`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&message=payment_not_found`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1982,9 +2275,33 @@ const CoursePaymentCallback = async (req, res) => {
|
||||
purchased_at: new Date(),
|
||||
});
|
||||
|
||||
const courseOwnerId = await resolveCourseOwnerId(course);
|
||||
await createCoursePurchaseNotification({
|
||||
ownerId: courseOwnerId,
|
||||
buyerId: userId,
|
||||
courseId: course._id,
|
||||
});
|
||||
|
||||
if (courseOwnerId) {
|
||||
const sellerNetAmount = Math.round(
|
||||
paymentRecord.price * (1 - ACADEMY_COMMISSION_RATE)
|
||||
);
|
||||
if (sellerNetAmount > 0) {
|
||||
await WalletTransactionModel.create({
|
||||
user: courseOwnerId,
|
||||
bucket: 'academy',
|
||||
type: 'credit',
|
||||
amount: sellerNetAmount,
|
||||
referenceType: 'payment',
|
||||
referenceId: paymentRecord._id,
|
||||
description: `درآمد فروش پکیج آموزشی «${course.title || ''}» (پس از کسر ۱۵٪ کارمزد)`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// هدایت به صفحه موفقیت
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/academy/${course._id}/success?payment=success`
|
||||
`${process.env.APP_SITE}/settings/academy/my-courses?payment=success`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1994,12 +2311,12 @@ const CoursePaymentCallback = async (req, res) => {
|
||||
});
|
||||
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?code=${data.code}`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&code=${data.code}`
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Error in CoursePaymentCallback:", err);
|
||||
return res.redirect(
|
||||
`${process.env.APP_SITE}/offer/payment/failed?message=verification_error`
|
||||
`${process.env.APP_SITE}/settings/academy/course?payment=failed&message=verification_error`
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -2025,7 +2342,7 @@ const getUserPurchasedCourses = async (req, res) => {
|
||||
|
||||
const courses = await CourseModel.find({
|
||||
_id: { $in: courseIds },
|
||||
status: "accept", // فقط دورههای تایید شده
|
||||
status: coursePublicStatusFilter(), // فقط دورههای تایید شده
|
||||
});
|
||||
|
||||
// محاسبه تعداد کل
|
||||
@@ -2072,7 +2389,7 @@ const getUserPurchasedCoursesWithPopulate = async (req, res) => {
|
||||
populate: {
|
||||
path: "course_id",
|
||||
model: "Course",
|
||||
match: { status: "accept" }, // فقط دورههای تایید شده
|
||||
match: { status: coursePublicStatusFilter() },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2232,6 +2549,98 @@ const getAllAcademyPayments = async (req, res) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const ANALYTICS_RANGE_DAYS = { "7d": 7, "30d": 30, "90d": 90, all: null };
|
||||
|
||||
/** Revenue-over-time + best-selling courses for the seller's Academy — backs the "Academy" analytics tab. */
|
||||
const getAcademyAnalytics = async (req, res) => {
|
||||
try {
|
||||
const token = req.header("Authorization")?.split(" ")[1];
|
||||
if (!token) return res.status(401).json({ success: false, message: "دسترسی غیرمجاز" });
|
||||
|
||||
let decodedToken;
|
||||
try {
|
||||
decodedToken = jwt.verify(token, process.env.APP_SECRET);
|
||||
} catch {
|
||||
return res.status(401).json({ success: false, message: "توکن نامعتبر است" });
|
||||
}
|
||||
|
||||
const academy = await AcademyModel.findOne({ userId: decodedToken.id });
|
||||
if (!academy) {
|
||||
return res.json({
|
||||
range: req.query.range || "30d",
|
||||
series: [],
|
||||
totals: { totalRevenue: 0, totalCount: 0 },
|
||||
topItems: [],
|
||||
rating: { average: academy?.rate || 0, count: academy?.number_of_rate || 0 },
|
||||
});
|
||||
}
|
||||
|
||||
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 * 24 * 60 * 60 * 1000) : new Date(0);
|
||||
|
||||
const match = {
|
||||
academy_id: academy._id,
|
||||
status: { $in: ["success", "settled"] },
|
||||
createdAt: { $gte: since },
|
||||
};
|
||||
|
||||
const [series, totalsAgg, bestCoursesAgg] = await Promise.all([
|
||||
AcademyPaymentModel.aggregate([
|
||||
{ $match: match },
|
||||
{
|
||||
$group: {
|
||||
_id: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },
|
||||
revenue: { $sum: "$price" },
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
{ $sort: { _id: 1 } },
|
||||
]),
|
||||
AcademyPaymentModel.aggregate([
|
||||
{ $match: match },
|
||||
{ $group: { _id: null, totalRevenue: { $sum: "$price" }, totalCount: { $sum: 1 } } },
|
||||
]),
|
||||
AcademyPaymentModel.aggregate([
|
||||
{ $match: match },
|
||||
{ $group: { _id: "$course_id", revenue: { $sum: "$price" }, count: { $sum: 1 } } },
|
||||
{ $sort: { revenue: -1 } },
|
||||
{ $limit: 5 },
|
||||
]),
|
||||
]);
|
||||
|
||||
const courseIds = bestCoursesAgg.map((row) => row._id).filter(Boolean);
|
||||
const courses = courseIds.length
|
||||
? await CourseModel.find({ _id: { $in: courseIds } }).select("cuorse_name course_image")
|
||||
: [];
|
||||
const courseMap = new Map(courses.map((c) => [String(c._id), c]));
|
||||
|
||||
const topItems = bestCoursesAgg
|
||||
.filter((row) => row._id && courseMap.has(String(row._id)))
|
||||
.map((row) => {
|
||||
const course = courseMap.get(String(row._id));
|
||||
return {
|
||||
id: String(row._id),
|
||||
title: course.cuorse_name,
|
||||
image: course.course_image || null,
|
||||
metric: row.revenue,
|
||||
};
|
||||
});
|
||||
|
||||
return res.json({
|
||||
range,
|
||||
series,
|
||||
totals: totalsAgg[0] || { totalRevenue: 0, totalCount: 0 },
|
||||
topItems,
|
||||
rating: { average: academy.rate || 0, count: academy.number_of_rate || 0 },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in getAcademyAnalytics:", error);
|
||||
res.status(500).json({ success: false, message: "خطا در دریافت آنالیتیکس آموزشگاه" });
|
||||
}
|
||||
};
|
||||
|
||||
const checkCoursePurchase = async (req, res) => {
|
||||
try {
|
||||
const token = req.header("Authorization")?.split(" ")[1];
|
||||
@@ -2301,7 +2710,7 @@ const getAcademyCourse = async (req, res, next) => {
|
||||
// ✅ شرط جستجو - فقط دورههای تایید شده
|
||||
const query = {
|
||||
academyId: academy._id,
|
||||
status: "accept",
|
||||
status: coursePublicStatusFilter(),
|
||||
};
|
||||
|
||||
// دریافت تعداد کل دورههای تایید شده
|
||||
@@ -4206,7 +4615,19 @@ const getFreeAcademyExploreContent = async (req, res, next) => {
|
||||
const limit = parseInt(req.query.limit, 10) || 15;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const query = { is_free: true, status: "accept" };
|
||||
const freeCourses = await CourseModel.find({ is_free: true })
|
||||
.select("_id")
|
||||
.lean();
|
||||
const freeCourseIds = freeCourses.map((course) => String(course._id));
|
||||
|
||||
const query = {
|
||||
status: academyContentPublicStatusFilter(),
|
||||
$or: [
|
||||
{ is_free: true },
|
||||
{ type: "video", courseId: { $in: freeCourseIds } },
|
||||
],
|
||||
};
|
||||
|
||||
const totalItems = await AcademyContentModel.countDocuments(query);
|
||||
const contents = await AcademyContentModel.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
@@ -4220,7 +4641,7 @@ const getFreeAcademyExploreContent = async (req, res, next) => {
|
||||
const courses = courseIds.length
|
||||
? await CourseModel.find({ _id: { $in: courseIds } })
|
||||
.select(
|
||||
"cuorse_name course_image teacher_name academyId user_id caption likes"
|
||||
"cuorse_name course_image teacher_name academyId user_id caption likes category"
|
||||
)
|
||||
.lean()
|
||||
: [];
|
||||
@@ -4240,7 +4661,7 @@ const getFreeAcademyExploreContent = async (req, res, next) => {
|
||||
users.map((user) => [String(user._id), user])
|
||||
);
|
||||
|
||||
const items = contents.map((content) => {
|
||||
const mapContentItem = (content) => {
|
||||
const course = courseMap[String(content.courseId)] || {};
|
||||
const user = userMap[String(course.user_id)] || {};
|
||||
return {
|
||||
@@ -4255,7 +4676,77 @@ const getFreeAcademyExploreContent = async (req, res, next) => {
|
||||
profile_image: user.profile_image || "",
|
||||
likesCount: Array.isArray(course.likes) ? course.likes.length : 0,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
let items = contents.map(mapContentItem);
|
||||
|
||||
// بلند کردن محتوای مرتبط با علایق کاربر (فقط صفحه اول، بدون فیلتر دستی)
|
||||
if (page === 1) {
|
||||
try {
|
||||
const token = req.header("Authorization")?.split(" ")[1];
|
||||
const viewerId = token ? jwt.verify(token, process.env.APP_SECRET)?.id : null;
|
||||
if (viewerId) {
|
||||
const interestWeights = await buildUnifiedInterestScore(viewerId);
|
||||
const tags = Object.keys(interestWeights).filter((tag) => interestWeights[tag] > 0);
|
||||
if (tags.length) {
|
||||
const interestCategories = await AcademyCategoryModel.find({
|
||||
relatedExpertise: { $in: tags },
|
||||
})
|
||||
.select("title")
|
||||
.lean();
|
||||
const categoryTitles = interestCategories.map((c) => c.title);
|
||||
if (categoryTitles.length) {
|
||||
const interestCourseIds = (
|
||||
await CourseModel.find({ category: { $in: categoryTitles } }).select("_id").lean()
|
||||
).map((c) => String(c._id));
|
||||
if (interestCourseIds.length) {
|
||||
const existingContentIds = items.map((i) => i._id);
|
||||
const interestContents = await AcademyContentModel.find({
|
||||
...query,
|
||||
courseId: { $in: interestCourseIds },
|
||||
_id: { $nin: existingContentIds },
|
||||
})
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(5)
|
||||
.lean();
|
||||
if (interestContents.length) {
|
||||
const extraCourseIds = interestContents
|
||||
.map((c) => String(c.courseId))
|
||||
.filter((id) => !courseMap[id]);
|
||||
if (extraCourseIds.length) {
|
||||
const extraCourses = await CourseModel.find({ _id: { $in: extraCourseIds } })
|
||||
.select("cuorse_name course_image teacher_name academyId user_id caption likes category")
|
||||
.lean();
|
||||
extraCourses.forEach((course) => {
|
||||
courseMap[String(course._id)] = course;
|
||||
});
|
||||
const extraUserIds = extraCourses
|
||||
.map((c) => String(c.user_id))
|
||||
.filter((id) => id && !userMap[id]);
|
||||
if (extraUserIds.length) {
|
||||
const extraUsers = await UserModel.find({ _id: { $in: extraUserIds } })
|
||||
.select("user_name first_name last_name profile_image")
|
||||
.lean();
|
||||
extraUsers.forEach((user) => {
|
||||
userMap[String(user._id)] = user;
|
||||
});
|
||||
}
|
||||
}
|
||||
const mappedInterestContents = interestContents.map(mapContentItem);
|
||||
items = blendInterestIntoRanked(
|
||||
items,
|
||||
[...items, ...mappedInterestContents],
|
||||
0.25
|
||||
).slice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* non-blocking: personalization is a bonus, never break the listing */
|
||||
}
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil(totalItems / limit) || 1;
|
||||
|
||||
@@ -4416,6 +4907,7 @@ module.exports = {
|
||||
|
||||
// دریافت همه پرداختهای آکادمی
|
||||
getAllAcademyPayments,
|
||||
getAcademyAnalytics,
|
||||
|
||||
// بررسی خرید یک دوره توسط کاربر (برای صفحه دوره)
|
||||
checkCoursePurchase,
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
const moment = require('moment-jalaali')
|
||||
const MessageModel = require('../models/MessageModel')
|
||||
const UserModel = require('../models/UserModel')
|
||||
|
||||
function formatTimeOnly(date) {
|
||||
return moment(date).locale('fa').format('HH:mm:ss')
|
||||
}
|
||||
|
||||
async function enrichMessage(message) {
|
||||
const doc = message._doc ? { ...message._doc } : { ...message }
|
||||
let replyTo = null
|
||||
|
||||
if (doc.replyToId) {
|
||||
const parent = await MessageModel.findById(doc.replyToId).lean()
|
||||
if (parent) {
|
||||
const parentUser =
|
||||
String(parent.senderId) === String(doc.senderId)
|
||||
? await UserModel.findById(doc.senderId).select('first_name last_name user_name').lean()
|
||||
: await UserModel.findById(parent.senderId).select('first_name last_name user_name').lean()
|
||||
replyTo = {
|
||||
_id: parent._id,
|
||||
content: (parent.content || 'پیام').slice(0, 200),
|
||||
senderName: parentUser
|
||||
? `${parentUser.first_name || ''} ${parentUser.last_name || ''}`.trim() || parentUser.user_name
|
||||
: 'کاربر'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...doc,
|
||||
createdAt: formatTimeOnly(doc.createdAt),
|
||||
createdAtIso: doc.createdAt ? new Date(doc.createdAt).toISOString() : undefined,
|
||||
expiresAt: doc.expiresAt ? new Date(doc.expiresAt).toISOString() : undefined,
|
||||
viewOnce: Boolean(doc.viewOnce),
|
||||
viewOnceLocked: Boolean(doc.viewOnceLocked),
|
||||
viewOnceExpired: Boolean(doc.viewOnceExpired),
|
||||
viewOnceOpenedAt: doc.viewOnceOpenedAt
|
||||
? new Date(doc.viewOnceOpenedAt).toISOString()
|
||||
: undefined,
|
||||
replyTo,
|
||||
forwardedFrom: doc.forwardedFrom || undefined
|
||||
}
|
||||
}
|
||||
|
||||
function emitChatEvents(io, message, senderId, receiverId) {
|
||||
const payload = message
|
||||
const roomA = `chat:${senderId}:${receiverId}`
|
||||
const roomB = `chat:${receiverId}:${senderId}`
|
||||
io.to(roomA).to(roomB).emit('newMessage', payload)
|
||||
io.to(`user:${senderId}`).to(`user:${receiverId}`).emit('chatListUpdate', {
|
||||
senderId,
|
||||
receiverId,
|
||||
message: payload
|
||||
})
|
||||
}
|
||||
|
||||
function parseForwardedContent(content) {
|
||||
if (!content) return { body: '', forwardedFrom: null }
|
||||
try {
|
||||
const line = content.split('\n')[0]
|
||||
const j = JSON.parse(line)
|
||||
if (j.forwardedFrom) {
|
||||
return {
|
||||
forwardedFrom: j.forwardedFrom,
|
||||
body: content.replace(/^\{.*\}\n?/, '').trim()
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
/* not json */
|
||||
}
|
||||
return { body: content, forwardedFrom: null }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatTimeOnly,
|
||||
enrichMessage,
|
||||
emitChatEvents,
|
||||
parseForwardedContent
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const MessageModel = require('../models/MessageModel')
|
||||
|
||||
const ALLOWED_SELF_DESTRUCT_SECONDS = [10, 30, 60, 300, 3600]
|
||||
|
||||
function parseSelfDestructSeconds(raw) {
|
||||
if (raw == null || raw === '' || raw === '0' || raw === 0) return null
|
||||
const n = parseInt(String(raw), 10)
|
||||
if (!Number.isFinite(n) || !ALLOWED_SELF_DESTRUCT_SECONDS.includes(n)) return null
|
||||
return n
|
||||
}
|
||||
|
||||
function buildExpiresAt(seconds) {
|
||||
if (!seconds) return undefined
|
||||
return new Date(Date.now() + seconds * 1000)
|
||||
}
|
||||
|
||||
function activeMessageFilter(baseFilter) {
|
||||
return {
|
||||
$and: [
|
||||
baseFilter,
|
||||
{
|
||||
$or: [
|
||||
{ expiresAt: { $exists: false } },
|
||||
{ expiresAt: null },
|
||||
{ expiresAt: { $gt: new Date() } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async function removeMessageFiles(messages) {
|
||||
for (const msg of messages) {
|
||||
if (!msg.file) continue
|
||||
const relative = String(msg.file).replace(/^\//, '')
|
||||
const filePath = path.join(__dirname, '../storage', relative)
|
||||
if (fs.existsSync(filePath)) {
|
||||
await fs.remove(filePath).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMessagesAndNotify(io, messages, options = {}) {
|
||||
if (!messages?.length) return []
|
||||
|
||||
await removeMessageFiles(messages)
|
||||
const deletedIds = messages.map((m) => String(m._id))
|
||||
await MessageModel.deleteMany({ _id: { $in: deletedIds } })
|
||||
|
||||
if (io) {
|
||||
const byThread = new Map()
|
||||
messages.forEach((m) => {
|
||||
const s = String(m.senderId)
|
||||
const r = String(m.receiverId)
|
||||
const key = [s, r].sort().join(':')
|
||||
if (!byThread.has(key)) byThread.set(key, { s, r, ids: [] })
|
||||
byThread.get(key).ids.push(String(m._id))
|
||||
})
|
||||
|
||||
byThread.forEach(({ s, r, ids }) => {
|
||||
io.to(`chat:${s}:${r}`)
|
||||
.to(`chat:${r}:${s}`)
|
||||
.emit('messagesDeleted', {
|
||||
messageIds: ids,
|
||||
deletedBy: options.deletedBy,
|
||||
reason: options.reason || (options.deletedBy ? 'manual' : 'expired')
|
||||
})
|
||||
io.to(`user:${s}`).to(`user:${r}`).emit('chatListUpdate', { senderId: s, receiverId: r })
|
||||
})
|
||||
}
|
||||
|
||||
return deletedIds
|
||||
}
|
||||
|
||||
async function purgeExpiredMessages(io) {
|
||||
try {
|
||||
const expired = await MessageModel.find({ expiresAt: { $lte: new Date() } })
|
||||
if (!expired.length) return []
|
||||
return deleteMessagesAndNotify(io, expired)
|
||||
} catch (err) {
|
||||
console.error('purgeExpiredMessages error:', err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const scheduledTimers = new Map()
|
||||
|
||||
function scheduleMessageExpiry(io, message) {
|
||||
if (!message?.expiresAt || !message._id) return
|
||||
|
||||
const id = String(message._id)
|
||||
if (scheduledTimers.has(id)) {
|
||||
clearTimeout(scheduledTimers.get(id))
|
||||
}
|
||||
|
||||
const ms = new Date(message.expiresAt).getTime() - Date.now()
|
||||
if (ms <= 0) {
|
||||
MessageModel.findById(id)
|
||||
.then((doc) => doc && deleteMessagesAndNotify(io, [doc]))
|
||||
.catch(() => {})
|
||||
return
|
||||
}
|
||||
|
||||
const timer = setTimeout(async () => {
|
||||
scheduledTimers.delete(id)
|
||||
try {
|
||||
const doc = await MessageModel.findById(id)
|
||||
if (doc && doc.expiresAt && new Date(doc.expiresAt) <= new Date()) {
|
||||
await deleteMessagesAndNotify(io, [doc])
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('scheduleMessageExpiry error:', err)
|
||||
}
|
||||
}, ms)
|
||||
|
||||
scheduledTimers.set(id, timer)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ALLOWED_SELF_DESTRUCT_SECONDS,
|
||||
parseSelfDestructSeconds,
|
||||
buildExpiresAt,
|
||||
activeMessageFilter,
|
||||
purgeExpiredMessages,
|
||||
scheduleMessageExpiry,
|
||||
deleteMessagesAndNotify
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
const MessageModel = require('../models/MessageModel')
|
||||
const { deleteMessagesAndNotify } = require('./expiredMessages')
|
||||
|
||||
function parseViewOnce(raw) {
|
||||
if (raw == null || raw === '' || raw === false || raw === 'false' || raw === 0) {
|
||||
return false
|
||||
}
|
||||
return raw === true || raw === 'true' || raw === 1 || raw === '1'
|
||||
}
|
||||
|
||||
const VIEW_ONCE_MEDIA = new Set(['image', 'video', 'voice'])
|
||||
|
||||
function isViewOnceMediaType(fileType) {
|
||||
return VIEW_ONCE_MEDIA.has(fileType)
|
||||
}
|
||||
|
||||
/** Hide file URL for receiver until they open view-once media */
|
||||
function sanitizeMessageForViewer(message, viewerId) {
|
||||
const doc = message._doc ? { ...message._doc } : { ...message }
|
||||
const viewer = String(viewerId)
|
||||
const sender = String(doc.senderId)
|
||||
const receiver = String(doc.receiverId)
|
||||
|
||||
if (!doc.viewOnce || !isViewOnceMediaType(doc.fileType)) {
|
||||
return doc
|
||||
}
|
||||
|
||||
if (viewer === sender) {
|
||||
return doc
|
||||
}
|
||||
|
||||
if (viewer === receiver) {
|
||||
if (doc.viewOnceOpenedAt) {
|
||||
return {
|
||||
...doc,
|
||||
file: '',
|
||||
viewOnceExpired: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
...doc,
|
||||
file: '',
|
||||
viewOnceLocked: true
|
||||
}
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
async function openViewOnceMessage(messageId, userId) {
|
||||
const msg = await MessageModel.findById(messageId)
|
||||
if (!msg || !msg.viewOnce) {
|
||||
return { error: 'پیام یافت نشد', status: 404 }
|
||||
}
|
||||
if (String(msg.receiverId) !== String(userId)) {
|
||||
return { error: 'دسترسی مجاز نیست', status: 403 }
|
||||
}
|
||||
if (msg.viewOnceOpenedAt) {
|
||||
return { error: 'این پیام قبلاً باز شده است', status: 410 }
|
||||
}
|
||||
|
||||
msg.viewOnceOpenedAt = new Date()
|
||||
await msg.save()
|
||||
|
||||
return {
|
||||
message: msg.toObject(),
|
||||
file: msg.file,
|
||||
fileType: msg.fileType
|
||||
}
|
||||
}
|
||||
|
||||
async function completeViewOnceMessage(io, messageId, userId) {
|
||||
const msg = await MessageModel.findById(messageId)
|
||||
if (!msg || !msg.viewOnce) {
|
||||
return { error: 'پیام یافت نشد', status: 404 }
|
||||
}
|
||||
|
||||
const uid = String(userId)
|
||||
const isReceiver = String(msg.receiverId) === uid
|
||||
const isSender = String(msg.senderId) === uid
|
||||
|
||||
if (!isReceiver && !isSender) {
|
||||
return { error: 'دسترسی مجاز نیست', status: 403 }
|
||||
}
|
||||
|
||||
if (isReceiver && !msg.viewOnceOpenedAt) {
|
||||
return { error: 'پیام هنوز باز نشده', status: 400 }
|
||||
}
|
||||
|
||||
await deleteMessagesAndNotify(io, [msg], {
|
||||
deletedBy: userId,
|
||||
reason: 'viewOnce'
|
||||
})
|
||||
|
||||
return { deletedIds: [String(msg._id)] }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseViewOnce,
|
||||
isViewOnceMediaType,
|
||||
sanitizeMessageForViewer,
|
||||
openViewOnceMessage,
|
||||
completeViewOnceMessage
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const path = require('path');
|
||||
const http = require('http').createServer(app);
|
||||
const cors = require('cors');
|
||||
const moment = require('moment-jalaali');
|
||||
const MessageModel = require('./models/MessageModel');
|
||||
const fs = require('fs-extra');
|
||||
const cron = require('node-cron');
|
||||
const UserModel = require('./models/UserModel');
|
||||
const blockCheck = require('./middlewares/blockCheck');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { default: axios } = require('axios');
|
||||
|
||||
const allowedOrigins = [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:3001', // اضافه شد — مهم برای توسعه فرانت
|
||||
'http://localhost:3002',
|
||||
'http://localhost:3003',
|
||||
'http://localhost',
|
||||
'https://modstagram.com',
|
||||
'https://www.modstagram.com',
|
||||
'https://api.modstagram.com',
|
||||
'https://panel.modstagram.com',
|
||||
'https://www.panel.modstagram.com',
|
||||
'https://panel.modstagram.ir',
|
||||
'https://www.panel.modstagram.ir',
|
||||
'ionic://localhost',
|
||||
'capacitor://localhost',
|
||||
// IPهای محلی (برای تست در شبکه داخلی)
|
||||
/^http:\/\/192\.168\.\d{1,3}\.\d{1,3}:\d+$/,
|
||||
/^http:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/,
|
||||
];
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
// در محیط توسعه، همه originها مجاز باشن
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// اگر origin وجود نداشته باشه (مثل درخواست از موبایل یا Postman) اجازه بده
|
||||
if (!origin) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// چک لیست یا RegExp
|
||||
const isAllowed = allowedOrigins.some(item => {
|
||||
if (typeof item === 'string') return item === origin;
|
||||
if (item instanceof RegExp) return item.test(origin);
|
||||
return false;
|
||||
});
|
||||
|
||||
if (isAllowed) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'x-no-toast'],
|
||||
credentials: true,
|
||||
optionsSuccessStatus: 200 // برای مرورگرهای قدیمی
|
||||
}));
|
||||
|
||||
// Socket.IO CORS
|
||||
const io = require('socket.io')(http, {
|
||||
cors: {
|
||||
origin: (origin, callback) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
if (!origin) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
const isAllowed = allowedOrigins.some(item => {
|
||||
if (typeof item === 'string') return item === origin;
|
||||
if (item instanceof RegExp) return item.test(origin);
|
||||
return false;
|
||||
});
|
||||
|
||||
if (isAllowed) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
credentials: true
|
||||
}
|
||||
});
|
||||
|
||||
// افزایش limit برای Base64 (1000mb کافیه)
|
||||
app.use(express.json({ limit: '1000mb' }));
|
||||
app.use(express.urlencoded({ limit: '1000mb', extended: true }));
|
||||
|
||||
app.use('/storage', express.static(path.join(__dirname, 'storage')));
|
||||
app.use('/storage/profiles', express.static(path.join(__dirname, '../storage/profiles')));
|
||||
app.use('/storage/carts', express.static(path.join(__dirname, '../storage/carts')));
|
||||
app.use('/storage/posts', express.static(path.join(__dirname, '../storage/posts')));
|
||||
app.use('/storage/messages', express.static(path.join(__dirname, '../storage/messages')));
|
||||
app.use('/storage/tickets', express.static(path.join(__dirname, '../storage/tickets')));
|
||||
app.use('/storage/advertising', express.static(path.join(__dirname, '../storage/advertising')));
|
||||
app.use('/storage/services', express.static(path.join(__dirname, '../storage/services')));
|
||||
|
||||
require('./boot');
|
||||
|
||||
// غیرفعال کردن پارسکنندههای بدنه برای روتهای آپلود فایل
|
||||
app.use((req, res, next) => {
|
||||
if (req.path === '/api/v1/posts/create' && req.method === 'POST') {
|
||||
console.log('DEBUG: Skipping body parsers for /api/v1/posts/create');
|
||||
return next();
|
||||
}
|
||||
express.json()(req, res, next);
|
||||
});
|
||||
app.use((req, res, next) => {
|
||||
if (req.path === '/api/v1/posts/create' && req.method === 'POST') {
|
||||
return next();
|
||||
}
|
||||
express.urlencoded({ extended: true })(req, res, next);
|
||||
});
|
||||
|
||||
require('./middlewares')(app);
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 3 * 60 * 1000,
|
||||
max: 3000,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: 'تعداد درخواستهای شما بیشتر از حد مجاز است، لطفاً بعداً دوباره تلاش کنید.',
|
||||
handler: (req, res) => {
|
||||
const retryAfter = Math.ceil((req.rateLimit.resetTime - Date.now()) / 1000);
|
||||
res.set('Retry-After', retryAfter);
|
||||
res.status(429).json({
|
||||
error: `شما بیش از حد مجاز درخواست ارسال کردهاید. لطفاً بعد از ${retryAfter} ثانیه دوباره تلاش کنید.`
|
||||
});
|
||||
}
|
||||
});
|
||||
app.use(limiter);
|
||||
app.set('trust proxy', 'loopback');
|
||||
|
||||
require('./routes')(app);
|
||||
|
||||
const { enrichMessage, emitChatEvents, parseForwardedContent } = require('./helpers/chatHelpers')
|
||||
|
||||
app.get('/api/v1/chat', async (req, res) => {
|
||||
try {
|
||||
const { senderId, receiverId, limit, page } = req.query
|
||||
const pageNumber = parseInt(page) || 1
|
||||
const limitPerPage = parseInt(limit) || 40
|
||||
|
||||
const filter = {
|
||||
$or: [
|
||||
{ senderId, receiverId },
|
||||
{ senderId: receiverId, receiverId: senderId }
|
||||
]
|
||||
}
|
||||
|
||||
const totalMessagesCount = await MessageModel.countDocuments(filter)
|
||||
const totalPages = Math.ceil(totalMessagesCount / limitPerPage) || 1
|
||||
const skip = (pageNumber - 1) * limitPerPage
|
||||
|
||||
const messages = await MessageModel.find(filter)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limitPerPage)
|
||||
|
||||
await MessageModel.updateMany(
|
||||
{ _id: { $in: messages.map((m) => m._id) }, receiverId: senderId },
|
||||
{ $set: { readStatus: 1 } }
|
||||
)
|
||||
|
||||
const enriched = await Promise.all(messages.map((m) => enrichMessage(m)))
|
||||
res.json({ messages: enriched.reverse(), totalPages })
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/v1/chat', [blockCheck], async (req, res) => {
|
||||
try {
|
||||
const { senderId, receiverId, content, replyToId, forwardedFrom: fwdBody } = req.body
|
||||
const { body, forwardedFrom: fwdParsed } = parseForwardedContent(content)
|
||||
|
||||
const payload = {
|
||||
senderId,
|
||||
receiverId,
|
||||
content: body || content,
|
||||
replyToId: replyToId || undefined,
|
||||
forwardedFrom: fwdBody || fwdParsed || undefined
|
||||
}
|
||||
|
||||
const newMessage = new MessageModel(payload)
|
||||
await newMessage.save()
|
||||
const formatted = await enrichMessage(newMessage)
|
||||
|
||||
res.status(201).json({ data: formatted })
|
||||
emitChatEvents(io, formatted, senderId, receiverId)
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
app.get('/api/v1/chat/read', async (req, res) => {
|
||||
try {
|
||||
const { senderId, receiverId } = req.query // شناسه فرستنده و گیرنده از درخواست دریافت شود
|
||||
// پیدا کردن تمام پیامهایی که کاربر دوم مشاهده کرده است و کاربر اول آنها را ارسال کرده است
|
||||
const messages = await MessageModel.find(
|
||||
{ senderId: receiverId, receiverId: senderId } // برای مواقعی که شناسهها معکوس باشند
|
||||
)
|
||||
// به روزرسانی وضعیت خوانده شده یا نشده بودن پیامها به "خوانده شده"
|
||||
await MessageModel.updateMany({ _id: { $in: messages.map(message => message._id) } }, { $set: { readStatus: 1 } })
|
||||
res.status(200).json({ message: 'Message read status updated successfully' })
|
||||
} catch (error) {
|
||||
console.error('Error updating message read status:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
|
||||
app.post("/api/v1/notification/send-sms", async (req, res) => {
|
||||
const { receiverId, message } = req.body;
|
||||
const user = await UserModel.findById(receiverId);
|
||||
if (!user || !user.mobile) return res.status(404).json({ error: "User not found" });
|
||||
console.log("3");
|
||||
|
||||
const mobile = user.mobile
|
||||
const user_name = user.user_name
|
||||
const data = JSON.stringify({
|
||||
mobile,
|
||||
templateId: '876533',
|
||||
parameters: [
|
||||
{ name: 'USER', value: user_name }
|
||||
]
|
||||
})
|
||||
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
res.status(200).json({ success: true });
|
||||
});
|
||||
|
||||
app.post('/api/v1/chat/file', [blockCheck], async (req, res) => {
|
||||
try {
|
||||
const { senderId, receiverId, content, replyToId, fileType } = req.body
|
||||
const { file } = req.files
|
||||
let fileUrl = null
|
||||
if (file) {
|
||||
const uploadDir = path.join(__dirname, '../storage/messages')
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true })
|
||||
}
|
||||
const uniqueFileName = `${Date.now()}-${file.name}`
|
||||
const filePath = path.join(uploadDir, uniqueFileName)
|
||||
await fs.move(file.path, filePath)
|
||||
fileUrl = `/messages/${uniqueFileName}`
|
||||
}
|
||||
|
||||
const newMessage = new MessageModel({
|
||||
senderId,
|
||||
receiverId,
|
||||
content: content || '',
|
||||
file: fileUrl,
|
||||
fileType: fileType || undefined,
|
||||
replyToId: replyToId || undefined
|
||||
})
|
||||
await newMessage.save()
|
||||
const formatted = await enrichMessage(newMessage)
|
||||
|
||||
res.status(201).json({ data: formatted })
|
||||
emitChatEvents(io, formatted, senderId, receiverId)
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error)
|
||||
res.status(500).json({ error: 'Server error' })
|
||||
}
|
||||
})
|
||||
// Errors
|
||||
require('./middlewares/exception')(app)
|
||||
require('./middlewares/404')(app)
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('joinUser', ({ userId }) => {
|
||||
if (userId) socket.join(`user:${userId}`)
|
||||
})
|
||||
|
||||
socket.on('joinChat', ({ userId, receiverId }) => {
|
||||
if (userId && receiverId) {
|
||||
socket.join(`chat:${userId}:${receiverId}`)
|
||||
socket.join(`chat:${receiverId}:${userId}`)
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('typing', ({ senderId, receiverId }) => {
|
||||
io.to(`chat:${receiverId}:${senderId}`).emit('userTyping', { userId: senderId })
|
||||
})
|
||||
|
||||
socket.on('stopTyping', ({ senderId, receiverId }) => {
|
||||
io.to(`chat:${receiverId}:${senderId}`).emit('userStoppedTyping', { userId: senderId })
|
||||
})
|
||||
|
||||
socket.on('messageSeen', async ({ messageId, senderId, receiverId }) => {
|
||||
try {
|
||||
if (messageId) {
|
||||
await MessageModel.findByIdAndUpdate(messageId, { readStatus: 1 })
|
||||
io.to(`chat:${receiverId}:${senderId}`).emit('messageStatusUpdate', {
|
||||
messageId,
|
||||
status: 'seen'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('messageSeen error', e)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Free Project
|
||||
require('./services/AdvertisingCron')
|
||||
require('./services/ProjectCron')
|
||||
cron.schedule('0 0 1 * *', async () => {
|
||||
try {
|
||||
await UserModel.updateMany({}, {
|
||||
daily_free_request: 1,
|
||||
last_free_request_date: new Date(),
|
||||
monthly_free_offer: 1,
|
||||
last_free_offer_date: new Date()
|
||||
})
|
||||
console.log('Monthly free requests reset for all users.')
|
||||
} catch (error) {
|
||||
console.error('Error resetting daily free requests:', error)
|
||||
}
|
||||
}, {
|
||||
scheduled: true,
|
||||
timezone: 'Asia/Tehran'
|
||||
})
|
||||
module.exports = (port) => {
|
||||
http.listen(port, () => {
|
||||
console.log(`HTTP server is running on port ${port}`)
|
||||
})
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[Wed Mar 13 00:31:27 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NjUHdHNE5ma0hGdHpQV040MkV0OUx0M285SkdBcGp6OFA5SGpoNHZHUzFEajkwc0ZWQWRWSlNBNFlOMWxxOC9k","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 00:31:27 2024] [VerificationCode] {"VerificationCodeId":634458365,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 00:34:14 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3M0WTRVeG9SeVNtN0xnMWUzQWo4WG1Oam9mNUw2SkJPV04rMGVOMUJ6Njhpb1RXMTl2LzFEdGRESXhZazBwaHBk","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 00:34:15 2024] [VerificationCode] {"VerificationCodeId":634459857,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 00:34:48 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NGN0NVV3V1K01VRTg3TGY1by9PcmtlSVpWaFpBa0l4RXNCOERHMzIxMDRkL1RYbWRhOXc5ZmovT0lFV0FDamJs","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 00:34:48 2024] [VerificationCode] {"VerificationCodeId":634460165,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 00:34:57 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NyNmxiRjUwVlF6UUlkSStnOWpOc0lLSyt5VHJjSjFRa0xsTkk0dFB2L3VSaE1PTDdQREY4WGlzcUFQeE01QWRP","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 00:34:57 2024] [VerificationCode] {"VerificationCodeId":634460241,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 00:39:39 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3N6MHErTmJEVzMydjZRcVJQaHNLb0R3MzJKcWk5bGJYNTh5UzFmSEpSSUY0b0Q4MFppdDRoeFlxWTZZSEQrL1hL","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 00:39:39 2024] [VerificationCode] {"VerificationCodeId":634462768,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 00:40:18 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NLRlVYejdBcTJxMGl6dXJVSTRsQkNiOGh2M241WTZrSGI3Z0JCY3kxeFZvUzVCTnRkdmVWalFzd25KQzIzV21I","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 00:40:18 2024] [VerificationCode] {"VerificationCodeId":634463121,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 00:41:38 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3NOMUs3ZVN3eDU0OVBZdXR1VU1Yd3dhS2lyWmMrWkpjTGpUVVkzWDQ4ZU10VDBTWnFZc0gwYlVSZHArS1FBU2ZL","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 00:41:38 2024] [VerificationCode] {"VerificationCodeId":634463845,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 01:11:21 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3M4em1oSG05eU9XbVlna0U1TDRjRWdlRi8zTE10YnAzYVZia0hRTmQ3bWV4cEUyUUl4TCtPMncvWStrNi9TSjNt","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 01:11:21 2024] [VerificationCode] {"VerificationCodeId":634470192,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
[Wed Mar 13 02:55:34 2024] [Token] {"TokenKey":"OVlma3FmZmJOTjNoY1A3bmxRbE5odHBHTTNZbGpjcGxvcVVJTmZIMDFHdjkvWDNYaG55WlB0cXNpaWRmclVCeHlXNFVrd2xxZThZQktIVEdGak9Xd29QTGNhMWtpYXlWUmwwNXcvZUdxQjhmbHFyenpPNVlDVk9ZbGZsUmplQ3MwSFZidjNtY0p1T3RwQVFlbkxJdjFHZ0xUaFpqZW9CdllCR21oNFNyaXRUczFaVUVSemZ3Rzk0TW0zRG50Q3VU","IsSuccessful":true,"Message":" درخواست Token با موفقیت انجام شد"}
|
||||
[Wed Mar 13 02:55:34 2024] [VerificationCode] {"VerificationCodeId":634481796,"IsSuccessful":true,"Message":"your verification code is sent"}
|
||||
@@ -1,9 +0,0 @@
|
||||
module.exports = (app) => {
|
||||
app.use((req, res, next) => {
|
||||
res.status(404).send({
|
||||
code: 'Not Found',
|
||||
status: 404,
|
||||
message: 'requested resource could not be found!'
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
const AdminModel = require('../models/AdminModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
if (!('authorization' in req.headers)) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'you are not authorized!'
|
||||
})
|
||||
}
|
||||
|
||||
const token = req.header('Authorization').split(' ')[1]
|
||||
if (!token) return res.status(401).send('Access Denied')
|
||||
let decodedToken
|
||||
try {
|
||||
decodedToken = jwt.verify(token, process.env.APP_SECRET)
|
||||
} catch (err) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'Your token is not valid!',
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
if (!decodedToken) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'Your token is not valid!'
|
||||
})
|
||||
}
|
||||
try {
|
||||
// پیدا کردن کاربر با استفاده از اطلاعات موجود در توکن
|
||||
const admin = await AdminModel.findOne({ _id: decodedToken.id })
|
||||
if (!admin) {
|
||||
return res.status(403).send({
|
||||
status: 'error',
|
||||
code: 403,
|
||||
message: 'Access denied!'
|
||||
})
|
||||
}
|
||||
|
||||
// ادامه مسیر در صورتی که کاربر ادمین باشد
|
||||
req.admin = admin
|
||||
next()
|
||||
} catch (error) {
|
||||
return res.status(500).send({
|
||||
status: 'error',
|
||||
code: 500,
|
||||
message: 'Internal Server Error',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
const TokenService = require('../services/TokenService')
|
||||
module.exports = (req, res, next) => {
|
||||
console.log(1);
|
||||
if (!('authorization' in req.headers)) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'you are not authorized!'
|
||||
})
|
||||
}
|
||||
const [, tokenValue] = req.headers.authorization.split(' ')
|
||||
const token = TokenService.verify(tokenValue)
|
||||
if (!token) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'your token is not valid!'
|
||||
})
|
||||
}
|
||||
next()
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
const UserModel = require('../models/UserModel')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const {
|
||||
expireBlockSuspensionIfNeeded,
|
||||
getBlockedAccountResponse
|
||||
} = require('../utils/blockSuspension')
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
try {
|
||||
if (!('authorization' in req.headers)) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'You are not authorized!'
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (!decodedToken) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'Your token is not valid!'
|
||||
})
|
||||
}
|
||||
|
||||
let user = await UserModel.findById(decodedToken.id)
|
||||
user = await expireBlockSuspensionIfNeeded(user)
|
||||
|
||||
const blockResponse = getBlockedAccountResponse(user)
|
||||
if (blockResponse) {
|
||||
return res.status(403).send(blockResponse)
|
||||
}
|
||||
|
||||
req.user = user
|
||||
next()
|
||||
} catch (error) {
|
||||
console.error('Error in blockCheck middleware:', error)
|
||||
res.status(500).send({
|
||||
status: 'error',
|
||||
code: 500,
|
||||
message: 'Server error'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = (app) => {
|
||||
app.use((error, req, res, next) => {
|
||||
const status = error.status || 500
|
||||
res.status(500).send({
|
||||
code: 'Eception',
|
||||
status,
|
||||
en_message: error.message,
|
||||
message: 'خطایی در عملیات مورد نظر رخ داده است.'
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const bodyParser = require('body-parser')
|
||||
const cors = require('cors')
|
||||
const formData = require('express-form-data')
|
||||
const session = require('express-session')
|
||||
const lastOnline = require('./lastOnline')
|
||||
module.exports = (app) => {
|
||||
app.use(cors()) // develop
|
||||
app.use(formData.parse())
|
||||
app.use(bodyParser.json())
|
||||
app.use(bodyParser.urlencoded({ extended: true }))
|
||||
app.use(session({
|
||||
secret: 'your_secret_key', // کلید مخفی برای امنیت جلسات
|
||||
resave: false,
|
||||
saveUninitialized: false
|
||||
}))
|
||||
app.use(lastOnline)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
const UserModel = require('../models/UserModel') // مسیر درست به مدل کاربر
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
console.log(3);
|
||||
try {
|
||||
console.log(31);
|
||||
if (!('authorization' in req.headers)) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'You are not authorized!'
|
||||
})
|
||||
}
|
||||
console.log(32);
|
||||
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)
|
||||
|
||||
if (!decodedToken) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'Your token is not valid!'
|
||||
})
|
||||
}
|
||||
console.log(33);
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user.user_name ||
|
||||
!user.first_name ||
|
||||
!user.last_name ||
|
||||
!user.user_type ||
|
||||
!user.password) {
|
||||
return res.status(403).send({
|
||||
status: 'error',
|
||||
code: 403,
|
||||
message: 'لطفا از بخش تنظیمات، ثبت نام خود را کامل کنید',
|
||||
type: 'not_register'
|
||||
})
|
||||
}
|
||||
console.log(34);
|
||||
req.user = user // کاربر را به درخواست اضافه کنید تا در سایر middleware ها و کنترلرها استفاده شود.
|
||||
next()
|
||||
} catch (error) {
|
||||
console.log(35);
|
||||
console.error('not_register middleware:', error)
|
||||
res.status(500).send({
|
||||
status: 'error',
|
||||
code: 500,
|
||||
message: 'Server error'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
const UserModel = require('../models/UserModel') // مسیر درست به مدل کاربر
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
try {
|
||||
if (!('authorization' in req.headers)) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'You are not authorized!'
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (!decodedToken) {
|
||||
return res.status(401).send({
|
||||
status: 'error',
|
||||
code: 401,
|
||||
message: 'Your token is not valid!'
|
||||
})
|
||||
}
|
||||
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (!user.user_name ||
|
||||
!user.first_name ||
|
||||
!user.last_name ||
|
||||
!user.password) {
|
||||
return res.status(403).send({
|
||||
status: 'error',
|
||||
code: 403,
|
||||
message: 'لطفا از بخش تنظیمات، ثبت نام خود را کامل کنید',
|
||||
type: 'not_register'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
req.user = user // کاربر را به درخواست اضافه کنید تا در سایر middleware ها و کنترلرها استفاده شود.
|
||||
next()
|
||||
} catch (error) {
|
||||
console.error('not_register middleware:', error)
|
||||
res.status(500).send({
|
||||
status: 'error',
|
||||
code: 500,
|
||||
message: 'Server error'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
const jwt = require('jsonwebtoken')
|
||||
const UserModel = require('../models/UserModel')
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
if (req.header && req.header('Authorization')) {
|
||||
const token = await req.header('Authorization').split(' ')[1]
|
||||
const decodedToken = await jwt.verify(token, process.env.APP_SECRET)
|
||||
const user = await UserModel.findById(decodedToken.id)
|
||||
if (user) {
|
||||
try {
|
||||
user.last_online = new Date()
|
||||
await user.save() // ذخیره تغییرات در دیتابیس
|
||||
} catch (error) {
|
||||
// بررسی و پردازش خطاها
|
||||
console.error('Error updating last online time:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const uploadDir = path.join(__dirname, '../storage/posts');
|
||||
fs.ensureDirSync(uploadDir); // مطمئن میشه مسیر وجود داره
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => cb(null, uploadDir),
|
||||
filename: (req, file, cb) => {
|
||||
const ext = path.extname(file.originalname);
|
||||
const name = file.originalname.replace(ext, '').replace(/\s/g, '_');
|
||||
cb(null, `${Date.now()}-${name}${ext}`);
|
||||
}
|
||||
});
|
||||
|
||||
const fileFilter = (req, file, cb) => {
|
||||
if (!file.mimetype.startsWith('image/')) return cb(new Error('فقط تصاویر مجاز است'));
|
||||
cb(null, true);
|
||||
};
|
||||
|
||||
const upload = multer({ storage, fileFilter, limits: { files: 10 } }); // حداکثر ۱۰ فایل
|
||||
|
||||
module.exports = upload;
|
||||
@@ -1,190 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
const mongoosePaginate = require('mongoose-paginate-v2');
|
||||
const timestamp = require('mongoose-timestamp');
|
||||
|
||||
const AcademyCategorySchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: [true, 'عنوان دستهبندی الزامی است'],
|
||||
trim: true,
|
||||
minLength: [2, 'عنوان باید حداقل ۲ کاراکتر باشد'],
|
||||
maxLength: [100, 'عنوان باید حداکثر ۱۰۰ کاراکتر باشد'],
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
slug: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
lowercase: true,
|
||||
trim: true,
|
||||
index: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
maxLength: 500,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '#3B82F6',
|
||||
match: /^#[0-9A-F]{6}$/i,
|
||||
},
|
||||
parent: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'AcademyCategory',
|
||||
required: false,
|
||||
default: null,
|
||||
index: true,
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 3,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
index: true,
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
course_count: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
min: 0,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['active', 'inactive', 'deleted'],
|
||||
required: true,
|
||||
default: 'active',
|
||||
index: true,
|
||||
},
|
||||
is_featured: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
index: true,
|
||||
},
|
||||
seo_title: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
maxLength: 70,
|
||||
},
|
||||
seo_description: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
maxLength: 160,
|
||||
},
|
||||
created_by: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true,
|
||||
},
|
||||
updated_by: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: false,
|
||||
},
|
||||
}, {
|
||||
toJSON: { virtuals: true },
|
||||
toObject: { virtuals: true }
|
||||
});
|
||||
|
||||
// Virtual برای زیردستهها
|
||||
AcademyCategorySchema.virtual('children', {
|
||||
ref: 'AcademyCategory',
|
||||
localField: '_id',
|
||||
foreignField: 'parent',
|
||||
justOne: false,
|
||||
options: { sort: { order: 1, title: 1 } }
|
||||
});
|
||||
|
||||
// Virtual برای دورههای این دسته
|
||||
AcademyCategorySchema.virtual('courses', {
|
||||
ref: 'Course',
|
||||
localField: '_id',
|
||||
foreignField: 'category_id',
|
||||
justOne: false,
|
||||
match: { status: 'accept' }
|
||||
});
|
||||
|
||||
// Middleware: قبل از save
|
||||
AcademyCategorySchema.pre('save', async function(next) {
|
||||
if (this.isModified('title') || this.isNew) {
|
||||
// تولید slug
|
||||
this.slug = this.title
|
||||
.replace(/[^\u0600-\u06FF\uFB8A\u067E\u0686\u06AF\u200C\uFB8E\u0698a-zA-Z0-9\s]/g, '')
|
||||
.trim()
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase();
|
||||
|
||||
// اگه parent داره، سطح رو محاسبه کن
|
||||
if (this.parent) {
|
||||
const parent = await this.constructor.findById(this.parent);
|
||||
if (parent) {
|
||||
this.level = parent.level + 1;
|
||||
}
|
||||
} else {
|
||||
this.level = 0;
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// استاتیک متدها
|
||||
AcademyCategorySchema.statics = {
|
||||
// دریافت درخت دستهبندیها
|
||||
async getTree() {
|
||||
const categories = await this.find({ status: 'active', parent: null })
|
||||
.sort({ order: 1, title: 1 })
|
||||
.populate({
|
||||
path: 'children',
|
||||
match: { status: 'active' },
|
||||
options: { sort: { order: 1, title: 1 } },
|
||||
populate: {
|
||||
path: 'children',
|
||||
match: { status: 'active' },
|
||||
options: { sort: { order: 1, title: 1 } }
|
||||
}
|
||||
});
|
||||
return categories;
|
||||
},
|
||||
|
||||
// افزایش تعداد دورهها
|
||||
async incrementCourseCount(categoryId, increment = 1) {
|
||||
return this.findByIdAndUpdate(categoryId, {
|
||||
$inc: { course_count: increment }
|
||||
});
|
||||
},
|
||||
|
||||
// دریافت دستهبندی با اسلاگ
|
||||
async getBySlug(slug) {
|
||||
return this.findOne({ slug, status: 'active' })
|
||||
.populate('children')
|
||||
.populate('parent');
|
||||
}
|
||||
};
|
||||
|
||||
AcademyCategorySchema.plugin(timestamp);
|
||||
AcademyCategorySchema.plugin(mongoosePaginate);
|
||||
|
||||
module.exports = mongoose.model('AcademyCategory', AcademyCategorySchema);
|
||||
@@ -1,71 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
const timestamp = require('mongoose-timestamp');
|
||||
const mongoosePaginate = require('mongoose-paginate-v2');
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
course_images: {
|
||||
type: [String], // آرایهای از مسیرهای تصاویر
|
||||
required: false,
|
||||
default: [],
|
||||
},
|
||||
course_video: {
|
||||
type: String, // مسیر ویدئو
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String, // نوع پست: 'image' یا 'video'
|
||||
required: true,
|
||||
enum: ['image', 'video'],
|
||||
},
|
||||
files: [
|
||||
{
|
||||
path: { type: String, required: false },
|
||||
type: { type: String, enum: ['image', 'video'], required: false },
|
||||
}
|
||||
],
|
||||
caption: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 1000,
|
||||
default: null,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
enum: ['pending', 'accept', 'reject'],
|
||||
default: 'accept',
|
||||
},
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
},
|
||||
likes: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
}],
|
||||
comments: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
courseId: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
is_free: {
|
||||
type: Boolean,
|
||||
},
|
||||
file_name: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model('AcademyContent', modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,25 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
|
||||
user_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
default: 0,
|
||||
},
|
||||
course_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("Likes", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,55 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
academy_image: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null,
|
||||
},
|
||||
academy_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null,
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rate: {
|
||||
type: Number,
|
||||
trim: true,
|
||||
default: 0
|
||||
},
|
||||
number_of_rate: {
|
||||
type: Number,
|
||||
trim: true,
|
||||
default: 0
|
||||
},
|
||||
sheba: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
bio: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
tag: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("Academy", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,24 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
price: { type: Number, required: true }, // قیمت نهایی شامل مالیات
|
||||
discountAmount: { type: Number, default: 0 }, // مبلغ تخفیف
|
||||
taxAmount: { type: Number, default: 0 }, // مبلغ مالیات
|
||||
taxRate: { type: Number, default: 9 }, // درصد مالیات
|
||||
course_name: { type: String, required: true },
|
||||
course_id: { type: mongoose.Schema.Types.ObjectId, ref: "Course", required: true },
|
||||
academy_id: { type: mongoose.Schema.Types.ObjectId, ref: "Academy", required: true },
|
||||
user_id: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
|
||||
payment_authority: { type: String },
|
||||
payment_ref_id: { type: String },
|
||||
status: { type: String, enum: ["pending", "success", "failed", "settled"], default: "success" },
|
||||
|
||||
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("AcademyPayment", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,80 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const adminSchema = new mongoose.Schema({
|
||||
mobile: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
otp: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 6,
|
||||
maxLength: 6
|
||||
},
|
||||
user_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
first_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
last_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
user_type: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
profile_image: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
last_online: {
|
||||
type: Date, // ذخیره تاریخ و زمان آخرین بازدید
|
||||
default: Date.now // تنظیم تاریخ به تاریخ فعلی
|
||||
}
|
||||
})
|
||||
|
||||
adminSchema.plugin(timestamp)
|
||||
adminSchema.plugin(mongoosePaginate)
|
||||
const AdminModel = mongoose.model('Admin', adminSchema)
|
||||
module.exports = AdminModel
|
||||
@@ -1,23 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const advertisingCategorySchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
status: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
advertisingCategorySchema.plugin(timestamp)
|
||||
advertisingCategorySchema.plugin(mongoosePaginate)
|
||||
const AdvertisingCategoryModel = mongoose.model('AdvertisingCategory', advertisingCategorySchema)
|
||||
module.exports = AdvertisingCategoryModel
|
||||
@@ -1,32 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
const advertisingCommentSchema = new mongoose.Schema({
|
||||
advertisingId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Advertising',
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'accepted', 'rejected'],
|
||||
default: 'pending'
|
||||
}
|
||||
})
|
||||
|
||||
advertisingCommentSchema.plugin(timestamp)
|
||||
advertisingCommentSchema.plugin(mongoosePaginate)
|
||||
const AdvertisingComment = mongoose.model('AdvertisingComment', advertisingCommentSchema)
|
||||
|
||||
module.exports = AdvertisingComment
|
||||
@@ -1,27 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const advertisingFeaturesSchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
status: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'boolean'
|
||||
}
|
||||
})
|
||||
|
||||
advertisingFeaturesSchema.plugin(timestamp)
|
||||
advertisingFeaturesSchema.plugin(mongoosePaginate)
|
||||
const AdvertisingFeaturesModel = mongoose.model('AdvertisingFeatures', advertisingFeaturesSchema)
|
||||
module.exports = AdvertisingFeaturesModel
|
||||
@@ -1,20 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const advertisingLikeSchema = new mongoose.Schema({
|
||||
advertisingId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Advertising',
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// تعریف مدل AdvertisingLikeModel
|
||||
const AdvertisingLikeModel = mongoose.model('AdvertisingLike', advertisingLikeSchema)
|
||||
|
||||
module.exports = AdvertisingLikeModel
|
||||
@@ -1,230 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const advertisingSchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: null
|
||||
},
|
||||
province: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
city: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
neighbourhood: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
lat: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 256,
|
||||
default: null
|
||||
},
|
||||
lng: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 256,
|
||||
default: null
|
||||
},
|
||||
images: [{
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
}],
|
||||
services: [{
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
originalPrice: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
discountPrice: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
discountPercentage: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: "UnderReview",
|
||||
required: false
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true
|
||||
}
|
||||
}],
|
||||
features: [{
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
}],
|
||||
contactInfo: {
|
||||
phone: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
mobile: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
telegramLink: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
whatsappNumber: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
instagramLink: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
saveInfoForNextAds: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['free', 'normal', 'special', 'highlight'],
|
||||
default: 'normal'
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pre_payment', 'paid', 'accepted', 'rejected', 'expired'],
|
||||
default: 'pre_payment'
|
||||
},
|
||||
payment_status: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
creator_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
reject_reason: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: null
|
||||
},
|
||||
mostDiscountPercentage: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
showDiscount: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
likesCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
commentsCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
ratings: [{
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
rating: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 5
|
||||
}
|
||||
}],
|
||||
averageRating: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
ratingsCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
viewCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
acceptedAt: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
advertisingSchema.plugin(timestamp)
|
||||
advertisingSchema.plugin(mongoosePaginate)
|
||||
const AdvertisingModel = mongoose.model('Advertising', advertisingSchema)
|
||||
module.exports = AdvertisingModel
|
||||
@@ -1,113 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
const advertisingProfileSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
profile_image: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
image_count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
like_count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
comment_count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
vitrine_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
about_us: {
|
||||
type: String,
|
||||
required: false,
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: null
|
||||
},
|
||||
province: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
city: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
lat: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
lng: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
neighbourhood: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
adTotalRatings: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
adRatingsCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
adAverageRating: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
contactInfo: {
|
||||
phone: String,
|
||||
mobile: String,
|
||||
telegramLink: String,
|
||||
whatsappNumber: String,
|
||||
instagramLink: String,
|
||||
saveInfoForNextAds: { type: Boolean, default: false }
|
||||
}
|
||||
})
|
||||
|
||||
advertisingProfileSchema.plugin(timestamp)
|
||||
advertisingProfileSchema.plugin(mongoosePaginate)
|
||||
|
||||
const AdvertisingProfileModel = mongoose.model('AdvertisingProfile', advertisingProfileSchema)
|
||||
|
||||
module.exports = AdvertisingProfileModel
|
||||
@@ -1,26 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const advertisingRatingSchema = new mongoose.Schema({
|
||||
advertisingId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Advertising',
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
rating: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 5
|
||||
}
|
||||
})
|
||||
|
||||
advertisingRatingSchema.plugin(timestamp)
|
||||
|
||||
const AdvertisingRatingModel = mongoose.model('AdvertisingRating', advertisingRatingSchema)
|
||||
module.exports = AdvertisingRatingModel
|
||||
@@ -1,25 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const advertisingTypeSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: true,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: 'active'
|
||||
}
|
||||
})
|
||||
|
||||
const AdvertisingTypeModel = mongoose.model('AdvertisingType', advertisingTypeSchema)
|
||||
module.exports = AdvertisingTypeModel
|
||||
@@ -1,55 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
const commentSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
project: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Project',
|
||||
required: false
|
||||
},
|
||||
offer: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Offer',
|
||||
required: false
|
||||
},
|
||||
creator: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
rating: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
comment: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
post: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Post',
|
||||
required: false
|
||||
},
|
||||
comment_for: {
|
||||
type: String,
|
||||
enum: ['user', 'project', 'post'],
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'accepted', 'rejected'],
|
||||
default: 'pending'
|
||||
}
|
||||
})
|
||||
|
||||
commentSchema.plugin(timestamp)
|
||||
commentSchema.plugin(mongoosePaginate)
|
||||
const CommentModel = mongoose.model('Comment', commentSchema)
|
||||
module.exports = CommentModel
|
||||
@@ -1,40 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
|
||||
user_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
course_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
comment: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
default: 0,
|
||||
},
|
||||
rate: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
default: 0,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'accepted', 'rejected'],
|
||||
default: 'accepted'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("CourseComment", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,79 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
price: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cuorse_name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
category:{
|
||||
type: String
|
||||
},
|
||||
offer: {
|
||||
type: String
|
||||
},
|
||||
academyId:{
|
||||
type: String
|
||||
},
|
||||
caption: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 1000,
|
||||
default: null,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
enum: ['pending', 'accept', 'reject'],
|
||||
default: 'accept',
|
||||
},
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
},
|
||||
likes: {
|
||||
type: Number,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: 0,
|
||||
},
|
||||
comments: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
enum: ['normal', 'pro', 'legend'],
|
||||
default: "normal",
|
||||
},
|
||||
course_time: {
|
||||
type: String
|
||||
},
|
||||
number_of_course_content: {
|
||||
type: String
|
||||
},
|
||||
teacher_number: {
|
||||
type: String
|
||||
},
|
||||
course_image: {
|
||||
type: String
|
||||
},
|
||||
teacher_name: {
|
||||
type: String
|
||||
},
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("Course", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,22 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
price: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cuorse_type: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
enum: ['normal', 'pro', 'legend'],
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("CoursePayment", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,47 +0,0 @@
|
||||
// const mongoose = require('mongoose')
|
||||
// const timestamp = require('mongoose-timestamp')
|
||||
|
||||
// const subExpertiseSchema = new mongoose.Schema({
|
||||
// id: {
|
||||
// type: mongoose.Schema.Types.ObjectId,
|
||||
// ref: 'ExpertiseModel'
|
||||
// },
|
||||
// name: {
|
||||
// type: String
|
||||
// }
|
||||
// })
|
||||
|
||||
// const expertiseSchema = new mongoose.Schema({
|
||||
// expertise: {
|
||||
// type: String,
|
||||
// unique: true
|
||||
// },
|
||||
// sub_expertise: [subExpertiseSchema]
|
||||
// })
|
||||
|
||||
// expertiseSchema.plugin(timestamp)
|
||||
|
||||
// const ExpertiseModel = mongoose.model('Expertise', expertiseSchema)
|
||||
// module.exports = ExpertiseModel
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const subExpertiseSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const expertiseSchema = new mongoose.Schema({
|
||||
expertise: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
sub_expertise: [subExpertiseSchema]
|
||||
})
|
||||
|
||||
expertiseSchema.plugin(timestamp)
|
||||
|
||||
const ExpertiseModel = mongoose.model('Expertise', expertiseSchema)
|
||||
module.exports = ExpertiseModel
|
||||
@@ -1,54 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const exploreInteractionSchema = new mongoose.Schema({
|
||||
viewerId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
targetType: {
|
||||
type: String,
|
||||
enum: ['post', 'profile', 'academy'],
|
||||
required: true
|
||||
},
|
||||
targetId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
required: true
|
||||
},
|
||||
authorId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
default: null
|
||||
},
|
||||
contentType: {
|
||||
type: String,
|
||||
enum: ['image', 'video', 'academy'],
|
||||
default: 'image'
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
enum: [
|
||||
'view',
|
||||
'profile_visit',
|
||||
'share',
|
||||
'like',
|
||||
'comment',
|
||||
'offer',
|
||||
'watch_complete',
|
||||
'skip',
|
||||
'dwell'
|
||||
],
|
||||
default: 'view'
|
||||
},
|
||||
dwellMs: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
exploreInteractionSchema.index({ viewerId: 1, createdAt: -1 })
|
||||
exploreInteractionSchema.plugin(timestamp)
|
||||
|
||||
module.exports = mongoose.model('ExploreInteraction', exploreInteractionSchema)
|
||||
@@ -1,15 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
user_id: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
|
||||
course_id: { type: mongoose.Schema.Types.ObjectId, ref: "Course", required: true },
|
||||
price_paid: { type: Number, required: true }, // قیمت پرداخت شده
|
||||
purchased_at: { type: Date, default: Date.now },
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("IsPaymentCourse", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,25 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const likeSchema = new mongoose.Schema({
|
||||
postId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Post',
|
||||
required: true
|
||||
},
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
likes: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
}]
|
||||
})
|
||||
|
||||
// تعریف مدل LikeModel
|
||||
likeSchema.plugin(timestamp)
|
||||
const LikeModel = mongoose.model('Like', likeSchema)
|
||||
|
||||
module.exports = LikeModel
|
||||
@@ -1,66 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const { Schema } = mongoose
|
||||
|
||||
const messageSchema = new Schema({
|
||||
senderId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
receiverId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true
|
||||
},
|
||||
file: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
enum: ['image', 'video', 'file', 'voice', 'location'],
|
||||
required: false
|
||||
},
|
||||
replyToId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Message',
|
||||
required: false
|
||||
},
|
||||
forwardedFrom: {
|
||||
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
|
||||
userName: String,
|
||||
displayName: String
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
readStatus: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/** Auto-delete after this time (self-destruct messages) */
|
||||
expiresAt: {
|
||||
type: Date,
|
||||
required: false,
|
||||
index: true
|
||||
},
|
||||
/** View-once photo / video / voice */
|
||||
viewOnce: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
viewOnceOpenedAt: {
|
||||
type: Date,
|
||||
required: false
|
||||
}
|
||||
})
|
||||
|
||||
const MessageModel = mongoose.model('Message', messageSchema)
|
||||
|
||||
module.exports = MessageModel
|
||||
@@ -1,37 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
const notificationSchema = new mongoose.Schema({
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User', // مرجع به مدل کاربر
|
||||
required: true
|
||||
},
|
||||
project_post_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
required: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
read: {
|
||||
type: Boolean,
|
||||
default: false // وضعیت خوانده شده یا نشده
|
||||
}
|
||||
})
|
||||
|
||||
notificationSchema.plugin(timestamp)
|
||||
notificationSchema.plugin(mongoosePaginate)
|
||||
|
||||
const NotificationModel = mongoose.model('Notification', notificationSchema)
|
||||
module.exports = NotificationModel
|
||||
@@ -1,32 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const offerSchema = new mongoose.Schema({
|
||||
sender: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
receiver: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'accepted', 'rejected'], // وضعیت درخواست
|
||||
default: 'pending'
|
||||
},
|
||||
transaction_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Payment',
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
offerSchema.plugin(timestamp)
|
||||
offerSchema.plugin(mongoosePaginate)
|
||||
|
||||
const OfferModel = mongoose.model('Offer', offerSchema)
|
||||
module.exports = OfferModel
|
||||
@@ -1,21 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const offerTypeModelSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: true,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255
|
||||
}
|
||||
})
|
||||
|
||||
const OfferTypeModel = mongoose.model('OfferTypeModel', offerTypeModelSchema)
|
||||
module.exports = OfferTypeModel
|
||||
@@ -1,46 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
const paymentSchema = new mongoose.Schema({
|
||||
amount: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'successful', 'failed'],
|
||||
default: 'pending'
|
||||
},
|
||||
authority: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
project_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Project'
|
||||
},
|
||||
advertising_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Advertising'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
installment_step: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
paymentSchema.plugin(timestamp)
|
||||
paymentSchema.plugin(mongoosePaginate)
|
||||
|
||||
const PaymentModel = mongoose.model('Payment', paymentSchema)
|
||||
module.exports = PaymentModel
|
||||
@@ -1,60 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
const timestamp = require('mongoose-timestamp');
|
||||
const mongoosePaginate = require('mongoose-paginate-v2');
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
post_images: {
|
||||
type: [String], // آرایهای از مسیرهای تصاویر
|
||||
required: false,
|
||||
default: [],
|
||||
},
|
||||
post_video: {
|
||||
type: String, // مسیر ویدئو
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String, // نوع پست: 'image' یا 'video'
|
||||
required: true,
|
||||
enum: ['image', 'video'],
|
||||
},
|
||||
files: [
|
||||
{
|
||||
path: { type: String, required: true },
|
||||
type: { type: String, enum: ['image', 'video'], required: true },
|
||||
}
|
||||
],
|
||||
caption: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 1000,
|
||||
default: null,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
enum: ['pending', 'accept', 'reject'],
|
||||
default: 'pending',
|
||||
},
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
},
|
||||
likes: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
}],
|
||||
comments: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model('Post', modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,186 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const projectSchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
expertise: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
sub_expertise: {
|
||||
type: [String], // تغییر نوع به آرایه از استرینگ
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
age: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
conversation_projects: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
province: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
city: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
offer_time: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
offer_price: {
|
||||
type: Number,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: null
|
||||
},
|
||||
project_type: {
|
||||
type: String,
|
||||
enum: ['free', 'normal', 'force', 'highlight'],
|
||||
default: 'normal'
|
||||
},
|
||||
payment_status: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
// فیلد برای وضعیت پروژه
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pre_payment', 'paid', 'accepted', 'rejected', 'done', 'cancled', 'ongoing'],
|
||||
default: 'pre_payment'
|
||||
},
|
||||
public_status: {
|
||||
type: String,
|
||||
enum: ['public', 'private'],
|
||||
default: 'public'
|
||||
},
|
||||
created_for_user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
default: null
|
||||
},
|
||||
creator_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
requested_users: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
}],
|
||||
selected_user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
default: null
|
||||
},
|
||||
final_price: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
final_time: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
installments: [{
|
||||
installment_number: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
amount: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
due_date: {
|
||||
type: Date,
|
||||
required: true
|
||||
}
|
||||
}],
|
||||
reject_reason: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: null
|
||||
},
|
||||
ratings: [{
|
||||
creator_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
rating: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
comment: {
|
||||
type: String,
|
||||
trim: true
|
||||
}
|
||||
}],
|
||||
acceptedAt: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isExpired: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
projectSchema.plugin(timestamp)
|
||||
// اضافه کردن پیجینیشن به مدل
|
||||
projectSchema.plugin(mongoosePaginate)
|
||||
const ProjectModel = mongoose.model('Project', projectSchema)
|
||||
module.exports = ProjectModel
|
||||
@@ -1,33 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const requestSchema = new mongoose.Schema({
|
||||
project: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Project',
|
||||
required: true
|
||||
},
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
time: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'accepted', 'rejected'],
|
||||
default: 'pending'
|
||||
}
|
||||
})
|
||||
|
||||
requestSchema.plugin(timestamp)
|
||||
|
||||
const RequestModel = mongoose.model('Request', requestSchema)
|
||||
module.exports = RequestModel
|
||||
@@ -1,19 +0,0 @@
|
||||
// models/Setting.js
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const settingSchema = new mongoose.Schema({
|
||||
key: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
settingSchema.plugin(timestamp)
|
||||
|
||||
const SettingModel = mongoose.model('Setting', settingSchema)
|
||||
module.exports = SettingModel
|
||||
@@ -1,19 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const provincesSchema = new mongoose.Schema({
|
||||
id: Number,
|
||||
name: String,
|
||||
slug: String
|
||||
})
|
||||
|
||||
const citiesSchema = new mongoose.Schema({
|
||||
id: Number,
|
||||
name: String,
|
||||
slug: String,
|
||||
province_id: Number
|
||||
})
|
||||
|
||||
const ProvinceModel = mongoose.model('Province', provincesSchema)
|
||||
const CityModel = mongoose.model('City', citiesSchema)
|
||||
|
||||
module.exports = { ProvinceModel, CityModel }
|
||||
@@ -1,41 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
|
||||
const storySchema = new mongoose.Schema({
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
media_path: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
media_type: {
|
||||
type: String,
|
||||
enum: ['image', 'video'],
|
||||
required: true
|
||||
},
|
||||
expires_at: {
|
||||
type: Date,
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
viewers: [{
|
||||
user_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
viewed_at: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
storySchema.index({ expires_at: 1 }, { expireAfterSeconds: 0 })
|
||||
|
||||
storySchema.plugin(timestamp)
|
||||
|
||||
module.exports = mongoose.model('Story', storySchema)
|
||||
@@ -1,15 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const timestamp = require("mongoose-timestamp");
|
||||
const mongoosePaginate = require("mongoose-paginate-v2");
|
||||
|
||||
const modelSchema = new mongoose.Schema({
|
||||
type: { type: String, enum: ["course", "product", "service"], required: true },
|
||||
tax: { type: Number, required: true, default: 9 }, // درصد مالیات
|
||||
is_active: { type: Boolean, default: true },
|
||||
|
||||
});
|
||||
|
||||
modelSchema.plugin(timestamp);
|
||||
modelSchema.plugin(mongoosePaginate);
|
||||
const PostModel = mongoose.model("Tax", modelSchema);
|
||||
module.exports = PostModel;
|
||||
@@ -1,20 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
const ticketMessageSchema = new mongoose.Schema({
|
||||
text: { type: String, required: false },
|
||||
file: {
|
||||
type: String, // مسیر فایل در ذخیره شود
|
||||
required: false // فایل اختیاری است
|
||||
},
|
||||
senderType: { type: String, enum: ['User', 'Admin'], required: true },
|
||||
senderId: { type: mongoose.Schema.Types.ObjectId, refPath: 'senderType', required: true },
|
||||
ticket: { type: mongoose.Schema.Types.ObjectId, ref: 'Ticket', required: true }
|
||||
})
|
||||
|
||||
ticketMessageSchema.plugin(timestamp)
|
||||
// اضافه کردن پیجینیشن به مدل
|
||||
ticketMessageSchema.plugin(mongoosePaginate)
|
||||
const TicketMessageModel = mongoose.model('TicketMessage', ticketMessageSchema)
|
||||
module.exports = TicketMessageModel
|
||||
@@ -1,24 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
|
||||
const ticketSchema = new mongoose.Schema({
|
||||
title: { type: String, required: true },
|
||||
status: { type: String, enum: ['Pending', 'Answered', 'Customer Response', 'Closed'], default: 'Pending' },
|
||||
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
|
||||
new_message: {
|
||||
type: Boolean,
|
||||
default: false // وضعیت خوانده شده یا نشده
|
||||
}
|
||||
})
|
||||
|
||||
ticketSchema.pre('save', function (next) {
|
||||
this.updatedAt = Date.now()
|
||||
next()
|
||||
})
|
||||
|
||||
ticketSchema.plugin(timestamp)
|
||||
// اضافه کردن پیجینیشن به مدل
|
||||
ticketSchema.plugin(mongoosePaginate)
|
||||
const TicketModel = mongoose.model('Ticket', ticketSchema)
|
||||
module.exports = TicketModel
|
||||
@@ -1,21 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const typeAndPriceSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: true,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255
|
||||
}
|
||||
})
|
||||
|
||||
const TypeAndPriceModel = mongoose.model('TypeAndPrice', typeAndPriceSchema)
|
||||
module.exports = TypeAndPriceModel
|
||||
@@ -1,391 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const userSchema = new mongoose.Schema({
|
||||
mobile: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
otp: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 6,
|
||||
maxLength: 6,
|
||||
default: null
|
||||
},
|
||||
otpSentAt: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
user_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
first_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
last_name: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
user_type: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
is_verified: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: 'none'
|
||||
},
|
||||
profile_image: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
national_card_image: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
expertise: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
sub_expertise: {
|
||||
type: [String], // تغییر نوع به آرایه از استرینگ
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
weight: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
eye_color: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
hair_color: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
cooperation_type: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
cooperation_abroad: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
conversation_projects: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
bio: {
|
||||
type: String,
|
||||
required: false,
|
||||
minLength: 1,
|
||||
maxLength: 1024,
|
||||
default: null
|
||||
},
|
||||
national_code: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
birthday: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
is_Register: {
|
||||
type: String,
|
||||
|
||||
default: false
|
||||
},
|
||||
shaba: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
default: null
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
province: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
city: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
lat: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
lng: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
show_location: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
rate: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
referral_code: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
shop_id: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Shop',
|
||||
default: null
|
||||
},
|
||||
last_online: {
|
||||
type: Date, // ذخیره تاریخ و زمان آخرین بازدید
|
||||
default: Date.now // تنظیم تاریخ به تاریخ فعلی
|
||||
},
|
||||
business_license: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true,
|
||||
default: null
|
||||
},
|
||||
user_level: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
user_score: {
|
||||
type: String,
|
||||
trim: true,
|
||||
minLength: 1,
|
||||
maxLength: 512,
|
||||
default: null
|
||||
},
|
||||
last_post: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
// user_ratings: [{
|
||||
// project_id: {
|
||||
// type: mongoose.Schema.Types.ObjectId,
|
||||
// ref: 'Project',
|
||||
// required: true
|
||||
// },
|
||||
// creator_id: {
|
||||
// type: mongoose.Schema.Types.ObjectId,
|
||||
// ref: 'User',
|
||||
// required: true
|
||||
// },
|
||||
// rating: {
|
||||
// type: Number,
|
||||
// required: true
|
||||
// },
|
||||
// comment: {
|
||||
// type: String,
|
||||
// trim: true
|
||||
// }
|
||||
// }],
|
||||
daily_free_request: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
last_free_request_date: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
blocked_by: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
}],
|
||||
blocked_users: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
}],
|
||||
block_status: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
blocked_until: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
advertising_profiles: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'AdvertisingProfile'
|
||||
}],
|
||||
services: [{
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
originalPrice: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
discountPrice: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
discountPercentage: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: "UnderReview",
|
||||
required: false
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: false,
|
||||
trim: true
|
||||
}
|
||||
}],
|
||||
monthly_free_offer: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
last_free_offer_date: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
// contactInfo: {
|
||||
// phone: String,
|
||||
// mobile: String,
|
||||
// telegramLink: String,
|
||||
// whatsappNumber: String,
|
||||
// instagramLink: String,
|
||||
// saveInfoForNextAds: { type: Boolean, default: false }
|
||||
// },
|
||||
// adTotalRatings: {
|
||||
// type: Number,
|
||||
// default: 0
|
||||
// },
|
||||
// adRatingsCount: {
|
||||
// type: Number,
|
||||
// default: 0
|
||||
// },
|
||||
// adAverageRating: {
|
||||
// type: Number,
|
||||
// default: 0
|
||||
// }
|
||||
})
|
||||
|
||||
userSchema.plugin(timestamp)
|
||||
userSchema.plugin(mongoosePaginate)
|
||||
const UserModel = mongoose.model('User', userSchema)
|
||||
module.exports = UserModel
|
||||
@@ -1,12 +0,0 @@
|
||||
// models/VersionModel.js
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const versionSchema = new mongoose.Schema({
|
||||
version: { type: String, required: true },
|
||||
mandatory: { type: Boolean, required: true },
|
||||
releaseNotes: { type: String },
|
||||
updateUrls: { type: [String], required: true } // لینکهای آپدیت
|
||||
})
|
||||
|
||||
const VersionModel = mongoose.model('Version', versionSchema)
|
||||
module.exports = VersionModel
|
||||
@@ -1,14 +0,0 @@
|
||||
const mongoose = require('mongoose')
|
||||
const mongoosePaginate = require('mongoose-paginate-v2')
|
||||
const timestamp = require('mongoose-timestamp')
|
||||
const license = new mongoose.Schema({
|
||||
|
||||
userId : String,
|
||||
Confirmation : { type : Boolean, default : false},
|
||||
licenseImg : { type : String },
|
||||
})
|
||||
|
||||
license.plugin(timestamp)
|
||||
license.plugin(mongoosePaginate)
|
||||
const LicenseMoldel = mongoose.model('license', license)
|
||||
module.exports = LicenseMoldel
|
||||
@@ -1,423 +0,0 @@
|
||||
/* eslint-disable camelcase */
|
||||
const jwt = require('jsonwebtoken')
|
||||
const jMoment = require('moment-jalaali')
|
||||
const OfferModel = require('../../../models/OfferModel')
|
||||
const UserModel = require('../../../models/UserModel')
|
||||
const OfferTypeModel = require('../../../models/OfferTypeModel')
|
||||
const PaymentModel = require('../../../models/PaymentModel')
|
||||
const NotificationModel = require('../../../models/NotificationModel')
|
||||
const { default: axios } = require('axios')
|
||||
const CommentModel = require('../../../models/CommentModel')
|
||||
const {
|
||||
resolveRatingForComment,
|
||||
recalculateUserRating
|
||||
} = require('../../../utils/commentRating')
|
||||
|
||||
const getOfferPriceFromDatabase = async (itemName) => {
|
||||
const item = await OfferTypeModel.findOne({ name: itemName })
|
||||
if (!item) {
|
||||
throw new Error('Item not found in database')
|
||||
}
|
||||
return item.price
|
||||
}
|
||||
|
||||
const getOfferTypes = 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 userId = decodedToken.id
|
||||
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) {
|
||||
return res.status(422).json({
|
||||
error: true,
|
||||
message: 'کاربر یافت نشد'
|
||||
})
|
||||
}
|
||||
let offerTypes = await OfferTypeModel.find({}, 'name price')
|
||||
|
||||
// بررسی برای مواردی مانند پیدا نشدن انواع پروژه
|
||||
if (!offerTypes) {
|
||||
return res.status(404).json({ message: 'انواع پروژه یافت نشد.' })
|
||||
}
|
||||
// اگر کاربر درخواست ماهانه نداشته باشد، نوع پروژه ماهانه را حذف کنید
|
||||
if (user.monthly_free_offer <= 0) {
|
||||
offerTypes = offerTypes.filter(projectType => projectType.name !== 'free')
|
||||
}
|
||||
// ارسال انواع پروژه به کاربر
|
||||
res.status(200).json({ offerTypes })
|
||||
} catch (error) {
|
||||
// در صورت بروز خطا، ارسال پیام خطا به کاربر
|
||||
console.error('Error in getOfferTypes:', error)
|
||||
res.status(500).json({ message: 'خطا در دریافت انواع پروژه.' })
|
||||
}
|
||||
}
|
||||
const getUserOffers = 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 userId = decodedToken.id
|
||||
const { page = 1, limit = 10, status_filter, id } = req.query // افزودن پارامترهای صفحهبندی به درخواست
|
||||
let filter = {}
|
||||
let filterbyid = {receiver: id}
|
||||
let filterbyidsender = {sender: id}
|
||||
if (status_filter === 'درخواست') {
|
||||
filter = { sender: userId }
|
||||
} else {
|
||||
filter = { receiver: userId }
|
||||
}
|
||||
|
||||
// تنظیم گزینههای صفحهبندی
|
||||
const options = {
|
||||
page: parseInt(page), // تبدیل صفحه به عدد صحیح
|
||||
limit: parseInt(limit), // تبدیل محدودیت به عدد صحیح
|
||||
sort: { createdAt: -1 }, // بر اساس زمان ایجاد (createdAt) مرتب کنید (نزولی)
|
||||
populate: [
|
||||
{
|
||||
path: 'sender',
|
||||
select: 'profile_image user_level first_name last_name user_name is_verified user_score rate'
|
||||
},
|
||||
{
|
||||
path: 'receiver',
|
||||
select: 'profile_image user_level first_name last_name user_name is_verified user_score rate'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// دریافت آفرهای صفحهبندی شده
|
||||
let offer = await OfferModel.paginate(filter, options)
|
||||
let offerbyid = await OfferModel.paginate(filterbyid, options)
|
||||
let offerbyidsender = await OfferModel.paginate(filterbyidsender, options)
|
||||
|
||||
const totalPages = offer.totalPages
|
||||
const totalItems = offer.totalDocs
|
||||
|
||||
// فرمت کردن تاریخ و ارسال پاسخ
|
||||
offer = offer?.docs.map(offer => {
|
||||
const jDate = jMoment(offer.createdAt).format('jYYYY-jMM-jDD HH:mm')
|
||||
return {
|
||||
...offer._doc,
|
||||
createdAt: jDate
|
||||
}
|
||||
})
|
||||
res.json({
|
||||
offerbyidsender,
|
||||
offerbyid,
|
||||
offer,
|
||||
totalPages, // ارسال تعداد کل صفحات
|
||||
totalItems // ارسال تعداد کل آیتمها
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
const updateOfferStatus = 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 userId = decodedToken.id // ID کاربری که درخواست را بهروزرسانی میکند
|
||||
const reciverUser = await UserModel.findById(userId)
|
||||
|
||||
const { offerId, action } = req.body // آیدی آفر و اکشن (accept یا reject)
|
||||
|
||||
// بررسی اعتبار ورودیها
|
||||
if (!offerId || !['accept', 'reject'].includes(action)) {
|
||||
return res.status(400).json({ error: 'Invalid input' })
|
||||
}
|
||||
|
||||
// یافتن آفر موردنظر
|
||||
const offer = await OfferModel.findById(offerId)
|
||||
const senderUser = await UserModel.findById(offer?.sender)
|
||||
|
||||
if (!offer) {
|
||||
return res.status(404).json({ error: 'Offer not found' })
|
||||
}
|
||||
|
||||
// بررسی اینکه آیا کاربر گیرنده این آفر است یا خیر
|
||||
if (offer.receiver.toString() !== userId) {
|
||||
return res.status(403).json({ error: 'You are not authorized to update this offer' })
|
||||
}
|
||||
|
||||
// بررسی اینکه آیا آفر قبلاً بهروزرسانی شده یا نه
|
||||
if (offer.status !== 'pending') {
|
||||
return res.status(400).json({ error: 'Offer has already been updated', message: 'وضعیت این درخواست قبلا تغییر کرده است.' })
|
||||
}
|
||||
|
||||
// بهروزرسانی وضعیت آفر بر اساس اکشن
|
||||
offer.status = action === 'accept' ? 'accepted' : 'rejected'
|
||||
await offer.save()
|
||||
|
||||
if (action === 'accept') {
|
||||
const data = JSON.stringify({
|
||||
mobile: senderUser?.mobile,
|
||||
templateId: '633448',
|
||||
parameters: [
|
||||
{ name: 'USER', value: reciverUser?.first_name + ' ' + reciverUser?.last_name },
|
||||
{ name: 'EMPLOYER', value: senderUser?.first_name + ' ' + senderUser?.last_name }
|
||||
]
|
||||
})
|
||||
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
} else {
|
||||
const data = JSON.stringify({
|
||||
mobile: senderUser?.mobile,
|
||||
templateId: '682678',
|
||||
parameters: [
|
||||
{ name: 'USER', value: reciverUser?.first_name + ' ' + reciverUser?.last_name },
|
||||
{ name: 'EMPLOYER', value: senderUser?.first_name + ' ' + senderUser?.last_name }
|
||||
]
|
||||
})
|
||||
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
res.status(200).json({
|
||||
message: `Offer has been ${offer.status} successfully`,
|
||||
offerId: offer._id,
|
||||
status: offer.status
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Controller function for handling successful advertising payment
|
||||
const handleSuccessfulOfferPayment = async (req, res) => {
|
||||
try {
|
||||
const { reciverId, offerType } = req.body
|
||||
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 userId = decodedToken.id
|
||||
|
||||
// Check user's free offer eligibility
|
||||
const user = await UserModel.findById(userId)
|
||||
if (!user) return res.status(404).json({ error: 'User not found' })
|
||||
// Check user's free offer eligibility
|
||||
const reciverUser = await UserModel.findById(reciverId)
|
||||
if (!user) return res.status(404).json({ error: 'User not found' })
|
||||
|
||||
let price
|
||||
if (user.monthly_free_offer > 0 && offerType === 'free') {
|
||||
// Use free offer and decrement counter
|
||||
price = 0
|
||||
user.monthly_free_offer -= 1
|
||||
await user.save()
|
||||
} else {
|
||||
// Retrieve offer price based on type
|
||||
price = await getOfferPriceFromDatabase(offerType)
|
||||
|
||||
// Create a new payment entry
|
||||
const payment = new PaymentModel({
|
||||
amount: Number(price),
|
||||
status: 'successful',
|
||||
authority: '', // Add authority if available
|
||||
user_id: userId,
|
||||
type: 'offer',
|
||||
installment_step: null
|
||||
})
|
||||
await payment.save()
|
||||
}
|
||||
// Create a new payment entry
|
||||
const payment = new PaymentModel({
|
||||
amount: Number(price),
|
||||
status: 'successful',
|
||||
authority: '', // Add authority if available
|
||||
user_id: userId,
|
||||
type: 'offer',
|
||||
installment_step: null
|
||||
})
|
||||
await payment.save()
|
||||
|
||||
// Create a new offer
|
||||
const offer = new OfferModel({
|
||||
sender: userId,
|
||||
receiver: reciverId,
|
||||
transaction_id: payment._id // Link the payment to the offer
|
||||
})
|
||||
await offer.save()
|
||||
console.log(reciverUser?.first_name)
|
||||
|
||||
// Send notification to selected user
|
||||
const notification = new NotificationModel({
|
||||
user_id: reciverId,
|
||||
project_post_id: offer?._id,
|
||||
type: 'new_offer',
|
||||
title: 'درخواست همکاری جدید',
|
||||
description: `${reciverUser?.first_name} عزیز یک درخواست همکاری برای شما در مجموعه مدستاگرام ثبت شد`
|
||||
})
|
||||
await notification.save()
|
||||
|
||||
// Send Sms
|
||||
const data = JSON.stringify({
|
||||
mobile: reciverUser?.mobile,
|
||||
templateId: '569006',
|
||||
parameters: [
|
||||
{ name: 'FIRSTNAME', value: reciverUser?.first_name },
|
||||
{ name: 'LASTNAME', value: reciverUser?.last_name }
|
||||
|
||||
]
|
||||
})
|
||||
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: 'https://api.sms.ir/v1/send/verify',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/plain',
|
||||
'x-api-key': '2JKxGbTzE64t0YTkjip6RYHnm9mq1haMlidF8WWwQunwubHFVW6iaMmfFQbMXEDt'
|
||||
},
|
||||
data
|
||||
}
|
||||
axios(config)
|
||||
.then(function (response) {
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
// Send event based on payment status
|
||||
|
||||
// Emit a success event
|
||||
if ('eventEmitter' in req.app) {
|
||||
req.app.get('eventEmitter').emit('paymentSuccess', {
|
||||
message: 'Payment and offer created successfully',
|
||||
offerId: offer._id
|
||||
})
|
||||
} else {
|
||||
console.error('Error: eventEmitter is not defined or not properly configured.')
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Payment and offer recorded successfully',
|
||||
offerId: offer._id,
|
||||
price
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
// Controller function for handling failed advertising payment
|
||||
const handleFailedOfferPayment = async (req, res) => {
|
||||
try {
|
||||
const { offerType } = req.body
|
||||
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 userId = decodedToken.id
|
||||
|
||||
const price = await getOfferPriceFromDatabase(offerType)
|
||||
|
||||
// Create a new payment entry
|
||||
const payment = new PaymentModel({
|
||||
amount: Number(price),
|
||||
status: 'failed',
|
||||
authority: '', // Add authority if available
|
||||
user_id: userId,
|
||||
type: 'offer',
|
||||
installment_step: null
|
||||
})
|
||||
await payment.save()
|
||||
|
||||
// Emit a failure event
|
||||
if ('eventEmitter' in req.app) {
|
||||
req.app.get('eventEmitter').emit('paymentFailed', { message: 'Payment failed' })
|
||||
} else {
|
||||
console.error('Error: eventEmitter is not defined or not properly configured.')
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'Payment failed', price })
|
||||
} catch (error) {
|
||||
console.error('Error occurred:', error)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
}
|
||||
|
||||
const createOfferComment = 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 userId = decodedToken.id
|
||||
|
||||
const { offerId, comment, rate, user_id } = req.body
|
||||
if (!offerId || !comment || !user_id) {
|
||||
return res.status(400).send({ message: 'All fields are required' })
|
||||
}
|
||||
|
||||
const { ratingValue, isNewRating, requiresRating } = await resolveRatingForComment({
|
||||
creatorId: userId,
|
||||
targetUserId: user_id,
|
||||
rate
|
||||
})
|
||||
|
||||
if (requiresRating) {
|
||||
return res.status(400).send({ message: 'Rating must be between 1 and 5' })
|
||||
}
|
||||
|
||||
const newComment = new CommentModel({
|
||||
user: user_id,
|
||||
offer: offerId,
|
||||
creator: userId,
|
||||
rating: ratingValue,
|
||||
comment,
|
||||
comment_for: 'user',
|
||||
status: 'pending'
|
||||
})
|
||||
await newComment.save()
|
||||
|
||||
if (isNewRating) {
|
||||
await recalculateUserRating(user_id)
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'نظر شما با موفقیت ثبت شد' })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getUserOffers,
|
||||
getOfferTypes,
|
||||
handleSuccessfulOfferPayment,
|
||||
handleFailedOfferPayment,
|
||||
updateOfferStatus,
|
||||
createOfferComment
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const academyController = require("../../../controllers/application/academy/academyControllers");
|
||||
const multer = require("multer");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
// const academyCategoryController = require("../../../controllers/application/academy/academyCategoryController");
|
||||
|
||||
// ==========================================
|
||||
// ⚙️ تنظیمات اولیه و دایرکتوریها
|
||||
// ==========================================
|
||||
|
||||
// اطمینان از وجود دایرکتوریها
|
||||
const ensureDir = (dir) => {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
console.log("DEBUG: Created directory:", dir);
|
||||
}
|
||||
};
|
||||
|
||||
const BASE_DIR = process.cwd();
|
||||
const courseVideoDir = path.join(BASE_DIR, "storage", "courses", "videos");
|
||||
const courseImageDir = path.join(BASE_DIR, "storage", "courses", "images");
|
||||
|
||||
console.log("DEBUG: BASE_DIR:", BASE_DIR);
|
||||
console.log("DEBUG: courseVideoDir:", courseVideoDir);
|
||||
console.log("DEBUG: courseImageDir:", courseImageDir);
|
||||
|
||||
ensureDir(courseVideoDir);
|
||||
ensureDir(courseImageDir);
|
||||
|
||||
// ==========================================
|
||||
// 📁 تنظیمات آپلود فایل با Multer
|
||||
// ==========================================
|
||||
|
||||
// تنظیمات ذخیرهسازی فایلها
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
console.log("DEBUG: Multer destination - fieldname:", file.fieldname);
|
||||
// استفاده از دایرکتوری موقت برای آپلود اولیه
|
||||
const tempDir = "/tmp/modstagram-uploads";
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
cb(null, tempDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
||||
const filename = uniqueSuffix + path.extname(file.originalname);
|
||||
console.log("DEBUG: Generated filename:", filename);
|
||||
cb(null, filename);
|
||||
},
|
||||
});
|
||||
|
||||
// تنظیمات Multer با محدودیت حجم 500 مگابایت
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 500 * 1024 * 1024, // 500 MB
|
||||
},
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 🏢 مسیرهای مربوط به آکادمی
|
||||
// ==========================================
|
||||
|
||||
// دریافت اطلاعات آکادمی کاربر جاری
|
||||
router.get("/", academyController.findAcademy);
|
||||
|
||||
// یافتن آکادمی با شناسه
|
||||
router.post("/findAcademyById", academyController.findAcademyById);
|
||||
|
||||
// تکمیل پروفایل آکادمی
|
||||
router.post("/academy/profile", academyController.academyProfile);
|
||||
|
||||
// ==========================================
|
||||
// 📚 مسیرهای مدیریت دورهها (Courses)
|
||||
// ==========================================
|
||||
|
||||
// ایجاد دوره جدید
|
||||
router.post("/academy/creat/course", academyController.creatCourse);
|
||||
|
||||
// ویرایش دوره
|
||||
router.patch("/academy/update/course", academyController.updateCourse);
|
||||
|
||||
// حذف دوره (تغییر وضعیت به reject)
|
||||
router.delete("/academy/delete/course", academyController.deleteCourse);
|
||||
|
||||
// دریافت لیست دورههای آکادمی جاری
|
||||
router.get("/academy/get/course", academyController.getCourse);
|
||||
|
||||
// دریافت لیست همه دورهها با فیلتر و صفحهبندی
|
||||
router.get("/academy/get/curses", academyController.getCourses);
|
||||
|
||||
// محتوای رایگان آموزشگاه برای اکسپلور
|
||||
router.get(
|
||||
"/academy/explore/free-content",
|
||||
academyController.getFreeAcademyExploreContent
|
||||
);
|
||||
|
||||
// دریافت دورههای یک آکادمی خاص
|
||||
router.get("/academy/get/getAcademyCourse/:Id", academyController.getAcademyCourse);
|
||||
|
||||
// ==========================================
|
||||
// 💳 مسیرهای پرداخت و مالی
|
||||
// ==========================================
|
||||
|
||||
// پرداخت برای اشتراک پرو
|
||||
router.post("/academy/course/payment", academyController.proPayment);
|
||||
|
||||
// کالبک درگاه پرداخت زرینپال (اشتراک پرو)
|
||||
router.get("/academy/course/pro", academyController.handleCoursePaymentCallback);
|
||||
|
||||
// پرداخت برای خرید دوره
|
||||
router.post("/academy/course/payment-web", academyController.coursePayment);
|
||||
|
||||
// کالبک درگاه پرداخت زرینپال (خرید دوره)
|
||||
router.get("/academy/course/payment/verify", academyController.CoursePaymentCallback);
|
||||
|
||||
// ==========================================
|
||||
// 🎬 مسیرهای محتوای دوره (ویدیوها و فایلها)
|
||||
// ==========================================
|
||||
|
||||
// دریافت محتوای دوره (ویدیوها)
|
||||
router.get("/academy/course/content", academyController.getCourseContent);
|
||||
|
||||
// حذف ویدیو از دوره
|
||||
router.delete("/academy/course/content/video", academyController.deleteCourseVideo);
|
||||
|
||||
// ==========================================
|
||||
// 📹 مسیرهای آپلود ویدیو (۳ روش مختلف)
|
||||
// ==========================================
|
||||
|
||||
// روش 1: آپلود معمولی با Multipart (با استفاده از Multer)
|
||||
router.post(
|
||||
"/academy/course/creat/video",
|
||||
(req, res, next) => {
|
||||
console.log("===== STEP 1: Route hit =====");
|
||||
console.log("Content-Type:", req.headers["content-type"]);
|
||||
next();
|
||||
},
|
||||
(req, res, next) => {
|
||||
console.log("===== STEP 2: Before multer =====");
|
||||
next();
|
||||
},
|
||||
(req, res, next) => {
|
||||
// اجرای manual multer با try-catch
|
||||
upload.single("video")(req, res, (err) => {
|
||||
if (err) {
|
||||
console.log("===== MULTER ERROR =====");
|
||||
console.log("Error name:", err.name);
|
||||
console.log("Error message:", err.message);
|
||||
console.log("Full error:", err);
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `خطا در آپلود: ${err.message}`,
|
||||
error: err.toString(),
|
||||
});
|
||||
}
|
||||
console.log("===== STEP 3: Multer completed successfully =====");
|
||||
next();
|
||||
});
|
||||
},
|
||||
(req, res, next) => {
|
||||
console.log("===== STEP 4: After multer =====");
|
||||
console.log("req.file:", req.file);
|
||||
console.log("req.body:", req.body);
|
||||
|
||||
if (!req.file) {
|
||||
console.log("ERROR: No file in req.file!");
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "فایلی دریافت نشد",
|
||||
});
|
||||
}
|
||||
next();
|
||||
},
|
||||
academyController.creatCourseVideo
|
||||
);
|
||||
|
||||
// روش 2: آپلود با Base64 (برای فایلهای کوچک)
|
||||
router.post(
|
||||
"/academy/course/creat/video-base64",
|
||||
academyController.creatCourseVideoBase64
|
||||
);
|
||||
|
||||
// روش 3: آپلود تکهتکه (Chunk Upload) - مناسب برای فایلهای بزرگ
|
||||
// دریافت هر تکه از ویدیو
|
||||
router.post(
|
||||
"/academy/course/creat/video-chunk",
|
||||
academyController.creatCourseVideoChunk
|
||||
);
|
||||
|
||||
// ادغام تکهها بعد از اتمام آپلود
|
||||
router.post(
|
||||
"/academy/course/creat/video-chunk/merge",
|
||||
academyController.mergeVideoChunks
|
||||
);
|
||||
|
||||
// بررسی وضعیت آپلود (برای قابلیت Resume)
|
||||
router.get("/academy/course/upload-status", academyController.getUploadStatus);
|
||||
|
||||
// ==========================================
|
||||
// ❤️ مسیرهای لایک و تعامل
|
||||
// ==========================================
|
||||
|
||||
// لایک کردن محتوای دوره
|
||||
router.post("/academy/course/like", academyController.likeCourseContent);
|
||||
|
||||
// حذف لایک (دیسلایک)
|
||||
router.post("/academy/course/dislike", academyController.dontLikeCourseContent);
|
||||
|
||||
// بررسی وضعیت لایک کاربر
|
||||
router.post("/academy/course/is-like", academyController.isLikeCourseContent);
|
||||
|
||||
// دریافت تعداد لایکهای یک دوره
|
||||
router.get(
|
||||
"/academy/course/likes-count/:courseId",
|
||||
academyController.getCourseLikesCount
|
||||
);
|
||||
|
||||
// دریافت لیست دورههای لایک شده توسط کاربر
|
||||
router.get(
|
||||
"/academy/user/liked-courses",
|
||||
academyController.getUserLikedCourses
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 💬 مسیرهای کامنت و نظرات
|
||||
// ==========================================
|
||||
|
||||
// دریافت تعداد کل کامنتهای یک دوره
|
||||
router.get(
|
||||
"/course/:courseId/comments/count",
|
||||
academyController.getTotalCommentsCount
|
||||
);
|
||||
|
||||
// دریافت لیست کامنتهای یک دوره
|
||||
router.get("/course/:courseId/comments", academyController.getCourseComments);
|
||||
|
||||
// ایجاد کامنت جدید
|
||||
router.post("/comment/create", academyController.createComment);
|
||||
|
||||
// تغییر وضعیت کامنت (تایید/رد)
|
||||
router.patch(
|
||||
"/comment/:commentId/status",
|
||||
academyController.updateCommentStatus
|
||||
);
|
||||
|
||||
// حذف کامنت
|
||||
router.delete("/comment/:commentId/delete", academyController.deleteComment);
|
||||
|
||||
// ==========================================
|
||||
// 🛒 مسیرهای دورههای خریداری شده
|
||||
// ==========================================
|
||||
|
||||
// دریافت دورههای خریداری شده توسط کاربر جاری
|
||||
router.get(
|
||||
"/course/getUserPurchasedCourses",
|
||||
academyController.getUserPurchasedCourses
|
||||
);
|
||||
|
||||
// دریافت دورههای خریداری شده (با populate - روش بهینه)
|
||||
router.get(
|
||||
"/course/getUserPurchasedCoursesWithPopulate",
|
||||
academyController.getUserPurchasedCoursesWithPopulate
|
||||
);
|
||||
|
||||
// دریافت همه پرداختهای آکادمی
|
||||
router.get(
|
||||
"/course/getAllAcademyPayments",
|
||||
academyController.getAllAcademyPayments
|
||||
);
|
||||
|
||||
// بررسی خرید دوره توسط کاربر
|
||||
router.get(
|
||||
"/course/checkCoursePurchase/:courseId",
|
||||
academyController.checkCoursePurchase
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 📊 مسیرهای آمار و گزارشات (STATIC)
|
||||
// ==========================================
|
||||
|
||||
// آمار پرداختها
|
||||
router.get("/payments/stats", academyController.getPaymentStats);
|
||||
|
||||
// آمار دورهها
|
||||
router.get("/courses/stats", academyController.getCourseStats);
|
||||
|
||||
// آمار دورههای خریداری شده
|
||||
router.get("/purchased/stats", academyController.getPurchasedStats);
|
||||
|
||||
// آمار آکادمیها
|
||||
router.get("/academies/stats", academyController.getAcademyStats);
|
||||
|
||||
// ==========================================
|
||||
// ⚙️ مسیرهای تنظیمات (Settings)
|
||||
// ==========================================
|
||||
|
||||
// دریافت همه تنظیمات
|
||||
router.get("/settings/all", academyController.getAllSettings);
|
||||
router.get("/settings", academyController.getAllSettings);
|
||||
|
||||
// بهروزرسانی همه تنظیمات
|
||||
router.put("/settings/all", academyController.updateAllSettings);
|
||||
router.put("/settings", academyController.updateAllSettings);
|
||||
|
||||
// دریافت و ویرایش قیمت دورهها
|
||||
router.get("/settings/course-prices", academyController.getCoursePrices);
|
||||
router.put("/settings/course-price", academyController.updateCoursePrice);
|
||||
|
||||
// دریافت و ویرایش مالیات
|
||||
router.get("/settings/tax", academyController.getTax);
|
||||
router.put("/settings/tax", academyController.updateTax);
|
||||
|
||||
// ==========================================
|
||||
// 🏷️ مسیرهای دستهبندی آکادمی (Categories)
|
||||
// ==========================================
|
||||
|
||||
// دریافت لیست دستهبندیها (با فیلتر و صفحهبندی)
|
||||
router.get('/categories', academyController.getAll);
|
||||
|
||||
// دریافت درخت دستهبندی (ساختار سلسلهمراتبی)
|
||||
router.get('/categories/tree', academyController.getTree);
|
||||
|
||||
// دریافت دستهبندیهای ویژه (برای نمایش در هوم پیج)
|
||||
router.get('/categories/featured', academyController.getFeatured);
|
||||
|
||||
// دریافت دستهبندی با اسلاگ (برای سئو و لینکهای زیبا)
|
||||
router.get('/categories/slug/:slug', academyController.getBySlug);
|
||||
|
||||
// دریافت یک دستهبندی با شناسه
|
||||
router.get('/categories/:id', academyController.getOne);
|
||||
|
||||
// ایجاد دستهبندی جدید
|
||||
router.post('/categories', academyController.create);
|
||||
|
||||
// ویرایش دستهبندی (هم PUT و هم PATCH پشتیبانی میشود)
|
||||
router.put('/categories/:id', academyController.update);
|
||||
router.patch('/categories/:id', academyController.update);
|
||||
|
||||
// حذف دستهبندی (سافت دیلیت - فقط غیرفعال میشود)
|
||||
router.delete('/categories/:id', academyController.remove);
|
||||
|
||||
// حذف فیزیکی دستهبندی (به همراه query string permanent=true)
|
||||
router.delete('/categories/:id/permanent', (req, res) => {
|
||||
req.query.permanent = 'true';
|
||||
academyController.remove(req, res);
|
||||
});
|
||||
|
||||
// تغییر وضعیت دستهبندی (فعال/غیرفعال)
|
||||
router.patch('/categories/:id/status', academyController.toggleStatus);
|
||||
|
||||
// افزایش تعداد دورههای دستهبندی (هر بار یک دوره جدید اضافه میشود)
|
||||
router.post('/categories/:id/increment-count', academyController.incrementCourseCount);
|
||||
|
||||
// ==========================================
|
||||
// 👑 مسیرهای مدیریتی (پنل ادمین)
|
||||
// ==========================================
|
||||
|
||||
// ------ مدیریت پرداختها ------
|
||||
router.get("/payments", academyController.getAllPayments);
|
||||
router.put("/payments/status", academyController.updatePaymentStatus);
|
||||
|
||||
// ------ مدیریت دورهها ------
|
||||
router.get("/courses", academyController.getAllCourses);
|
||||
router.get("/courses/:id", academyController.getCourseById);
|
||||
router.put("/courses/:id", academyController.updateCourse);
|
||||
router.post("/courses", academyController.createCourse);
|
||||
router.put("/academy/courses/status", academyController.updateCourseStatus);
|
||||
router.delete("/courses/:courseId", academyController.admindeleteCourse);
|
||||
|
||||
// ------ مدیریت دورههای خریداری شده ------
|
||||
router.get("/purchased", academyController.getAllPurchasedCourses);
|
||||
|
||||
// ------ مدیریت آکادمیها ------
|
||||
router.get("/academies", academyController.getAllAcademies);
|
||||
router.get("/academies/:id", academyController.getAcademyById);
|
||||
router.put("/academies/:id", academyController.updateAcademy);
|
||||
router.post("/academies", academyController.createAcademy);
|
||||
router.put("/academies/status", academyController.updateAcademyStatus);
|
||||
router.delete("/academies/:id", academyController.deleteAcademy);
|
||||
|
||||
// ==========================================
|
||||
// 📤 خروجی روتها
|
||||
// ==========================================
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,45 +0,0 @@
|
||||
const express = require('express')
|
||||
const blockCheck = require('../../../middlewares/blockCheck')
|
||||
const { createAdvertisingValidationRules, createAdvertising, getAdvertisingTypes, getAdvertisingCategory, getSingleAdvertising, getAdvertisingFeatures, getAdvertisings, toggleLike, addComment, getComments, rateAdvertising, getUserAdvertisings, editAdvertising, getUserSingleAdvertising, getUserAdvertisingProfile, updateUserAdvertisingProfile, getAdvertisingsWeb, getSingleAdvertisingWeb, getUserAdvertisingProfileWeb } = require('../../../controllers/application/advertising/advertisingController')
|
||||
const auth = require('../../../middlewares/auth')
|
||||
const { initiateAdvertisingPayment, handleAdvertisingPaymentCallback, handleAdvertisingRepublishPaymentCallback, republishAdvertisingPayment, handleSuccessfulAdvertisingPayment, handleFailedAdvertisingPayment, handleFailedAdvertisingRepublishPayment, handleSuccessfulAdvertisingRepublishPayment } = require('../../../controllers/application/payment/paymentController')
|
||||
const { initiateAdvertisingPaymentWeb, handleAdvertisingPaymentCallbackWeb, republishAdvertisingPaymentWeb, handleAdvertisingRepublishPaymentCallbackWeb } = require('../../../controllers/application/payment/paymentControllerWeb')
|
||||
const router = express.Router()
|
||||
router.get('/', [auth], getAdvertisings)
|
||||
router.get('/web', getAdvertisingsWeb)
|
||||
router.get('/profile', [auth], getUserAdvertisingProfile)
|
||||
router.get('/profile-web', getUserAdvertisingProfileWeb)
|
||||
router.patch('/profile', [auth], [blockCheck], updateUserAdvertisingProfile)
|
||||
router.get('/user-advertising', [auth], getUserAdvertisings)
|
||||
router.post('/create', [auth], [blockCheck], createAdvertisingValidationRules(), createAdvertising)
|
||||
router.get('/types', [auth], getAdvertisingTypes)
|
||||
router.get('/categories', getAdvertisingCategory)
|
||||
router.get('/features', [auth], getAdvertisingFeatures)
|
||||
router.get('/get/:advertisingId', [auth], getSingleAdvertising)
|
||||
router.get('/get/web/:advertisingId', getSingleAdvertisingWeb)
|
||||
router.get('/get-detail/:advertisingId', [auth], getUserSingleAdvertising)
|
||||
router.post('/initiate-payment', [auth], [blockCheck], initiateAdvertisingPayment)
|
||||
router.post('/initiate-payment-web', [auth], [blockCheck], initiateAdvertisingPaymentWeb)
|
||||
router.post('/republish-payment', [auth], [blockCheck], republishAdvertisingPayment)
|
||||
router.post('/republish-payment-web', [auth], [blockCheck], republishAdvertisingPaymentWeb)
|
||||
router.get('/payment', handleAdvertisingPaymentCallback)
|
||||
router.get('/payment-web', handleAdvertisingPaymentCallbackWeb)
|
||||
router.get('/republish-verify-payment', handleAdvertisingRepublishPaymentCallback)
|
||||
router.get('/republish-verify-payment-web', handleAdvertisingRepublishPaymentCallbackWeb)
|
||||
router.post('/like', [blockCheck], [auth], toggleLike)
|
||||
router.post('/comment', [blockCheck], [auth], addComment)
|
||||
router.get('/comments', getComments)
|
||||
router.post('/rate', [blockCheck], [auth], rateAdvertising)
|
||||
router.post('/edit/:id', [blockCheck], [auth], editAdvertising)
|
||||
|
||||
// Route for handling successful payment
|
||||
router.post('/payment-success', handleSuccessfulAdvertisingPayment)
|
||||
// Route for handling failed payment
|
||||
router.post('/payment-failed', handleFailedAdvertisingPayment)
|
||||
// Route for handling successful payment
|
||||
router.post('/republish-payment-success', handleSuccessfulAdvertisingRepublishPayment)
|
||||
// Route for handling failed payment
|
||||
router.post('/republish-payment-failed', handleFailedAdvertisingRepublishPayment)
|
||||
|
||||
module.exports = router
|
||||
module.exports = router
|
||||
@@ -1,7 +0,0 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const citysController = require('../../../controllers/application/citysController')
|
||||
|
||||
router.get('/:id', citysController.getCities)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getExpertise } = require('../../../controllers/application/expertise/expertiseController')
|
||||
const router = express.Router()
|
||||
router.get('/', getExpertise)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getFinancial } = require('../../../controllers/application/financial/financialController')
|
||||
const router = express.Router()
|
||||
router.get('/', getFinancial)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,14 +0,0 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { loginValidationRules, loginUser } = require('../../../controllers/application/login/loginController')
|
||||
const { verifyUser } = require('../../../controllers/application/login/verifyController')
|
||||
const { changePasswordUser, changePasswordValidationRules, editChangePasswordUser } = require('../../../controllers/application/login/changePasswordController')
|
||||
const { loginWithUserName } = require('../../../controllers/application/login/loginWithUserName')
|
||||
|
||||
router.post('/', loginValidationRules(), loginUser)
|
||||
router.post('/verify', verifyUser)
|
||||
router.post('/change_password', changePasswordValidationRules(), changePasswordUser)
|
||||
router.patch('/change_password', changePasswordValidationRules(), editChangePasswordUser)
|
||||
router.post('/login_username', loginWithUserName)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getMessages, getUnreadMessages } = require('../../../controllers/application/messages/messageController')
|
||||
const router = express.Router()
|
||||
router.get('/', getMessages)
|
||||
router.get('/unread', getUnreadMessages)
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getNotifications, unreadNotifications } = require('../../../controllers/application/notification/notificationController')
|
||||
const router = express.Router()
|
||||
router.get('/', getNotifications)
|
||||
router.get('/unread', unreadNotifications)
|
||||
module.exports = router
|
||||
@@ -1,18 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getUserOffers, getOfferTypes, handleSuccessfulOfferPayment, handleFailedOfferPayment, updateOfferStatus, createOfferComment } = require('../../../controllers/application/offer/offerController')
|
||||
const { initiateOfferPaymentWeb, handleOfferPaymentCallback } = require('../../../controllers/application/payment/paymentControllerWeb')
|
||||
const blockCheck = require('../../../middlewares/blockCheck')
|
||||
const isRegister = require('../../../middlewares/isRegister')
|
||||
const auth = require('./../../../middlewares/auth')
|
||||
|
||||
const router = express.Router()
|
||||
router.get('/', getUserOffers)
|
||||
router.get('/types', [auth], getOfferTypes)
|
||||
router.post('/update-status', [auth], updateOfferStatus)
|
||||
router.post('/payment-success', [auth], handleSuccessfulOfferPayment)
|
||||
router.post('/payment-failed', [auth], handleFailedOfferPayment)
|
||||
router.post('/comment', [auth], createOfferComment)
|
||||
router.post('/initiate-payment-web', [auth], [blockCheck], initiateOfferPaymentWeb)
|
||||
router.get('/payment-web', handleOfferPaymentCallback)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,205 +0,0 @@
|
||||
const express = require('express');
|
||||
const { createPostValidationRules, createPost, getUserPosts, getUserPostsWeb, getPostByIdWeb } = require('../../../controllers/application/posts/postController');
|
||||
const { toggleLike } = require('../../../controllers/application/posts/likeController');
|
||||
const blockCheck = require('../../../middlewares/blockCheck');
|
||||
const auth = require('../../../middlewares/auth');
|
||||
const isHalfRegister = require('../../../middlewares/isHalfRegister');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const multer = require('multer');
|
||||
|
||||
console.log('DEBUG: Starting to load routes/posts.js');
|
||||
|
||||
const ensureDir = (dir) => {
|
||||
console.log('DEBUG: Ensuring directory exists:', dir);
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log('DEBUG: Directory does not exist, creating:', dir);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
} else {
|
||||
console.log('DEBUG: Directory already exists:', dir);
|
||||
}
|
||||
};
|
||||
|
||||
const imageDir = path.join(__dirname, '../../../storage/posts/images');
|
||||
const videoDir = path.join(__dirname, '../../../storage/posts/videos');
|
||||
console.log('DEBUG: imageDir path:', imageDir);
|
||||
console.log('DEBUG: videoDir path:', videoDir);
|
||||
ensureDir(imageDir);
|
||||
ensureDir(videoDir);
|
||||
|
||||
console.log('DEBUG: Setting up multer storage');
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const dir = file.mimetype.startsWith('video/') ? videoDir : imageDir;
|
||||
console.log('DEBUG: multer destination for file', file.originalname, ':', dir);
|
||||
cb(null, dir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const filename = `${Date.now()}-${file.originalname}`;
|
||||
console.log('DEBUG: Generated filename for', file.originalname, ':', filename);
|
||||
cb(null, filename);
|
||||
},
|
||||
});
|
||||
|
||||
console.log('DEBUG: Setting up multer instance with limits and filter');
|
||||
|
||||
const upload = multer({
|
||||
storage,
|
||||
limits: {
|
||||
fileSize: 100 * 1024 * 1024, // حداکثر 100MB
|
||||
files: 10, // حداکثر 10 فایل
|
||||
fieldSize: 1024 * 1024,
|
||||
fieldNameSize: 100,
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
console.log('DEBUG: Checking file filter for', file.originalname, 'mimetype:', file.mimetype);
|
||||
if (file.mimetype.startsWith('image/') || file.mimetype.startsWith('video/')) {
|
||||
console.log('DEBUG: File accepted:', file.originalname);
|
||||
cb(null, true);
|
||||
} else {
|
||||
console.log('DEBUG: File rejected:', file.originalname);
|
||||
cb(new Error('فقط تصاویر یا ویدئوها مجاز هستند'), false);
|
||||
}
|
||||
},
|
||||
}).array('post_files', 10);
|
||||
|
||||
const multerErrorHandler = (err, req, res, next) => {
|
||||
console.log('DEBUG: Request headers:', JSON.stringify(req.headers, null, 2));
|
||||
console.log('DEBUG: Request body (raw):', req.body);
|
||||
console.log('DEBUG: Request files:', req.files || 'No files received');
|
||||
if (err instanceof multer.MulterError) {
|
||||
console.error('DEBUG: Multer error:', err.message, err.code, err.field);
|
||||
return res.status(400).json({
|
||||
error: true,
|
||||
message: `خطای آپلود: ${err.message} (${err.code})`,
|
||||
field: err.field,
|
||||
});
|
||||
} else if (err) {
|
||||
console.error('DEBUG: File filter error:', err.message);
|
||||
return res.status(400).json({
|
||||
error: true,
|
||||
message: err.message,
|
||||
});
|
||||
}
|
||||
console.log('DEBUG: Multer processing completed, files:', req.files || 'No files');
|
||||
console.log('DEBUG: Request body after multer:', req.body);
|
||||
next();
|
||||
};
|
||||
|
||||
console.log('DEBUG: Setting up express router');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// روت اصلی با multipart/form-data
|
||||
router.post(
|
||||
'/create',
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached auth');
|
||||
console.log('DEBUG: Raw request headers:', JSON.stringify(req.headers, null, 2));
|
||||
next();
|
||||
},
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached blockCheck');
|
||||
next();
|
||||
},
|
||||
blockCheck,
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached isHalfRegister');
|
||||
next();
|
||||
},
|
||||
isHalfRegister,
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached multer');
|
||||
console.log('DEBUG: Content-Length:', req.headers['content-length']);
|
||||
console.log('DEBUG: Content-Type:', req.headers['content-type']);
|
||||
next();
|
||||
},
|
||||
upload,
|
||||
multerErrorHandler,
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached validation');
|
||||
console.log('DEBUG: Parsed files:', req.files || 'No files');
|
||||
console.log('DEBUG: Parsed body:', req.body);
|
||||
next();
|
||||
},
|
||||
createPostValidationRules(),
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached controller');
|
||||
next();
|
||||
},
|
||||
createPost
|
||||
);
|
||||
|
||||
// روت جدید برای Base64
|
||||
router.post(
|
||||
'/create-base64',
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached auth for base64');
|
||||
console.log('DEBUG: Raw request headers:', JSON.stringify(req.headers, null, 2));
|
||||
next();
|
||||
},
|
||||
auth,
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached blockCheck for base64');
|
||||
next();
|
||||
},
|
||||
blockCheck,
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: reached isHalfRegister for base64');
|
||||
next();
|
||||
},
|
||||
isHalfRegister,
|
||||
(req, res, next) => {
|
||||
console.log('DEBUG: Request body (base64):', JSON.stringify(req.body, null, 2));
|
||||
next();
|
||||
},
|
||||
createPostValidationRules(),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { files, caption } = req.body;
|
||||
console.log('DEBUG: Received files (base64):', files.length);
|
||||
console.log('DEBUG: Received caption:', caption);
|
||||
|
||||
// تبدیل Base64 به فایل
|
||||
const savedFiles = await Promise.all(
|
||||
files.map(async (file, index) => {
|
||||
const buffer = Buffer.from(file.data.split(',')[1], 'base64');
|
||||
const dir = file.type.startsWith('video/') ? videoDir : imageDir;
|
||||
const filename = `${Date.now()}-${index}-${file.name}`;
|
||||
const filePath = path.join(dir, filename);
|
||||
console.log('DEBUG: Saving file:', filePath);
|
||||
await fs.promises.writeFile(filePath, buffer);
|
||||
return {
|
||||
path: filePath,
|
||||
filename,
|
||||
mimetype: file.type,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// فراخوانی createPost با فرمت مشابه
|
||||
req.files = savedFiles;
|
||||
req.body = { caption };
|
||||
console.log('DEBUG: Prepared files for createPost:', savedFiles);
|
||||
console.log('DEBUG: Prepared body for createPost:', req.body);
|
||||
await createPost(req, res);
|
||||
} catch (err) {
|
||||
console.error('DEBUG: Base64 processing error:', err.message);
|
||||
res.status(400).json({
|
||||
error: true,
|
||||
message: `خطای پردازش فایلها: ${err.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.post('/like', [auth, blockCheck], toggleLike);
|
||||
router.get('/web/:postId', getPostByIdWeb);
|
||||
router.get('/user-posts', [auth], getUserPosts);
|
||||
router.get('/user-posts/web', getUserPostsWeb);
|
||||
|
||||
console.log('DEBUG: Finished loading routes/posts.js');
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,8 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getProfile , getUserById} = require('../../../controllers/application/profile/profileController')
|
||||
const auth = require('../../../middlewares/auth')
|
||||
const router = express.Router()
|
||||
router.get('/',[auth], getProfile)
|
||||
router.get('/:id', getUserById)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,44 +0,0 @@
|
||||
const express = require('express')
|
||||
const { createProject, createProjectValidationRules } = require('../../../controllers/application/projects/createProjectController')
|
||||
const { getProjects, getSingleProjects, getSingleProjectsWeb, getUserProjects, getUserProjectsWeb } = require('../../../controllers/application/projects/getProjectController')
|
||||
const { getProjectTypes } = require('../../../controllers/application/projects/projectTypesController')
|
||||
const { initiatePayment, handlePaymentCallback, paymentRequestStepOne, paymentRequestStepTwo, paymentRequestStepAll, paymentRequestStepOneCallback, paymentRequestStepTwoCallback, paymentRequestStepAllCallback, handleSuccessfulPayment, handleFailedPayment } = require('../../../controllers/application/payment/paymentController')
|
||||
const auth = require('../../../middlewares/auth')
|
||||
const { requestProjectValidationRules, requestProject, acceptProject, editRequestProject } = require('../../../controllers/application/projects/requestProjectController')
|
||||
const { editProject, doneProject, cancleProject, editProjectValidationRules, setRateProject, doneProjectWeb } = require('../../../controllers/application/projects/updateProjectController')
|
||||
const blockCheck = require('../../../middlewares/blockCheck')
|
||||
const isRegister = require('../../../middlewares/isRegister')
|
||||
const { initiatePaymentWeb, handlePaymentCallbackWeb, paymentRequestAcceptWeb } = require('../../../controllers/application/payment/paymentControllerWeb')
|
||||
const router = express.Router()
|
||||
router.get('/', getProjects)
|
||||
router.get('/user-projects', [auth], getUserProjects)
|
||||
router.get('/user-projects/web', getUserProjectsWeb)
|
||||
router.get('/types', [auth], getProjectTypes)
|
||||
router.get('/get/:projectId', [auth], getSingleProjects)
|
||||
router.get('/get/web/:projectId', getSingleProjectsWeb)
|
||||
router.get('/payment', handlePaymentCallback)
|
||||
router.get('/payment-web', handlePaymentCallbackWeb)
|
||||
router.post('/create', [blockCheck], [auth], createProjectValidationRules(), createProject)
|
||||
router.post('/edit', [blockCheck], [auth], editProjectValidationRules(), editProject)
|
||||
router.post('/rate', [blockCheck], [auth], [isRegister], setRateProject)
|
||||
router.post('/initiate-payment', [blockCheck], [isRegister], [auth], initiatePayment)
|
||||
router.post('/initiate-payment-web', [blockCheck], [isRegister], [auth], initiatePaymentWeb)
|
||||
router.post('/request', [blockCheck], [isRegister], [auth], requestProjectValidationRules(), requestProject)
|
||||
router.post('/edit-request', [isRegister], [blockCheck], [auth], requestProjectValidationRules(), editRequestProject)
|
||||
router.post('/payment-request-step-one', [isRegister], [blockCheck], [auth], paymentRequestStepOne)
|
||||
router.post('/payment-request-step-one/web', [isRegister], [blockCheck], [auth], paymentRequestAcceptWeb)
|
||||
router.post('/payment-request-step-two', [isRegister], [blockCheck], [auth], paymentRequestStepTwo)
|
||||
router.post('/payment-request-all', [isRegister], [blockCheck], [auth], paymentRequestStepAll)
|
||||
router.get('/payment-request-step-one-callback', paymentRequestStepOneCallback)
|
||||
router.get('/payment-request-step-two-callback', paymentRequestStepTwoCallback)
|
||||
router.get('/payment-request-step-all-callback', paymentRequestStepAllCallback)
|
||||
router.post('/accept', [isRegister], [blockCheck], [auth], acceptProject)
|
||||
router.post('/done', [isRegister], [auth], doneProject)
|
||||
router.post('/done/web', [isRegister], [auth], doneProjectWeb)
|
||||
router.post('/cancle', [isRegister], [blockCheck], [auth], cancleProject)
|
||||
|
||||
// Route for handling successful payment
|
||||
router.post('/payment-success', handleSuccessfulPayment)
|
||||
// Route for handling failed payment
|
||||
router.post('/payment-failed', handleFailedPayment)
|
||||
module.exports = router
|
||||
@@ -1,7 +0,0 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const provincesController = require('../../../controllers/application/provincesController')
|
||||
|
||||
router.get('/', provincesController.getProvinces)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,16 +0,0 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { registerValidationRules, registerUser } = require('../../../controllers/application/register/registerController')
|
||||
const { verifyUser } = require('../../../controllers/application/register/verifyController')
|
||||
const { setUserName, updateUserName } = require('../../../controllers/application/register/usernameController')
|
||||
const { setUserFullName, setUserFullNameValidationRules } = require('../../../controllers/application/register/userFullNameController')
|
||||
const { setUserType } = require('../../../controllers/application/register/userTypeController')
|
||||
|
||||
router.post('/', registerValidationRules(), registerUser)
|
||||
router.post('/verify', verifyUser)
|
||||
router.post('/username', setUserName)
|
||||
router.patch('/username', updateUserName)
|
||||
router.post('/fullname', setUserFullNameValidationRules(), setUserFullName)
|
||||
router.post('/user_type', setUserType)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getSearch } = require('../../../controllers/application/search-web/searchController')
|
||||
const router = express.Router()
|
||||
router.get('/', getSearch)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getSearch } = require('../../../controllers/application/search/searchController')
|
||||
const router = express.Router()
|
||||
router.get('/', getSearch)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getSetting } = require('../../../controllers/application/settings/settingsController')
|
||||
const router = express.Router()
|
||||
router.get('/:key', getSetting)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,19 +0,0 @@
|
||||
const express = require('express')
|
||||
const auth = require('../../../middlewares/auth')
|
||||
const blockCheck = require('../../../middlewares/blockCheck')
|
||||
const isHalfRegister = require('../../../middlewares/isHalfRegister')
|
||||
const {
|
||||
createStoryBase64,
|
||||
getStoriesFeed,
|
||||
markStoryViewed,
|
||||
deleteStory
|
||||
} = require('../../../controllers/application/stories/storyController')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/feed', getStoriesFeed)
|
||||
router.post('/create-base64', auth, blockCheck, isHalfRegister, createStoryBase64)
|
||||
router.post('/view', auth, blockCheck, markStoryViewed)
|
||||
router.delete('/:storyId', auth, blockCheck, deleteStory)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,9 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getUserTickets, createTicket, addUserMessageToTicket, getTicketMessages } = require('../../../controllers/application/tickets/ticketsController')
|
||||
const router = express.Router()
|
||||
router.get('/', getUserTickets)
|
||||
router.get('/messages', getTicketMessages)
|
||||
router.post('/create', createTicket)
|
||||
router.post('/message', addUserMessageToTicket)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,23 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getUsers, getSingleUser, blockUser, unblockUser, getComments,createLicense, getAllLicenses,updateLicenseConfirmation, getLicenseByUserId, inviteEmployer, getUserContactInfo, getPostsWeb, getSingleUserWeb, createUserComment, recordExploreInteraction } = require('../../../controllers/application/users/getUserController')
|
||||
const blockCheck = require('../../../middlewares/blockCheck')
|
||||
const isRegister = require('../../../middlewares/isRegister')
|
||||
const auth = require('../../../middlewares/auth')
|
||||
const router = express.Router()
|
||||
router.get('/', [auth], getUsers)
|
||||
router.get('/web', getPostsWeb)
|
||||
router.get('/get', [auth], getSingleUser)
|
||||
router.get('/get/web', getSingleUserWeb)
|
||||
router.post('/block', [auth], [isRegister], [blockCheck], blockUser)
|
||||
router.post('/unblock', [auth], [isRegister], [blockCheck], unblockUser)
|
||||
router.get('/comments', [auth], getComments)
|
||||
router.post('/comments', [auth], [isRegister], [blockCheck], createUserComment)
|
||||
router.post('/explore/interaction', [auth], recordExploreInteraction)
|
||||
router.get('/invite-employer', [auth], [isRegister], [blockCheck], inviteEmployer)
|
||||
router.get('/contact-info', [auth], getUserContactInfo)
|
||||
router.get('/License', getAllLicenses)
|
||||
router.get('/License/:userId', getLicenseByUserId)
|
||||
router.post('/License', createLicense)
|
||||
router.put('/License/:userId', updateLicenseConfirmation)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,40 +0,0 @@
|
||||
const express = require('express')
|
||||
const { setProfileImage, updateProfileImage } = require('../../../controllers/application/verify/setProfileImageController')
|
||||
const { setExpertise, updateExpertise } = require('../../../controllers/application/verify/expertiseController')
|
||||
const { setGender } = require('../../../controllers/application/verify/genderController')
|
||||
const { setSizes, updateSizes } = require('../../../controllers/application/verify/sizesController')
|
||||
const { setColors, updateColors } = require('../../../controllers/application/verify/colorsController')
|
||||
const { setCooperationType, updateCooperationType } = require('../../../controllers/application/verify/cooperationTypeController')
|
||||
const { setConversation, updateConversation } = require('../../../controllers/application/verify/conversationController')
|
||||
const { saveAuth, saveAuthValidationRules, updateShaba } = require('../../../controllers/application/verify/authController')
|
||||
const { setAddress, updateAddress } = require('../../../controllers/application/verify/addressController')
|
||||
const { setNationalCardImage } = require('../../../controllers/application/verify/setNationalCardImageController')
|
||||
const { completeRegistration } = require('../../../controllers/application/verify/completeRegistrationController')
|
||||
const blockCheck = require('../../../middlewares/blockCheck')
|
||||
const auth = require('../../../middlewares/auth')
|
||||
const { setServices, updateServices } = require('../../../controllers/application/verify/servicesController')
|
||||
const router = express.Router()
|
||||
|
||||
router.post('/profile_image', setProfileImage)
|
||||
router.patch('/profile_image', [blockCheck], [auth], updateProfileImage)
|
||||
router.post('/expertise', setExpertise)
|
||||
router.patch('/expertise', [blockCheck], [auth], updateExpertise)
|
||||
router.post('/gender', setGender)
|
||||
router.post('/sizes', setSizes)
|
||||
router.patch('/sizes', [blockCheck], [auth], updateSizes)
|
||||
router.post('/services', setServices)
|
||||
router.patch('/services', [blockCheck], [auth], updateServices)
|
||||
router.post('/colors', setColors)
|
||||
router.patch('/colors', [blockCheck], [auth], updateColors)
|
||||
router.post('/cooperation_type', setCooperationType)
|
||||
router.patch('/cooperation_type', [blockCheck], [auth], updateCooperationType)
|
||||
router.post('/conversation', setConversation)
|
||||
router.patch('/conversation', [blockCheck], [auth], updateConversation)
|
||||
router.post('/auth', saveAuthValidationRules(), saveAuth)
|
||||
router.patch('/shaba', [blockCheck], [auth], updateShaba)
|
||||
router.post('/address', setAddress)
|
||||
router.patch('/address', [blockCheck], [auth], updateAddress)
|
||||
router.post('/national_card_image', setNationalCardImage)
|
||||
router.post('/complete', completeRegistration)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,6 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getVersion } = require('../../../controllers/application/version/versionController')
|
||||
const router = express.Router()
|
||||
router.get('/', getVersion)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,8 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getProjects, getSingleProject, getProjectRequests } = require('../../../controllers/application/workroom/workroomController')
|
||||
const router = express.Router()
|
||||
router.get('/', getProjects)
|
||||
router.get('/get', getSingleProject)
|
||||
router.get('/get-requests/:projectId', getProjectRequests)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,79 +0,0 @@
|
||||
const registerRouter = require('./application/register')
|
||||
const loginRouter = require('./application/login')
|
||||
const verifyRouter = require('./application/verify')
|
||||
const provincesRouter = require('./application/provinces')
|
||||
const citiesRouter = require('./application/city')
|
||||
const projectsRouter = require('./application/projects')
|
||||
const usersRouter = require('./application/users')
|
||||
const postsRouter = require('./application/posts')
|
||||
const expertiseRouter = require('./application/expertise')
|
||||
const profileRouter = require('./application/profile')
|
||||
const workroomRouter = require('./application/workroom')
|
||||
const messagesRouter = require('./application/messages')
|
||||
const searchRouter = require('./application/search')
|
||||
const searchRouterWeb = require('./application/search-web')
|
||||
const financialRouter = require('./application/financial')
|
||||
const notificationRouter = require('./application/notification')
|
||||
const ticketsRouter = require('./application/tickets')
|
||||
const advertisingRouter = require('./application/advertising')
|
||||
const settingsRouter = require('./application/settings')
|
||||
const versionRouter = require('./application/version')
|
||||
const offerRouter = require('./application/offers')
|
||||
const academyRoute = require('./application/academy')
|
||||
const storiesRouter = require('./application/stories')
|
||||
|
||||
const projectsPanelRouter = require('./panel/projects')
|
||||
const postsPanelRouter = require('./panel/posts')
|
||||
const expertisePanelRouter = require('./panel/expertise')
|
||||
const loginPanelRouter = require('./panel/login')
|
||||
const usersPanelRouter = require('./panel/users')
|
||||
const ticketsPanelRouter = require('./panel/tickets')
|
||||
const financialPanelRouter = require('./panel/financial')
|
||||
const commentsPanelRouter = require('./panel/comments')
|
||||
const advertisingPanelRouter = require('./panel/advertising')
|
||||
const shopsPanelRouter = require('./panel/shops')
|
||||
const settingsPanelRouter = require('./panel/settings')
|
||||
const versionPanelRouter = require('./panel/version')
|
||||
|
||||
const auth = require('../middlewares/auth')
|
||||
const adminAuth = require('../middlewares/adminAuth')
|
||||
module.exports = (app) => {
|
||||
// App
|
||||
app.use('/api/v1/register', registerRouter)
|
||||
app.use('/api/v1/login', loginRouter)
|
||||
app.use('/api/v1/verify', [auth], verifyRouter)
|
||||
app.use('/api/v1/provinces', provincesRouter)
|
||||
app.use('/api/v1/cities', citiesRouter)
|
||||
app.use('/api/v1/projects', projectsRouter)
|
||||
app.use('/api/v1/users', usersRouter)
|
||||
app.use('/api/v1/posts', postsRouter)
|
||||
app.use('/api/v1/expertise', expertiseRouter)
|
||||
app.use('/api/v1/profile', profileRouter)
|
||||
app.use('/api/v1/workroom', [auth], workroomRouter)
|
||||
app.use('/api/v1/messages', [auth], messagesRouter)
|
||||
app.use('/api/v1/notifications', [auth], notificationRouter)
|
||||
app.use('/api/v1/search', [auth], searchRouter)
|
||||
app.use('/api/v1/search-web', searchRouterWeb)
|
||||
app.use('/api/v1/financial', [auth], financialRouter)
|
||||
app.use('/api/v1/tickets', [auth], ticketsRouter)
|
||||
app.use('/api/v1/offers', offerRouter)
|
||||
app.use('/api/v1/advertising', advertisingRouter)
|
||||
app.use('/api/v1/settings', settingsRouter)
|
||||
app.use('/api/v1/version', versionRouter)
|
||||
app.use('/api/v1/academy', academyRoute)
|
||||
app.use('/api/v1/stories', storiesRouter)
|
||||
|
||||
// Panel
|
||||
app.use('/api/v1/panel/login', loginPanelRouter)
|
||||
app.use('/api/v1/panel/users', usersPanelRouter)
|
||||
app.use('/api/v1/panel/projects', [adminAuth], projectsPanelRouter)
|
||||
app.use('/api/v1/panel/posts', [adminAuth], postsPanelRouter)
|
||||
app.use('/api/v1/panel/expertise', [adminAuth], expertisePanelRouter)
|
||||
app.use('/api/v1/panel/tickets', [adminAuth], ticketsPanelRouter)
|
||||
app.use('/api/v1/panel/financial', [adminAuth], financialPanelRouter)
|
||||
app.use('/api/v1/panel/comments', [adminAuth], commentsPanelRouter)
|
||||
app.use('/api/v1/panel/advertising', [adminAuth], advertisingPanelRouter)
|
||||
app.use('/api/v1/panel/shops', [adminAuth], shopsPanelRouter)
|
||||
app.use('/api/v1/panel/settings', [adminAuth], settingsPanelRouter)
|
||||
app.use('/api/v1/panel/version', [adminAuth], versionPanelRouter)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getAdvertisings, getSingleAdvertising, acceptAdvertising, rejectAdvertising, getAdvertisingCategories, getAdvertisingCategoryById,createAdvertisingCategory , deleteAdvertisingCategory } = require('../../../controllers/panel/advertising/advertisingController')
|
||||
const { getAdvertisingComments, getAdvertisingCommentDetail, acceptAdvertisingComment } = require('../../../controllers/panel/comments/advertisingCommentController')
|
||||
const router = express.Router()
|
||||
router.get('/', getAdvertisings)
|
||||
router.get('/detail', getSingleAdvertising)
|
||||
router.post('/accept', acceptAdvertising)
|
||||
router.post('/reject', rejectAdvertising)
|
||||
|
||||
router.get('/comments', getAdvertisingComments)
|
||||
router.get('/comments/detail', getAdvertisingCommentDetail)
|
||||
router.post('/comments/accept', acceptAdvertisingComment)
|
||||
|
||||
router.post('/advertising-categories', createAdvertisingCategory)
|
||||
router.delete('/advertising-categories/:id', deleteAdvertisingCategory)
|
||||
// لیست دستهبندیها (پشتیبانی از query params)
|
||||
router.get('/advertising-categories', getAdvertisingCategories);
|
||||
|
||||
// گرفتن یک دستهبندی با id
|
||||
router.get('/advertising-categories/:id', getAdvertisingCategoryById);
|
||||
|
||||
module.exports = router
|
||||
@@ -1,8 +0,0 @@
|
||||
const express = require('express')
|
||||
const { acceptComment, getComments, getCommentDetail } = require('../../../controllers/panel/comments/commentController')
|
||||
const router = express.Router()
|
||||
router.get('/', getComments)
|
||||
router.get('/detail', getCommentDetail)
|
||||
router.post('/accept', acceptComment)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,9 +0,0 @@
|
||||
const express = require('express')
|
||||
const { createExpertise, createSubExpertise, getExpertise, editExpertise } = require('../../../controllers/panel/expertise/expertiseController')
|
||||
const router = express.Router()
|
||||
router.get('/', getExpertise)
|
||||
router.post('/create', createExpertise)
|
||||
router.post('/create/sub', createSubExpertise)
|
||||
router.put('/update/:id', editExpertise)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,7 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getFinancial, getFinancialDetail } = require('../../../controllers/panel/financial/financialController')
|
||||
const router = express.Router()
|
||||
router.get('/', getFinancial)
|
||||
router.get('/detail', getFinancialDetail)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,8 +0,0 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { adminLogin, changePassword } = require('../../../controllers/panel/login/loginController')
|
||||
const adminAuth = require('../../../middlewares/adminAuth')
|
||||
router.post('/', adminLogin)
|
||||
router.post('/change-password', [adminAuth], changePassword)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,8 +0,0 @@
|
||||
const express = require('express')
|
||||
const { acceptPost, getPosts, getPostDetail } = require('../../../controllers/panel/posts/postController')
|
||||
const router = express.Router()
|
||||
router.get('/', getPosts)
|
||||
router.get('/detail', getPostDetail)
|
||||
router.post('/accept', acceptPost)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,11 +0,0 @@
|
||||
const express = require('express')
|
||||
const { createProjectTypes, getProjectTypes } = require('../../../controllers/panel/projects/projectTypesController')
|
||||
const { getProjects, getProjectDetails, acceptProject, rejectProject } = require('../../../controllers/panel/projects/projectController')
|
||||
const router = express.Router()
|
||||
router.get('/', getProjects)
|
||||
router.get('/detail', getProjectDetails)
|
||||
router.post('/accept', acceptProject)
|
||||
router.post('/reject', rejectProject)
|
||||
router.get('/types', getProjectTypes)
|
||||
router.post('/types/create', createProjectTypes)
|
||||
module.exports = router
|
||||
@@ -1,7 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getSetting, updateSetting } = require('../../../controllers/panel/settings/settingsController')
|
||||
const router = express.Router()
|
||||
router.get('/:key', getSetting)
|
||||
router.put('/:key', updateSetting)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,8 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getShops, getShopDetail, updateShop } = require('../../../controllers/panel/shops/shopsController')
|
||||
const router = express.Router()
|
||||
router.get('/', getShops)
|
||||
router.get('/detail', getShopDetail)
|
||||
router.put('/update', updateShop)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,9 +0,0 @@
|
||||
const express = require('express')
|
||||
const { addAdminMessageToTicket, getAllTickets, getTicketMessages, closeTicket } = require('../../../controllers/panel/tickets/ticketsController')
|
||||
const router = express.Router()
|
||||
router.get('/', getAllTickets)
|
||||
router.get('/messages', getTicketMessages)
|
||||
router.post('/message', addAdminMessageToTicket)
|
||||
router.post('/close', closeTicket)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,12 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getUsers, getUserDetail,changePass, updateUser, verifyUser, rejectUser, updateUserStatus } = require('../../../controllers/panel/users/usersController')
|
||||
const router = express.Router()
|
||||
router.get('/', getUsers)
|
||||
router.post('/change-password', changePass)
|
||||
router.get('/detail', getUserDetail)
|
||||
router.put('/update', updateUser)
|
||||
router.post('/verify', verifyUser)
|
||||
router.post('/reject', rejectUser)
|
||||
router.post('/update-status', updateUserStatus);
|
||||
|
||||
module.exports = router
|
||||
@@ -1,7 +0,0 @@
|
||||
const express = require('express')
|
||||
const { getVersion, upsertVersion } = require('../../../controllers/panel/version/versionController')
|
||||
const router = express.Router()
|
||||
router.get('/', getVersion)
|
||||
router.post('/', upsertVersion)
|
||||
|
||||
module.exports = router
|
||||
Reference in New Issue
Block a user