nodebb-plugin-niki-loyalty 1.0.16 → 1.0.17

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.
Files changed (2) hide show
  1. package/library.js +168 -92
  2. package/package.json +1 -1
package/library.js CHANGED
@@ -7,21 +7,53 @@ const routeHelpers = require.main.require('./src/controllers/helpers');
7
7
  const Plugin = {};
8
8
 
9
9
  const SETTINGS = {
10
- pointsPerHeartbeat: 5,
11
- dailyCap: 250,
12
- coffeeCost: 250
10
+ pointsPerHeartbeat: 50000000,
11
+ dailyCap: 250000000000,
12
+ coffeeCost: 1
13
13
  };
14
14
 
15
+ // =========================
16
+ // JSON SAFE HELPERS
17
+ // =========================
18
+ function safeParseMaybeJson(x) {
19
+ if (x == null) return null;
20
+
21
+ // bazı DB’lerde object dönebilir
22
+ if (typeof x === 'object') return x;
23
+
24
+ if (typeof x === 'string') {
25
+ try {
26
+ return JSON.parse(x);
27
+ } catch (e) {
28
+ // "[object Object]" gibi bozuk kayıtlar
29
+ return null;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function safeStringify(obj) {
36
+ try {
37
+ return JSON.stringify(obj);
38
+ } catch (e) {
39
+ return null;
40
+ }
41
+ }
42
+
15
43
  // --- LOG FONKSİYONLARI ---
16
44
  async function addUserLog(uid, type, amount, desc) {
17
45
  const logEntry = {
18
46
  ts: Date.now(),
19
47
  type: type, // 'earn' veya 'spend'
20
48
  amt: amount,
21
- txt: desc
49
+ txt: desc // "Kahve Keyfi ☕" gibi metin
22
50
  };
23
- // Öğrenci geçmişine ekle
24
- await db.listAppend(`niki:activity:${uid}`, logEntry);
51
+
52
+ const payload = safeStringify(logEntry);
53
+ if (!payload) return;
54
+
55
+ // Öğrenci geçmişine ekle (Son 50 işlem)
56
+ await db.listAppend(`niki:activity:${uid}`, payload);
25
57
  await db.listTrim(`niki:activity:${uid}`, -50, -1);
26
58
  }
27
59
 
@@ -33,8 +65,12 @@ async function addKasaLog(staffUid, customerName, customerUid) {
33
65
  cuid: customerUid,
34
66
  amt: SETTINGS.coffeeCost
35
67
  };
36
- // Kasa defterine ekle
37
- await db.listAppend('niki:kasa:history', logEntry);
68
+
69
+ const payload = safeStringify(logEntry);
70
+ if (!payload) return;
71
+
72
+ // Kasa defterine ekle (Son 100 işlem)
73
+ await db.listAppend('niki:kasa:history', payload);
38
74
  await db.listTrim('niki:kasa:history', -100, -1);
39
75
  }
40
76
 
@@ -44,112 +80,152 @@ Plugin.init = async function (params) {
44
80
 
45
81
  // 1. HEARTBEAT (Puan Kazanma)
46
82
  router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
47
- const uid = req.uid;
48
- const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
49
- const dailyKey = `niki:daily:${uid}:${today}`;
50
-
51
- const currentDailyScore = await db.getObjectField(dailyKey, 'score') || 0;
52
-
53
- if (parseInt(currentDailyScore) >= SETTINGS.dailyCap) {
54
- return res.json({ earned: false, reason: 'daily_cap' });
55
- }
83
+ try {
84
+ const uid = req.uid;
85
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
86
+ const dailyKey = `niki:daily:${uid}:${today}`;
56
87
 
57
- await user.incrementUserFieldBy(uid, 'niki_points', SETTINGS.pointsPerHeartbeat);
58
- await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
88
+ const currentDailyScore = await db.getObjectField(dailyKey, 'score') || 0;
59
89
 
60
- const newBalance = await user.getUserField(uid, 'niki_points');
61
- return res.json({ earned: true, points: SETTINGS.pointsPerHeartbeat, total: newBalance });
90
+ if (parseInt(currentDailyScore) >= SETTINGS.dailyCap) {
91
+ return res.json({ earned: false, reason: 'daily_cap' });
92
+ }
93
+
94
+ await user.incrementUserFieldBy(uid, 'niki_points', SETTINGS.pointsPerHeartbeat);
95
+ await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
96
+
97
+ const newBalance = await user.getUserField(uid, 'niki_points');
98
+ return res.json({ earned: true, points: SETTINGS.pointsPerHeartbeat, total: newBalance });
99
+ } catch (err) {
100
+ return res.status(500).json({ earned: false, reason: 'server_error' });
101
+ }
62
102
  });
63
103
 
64
104
  // 2. WALLET DATA (Cüzdan + Geçmiş)
65
105
  router.get('/api/niki-loyalty/wallet-data', middleware.ensureLoggedIn, async (req, res) => {
66
- const uid = req.uid;
67
- const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
68
-
69
- const [userData, dailyData, history] = await Promise.all([
70
- user.getUserFields(uid, ['niki_points']),
71
- db.getObject(`niki:daily:${uid}:${today}`),
72
- db.getListRange(`niki:activity:${uid}`, 0, -1)
73
- ]);
74
-
75
- const currentPoints = parseInt(userData.niki_points) || 0;
76
- const dailyScore = parseInt(dailyData ? dailyData.score : 0) || 0;
77
- let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
78
- if (dailyPercent > 100) dailyPercent = 100;
79
-
80
- res.json({
81
- points: currentPoints,
82
- dailyScore: dailyScore,
83
- dailyCap: SETTINGS.dailyCap,
84
- dailyPercent: dailyPercent,
85
- history: (history || []).reverse()
86
- });
106
+ try {
107
+ const uid = req.uid;
108
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
109
+
110
+ const [userData, dailyData, historyRaw] = await Promise.all([
111
+ user.getUserFields(uid, ['niki_points']),
112
+ db.getObject(`niki:daily:${uid}:${today}`),
113
+ db.getListRange(`niki:activity:${uid}`, 0, -1)
114
+ ]);
115
+
116
+ const currentPoints = parseInt(userData.niki_points) || 0;
117
+ const dailyScore = parseInt(dailyData ? dailyData.score : 0) || 0;
118
+ let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
119
+ if (dailyPercent > 100) dailyPercent = 100;
120
+
121
+ // ✅ history parse
122
+ const history = (historyRaw || [])
123
+ .map(safeParseMaybeJson)
124
+ .filter(Boolean)
125
+ .reverse(); // En yeni en üstte
126
+
127
+ res.json({
128
+ points: currentPoints,
129
+ dailyScore: dailyScore,
130
+ dailyCap: SETTINGS.dailyCap,
131
+ dailyPercent: dailyPercent,
132
+ history
133
+ });
134
+ } catch (err) {
135
+ return res.status(500).json({
136
+ points: 0,
137
+ dailyScore: 0,
138
+ dailyCap: SETTINGS.dailyCap,
139
+ dailyPercent: 0,
140
+ history: []
141
+ });
142
+ }
87
143
  });
88
144
 
89
- // --- İŞTE EKSİK OLAN KISIM BURASIYDI ---
90
- // 3. KASA GEÇMİŞİ (Personel Ekranı İçin)
145
+ // 3. KASA GEÇMİŞİ
91
146
  router.get('/api/niki-loyalty/kasa-history', middleware.ensureLoggedIn, async (req, res) => {
92
- const isAdmin = await user.isAdministrator(req.uid);
93
- const isMod = await user.isGlobalModerator(req.uid);
94
-
95
- if (!isAdmin && !isMod) return res.status(403).json([]);
96
-
97
- // Veritabanından listeyi çek
98
- const history = await db.getListRange('niki:kasa:history', 0, -1);
99
-
100
- // Kullanıcı resimlerini de ekleyerek zenginleştir
101
- const enrichedHistory = await Promise.all((history || []).reverse().map(async (item) => {
102
- const uData = await user.getUserFields(item.cuid, ['picture']);
103
- item.picture = uData.picture;
104
- return item;
105
- }));
106
-
107
- res.json(enrichedHistory);
147
+ try {
148
+ const isAdmin = await user.isAdministrator(req.uid);
149
+ const isMod = await user.isGlobalModerator(req.uid);
150
+
151
+ if (!isAdmin && !isMod) return res.status(403).json([]);
152
+
153
+ const historyRaw = await db.getListRange('niki:kasa:history', 0, -1);
154
+
155
+ const rows = (historyRaw || [])
156
+ .map(safeParseMaybeJson)
157
+ .filter(Boolean)
158
+ .reverse();
159
+
160
+ // Kullanıcı resimlerini de ekleyerek gönder (tek kayıt bozulsa bile endpoint patlamasın)
161
+ const enrichedHistory = await Promise.all(rows.map(async (item) => {
162
+ if (!item || !item.cuid) return item;
163
+
164
+ try {
165
+ const uData = await user.getUserFields(item.cuid, ['picture']);
166
+ return { ...item, picture: uData?.picture };
167
+ } catch (e) {
168
+ return item;
169
+ }
170
+ }));
171
+
172
+ res.json(enrichedHistory);
173
+ } catch (err) {
174
+ // ✅ asla crash yok
175
+ return res.status(500).json([]);
176
+ }
108
177
  });
109
- // ---------------------------------------
110
178
 
111
179
  // 4. QR OLUŞTUR
112
180
  router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
113
- const uid = req.uid;
114
- const points = parseInt(await user.getUserField(uid, 'niki_points')) || 0;
115
-
116
- if (points < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Yetersiz Puan' });
181
+ try {
182
+ const uid = req.uid;
183
+ const points = parseInt(await user.getUserField(uid, 'niki_points')) || 0;
184
+
185
+ if (points < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Yetersiz Puan' });
117
186
 
118
- const token = Math.random().toString(36).substring(2) + Date.now().toString(36);
119
- await db.set(`niki:qr:${token}`, uid);
120
- await db.expire(`niki:qr:${token}`, 120);
187
+ const token = Math.random().toString(36).substring(2) + Date.now().toString(36);
188
+ await db.set(`niki:qr:${token}`, uid);
189
+ await db.expire(`niki:qr:${token}`, 120);
121
190
 
122
- return res.json({ success: true, token: token });
191
+ return res.json({ success: true, token: token });
192
+ } catch (err) {
193
+ return res.status(500).json({ success: false, message: 'Sunucu hatası' });
194
+ }
123
195
  });
124
196
 
125
- // 5. QR OKUT (Ödeme Alma)
197
+ // 5. QR OKUT (Ödeme Alma + LOGLAMA)
126
198
  router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
127
- const { token } = req.body;
128
-
129
- const isAdmin = await user.isAdministrator(req.uid);
130
- const isMod = await user.isGlobalModerator(req.uid);
131
- if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
199
+ try {
200
+ const { token } = req.body;
201
+
202
+ const isAdmin = await user.isAdministrator(req.uid);
203
+ const isMod = await user.isGlobalModerator(req.uid);
204
+ if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
132
205
 
133
- const customerUid = await db.get(`niki:qr:${token}`);
134
- if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
206
+ const customerUid = await db.get(`niki:qr:${token}`);
207
+ if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
135
208
 
136
- const pts = parseInt(await user.getUserField(customerUid, 'niki_points')) || 0;
137
- if (pts < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Yetersiz Bakiye' });
209
+ const pts = parseInt(await user.getUserField(customerUid, 'niki_points')) || 0;
210
+ if (pts < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Yetersiz Bakiye' });
138
211
 
139
- // İŞLEM
140
- await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
141
- await db.delete(`niki:qr:${token}`);
212
+ // İŞLEM
213
+ await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
214
+ await db.delete(`niki:qr:${token}`);
142
215
 
143
- // LOGLARI KAYDET
144
- const customerData = await user.getUserFields(customerUid, ['username', 'picture']);
145
-
146
- // Cüzdan için: "Kahve Keyfi"
147
- await addUserLog(customerUid, 'spend', SETTINGS.coffeeCost, 'Kahve Keyfi ☕');
148
-
149
- // Kasa için: Kayıt
150
- await addKasaLog(req.uid, customerData.username, customerUid);
216
+ // DETAYLI LOG KAYDI
217
+ const customerData = await user.getUserFields(customerUid, ['username', 'picture']);
151
218
 
152
- return res.json({ success: true, customer: customerData, message: 'Onaylandı!' });
219
+ // Cüzdan için açıklama
220
+ await addUserLog(customerUid, 'spend', SETTINGS.coffeeCost, 'Kahve Keyfi ☕');
221
+
222
+ // Kasa için kayıt
223
+ await addKasaLog(req.uid, customerData.username, customerUid);
224
+
225
+ return res.json({ success: true, customer: customerData, message: 'Onaylandı!' });
226
+ } catch (err) {
227
+ return res.status(500).json({ success: false, message: 'Sunucu hatası' });
228
+ }
153
229
  });
154
230
 
155
231
  // SAYFA ROTASI
@@ -171,4 +247,4 @@ Plugin.addNavigation = async function (nav) {
171
247
  return nav;
172
248
  };
173
249
 
174
- module.exports = Plugin;
250
+ module.exports = Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-niki-loyalty",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {