nodebb-plugin-niki-loyalty 1.2.9 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/library.js CHANGED
@@ -169,51 +169,69 @@ Plugin.onPostCreate = async function (data) {
169
169
  };
170
170
 
171
171
  // 4. BEĞENİ (Like Atma ve Alma) - Spam Korumalı + Debug Loglı
172
+ // NodeBB upvote hook'u { pid, uid, ... } formatında data gönderir (post nesnesi değil!)
172
173
  Plugin.onUpvote = async function (data) {
173
- console.log('[Niki-Loyalty] 👍 Upvote hook tetiklendi. Data:', JSON.stringify({
174
- voterUid: data.uid,
175
- postPid: data.post?.pid,
176
- postOwnerUid: data.post?.uid
177
- }));
174
+ console.log('[Niki-Loyalty] 👍 Upvote hook tetiklendi. Raw Data:', JSON.stringify(data));
175
+
176
+ // NodeBB bazen farklı formatlar gönderebilir, hepsini kontrol et
177
+ const pid = data.pid || (data.post && data.post.pid);
178
+ const voterUid = data.uid || (data.current && data.current.uid);
178
179
 
179
- const pid = data.post && data.post.pid;
180
180
  if (!pid) {
181
181
  console.log('[Niki-Loyalty] ⚠️ Post PID bulunamadı, işlem iptal.');
182
182
  return;
183
183
  }
184
184
 
185
+ if (!voterUid) {
186
+ console.log('[Niki-Loyalty] ⚠️ Voter UID bulunamadı, işlem iptal.');
187
+ return;
188
+ }
189
+
190
+ // Post sahibini bul (NodeBB upvote hook'u post sahibini göndermez!)
191
+ let postOwnerUid;
192
+ try {
193
+ postOwnerUid = await posts.getPostField(pid, 'uid');
194
+ console.log(`[Niki-Loyalty] Post sahibi bulundu: PID=${pid}, Owner UID=${postOwnerUid}`);
195
+ } catch (err) {
196
+ console.log('[Niki-Loyalty] ⚠️ Post sahibi bulunamadı:', err.message);
197
+ return;
198
+ }
199
+
200
+ if (!postOwnerUid) {
201
+ console.log('[Niki-Loyalty] ⚠️ Post sahibi UID boş, işlem iptal.');
202
+ return;
203
+ }
204
+
185
205
  const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
186
206
 
187
207
  // Like Atan Kazanır:
188
- if (data.uid) {
189
- const likeGivenKey = `niki:liked:${data.uid}:${today}`;
190
- const alreadyLiked = await db.isSetMember(likeGivenKey, pid.toString());
208
+ const likeGivenKey = `niki:liked:${voterUid}:${today}`;
209
+ const alreadyLiked = await db.isSetMember(likeGivenKey, pid.toString());
191
210
 
192
- console.log(`[Niki-Loyalty] Like Atan: UID=${data.uid}, PID=${pid}, Daha önce beğenmiş mi=${alreadyLiked}`);
211
+ console.log(`[Niki-Loyalty] Like Atan: UID=${voterUid}, PID=${pid}, Daha önce beğenmiş mi=${alreadyLiked}`);
193
212
 
194
- if (!alreadyLiked) {
195
- const result = await awardDailyAction(data.uid, 'like_given');
196
- console.log('[Niki-Loyalty] like_given sonuç:', result);
197
- await db.setAdd(likeGivenKey, pid.toString());
198
- await db.expire(likeGivenKey, 86400);
199
- }
213
+ if (!alreadyLiked) {
214
+ const result = await awardDailyAction(voterUid, 'like_given');
215
+ console.log('[Niki-Loyalty] like_given sonuç:', result);
216
+ await db.setAdd(likeGivenKey, pid.toString());
217
+ await db.expire(likeGivenKey, 86400);
200
218
  }
201
219
 
202
- // Like Alan Kazanır (Post sahibi):
203
- if (data.post && data.post.uid && data.post.uid !== data.uid) {
204
- const likeTakenKey = `niki:liked_taken:${data.post.uid}:${today}`;
220
+ // Like Alan Kazanır (Post sahibi - kendine beğeni atamaz):
221
+ if (postOwnerUid && postOwnerUid !== voterUid) {
222
+ const likeTakenKey = `niki:liked_taken:${postOwnerUid}:${today}`;
205
223
  const alreadyTaken = await db.isSetMember(likeTakenKey, pid.toString());
206
224
 
207
- console.log(`[Niki-Loyalty] Like Alan: UID=${data.post.uid}, PID=${pid}, Daha önce puan almış mı=${alreadyTaken}`);
225
+ console.log(`[Niki-Loyalty] Like Alan: UID=${postOwnerUid}, PID=${pid}, Daha önce puan almış mı=${alreadyTaken}`);
208
226
 
209
227
  if (!alreadyTaken) {
210
- const result = await awardDailyAction(data.post.uid, 'like_taken');
228
+ const result = await awardDailyAction(postOwnerUid, 'like_taken');
211
229
  console.log('[Niki-Loyalty] like_taken sonuç:', result);
212
230
  await db.setAdd(likeTakenKey, pid.toString());
213
231
  await db.expire(likeTakenKey, 86400);
214
232
  }
215
233
  } else {
216
- console.log('[Niki-Loyalty] ⚠️ Like alan kontrol edilemedi. Post owner:', data.post?.uid, 'Voter:', data.uid);
234
+ console.log('[Niki-Loyalty] ⚠️ Kullanıcı kendi postunu beğenmiş veya post sahibi bulunamadı. Post owner:', postOwnerUid, 'Voter:', voterUid);
217
235
  }
218
236
  };
219
237
 
@@ -301,9 +319,8 @@ Plugin.init = async function (params) {
301
319
  }
302
320
  });
303
321
 
304
- // 3) KASA HISTORY
322
+ // 3) KASA HISTORY - BASİT VERSİYON
305
323
  router.get('/api/niki-loyalty/kasa-history', middleware.ensureLoggedIn, async (req, res) => {
306
- // ... (Mevcut kodunun aynısı - sadece yetki kontrolü var)
307
324
  try {
308
325
  const isAdmin = await user.isAdministrator(req.uid);
309
326
  const isMod = await user.isGlobalModerator(req.uid);
@@ -312,7 +329,6 @@ Plugin.init = async function (params) {
312
329
  const raw = await db.getListRange('niki:kasa:history', 0, -1);
313
330
  const rows = (raw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
314
331
 
315
- // Kullanıcı detaylarını doldurma (Map logic)
316
332
  const uids = rows.map(r => parseInt(r.cuid, 10)).filter(n => Number.isFinite(n) && n > 0);
317
333
  const users = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'icon:bgColor']);
318
334
  const userMap = {};
@@ -331,7 +347,10 @@ Plugin.init = async function (params) {
331
347
  };
332
348
  });
333
349
  return res.json(enriched);
334
- } catch (e) { return res.status(500).json([]); }
350
+ } catch (e) {
351
+ console.error('[Niki-Loyalty] Kasa history error:', e);
352
+ return res.status(500).json([]);
353
+ }
335
354
  });
336
355
 
337
356
  // 4) QR OLUŞTURMA
@@ -566,10 +585,147 @@ Plugin.adminManagePoints = async function (socket, data) {
566
585
  const newPoints = await user.getUserField(targetUid, 'niki_points');
567
586
  return { success: true, newPoints: parseFloat(newPoints) };
568
587
  };
588
+ // 4) KULLANICI DETAY (Admin için)
589
+ Plugin.adminGetUserDetail = async function (socket, data) {
590
+ const uid = socket.uid;
591
+ if (!uid) throw new Error('Giriş yapmalısınız.');
592
+
593
+ const isAdmin = await user.isAdministrator(uid);
594
+ const isMod = await user.isGlobalModerator(uid);
595
+ if (!isAdmin && !isMod) throw new Error('Yetkisiz Erişim');
596
+
597
+ const targetUid = data.uid;
598
+ if (!targetUid) throw new Error('Kullanıcı ID gerekli.');
599
+
600
+ // Kullanıcı bilgileri
601
+ const userData = await user.getUserFields(targetUid, [
602
+ 'uid', 'username', 'userslug', 'picture', 'email',
603
+ 'niki_points', 'icon:bgColor', 'joindate', 'lastonline'
604
+ ]);
605
+
606
+ if (!userData || !userData.uid) throw new Error('Kullanıcı bulunamadı.');
607
+
608
+ // Aktivite geçmişi
609
+ const activityRaw = await db.getListRange(`niki:activity:${targetUid}`, 0, -1);
610
+ const activities = (activityRaw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
611
+
612
+ // Kazanılan ve harcanan puanları ayır
613
+ let totalEarned = 0;
614
+ let totalSpent = 0;
615
+ const earnHistory = [];
616
+ const spendHistory = [];
617
+
618
+ activities.forEach(a => {
619
+ if (a.type === 'earn' || a.type === 'admin_adjust') {
620
+ if (a.type === 'admin_adjust' && a.txt && a.txt.includes('-')) {
621
+ totalSpent += parseFloat(a.amt) || 0;
622
+ spendHistory.push(a);
623
+ } else {
624
+ totalEarned += parseFloat(a.amt) || 0;
625
+ earnHistory.push(a);
626
+ }
627
+ } else if (a.type === 'spend') {
628
+ totalSpent += parseFloat(a.amt) || 0;
629
+ spendHistory.push(a);
630
+ }
631
+ });
632
+
633
+ // Günlük limit durumu
634
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
635
+ const dailyData = await db.getObject(`niki:daily:${targetUid}:${today}`);
636
+ const actionCounts = await db.getObject(`niki:daily:${targetUid}:${today}:counts`);
637
+
638
+ // Bu kullanıcının kasa işlemleri (harcamaları)
639
+ const kasaRaw = await db.getListRange('niki:kasa:history', 0, -1);
640
+ const userKasaHistory = (kasaRaw || [])
641
+ .map(safeParseMaybeJson)
642
+ .filter(k => k && String(k.cuid) === String(targetUid))
643
+ .reverse()
644
+ .slice(0, 20);
645
+
646
+ const rp = require.main.require('nconf').get('relative_path') || '';
647
+
648
+ return {
649
+ user: {
650
+ uid: userData.uid,
651
+ username: userData.username,
652
+ userslug: userData.userslug,
653
+ picture: userData.picture || '',
654
+ email: userData.email || '',
655
+ iconBg: userData['icon:bgColor'] || '#4b5563',
656
+ points: parseFloat(userData.niki_points || 0),
657
+ joindate: userData.joindate,
658
+ lastonline: userData.lastonline,
659
+ profileUrl: userData.userslug ? `${rp}/user/${userData.userslug}` : ''
660
+ },
661
+ stats: {
662
+ totalEarned,
663
+ totalSpent,
664
+ currentPoints: parseFloat(userData.niki_points || 0),
665
+ todayScore: parseFloat(dailyData?.score || 0),
666
+ todayCounts: actionCounts || {}
667
+ },
668
+ earnHistory: earnHistory.slice(0, 30),
669
+ spendHistory: spendHistory.slice(0, 30),
670
+ kasaHistory: userKasaHistory,
671
+ actions: ACTIONS
672
+ };
673
+ };
674
+
675
+ // 5) GENEL İSTATİSTİKLER (Dashboard için)
676
+ Plugin.adminGetStats = async function (socket, data) {
677
+ const uid = socket.uid;
678
+ if (!uid) throw new Error('Giriş yapmalısınız.');
679
+
680
+ const isAdmin = await user.isAdministrator(uid);
681
+ const isMod = await user.isGlobalModerator(uid);
682
+ if (!isAdmin && !isMod) throw new Error('Yetkisiz Erişim');
683
+
684
+ // Tüm kullanıcıları al
685
+ const uids = await db.getSortedSetRange('users:joindate', 0, 499);
686
+ if (!uids || uids.length === 0) return { users: 0, totalPoints: 0, avgPoints: 0 };
687
+
688
+ const usersData = await user.getUsersFields(uids, ['niki_points']);
689
+
690
+ let totalPoints = 0;
691
+ let usersWithPoints = 0;
692
+
693
+ usersData.forEach(u => {
694
+ const pts = parseFloat(u.niki_points || 0);
695
+ if (pts > 0) {
696
+ totalPoints += pts;
697
+ usersWithPoints++;
698
+ }
699
+ });
700
+
701
+ // Kasa geçmişinden toplam harcama
702
+ const kasaRaw = await db.getListRange('niki:kasa:history', 0, -1);
703
+ const kasaData = (kasaRaw || []).map(safeParseMaybeJson).filter(Boolean);
704
+ const totalRedeemed = kasaData.reduce((sum, k) => sum + (parseFloat(k.amt) || 0), 0);
705
+
706
+ // Bugünkü işlemler
707
+ const today = new Date().toISOString().slice(0, 10);
708
+ const todayTransactions = kasaData.filter(k => {
709
+ const d = new Date(k.ts).toISOString().slice(0, 10);
710
+ return d === today;
711
+ }).length;
712
+
713
+ return {
714
+ usersWithPoints,
715
+ totalPoints: Math.floor(totalPoints),
716
+ avgPoints: usersWithPoints > 0 ? Math.floor(totalPoints / usersWithPoints) : 0,
717
+ totalRedeemed: Math.floor(totalRedeemed),
718
+ totalTransactions: kasaData.length,
719
+ todayTransactions
720
+ };
721
+ };
722
+
569
723
  // Soket'e kaydet (Client: socket.emit('plugins.niki.getUsers', ...))
570
724
  if (SocketPlugins) {
571
725
  SocketPlugins.niki = {
572
726
  getUsers: Plugin.adminGetUsers,
727
+ getUserDetail: Plugin.adminGetUserDetail,
728
+ getStats: Plugin.adminGetStats,
573
729
  scanQR: Plugin.socketScanQR,
574
730
  getKasaHistory: Plugin.socketKasaHistory,
575
731
  managePoints: Plugin.adminManagePoints
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-niki-loyalty",
3
- "version": "1.2.9",
3
+ "version": "1.3.1",
4
4
  "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {