nodebb-plugin-niki-loyalty 1.0.8 → 1.0.10
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 +135 -116
- package/package.json +1 -1
package/library.js
CHANGED
|
@@ -1,128 +1,147 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
const db = require.main.require('./src/database');
|
|
4
|
+
const user = require.main.require('./src/user');
|
|
5
|
+
const routeHelpers = require.main.require('./src/controllers/helpers');
|
|
6
|
+
|
|
7
|
+
const Plugin = {};
|
|
8
|
+
|
|
9
|
+
const SETTINGS = {
|
|
10
|
+
pointsPerHeartbeat: 5,
|
|
11
|
+
dailyCap: 250,
|
|
12
|
+
coffeeCost: 250
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// --- YARDIMCI: İŞLEM GEÇMİŞİNE EKLE ---
|
|
16
|
+
async function addLog(uid, type, amount, description) {
|
|
17
|
+
const logEntry = {
|
|
18
|
+
timestamp: Date.now(),
|
|
19
|
+
type: type, // 'earn' veya 'spend'
|
|
20
|
+
amount: amount,
|
|
21
|
+
desc: description
|
|
22
|
+
};
|
|
23
|
+
// Listeye ekle (Sondan ekler)
|
|
24
|
+
await db.listAppend(`niki:activity:${uid}`, logEntry);
|
|
25
|
+
|
|
26
|
+
// Son 50 işlemi tut, gerisini sil (Veritabanı şişmesin)
|
|
27
|
+
await db.listTrim(`niki:activity:${uid}`, -50, -1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Plugin.init = async function (params) {
|
|
31
|
+
const router = params.router;
|
|
32
|
+
const middleware = params.middleware;
|
|
33
|
+
|
|
34
|
+
// 1. HEARTBEAT (Puan Kazanma + LOG)
|
|
35
|
+
router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
|
|
36
|
+
const uid = req.uid;
|
|
37
|
+
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
38
|
+
const dailyKey = `niki:daily:${uid}:${today}`;
|
|
39
|
+
|
|
40
|
+
const currentDailyScore = await db.getObjectField(dailyKey, 'score') || 0;
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const cachedPoints = localStorage.getItem('niki_last_points');
|
|
33
|
-
if (cachedPoints !== null) {
|
|
34
|
-
$('#niki-live-points').text(cachedPoints);
|
|
35
|
-
$('#niki-floating-widget').removeClass('niki-hidden');
|
|
42
|
+
if (parseInt(currentDailyScore) >= SETTINGS.dailyCap) {
|
|
43
|
+
return res.json({ earned: false, reason: 'daily_cap' });
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
await user.incrementUserFieldBy(uid, 'niki_points', SETTINGS.pointsPerHeartbeat);
|
|
47
|
+
await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
|
|
48
|
+
|
|
49
|
+
// LOG EKLEME (Her 5 puan için log şişirmemek adına, sadece veritabanına sessizce işleyelim)
|
|
50
|
+
// Ancak kullanıcı her saniye log görmesin diye buraya eklemiyoruz.
|
|
51
|
+
// İstersen "Ders Çalışma Oturumu" bittiğinde toplu ekleyebiliriz ama şimdilik basit tutalım.
|
|
52
|
+
|
|
53
|
+
const newBalance = await user.getUserField(uid, 'niki_points');
|
|
54
|
+
return res.json({ earned: true, points: SETTINGS.pointsPerHeartbeat, total: newBalance });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 2. WALLET DATA + GEÇMİŞİ ÇEKME
|
|
58
|
+
router.get('/api/niki-loyalty/wallet-data', middleware.ensureLoggedIn, async (req, res) => {
|
|
59
|
+
const uid = req.uid;
|
|
60
|
+
const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
61
|
+
|
|
62
|
+
const [userData, dailyData, history] = await Promise.all([
|
|
63
|
+
user.getUserFields(uid, ['niki_points']),
|
|
64
|
+
db.getObject(`niki:daily:${uid}:${today}`),
|
|
65
|
+
db.getListRange(`niki:activity:${uid}`, 0, -1) // Tüm geçmişi çek
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
// Geçmişi ters çevir (En yeni en üstte)
|
|
69
|
+
const recentActivity = (history || []).reverse();
|
|
70
|
+
|
|
71
|
+
const currentPoints = parseInt(userData.niki_points) || 0;
|
|
72
|
+
const dailyScore = parseInt(dailyData ? dailyData.score : 0) || 0;
|
|
73
|
+
let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
|
|
74
|
+
if (dailyPercent > 100) dailyPercent = 100;
|
|
75
|
+
|
|
76
|
+
res.json({
|
|
77
|
+
points: currentPoints,
|
|
78
|
+
dailyScore: dailyScore,
|
|
79
|
+
dailyCap: SETTINGS.dailyCap,
|
|
80
|
+
dailyPercent: dailyPercent,
|
|
81
|
+
history: recentActivity // Frontend'e gönder
|
|
61
82
|
});
|
|
62
|
-
}
|
|
83
|
+
});
|
|
63
84
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
}
|
|
85
|
+
// 3. QR TOKEN ÜRET
|
|
86
|
+
router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
|
|
87
|
+
const uid = req.uid;
|
|
88
|
+
const points = parseInt(await user.getUserField(uid, 'niki_points')) || 0;
|
|
89
|
+
|
|
90
|
+
if (points < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Yetersiz Puan' });
|
|
71
91
|
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
const token = Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
93
|
+
await db.set(`niki:qr:${token}`, uid);
|
|
94
|
+
await db.expire(`niki:qr:${token}`, 120);
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
$(window).on('action:ajaxify.end', function () {
|
|
77
|
-
initNikiWidget();
|
|
78
|
-
setTimeout(fixLogo, 500); // 0.5sn sonra son bir kontrol
|
|
96
|
+
return res.json({ success: true, token: token });
|
|
79
97
|
});
|
|
80
98
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
99
|
+
// 4. QR OKUTMA (Harcama + LOG)
|
|
100
|
+
router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
|
|
101
|
+
const { token } = req.body;
|
|
102
|
+
|
|
103
|
+
const isAdmin = await user.isAdministrator(req.uid);
|
|
104
|
+
const isMod = await user.isGlobalModerator(req.uid);
|
|
105
|
+
if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
|
|
106
|
+
|
|
107
|
+
const customerUid = await db.get(`niki:qr:${token}`);
|
|
108
|
+
if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
|
|
109
|
+
|
|
110
|
+
const pts = parseInt(await user.getUserField(customerUid, 'niki_points')) || 0;
|
|
111
|
+
if (pts < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Bakiye Yetersiz' });
|
|
112
|
+
|
|
113
|
+
// Puan Düş
|
|
114
|
+
await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
|
|
115
|
+
await db.delete(`niki:qr:${token}`);
|
|
116
|
+
|
|
117
|
+
// LOG KAYDET: Harcama
|
|
118
|
+
await addLog(customerUid, 'spend', SETTINGS.coffeeCost, 'Kahve Harcaması');
|
|
119
|
+
|
|
120
|
+
const customerData = await user.getUserFields(customerUid, ['username', 'picture']);
|
|
121
|
+
return res.json({ success: true, customer: customerData, message: 'Onaylandı!' });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ** Manuel Puan Ekleme API'si (Test veya Bonus İçin) **
|
|
125
|
+
// Buraya "Kazanma Logu" ekleyelim ki tabloda görünsün.
|
|
126
|
+
// Client tarafındaki "heartbeat" çok sık çalıştığı için veritabanını şişirmemek adına
|
|
127
|
+
// oraya log koymadık. Ama günlük hedefe ulaşınca veya manuel eklenince log düşebiliriz.
|
|
85
128
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (res.earned) {
|
|
106
|
-
// Puanı güncelle
|
|
107
|
-
$('#niki-live-points').text(res.total);
|
|
108
|
-
// Hafızayı da güncelle
|
|
109
|
-
localStorage.setItem('niki_last_points', res.total);
|
|
110
|
-
|
|
111
|
-
showNikiToast(`+${res.points} Puan Kazandın! ☕`);
|
|
112
|
-
$('#niki-floating-widget').addClass('niki-bounce');
|
|
113
|
-
setTimeout(() => $('#niki-floating-widget').removeClass('niki-bounce'), 500);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function showNikiToast(msg) {
|
|
119
|
-
$('.niki-toast').remove();
|
|
120
|
-
const toast = $(`<div class="niki-toast"><i class="fa fa-paw"></i> ${msg}</div>`);
|
|
121
|
-
$('body').append(toast);
|
|
122
|
-
setTimeout(() => { toast.addClass('show'); }, 100);
|
|
123
|
-
setTimeout(() => {
|
|
124
|
-
toast.removeClass('show');
|
|
125
|
-
setTimeout(() => toast.remove(), 3000);
|
|
126
|
-
}, 3000);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
+
// NIKI KASA SAYFASI
|
|
130
|
+
routeHelpers.setupPageRoute(router, '/niki-kasa', middleware, [], async (req, res) => {
|
|
131
|
+
const isAdmin = await user.isAdministrator(req.uid);
|
|
132
|
+
if (!isAdmin) return res.render('403', {});
|
|
133
|
+
res.render('niki-kasa', { title: 'Niki Kasa' });
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
Plugin.addScripts = async function (scripts) {
|
|
138
|
+
scripts.push('plugins/nodebb-plugin-niki-loyalty/static/lib/client.js');
|
|
139
|
+
return scripts;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
Plugin.addNavigation = async function (nav) {
|
|
143
|
+
nav.push({ route: "/niki-wallet", title: "Niki Cüzdan", enabled: true, iconClass: "fa-coffee", text: "Niki Cüzdan" });
|
|
144
|
+
return nav;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
module.exports = Plugin;
|