Mark-Lasfar commited on
Commit
c0a8271
·
1 Parent(s): 41e454e

Add GraphQL

Browse files
Files changed (2) hide show
  1. graphql/resolvers.js +7 -3
  2. server.js +510 -6
graphql/resolvers.js CHANGED
@@ -36,7 +36,8 @@ const {
36
  PlatformPaymentMethod,
37
  SubscriptionPlan,
38
  StoreSettings,
39
- StoreTheme
 
40
  } = require('../server');
41
 
42
  // دوال مساعدة
@@ -8096,7 +8097,8 @@ const Mutation = {
8096
  if (!userId) return [];
8097
 
8098
  try {
8099
- const views = await ProfileView.find({ targetUserId: userId })
 
8100
  .sort({ viewedAt: -1 })
8101
  .limit(10)
8102
  .lean();
@@ -8160,12 +8162,14 @@ const Mutation = {
8160
  }
8161
 
8162
  try {
 
8163
  const analytics = await ProfileAnalytics.findOne({ userId });
8164
 
8165
  const weekAgo = new Date();
8166
  weekAgo.setDate(weekAgo.getDate() - 7);
8167
 
8168
- const weeklyViews = await ProfileView.countDocuments({
 
8169
  targetUserId: userId,
8170
  viewedAt: { $gte: weekAgo }
8171
  });
 
36
  PlatformPaymentMethod,
37
  SubscriptionPlan,
38
  StoreSettings,
39
+ StoreTheme,
40
+ AlsoViewed
41
  } = require('../server');
42
 
43
  // دوال مساعدة
 
8097
  if (!userId) return [];
8098
 
8099
  try {
8100
+ // استخدم AlsoViewed بدلاً من ProfileView
8101
+ const views = await AlsoViewed.find({ targetUserId: userId })
8102
  .sort({ viewedAt: -1 })
8103
  .limit(10)
8104
  .lean();
 
8162
  }
8163
 
8164
  try {
8165
+ // ✅ استخدم AlsoViewed بدلاً من ProfileView
8166
  const analytics = await ProfileAnalytics.findOne({ userId });
8167
 
8168
  const weekAgo = new Date();
8169
  weekAgo.setDate(weekAgo.getDate() - 7);
8170
 
8171
+ // استخدم AlsoViewed بدلاً من ProfileView
8172
+ const weeklyViews = await AlsoViewed.countDocuments({
8173
  targetUserId: userId,
8174
  viewedAt: { $gte: weekAgo }
8175
  });
server.js CHANGED
@@ -376,6 +376,7 @@ const excludedPaths = [
376
  '/api/subscription/check',
377
  '/api/subscription/renew',
378
  '/api/subscription/cancel',
 
379
  '/api/users/:userId',
380
  '/api/admin/stores/:userId/toggle',
381
  '/api/admin/stores/:userId',
@@ -4318,6 +4319,38 @@ const userSchema = new mongoose.Schema({
4318
 
4319
  storeEnabled: { type: Boolean, default: false },
4320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4321
  // ✅ إعدادات إظهار/إخفاء الأقسام
4322
  sectionVisibility: {
4323
  about: true,
@@ -8118,7 +8151,6 @@ app.get('/api/trending/posts', authenticateToken, async (req, res) => {
8118
  });
8119
 
8120
 
8121
-
8122
  app.put('/api/users/:userId', authenticateToken, isAdmin, [
8123
  param('userId').isMongoId().withMessage('Invalid user ID'),
8124
  body('role').isIn(['User', 'Admin']).withMessage('Role must be either User or Admin')
@@ -9372,7 +9404,7 @@ app.get('/api/profile/me', authenticateToken, async (req, res) => {
9372
 
9373
  } catch (error) {
9374
  logger.error(`Error fetching profile/me: ${error.message}`);
9375
- res.status(500).json({ error: 'خطأ في استرجاع الملف الشخصي' });
9376
  }
9377
  });
9378
 
@@ -10152,6 +10184,100 @@ app.get('/api/profile/:nickname', async (req, res) => {
10152
  }
10153
  });
10154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10155
 
10156
  // ============================================
10157
  // GET /api/users/:userId - جلب بيانات مستخدم معين للوسام
@@ -19210,7 +19336,6 @@ app.post('/api/analytics/track-view', async (req, res) => {
19210
  );
19211
  } else {
19212
  // ✅ زائر (غير مسجل) - نستخدم IP
19213
- // نتحقق إذا كان هذا الـ IP شاهد خلال آخر 24 ساعة
19214
  const existingView = await AlsoViewed.findOne({
19215
  targetUserId,
19216
  viewerIp: viewerIp,
@@ -19227,7 +19352,24 @@ app.post('/api/analytics/track-view', async (req, res) => {
19227
  }
19228
  }
19229
 
19230
- res.json({ success: true });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19231
 
19232
  } catch (error) {
19233
  logger.error(`Error tracking profile view: ${error.message}`);
@@ -19235,8 +19377,6 @@ app.post('/api/analytics/track-view', async (req, res) => {
19235
  res.json({ success: true });
19236
  }
19237
  });
19238
-
19239
-
19240
  // ============================================
19241
  // POST /api/ratings - تقييم مستخدم
19242
  // ============================================
@@ -19662,6 +19802,370 @@ app.post('/api/websocket/token', authenticateToken, async (req, res) => {
19662
 
19663
 
19664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19665
 
19666
  // ============================================
19667
  // ============================================
 
376
  '/api/subscription/check',
377
  '/api/subscription/renew',
378
  '/api/subscription/cancel',
379
+ '/api/users/:userId/badges',
380
  '/api/users/:userId',
381
  '/api/admin/stores/:userId/toggle',
382
  '/api/admin/stores/:userId',
 
4319
 
4320
  storeEnabled: { type: Boolean, default: false },
4321
 
4322
+
4323
+ badges: {
4324
+ type: {
4325
+ // 🥇 برونزيات المشاهدات (Profile Views)
4326
+ views: {
4327
+ silver: { type: Boolean, default: false }, // 20 مشاهدة
4328
+ gold: { type: Boolean, default: false }, // 100 مشاهدة
4329
+ red: { type: Boolean, default: false }, // 200 مشاهدة
4330
+ },
4331
+ // ❤️ برونزيات الإعجابات (Post Likes)
4332
+ likes: {
4333
+ bronze: { type: Boolean, default: false }, // 10 إعجابات
4334
+ silver: { type: Boolean, default: false }, // 30 إعجاب
4335
+ gold: { type: Boolean, default: false }, // 60 إعجاب
4336
+ red: { type: Boolean, default: false }, // 150 إعجاب
4337
+ },
4338
+ // 💬 برونزيات التفاعل (Comments/Replies)
4339
+ interaction: {
4340
+ bronze: { type: Boolean, default: false }, // 10 ردود
4341
+ },
4342
+ unlockedAt: { type: Date, default: null },
4343
+ updatedAt: { type: Date, default: Date.now }
4344
+ },
4345
+ default: {
4346
+ views: { silver: false, gold: false, red: false },
4347
+ likes: { bronze: false, silver: false, gold: false, red: false },
4348
+ interaction: { bronze: false },
4349
+ unlockedAt: null,
4350
+ updatedAt: Date.now
4351
+ }
4352
+ },
4353
+
4354
  // ✅ إعدادات إظهار/إخفاء الأقسام
4355
  sectionVisibility: {
4356
  about: true,
 
8151
  });
8152
 
8153
 
 
8154
  app.put('/api/users/:userId', authenticateToken, isAdmin, [
8155
  param('userId').isMongoId().withMessage('Invalid user ID'),
8156
  body('role').isIn(['User', 'Admin']).withMessage('Role must be either User or Admin')
 
9404
 
9405
  } catch (error) {
9406
  logger.error(`Error fetching profile/me: ${error.message}`);
9407
+ res.status(500).json({ error: 'Profile retrieval error' });
9408
  }
9409
  });
9410
 
 
10184
  }
10185
  });
10186
 
10187
+ // ============================================
10188
+ // GET /api/users/:userId/badges - جلب البرونزيات
10189
+ // ============================================
10190
+ app.get('/api/users/:userId/badges', async (req, res) => {
10191
+ try {
10192
+ const { userId } = req.params;
10193
+
10194
+ // ✅ جلب جميع البيانات
10195
+ const badges = await getUserBadges(userId);
10196
+ const viewCount = await getUniqueViewsCount(userId);
10197
+ const likeCount = await getTotalPostLikes(userId);
10198
+ const replyCount = await getTotalUserReplies(userId);
10199
+
10200
+ // ✅ حساب التالي
10201
+ let nextBadge = null;
10202
+ let nextThreshold = null;
10203
+ let progress = 100;
10204
+
10205
+ // ✅ برونزيات المشاهدات
10206
+ if (!badges.views.silver && viewCount < VIEW_BADGE_THRESHOLDS.SILVER) {
10207
+ nextBadge = 'Views Silver';
10208
+ nextThreshold = VIEW_BADGE_THRESHOLDS.SILVER;
10209
+ progress = (viewCount / VIEW_BADGE_THRESHOLDS.SILVER) * 100;
10210
+ } else if (!badges.views.gold && viewCount < VIEW_BADGE_THRESHOLDS.GOLD) {
10211
+ nextBadge = 'Views Gold';
10212
+ nextThreshold = VIEW_BADGE_THRESHOLDS.GOLD;
10213
+ progress = (viewCount / VIEW_BADGE_THRESHOLDS.GOLD) * 100;
10214
+ } else if (!badges.views.red && viewCount < VIEW_BADGE_THRESHOLDS.RED) {
10215
+ nextBadge = 'Views Red';
10216
+ nextThreshold = VIEW_BADGE_THRESHOLDS.RED;
10217
+ progress = (viewCount / VIEW_BADGE_THRESHOLDS.RED) * 100;
10218
+ }
10219
+
10220
+ // ✅ برونزيات الإعجابات (إذا لم يكن هناك برونزية مشاهدات تالية)
10221
+ if (!nextBadge) {
10222
+ if (!badges.likes.bronze && likeCount < LIKE_BADGE_THRESHOLDS.BRONZE) {
10223
+ nextBadge = 'Likes Bronze';
10224
+ nextThreshold = LIKE_BADGE_THRESHOLDS.BRONZE;
10225
+ progress = (likeCount / LIKE_BADGE_THRESHOLDS.BRONZE) * 100;
10226
+ } else if (!badges.likes.red && likeCount < LIKE_BADGE_THRESHOLDS.RED) {
10227
+ nextBadge = 'Likes Red';
10228
+ nextThreshold = LIKE_BADGE_THRESHOLDS.RED;
10229
+ progress = (likeCount / LIKE_BADGE_THRESHOLDS.RED) * 100;
10230
+ } else if (!badges.likes.silver && likeCount < LIKE_BADGE_THRESHOLDS.SILVER) {
10231
+ nextBadge = 'Likes Silver';
10232
+ nextThreshold = LIKE_BADGE_THRESHOLDS.SILVER;
10233
+ progress = (likeCount / LIKE_BADGE_THRESHOLDS.SILVER) * 100;
10234
+ } else if (!badges.likes.gold && likeCount < LIKE_BADGE_THRESHOLDS.GOLD) {
10235
+ nextBadge = 'Likes Gold';
10236
+ nextThreshold = LIKE_BADGE_THRESHOLDS.GOLD;
10237
+ progress = (likeCount / LIKE_BADGE_THRESHOLDS.GOLD) * 100;
10238
+ }
10239
+ }
10240
+
10241
+ // ✅ برونزيات التفاعل (إذا لم يكن هناك برونزية تالية)
10242
+ if (!nextBadge) {
10243
+ if (!badges.interaction.bronze && replyCount < INTERACTION_BADGE_THRESHOLDS.BRONZE) {
10244
+ nextBadge = 'Interaction Bronze';
10245
+ nextThreshold = INTERACTION_BADGE_THRESHOLDS.BRONZE;
10246
+ progress = (replyCount / INTERACTION_BADGE_THRESHOLDS.BRONZE) * 100;
10247
+ }
10248
+ }
10249
+
10250
+ // ✅ إذا اكتمل كل شيء
10251
+ if (!nextBadge) {
10252
+ nextBadge = 'All badges collected! 🎉';
10253
+ nextThreshold = null;
10254
+ progress = 100;
10255
+ }
10256
+
10257
+ res.json({
10258
+ userId,
10259
+ viewCount,
10260
+ likeCount,
10261
+ replyCount,
10262
+ badges,
10263
+ thresholds: {
10264
+ views: VIEW_BADGE_THRESHOLDS,
10265
+ likes: LIKE_BADGE_THRESHOLDS,
10266
+ interaction: INTERACTION_BADGE_THRESHOLDS
10267
+ },
10268
+ nextBadge,
10269
+ nextThreshold,
10270
+ progress: Math.min(progress, 100),
10271
+ totalBadges: badges.totalCount || 0
10272
+ });
10273
+
10274
+ } catch (error) {
10275
+ console.error('Error getting badges:', error);
10276
+ res.status(500).json({ error: 'Failed to get badges' });
10277
+ }
10278
+ });
10279
+
10280
+
10281
 
10282
  // ============================================
10283
  // GET /api/users/:userId - جلب بيانات مستخدم معين للوسام
 
19336
  );
19337
  } else {
19338
  // ✅ زائر (غير مسجل) - نستخدم IP
 
19339
  const existingView = await AlsoViewed.findOne({
19340
  targetUserId,
19341
  viewerIp: viewerIp,
 
19352
  }
19353
  }
19354
 
19355
+ // ============================
19356
+ // 🏆 3. تحديث البرونزيات للمستخدم المستهدف
19357
+ // ============================
19358
+ const badgeResult = await updateUserBadges(targetUserId);
19359
+
19360
+ // ✅ الرد مع بيانات البرونزيات
19361
+ res.json({
19362
+ success: true,
19363
+ message: 'View tracked successfully',
19364
+ badges: badgeResult,
19365
+ // ✅ أضف الإحصائيات للـ Frontend
19366
+ stats: {
19367
+ viewCount: badgeResult?.viewCount || 0,
19368
+ likeCount: badgeResult?.likeCount || 0,
19369
+ replyCount: badgeResult?.replyCount || 0,
19370
+ totalBadges: badgeResult?.totalBadgeCount || 0
19371
+ }
19372
+ });
19373
 
19374
  } catch (error) {
19375
  logger.error(`Error tracking profile view: ${error.message}`);
 
19377
  res.json({ success: true });
19378
  }
19379
  });
 
 
19380
  // ============================================
19381
  // POST /api/ratings - تقييم مستخدم
19382
  // ============================================
 
19802
 
19803
 
19804
 
19805
+ // ============================================
19806
+ // ============================================
19807
+ // 🥇 برونزيات
19808
+ // ============================================
19809
+ // ============================================
19810
+ // ============================================
19811
+ // 🥇 برونزيات المشاهدات (Profile Views)
19812
+ // ============================================
19813
+ const VIEW_BADGE_THRESHOLDS = {
19814
+ SILVER: 20,
19815
+ GOLD: 100,
19816
+ RED: 200
19817
+ };
19818
+
19819
+ const VIEW_BADGE_ICONS = {
19820
+ silver: '/assets/img/fire.svg',
19821
+ gold: '/assets/img/firegolden.svg',
19822
+ red: '/assets/img/firered.svg'
19823
+ };
19824
+
19825
+ // ============================================
19826
+ // ❤️ برونزيات الإعجابات (Post Likes)
19827
+ // ============================================
19828
+ const LIKE_BADGE_THRESHOLDS = {
19829
+ BRONZE: 10,
19830
+ RED: 128,
19831
+ SILVER: 512,
19832
+ GOLD: 4096
19833
+ };
19834
+
19835
+ const LIKE_BADGE_ICONS = {
19836
+ bronze: '/assets/img/StarStruck_SkinTone1.png',
19837
+ red: '/assets/img/StarStruck_Bronze.png',
19838
+ silver: '/assets/img/StarStruck_Silver.png',
19839
+ gold: '/assets/img/StarStruck_Gold.png'
19840
+ };
19841
+
19842
+ // ============================================
19843
+ // 💬 برونزيات التفاعل (Comments/Replies)
19844
+ // ============================================
19845
+ const INTERACTION_BADGE_THRESHOLDS = {
19846
+ BRONZE: 10 // 10 ردود على تعليقات الآخرين
19847
+ };
19848
+
19849
+ const INTERACTION_BADGE_ICONS = {
19850
+ bronze: '/assets/img/QuickDraw_SkinTone1.png'
19851
+ };
19852
+
19853
+ /**
19854
+ * حساب عدد المشاهدات الفريدة للمستخدم
19855
+ */
19856
+ async function getUniqueViewsCount(userId) {
19857
+ const analytics = await ProfileAnalytics.findOne({ userId });
19858
+ if (!analytics || !analytics.profileViews) return 0;
19859
+
19860
+ // ✅ عدد المشاهدين الفريدين (منع تكرار المشاهدات)
19861
+ const uniqueViewers = new Set();
19862
+ analytics.profileViews.forEach(view => {
19863
+ if (view.viewerId) {
19864
+ uniqueViewers.add(view.viewerId.toString());
19865
+ } else if (view.viewerIp) {
19866
+ uniqueViewers.add(view.viewerIp);
19867
+ }
19868
+ });
19869
+
19870
+ return uniqueViewers.size;
19871
+ }
19872
+
19873
+ /**
19874
+ * حساب عدد الإعجابات التي حصلت عليها منشورات المستخدم
19875
+ */
19876
+ async function getTotalPostLikes(userId) {
19877
+ const posts = await Post.find({ userId }).select('likes').lean();
19878
+ let totalLikes = 0;
19879
+ posts.forEach(post => {
19880
+ totalLikes += post.likes?.length || 0;
19881
+ });
19882
+ return totalLikes;
19883
+ }
19884
+
19885
+ /**
19886
+ * حساب عدد ردود المستخدم على تعليقات الآخرين
19887
+ * (تشمل ردود على تعليقات المنشورات + ردود على تعليقات المشاريع)
19888
+ */
19889
+ async function getTotalUserReplies(userId) {
19890
+ let totalReplies = 0;
19891
+
19892
+ try {
19893
+ // ✅ 1. ردود المستخدم على تعليقات الآخرين في المنشورات (Post.comments.replies)
19894
+ const posts = await Post.find({}).select('comments').lean();
19895
+ posts.forEach(post => {
19896
+ post.comments?.forEach(comment => {
19897
+ comment.replies?.forEach(reply => {
19898
+ if (reply.userId && reply.userId.toString() === userId.toString()) {
19899
+ totalReplies++;
19900
+ }
19901
+ });
19902
+ });
19903
+ });
19904
+
19905
+ // ✅ 2. ردود المستخدم على تعليقات الآخرين في المشاريع (Comment.replies)
19906
+ const projectComments = await Comment.find({}).select('replies').lean();
19907
+ projectComments.forEach(comment => {
19908
+ comment.replies?.forEach(reply => {
19909
+ if (reply.userId && reply.userId.toString() === userId.toString()) {
19910
+ totalReplies++;
19911
+ }
19912
+ });
19913
+ });
19914
+
19915
+ } catch (error) {
19916
+ console.error('Error calculating total user replies:', error);
19917
+ }
19918
+
19919
+ return totalReplies;
19920
+ }
19921
+
19922
+ /**
19923
+ * حساب جميع البرونزيات المستحقة
19924
+ */
19925
+ function calculateAllBadges(viewCount, likeCount, replyCount) {
19926
+ const result = {
19927
+ views: { silver: false, gold: false, red: false, count: 0 },
19928
+ likes: { bronze: false, silver: false, gold: false, red: false, count: 0 },
19929
+ interaction: { bronze: false, count: 0 }
19930
+ };
19931
+
19932
+ // ✅ برونزيات المشاهدات
19933
+ if (viewCount >= VIEW_BADGE_THRESHOLDS.RED) {
19934
+ result.views.red = true;
19935
+ result.views.gold = true;
19936
+ result.views.silver = true;
19937
+ result.views.count = 3;
19938
+ } else if (viewCount >= VIEW_BADGE_THRESHOLDS.GOLD) {
19939
+ result.views.gold = true;
19940
+ result.views.silver = true;
19941
+ result.views.count = 2;
19942
+ } else if (viewCount >= VIEW_BADGE_THRESHOLDS.SILVER) {
19943
+ result.views.silver = true;
19944
+ result.views.count = 1;
19945
+ }
19946
+
19947
+ // ✅ برونزيات الإعجابات
19948
+ if (likeCount >= LIKE_BADGE_THRESHOLDS.GOLD) {
19949
+ result.likes.gold = true;
19950
+ result.likes.silver = true;
19951
+ result.likes.red = true;
19952
+ result.likes.bronze = true;
19953
+ result.likes.count = 4;
19954
+ } else if (likeCount >= LIKE_BADGE_THRESHOLDS.SILVER) {
19955
+ result.likes.silver = true;
19956
+ result.likes.red = true;
19957
+ result.likes.bronze = true;
19958
+ result.likes.count = 3;
19959
+ } else if (likeCount >= LIKE_BADGE_THRESHOLDS.RED) {
19960
+ result.likes.red = true;
19961
+ result.likes.bronze = true;
19962
+ result.likes.count = 2;
19963
+ } else if (likeCount >= LIKE_BADGE_THRESHOLDS.BRONZE) {
19964
+ result.likes.bronze = true;
19965
+ result.likes.count = 1;
19966
+ }
19967
+
19968
+ // ✅ برونزيات التفاعل
19969
+ if (replyCount >= INTERACTION_BADGE_THRESHOLDS.BRONZE) {
19970
+ result.interaction.bronze = true;
19971
+ result.interaction.count = 1;
19972
+ }
19973
+
19974
+ return result;
19975
+ }
19976
+
19977
+ /**
19978
+ * تحديث البرونزيات للمستخدم
19979
+ */
19980
+ async function updateUserBadges(userId) {
19981
+ try {
19982
+ // ✅ جلب جميع البيانات
19983
+ const viewCount = await getUniqueViewsCount(userId);
19984
+ const likeCount = await getTotalPostLikes(userId);
19985
+ const replyCount = await getTotalUserReplies(userId);
19986
+
19987
+ const badges = calculateAllBadges(viewCount, likeCount, replyCount);
19988
+
19989
+ const user = await User.findById(userId);
19990
+ if (!user) return null;
19991
+
19992
+ // ✅ تهيئة البرونزيات
19993
+ if (!user.profile.badges) {
19994
+ user.profile.badges = {
19995
+ views: { silver: false, gold: false, red: false },
19996
+ likes: { bronze: false, silver: false, gold: false, red: false },
19997
+ interaction: { bronze: false },
19998
+ unlockedAt: null,
19999
+ updatedAt: Date.now()
20000
+ };
20001
+ }
20002
+
20003
+ let newBadgeUnlocked = false;
20004
+ let unlockedBadges = [];
20005
+
20006
+ // ✅ تحقق من برونزيات المشاهدات
20007
+ const viewBadgeMap = [
20008
+ { key: 'silver', value: badges.views.silver, existing: user.profile.badges.views.silver, name: 'Views Silver', icon: '🥈' },
20009
+ { key: 'gold', value: badges.views.gold, existing: user.profile.badges.views.gold, name: 'Views Gold', icon: '🥇' },
20010
+ { key: 'red', value: badges.views.red, existing: user.profile.badges.views.red, name: 'Views Red', icon: '🔥' }
20011
+ ];
20012
+
20013
+ viewBadgeMap.forEach(b => {
20014
+ if (b.value && !b.existing) {
20015
+ user.profile.badges.views[b.key] = true;
20016
+ newBadgeUnlocked = true;
20017
+ unlockedBadges.push(`${b.icon} ${b.name}`);
20018
+ }
20019
+ });
20020
+
20021
+ // ✅ تحقق من برونزيات الإعجابات
20022
+ const likeBadgeMap = [
20023
+ { key: 'bronze', value: badges.likes.bronze, existing: user.profile.badges.likes.bronze, name: 'Likes Bronze', icon: '🟤' },
20024
+ { key: 'red', value: badges.likes.red, existing: user.profile.badges.likes.red, name: 'Likes Red', icon: '🔴' },
20025
+ { key: 'silver', value: badges.likes.silver, existing: user.profile.badges.likes.silver, name: 'Likes Silver', icon: '🥈' },
20026
+ { key: 'gold', value: badges.likes.gold, existing: user.profile.badges.likes.gold, name: 'Likes Gold', icon: '🥇' }
20027
+ ];
20028
+
20029
+ likeBadgeMap.forEach(b => {
20030
+ if (b.value && !b.existing) {
20031
+ user.profile.badges.likes[b.key] = true;
20032
+ newBadgeUnlocked = true;
20033
+ unlockedBadges.push(`${b.icon} ${b.name}`);
20034
+ }
20035
+ });
20036
+
20037
+ // ✅ تحقق من برونزيات التفاعل
20038
+ if (badges.interaction.bronze && !user.profile.badges.interaction.bronze) {
20039
+ user.profile.badges.interaction.bronze = true;
20040
+ newBadgeUnlocked = true;
20041
+ unlockedBadges.push('💬 Interaction Bronze');
20042
+ }
20043
+
20044
+ user.profile.badges.updatedAt = Date.now();
20045
+
20046
+ if (newBadgeUnlocked) {
20047
+ user.profile.badges.unlockedAt = Date.now();
20048
+ const badgeList = unlockedBadges.join(', ');
20049
+
20050
+ // ✅ 1. إشعار في User.notifications (جوه الـ User)
20051
+ user.notifications = user.notifications || [];
20052
+ user.notifications.push({
20053
+ message: `🎉 Congratulations! You unlocked new badges: ${badgeList}!`,
20054
+ type: 'success',
20055
+ link: '/profile/me',
20056
+ read: false,
20057
+ createdAt: new Date(),
20058
+ metadata: {
20059
+ badges: unlockedBadges,
20060
+ viewCount,
20061
+ likeCount,
20062
+ replyCount
20063
+ }
20064
+ });
20065
+
20066
+ // ✅ 2. إشعار في Notification model (منفصل)
20067
+ try {
20068
+ await Notification.create({
20069
+ userId: userId,
20070
+ type: 'system',
20071
+ actorId: userId,
20072
+ actorName: 'System',
20073
+ content: `🎉 You unlocked ${unlockedBadges.length} new badge(s): ${badgeList}!`,
20074
+ read: false,
20075
+ createdAt: new Date(),
20076
+ metadata: {
20077
+ badges: unlockedBadges,
20078
+ viewCount,
20079
+ likeCount,
20080
+ replyCount
20081
+ }
20082
+ });
20083
+ } catch (notifError) {
20084
+ console.error('Error creating notification in Notification model:', notifError);
20085
+ }
20086
+
20087
+ console.log(`🏆 Badges unlocked for user ${userId}: ${badgeList}`);
20088
+ }
20089
+
20090
+ await user.save();
20091
+
20092
+ return {
20093
+ badges: user.profile.badges,
20094
+ viewCount,
20095
+ likeCount,
20096
+ replyCount,
20097
+ newBadgeUnlocked,
20098
+ unlockedBadges,
20099
+ totalBadgeCount: badges.views.count + badges.likes.count + badges.interaction.count
20100
+ };
20101
+
20102
+ } catch (error) {
20103
+ console.error('Error updating badges:', error);
20104
+ return null;
20105
+ }
20106
+ }
20107
+
20108
+ /**
20109
+ * الحصول على أيقونة البرونزية المناسبة (للمشاهدات)
20110
+ */
20111
+ function getBadgeIcon(badges) {
20112
+ if (badges.red) return VIEW_BADGE_ICONS.red;
20113
+ if (badges.gold) return VIEW_BADGE_ICONS.gold;
20114
+ if (badges.silver) return VIEW_BADGE_ICONS.silver;
20115
+ return null;
20116
+ }
20117
+
20118
+ /**
20119
+ * جلب حالة البرونزيات للمستخدم
20120
+ */
20121
+ async function getUserBadges(userId) {
20122
+ try {
20123
+ const user = await User.findById(userId).select('profile.badges').lean();
20124
+ if (!user || !user.profile?.badges) {
20125
+ return {
20126
+ views: { silver: false, gold: false, red: false, count: 0 },
20127
+ likes: { bronze: false, silver: false, gold: false, red: false, count: 0 },
20128
+ interaction: { bronze: false, count: 0 },
20129
+ totalCount: 0
20130
+ };
20131
+ }
20132
+
20133
+ const badges = user.profile.badges;
20134
+
20135
+ let viewCount = 0;
20136
+ if (badges.views?.silver) viewCount++;
20137
+ if (badges.views?.gold) viewCount++;
20138
+ if (badges.views?.red) viewCount++;
20139
+
20140
+ let likeCount = 0;
20141
+ if (badges.likes?.bronze) likeCount++;
20142
+ if (badges.likes?.red) likeCount++;
20143
+ if (badges.likes?.silver) likeCount++;
20144
+ if (badges.likes?.gold) likeCount++;
20145
+
20146
+ let interactionCount = 0;
20147
+ if (badges.interaction?.bronze) interactionCount++;
20148
+
20149
+ const totalCount = viewCount + likeCount + interactionCount;
20150
+
20151
+ return {
20152
+ views: { ...badges.views, count: viewCount },
20153
+ likes: { ...badges.likes, count: likeCount },
20154
+ interaction: { ...badges.interaction, count: interactionCount },
20155
+ totalCount,
20156
+ unlockedAt: badges.unlockedAt || null
20157
+ };
20158
+ } catch (error) {
20159
+ console.error('Error getting badges:', error);
20160
+ return {
20161
+ views: { silver: false, gold: false, red: false, count: 0 },
20162
+ likes: { bronze: false, silver: false, gold: false, red: false, count: 0 },
20163
+ interaction: { bronze: false, count: 0 },
20164
+ totalCount: 0
20165
+ };
20166
+ }
20167
+ }
20168
+
20169
 
20170
  // ============================================
20171
  // ============================================