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 +316 -48
- package/package.json +1 -1
- package/plugin.json +32 -8
- package/static/lib/client.js +3 -3
- package/static/widgets/niki-admin.tpl +686 -0
- package/templates/niki-kasa.tpl +562 -0
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
newTotal
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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 (
|
|
171
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
342
|
+
(usersData || []).forEach(u => userMap[u.uid] = u);
|
|
283
343
|
|
|
284
344
|
const rp = nconf.get('relative_path') || '';
|
|
285
|
-
|
|
286
|
-
|
|
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:
|
|
290
|
-
picture:
|
|
291
|
-
iconBg:
|
|
292
|
-
profileUrl:
|
|
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
|
-
|
|
297
|
-
|
|
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
package/plugin.json
CHANGED
|
@@ -5,14 +5,38 @@
|
|
|
5
5
|
"url": "https://forum.ieu.app",
|
|
6
6
|
"library": "./library.js",
|
|
7
7
|
"hooks": [
|
|
8
|
-
{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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"
|
package/static/lib/client.js
CHANGED
|
@@ -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 + ' /
|
|
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,
|
|
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:
|