nodebb-plugin-niki-loyalty 1.2.8 → 1.3.0

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
@@ -73,24 +73,35 @@ async function awardDailyAction(uid, actionKey) {
73
73
  const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
74
74
  const rule = ACTIONS[actionKey];
75
75
 
76
- if (!rule) return;
76
+ if (!rule) {
77
+ console.log(`[Niki-Loyalty] Bilinmeyen aksiyon: ${actionKey}`);
78
+ return { success: false, reason: 'unknown_action' };
79
+ }
77
80
 
78
81
  // 1. Genel Günlük Limit Kontrolü
79
82
  const dailyScoreKey = `niki:daily:${uid}:${today}`;
80
83
  const currentDailyScore = parseFloat((await db.getObjectField(dailyScoreKey, 'score')) || 0);
81
- if (currentDailyScore >= SETTINGS.dailyCap) return;
84
+ if (currentDailyScore >= SETTINGS.dailyCap) {
85
+ console.log(`[Niki-Loyalty] Günlük limit doldu. UID: ${uid}, Score: ${currentDailyScore}`);
86
+ return { success: false, reason: 'daily_cap_reached' };
87
+ }
82
88
 
83
89
  // 2. Eylem Bazlı Limit Kontrolü
84
90
  const actionCountKey = `niki:daily:${uid}:${today}:counts`;
85
91
  const currentActionCount = parseInt((await db.getObjectField(actionCountKey, actionKey)) || 0, 10);
86
- if (currentActionCount >= rule.limit) return;
92
+ if (currentActionCount >= rule.limit) {
93
+ console.log(`[Niki-Loyalty] Aksiyon limiti doldu. UID: ${uid}, Action: ${actionKey}, Count: ${currentActionCount}/${rule.limit}`);
94
+ return { success: false, reason: 'action_limit_reached' };
95
+ }
87
96
 
88
97
  // 3. Puan Hesapla
89
98
  let pointsToGive = rule.points;
90
99
  if (currentDailyScore + pointsToGive > SETTINGS.dailyCap) {
91
100
  pointsToGive = SETTINGS.dailyCap - currentDailyScore;
92
101
  }
93
- if (pointsToGive <= 0) return;
102
+ if (pointsToGive <= 0) {
103
+ return { success: false, reason: 'no_points_to_give' };
104
+ }
94
105
 
95
106
  // 4. DB Güncellemeleri
96
107
  await user.incrementUserFieldBy(uid, 'niki_points', pointsToGive);
@@ -100,17 +111,30 @@ async function awardDailyAction(uid, actionKey) {
100
111
  // Logla
101
112
  await addUserLog(uid, 'earn', pointsToGive, rule.name);
102
113
 
103
- //YENİ EKLENEN KISIM: Kullanıcıya Bildirim Gönder (Socket Emit)
104
- if (socketHelpers && socketHelpers.server) {
105
- socketHelpers.server.sockets.in('uid_' + uid).emit('event:niki_award', {
106
- title: 'Tebrikler! 🥳',
107
- message: `${rule.name} işleminden <strong style="color:#ffd700">+${pointsToGive} Puan</strong> kazandın!`,
108
- newTotal: parseFloat((await user.getUserField(uid, 'niki_points')) || 0)
109
- });
114
+ console.log(`[Niki-Loyalty]PUAN VERİLDİ! UID: ${uid}, Action: ${actionKey}, Points: +${pointsToGive}`);
115
+
116
+ // Kullanıcıya Bildirim Gönder (Socket Emit) - Güçlendirilmiş
117
+ try {
118
+ if (socketHelpers && socketHelpers.server && socketHelpers.server.sockets) {
119
+ const newTotal = parseFloat((await user.getUserField(uid, 'niki_points')) || 0);
120
+ socketHelpers.server.sockets.in('uid_' + uid).emit('event:niki_award', {
121
+ title: 'Tebrikler! 🥳',
122
+ message: `${rule.name} işleminden <strong style="color:#ffd700">+${pointsToGive} Puan</strong> kazandın!`,
123
+ newTotal: newTotal
124
+ });
125
+ console.log(`[Niki-Loyalty] 📢 Socket bildirim gönderildi. UID: ${uid}`);
126
+ } else {
127
+ console.log(`[Niki-Loyalty] ⚠️ Socket server hazır değil, bildirim gönderilemedi.`);
128
+ }
129
+ } catch (socketErr) {
130
+ console.error(`[Niki-Loyalty] Socket emit hatası:`, socketErr.message);
110
131
  }
111
132
 
133
+ return { success: true, points: pointsToGive };
134
+
112
135
  } catch (err) {
113
136
  console.error(`[Niki-Loyalty] Error awarding points for ${actionKey}:`, err);
137
+ return { success: false, reason: 'error', error: err.message };
114
138
  }
115
139
  }
116
140
 
@@ -144,39 +168,70 @@ Plugin.onPostCreate = async function (data) {
144
168
  await awardDailyAction(data.post.uid, 'reply');
145
169
  };
146
170
 
147
- // 4. BEĞENİ (Like Atma ve Alma) - Spam Korumalı
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!)
148
173
  Plugin.onUpvote = async function (data) {
149
- // data = { post: { pid, uid, ... }, uid: <like atan>, ... }
150
- const pid = data.post && data.post.pid;
151
- if (!pid) return;
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);
179
+
180
+ if (!pid) {
181
+ console.log('[Niki-Loyalty] ⚠️ Post PID bulunamadı, işlem iptal.');
182
+ return;
183
+ }
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
+ }
152
204
 
153
205
  const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
154
206
 
155
207
  // Like Atan Kazanır:
156
- if (data.uid) {
157
- // Bu postu bugün zaten beğenmiş mi?
158
- const likeGivenKey = `niki:liked:${data.uid}:${today}`;
159
- const alreadyLiked = await db.isSetMember(likeGivenKey, pid.toString());
160
-
161
- if (!alreadyLiked) {
162
- await awardDailyAction(data.uid, 'like_given');
163
- await db.setAdd(likeGivenKey, pid.toString());
164
- // 24 saat sonra expire olsun
165
- await db.expire(likeGivenKey, 86400);
166
- }
208
+ const likeGivenKey = `niki:liked:${voterUid}:${today}`;
209
+ const alreadyLiked = await db.isSetMember(likeGivenKey, pid.toString());
210
+
211
+ console.log(`[Niki-Loyalty] Like Atan: UID=${voterUid}, PID=${pid}, Daha önce beğenmiş mi=${alreadyLiked}`);
212
+
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);
167
218
  }
168
219
 
169
- // Like Alan Kazanır (Post sahibi):
170
- if (data.post && data.post.uid && data.post.uid !== data.uid) {
171
- // Bu post için bugün zaten puan almış mı?
172
- 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}`;
173
223
  const alreadyTaken = await db.isSetMember(likeTakenKey, pid.toString());
174
224
 
225
+ console.log(`[Niki-Loyalty] Like Alan: UID=${postOwnerUid}, PID=${pid}, Daha önce puan almış mı=${alreadyTaken}`);
226
+
175
227
  if (!alreadyTaken) {
176
- await awardDailyAction(data.post.uid, 'like_taken');
228
+ const result = await awardDailyAction(postOwnerUid, 'like_taken');
229
+ console.log('[Niki-Loyalty] like_taken sonuç:', result);
177
230
  await db.setAdd(likeTakenKey, pid.toString());
178
231
  await db.expire(likeTakenKey, 86400);
179
232
  }
233
+ } else {
234
+ console.log('[Niki-Loyalty] ⚠️ Kullanıcı kendi postunu beğenmiş veya post sahibi bulunamadı. Post owner:', postOwnerUid, 'Voter:', voterUid);
180
235
  }
181
236
  };
182
237
 
@@ -264,37 +319,113 @@ Plugin.init = async function (params) {
264
319
  }
265
320
  });
266
321
 
267
- // 3) KASA HISTORY
322
+ // 3) KASA HISTORY - GELİŞMİŞ VERSİYON (Filtre + İstatistik)
268
323
  router.get('/api/niki-loyalty/kasa-history', middleware.ensureLoggedIn, async (req, res) => {
269
- // ... (Mevcut kodunun aynısı - sadece yetki kontrolü var)
270
324
  try {
271
325
  const isAdmin = await user.isAdministrator(req.uid);
272
326
  const isMod = await user.isGlobalModerator(req.uid);
273
- if (!isAdmin && !isMod) return res.status(403).json([]);
327
+ if (!isAdmin && !isMod) return res.status(403).json({ error: 'Yetkisiz' });
328
+
329
+ // Query parametreleri
330
+ const { startDate, endDate, search, rewardType, exportAll } = req.query;
274
331
 
275
332
  const raw = await db.getListRange('niki:kasa:history', 0, -1);
276
- const rows = (raw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
333
+ let rows = (raw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
334
+
335
+ // Personel bilgilerini de al
336
+ const staffUids = [...new Set(rows.map(r => parseInt(r.staff, 10)).filter(n => Number.isFinite(n) && n > 0))];
337
+ const custUids = rows.map(r => parseInt(r.cuid, 10)).filter(n => Number.isFinite(n) && n > 0);
338
+ const allUids = [...new Set([...staffUids, ...custUids])];
277
339
 
278
- // Kullanıcı detaylarını doldurma (Map logic)
279
- const uids = rows.map(r => parseInt(r.cuid, 10)).filter(n => Number.isFinite(n) && n > 0);
280
- const users = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'icon:bgColor']);
340
+ const usersData = await user.getUsersFields(allUids, ['uid', 'username', 'userslug', 'picture', 'icon:bgColor']);
281
341
  const userMap = {};
282
- (users || []).forEach(u => userMap[u.uid] = u);
342
+ (usersData || []).forEach(u => userMap[u.uid] = u);
283
343
 
284
344
  const rp = nconf.get('relative_path') || '';
285
- const enriched = rows.map(r => {
286
- const u = userMap[r.cuid] || {};
345
+
346
+ // Zenginleştir
347
+ let enriched = rows.map(r => {
348
+ const custUser = userMap[r.cuid] || {};
349
+ const staffUser = userMap[r.staff] || {};
287
350
  return {
288
351
  ...r,
289
- cust: u.username || r.cust || 'Bilinmeyen',
290
- picture: u.picture || '',
291
- iconBg: u['icon:bgColor'] || '#4b5563',
292
- profileUrl: u.userslug ? `${rp}/user/${u.userslug}` : '',
293
- reward: r.reward || 'İşlem'
352
+ cust: custUser.username || r.cust || 'Bilinmeyen',
353
+ picture: custUser.picture || '',
354
+ iconBg: custUser['icon:bgColor'] || '#4b5563',
355
+ profileUrl: custUser.userslug ? `${rp}/user/${custUser.userslug}` : '',
356
+ reward: r.reward || 'İşlem',
357
+ staffName: staffUser.username || 'Personel',
358
+ staffPicture: staffUser.picture || '',
359
+ date: new Date(r.ts).toISOString().slice(0, 10) // YYYY-MM-DD
294
360
  };
295
361
  });
296
- return res.json(enriched);
297
- } catch (e) { return res.status(500).json([]); }
362
+
363
+ // FİLTRELEME
364
+ // 1. Tarih aralığı
365
+ if (startDate) {
366
+ const start = new Date(startDate).getTime();
367
+ enriched = enriched.filter(r => r.ts >= start);
368
+ }
369
+ if (endDate) {
370
+ const end = new Date(endDate).getTime() + 86400000; // gün sonu
371
+ enriched = enriched.filter(r => r.ts < end);
372
+ }
373
+
374
+ // 2. Arama (kullanıcı adı)
375
+ if (search && search.trim()) {
376
+ const q = search.toLowerCase().trim();
377
+ enriched = enriched.filter(r =>
378
+ (r.cust && r.cust.toLowerCase().includes(q)) ||
379
+ (r.staffName && r.staffName.toLowerCase().includes(q))
380
+ );
381
+ }
382
+
383
+ // 3. Ödül tipi
384
+ if (rewardType && rewardType !== 'all') {
385
+ enriched = enriched.filter(r => r.reward === rewardType);
386
+ }
387
+
388
+ // İSTATİSTİKLER
389
+ const stats = {
390
+ totalTransactions: enriched.length,
391
+ totalPoints: enriched.reduce((sum, r) => sum + (parseFloat(r.amt) || 0), 0),
392
+ byReward: {},
393
+ byStaff: {},
394
+ byDate: {}
395
+ };
396
+
397
+ enriched.forEach(r => {
398
+ // Ödül bazında
399
+ stats.byReward[r.reward] = (stats.byReward[r.reward] || 0) + 1;
400
+ // Personel bazında
401
+ stats.byStaff[r.staffName] = (stats.byStaff[r.staffName] || 0) + 1;
402
+ // Gün bazında (son 7 gün için chart)
403
+ stats.byDate[r.date] = (stats.byDate[r.date] || 0) + 1;
404
+ });
405
+
406
+ // Benzersiz ödül tipleri (filter dropdown için)
407
+ const rewardTypes = [...new Set(rows.map(r => r.reward || 'İşlem'))];
408
+
409
+ // Export all için sayfalama yok
410
+ if (exportAll === 'true') {
411
+ return res.json({
412
+ data: enriched,
413
+ stats,
414
+ rewardTypes
415
+ });
416
+ }
417
+
418
+ // Normal görünüm (son 100 işlem)
419
+ return res.json({
420
+ data: enriched.slice(0, 100),
421
+ stats,
422
+ rewardTypes,
423
+ hasMore: enriched.length > 100
424
+ });
425
+ } catch (e) {
426
+ console.error('[Niki-Loyalty] Kasa history error:', e);
427
+ return res.status(500).json({ error: 'Sunucu hatası' });
428
+ }
298
429
  });
299
430
 
300
431
  // 4) QR OLUŞTURMA
@@ -529,10 +660,147 @@ Plugin.adminManagePoints = async function (socket, data) {
529
660
  const newPoints = await user.getUserField(targetUid, 'niki_points');
530
661
  return { success: true, newPoints: parseFloat(newPoints) };
531
662
  };
663
+ // 4) KULLANICI DETAY (Admin için)
664
+ Plugin.adminGetUserDetail = async function (socket, data) {
665
+ const uid = socket.uid;
666
+ if (!uid) throw new Error('Giriş yapmalısınız.');
667
+
668
+ const isAdmin = await user.isAdministrator(uid);
669
+ const isMod = await user.isGlobalModerator(uid);
670
+ if (!isAdmin && !isMod) throw new Error('Yetkisiz Erişim');
671
+
672
+ const targetUid = data.uid;
673
+ if (!targetUid) throw new Error('Kullanıcı ID gerekli.');
674
+
675
+ // Kullanıcı bilgileri
676
+ const userData = await user.getUserFields(targetUid, [
677
+ 'uid', 'username', 'userslug', 'picture', 'email',
678
+ 'niki_points', 'icon:bgColor', 'joindate', 'lastonline'
679
+ ]);
680
+
681
+ if (!userData || !userData.uid) throw new Error('Kullanıcı bulunamadı.');
682
+
683
+ // Aktivite geçmişi
684
+ const activityRaw = await db.getListRange(`niki:activity:${targetUid}`, 0, -1);
685
+ const activities = (activityRaw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
686
+
687
+ // Kazanılan ve harcanan puanları ayır
688
+ let totalEarned = 0;
689
+ let totalSpent = 0;
690
+ const earnHistory = [];
691
+ const spendHistory = [];
692
+
693
+ activities.forEach(a => {
694
+ if (a.type === 'earn' || a.type === 'admin_adjust') {
695
+ if (a.type === 'admin_adjust' && a.txt && a.txt.includes('-')) {
696
+ totalSpent += parseFloat(a.amt) || 0;
697
+ spendHistory.push(a);
698
+ } else {
699
+ totalEarned += parseFloat(a.amt) || 0;
700
+ earnHistory.push(a);
701
+ }
702
+ } else if (a.type === 'spend') {
703
+ totalSpent += parseFloat(a.amt) || 0;
704
+ spendHistory.push(a);
705
+ }
706
+ });
707
+
708
+ // Günlük limit durumu
709
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
710
+ const dailyData = await db.getObject(`niki:daily:${targetUid}:${today}`);
711
+ const actionCounts = await db.getObject(`niki:daily:${targetUid}:${today}:counts`);
712
+
713
+ // Bu kullanıcının kasa işlemleri (harcamaları)
714
+ const kasaRaw = await db.getListRange('niki:kasa:history', 0, -1);
715
+ const userKasaHistory = (kasaRaw || [])
716
+ .map(safeParseMaybeJson)
717
+ .filter(k => k && String(k.cuid) === String(targetUid))
718
+ .reverse()
719
+ .slice(0, 20);
720
+
721
+ const rp = require.main.require('nconf').get('relative_path') || '';
722
+
723
+ return {
724
+ user: {
725
+ uid: userData.uid,
726
+ username: userData.username,
727
+ userslug: userData.userslug,
728
+ picture: userData.picture || '',
729
+ email: userData.email || '',
730
+ iconBg: userData['icon:bgColor'] || '#4b5563',
731
+ points: parseFloat(userData.niki_points || 0),
732
+ joindate: userData.joindate,
733
+ lastonline: userData.lastonline,
734
+ profileUrl: userData.userslug ? `${rp}/user/${userData.userslug}` : ''
735
+ },
736
+ stats: {
737
+ totalEarned,
738
+ totalSpent,
739
+ currentPoints: parseFloat(userData.niki_points || 0),
740
+ todayScore: parseFloat(dailyData?.score || 0),
741
+ todayCounts: actionCounts || {}
742
+ },
743
+ earnHistory: earnHistory.slice(0, 30),
744
+ spendHistory: spendHistory.slice(0, 30),
745
+ kasaHistory: userKasaHistory,
746
+ actions: ACTIONS
747
+ };
748
+ };
749
+
750
+ // 5) GENEL İSTATİSTİKLER (Dashboard için)
751
+ Plugin.adminGetStats = async function (socket, data) {
752
+ const uid = socket.uid;
753
+ if (!uid) throw new Error('Giriş yapmalısınız.');
754
+
755
+ const isAdmin = await user.isAdministrator(uid);
756
+ const isMod = await user.isGlobalModerator(uid);
757
+ if (!isAdmin && !isMod) throw new Error('Yetkisiz Erişim');
758
+
759
+ // Tüm kullanıcıları al
760
+ const uids = await db.getSortedSetRange('users:joindate', 0, 499);
761
+ if (!uids || uids.length === 0) return { users: 0, totalPoints: 0, avgPoints: 0 };
762
+
763
+ const usersData = await user.getUsersFields(uids, ['niki_points']);
764
+
765
+ let totalPoints = 0;
766
+ let usersWithPoints = 0;
767
+
768
+ usersData.forEach(u => {
769
+ const pts = parseFloat(u.niki_points || 0);
770
+ if (pts > 0) {
771
+ totalPoints += pts;
772
+ usersWithPoints++;
773
+ }
774
+ });
775
+
776
+ // Kasa geçmişinden toplam harcama
777
+ const kasaRaw = await db.getListRange('niki:kasa:history', 0, -1);
778
+ const kasaData = (kasaRaw || []).map(safeParseMaybeJson).filter(Boolean);
779
+ const totalRedeemed = kasaData.reduce((sum, k) => sum + (parseFloat(k.amt) || 0), 0);
780
+
781
+ // Bugünkü işlemler
782
+ const today = new Date().toISOString().slice(0, 10);
783
+ const todayTransactions = kasaData.filter(k => {
784
+ const d = new Date(k.ts).toISOString().slice(0, 10);
785
+ return d === today;
786
+ }).length;
787
+
788
+ return {
789
+ usersWithPoints,
790
+ totalPoints: Math.floor(totalPoints),
791
+ avgPoints: usersWithPoints > 0 ? Math.floor(totalPoints / usersWithPoints) : 0,
792
+ totalRedeemed: Math.floor(totalRedeemed),
793
+ totalTransactions: kasaData.length,
794
+ todayTransactions
795
+ };
796
+ };
797
+
532
798
  // Soket'e kaydet (Client: socket.emit('plugins.niki.getUsers', ...))
533
799
  if (SocketPlugins) {
534
800
  SocketPlugins.niki = {
535
801
  getUsers: Plugin.adminGetUsers,
802
+ getUserDetail: Plugin.adminGetUserDetail,
803
+ getStats: Plugin.adminGetStats,
536
804
  scanQR: Plugin.socketScanQR,
537
805
  getKasaHistory: Plugin.socketKasaHistory,
538
806
  managePoints: Plugin.adminManagePoints
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-niki-loyalty",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {
package/plugin.json CHANGED
@@ -5,14 +5,38 @@
5
5
  "url": "https://forum.ieu.app",
6
6
  "library": "./library.js",
7
7
  "hooks": [
8
- { "hook": "static:app.load", "method": "init" },
9
- { "hook": "filter:navigation.available", "method": "addNavigation" },
10
- { "hook": "filter:scripts.get", "method": "addScripts" },
11
-
12
- { "hook": "action:user.loggedIn", "method": "onLogin" },
13
- { "hook": "action:topic.save", "method": "onTopicCreate" },
14
- { "hook": "action:post.save", "method": "onPostCreate" },
15
- { "hook": "action:post.upvote", "method": "onUpvote" }
8
+ {
9
+ "hook": "static:app.load",
10
+ "method": "init"
11
+ },
12
+ {
13
+ "hook": "filter:navigation.available",
14
+ "method": "addNavigation"
15
+ },
16
+ {
17
+ "hook": "filter:scripts.get",
18
+ "method": "addScripts"
19
+ },
20
+ {
21
+ "hook": "action:user.loggedIn",
22
+ "method": "onLogin"
23
+ },
24
+ {
25
+ "hook": "action:topic.save",
26
+ "method": "onTopicCreate"
27
+ },
28
+ {
29
+ "hook": "action:post.save",
30
+ "method": "onPostCreate"
31
+ },
32
+ {
33
+ "hook": "action:post.upvote",
34
+ "method": "onUpvote"
35
+ },
36
+ {
37
+ "hook": "action:post.updatePostVoteCount",
38
+ "method": "onUpvote"
39
+ }
16
40
  ],
17
41
  "staticDirs": {
18
42
  "static": "./static"
@@ -284,7 +284,7 @@ $(document).ready(function () {
284
284
 
285
285
  let dailyScore = parseFloat(data.dailyScore);
286
286
  let scoreText = Number.isInteger(dailyScore) ? dailyScore : dailyScore.toFixed(1);
287
- $('#widget-daily-text').text(scoreText + ' / 28');
287
+ $('#widget-daily-text').text(scoreText + ' / 35');
288
288
 
289
289
  // 3. DETAYLI SAYAÇLAR (Counts)
290
290
  const c = data.counts || {}; // Backend'den gelen sayaç objesi
@@ -304,10 +304,10 @@ $(document).ready(function () {
304
304
  }
305
305
  }
306
306
 
307
- // Tek Tek Güncelle
307
+ // Tek Tek Güncelle (library.js ACTIONS ile eşleştirildi)
308
308
  setProgress('w-count-new_topic', c.new_topic, 1, 'item-new-topic');
309
309
  setProgress('w-count-reply', c.reply, 2, 'item-reply');
310
- setProgress('w-count-read', c.read, 8, 'item-read');
310
+ setProgress('w-count-read', c.read, 10, 'item-read');
311
311
 
312
312
  // Like (Alma ve Atma toplamı 4 limit demiştik, burada basitleştirip toplamı gösteriyoruz)
313
313
  // Backend'de like_given ve like_taken ayrı tutuluyor, ikisini toplayalım: