nodebb-plugin-niki-loyalty 1.0.14 → 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 +164 -85
  2. package/package.json +1 -1
package/library.js CHANGED
@@ -7,11 +7,39 @@ const routeHelpers = require.main.require('./src/controllers/helpers');
7
7
  const Plugin = {};
8
8
 
9
9
  const SETTINGS = {
10
- pointsPerHeartbeat: 50,
11
- dailyCap: 2500000,
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 = {
@@ -20,8 +48,12 @@ async function addUserLog(uid, type, amount, desc) {
20
48
  amt: amount,
21
49
  txt: desc // "Kahve Keyfi ☕" gibi metin
22
50
  };
51
+
52
+ const payload = safeStringify(logEntry);
53
+ if (!payload) return;
54
+
23
55
  // Öğrenci geçmişine ekle (Son 50 işlem)
24
- await db.listAppend(`niki:activity:${uid}`, logEntry);
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
  };
68
+
69
+ const payload = safeStringify(logEntry);
70
+ if (!payload) return;
71
+
36
72
  // Kasa defterine ekle (Son 100 işlem)
37
- await db.listAppend('niki:kasa:history', logEntry);
73
+ await db.listAppend('niki:kasa:history', payload);
38
74
  await db.listTrim('niki:kasa:history', -100, -1);
39
75
  }
40
76
 
@@ -44,109 +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() // En yeni en üstte
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
- // 3. KASA GEÇMİŞİ (Niki Kasa'nın Görmediği Kısım Burasıydı)
145
+ // 3. KASA GEÇMİŞİ
90
146
  router.get('/api/niki-loyalty/kasa-history', middleware.ensureLoggedIn, async (req, res) => {
91
- const isAdmin = await user.isAdministrator(req.uid);
92
- const isMod = await user.isGlobalModerator(req.uid);
93
-
94
- if (!isAdmin && !isMod) return res.status(403).json([]);
95
-
96
- const history = await db.getListRange('niki:kasa:history', 0, -1);
97
-
98
- // Kullanıcı resimlerini de ekleyerek gönder
99
- const enrichedHistory = await Promise.all((history || []).reverse().map(async (item) => {
100
- const uData = await user.getUserFields(item.cuid, ['picture']);
101
- item.picture = uData.picture;
102
- return item;
103
- }));
104
-
105
- 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
+ }
106
177
  });
107
178
 
108
179
  // 4. QR OLUŞTUR
109
180
  router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
110
- const uid = req.uid;
111
- const points = parseInt(await user.getUserField(uid, 'niki_points')) || 0;
112
-
113
- 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' });
114
186
 
115
- const token = Math.random().toString(36).substring(2) + Date.now().toString(36);
116
- await db.set(`niki:qr:${token}`, uid);
117
- 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);
118
190
 
119
- 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
+ }
120
195
  });
121
196
 
122
197
  // 5. QR OKUT (Ödeme Alma + LOGLAMA)
123
198
  router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
124
- const { token } = req.body;
125
-
126
- const isAdmin = await user.isAdministrator(req.uid);
127
- const isMod = await user.isGlobalModerator(req.uid);
128
- 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' });
129
205
 
130
- const customerUid = await db.get(`niki:qr:${token}`);
131
- 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' });
132
208
 
133
- const pts = parseInt(await user.getUserField(customerUid, 'niki_points')) || 0;
134
- 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' });
135
211
 
136
- // İŞLEM
137
- await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
138
- await db.delete(`niki:qr:${token}`);
212
+ // İŞLEM
213
+ await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
214
+ await db.delete(`niki:qr:${token}`);
139
215
 
140
- // DETAYLI LOG KAYDI
141
- const customerData = await user.getUserFields(customerUid, ['username', 'picture']);
142
-
143
- // Cüzdan için açıklama: "Kahve Keyfi ☕"
144
- await addUserLog(customerUid, 'spend', SETTINGS.coffeeCost, 'Kahve Keyfi ☕');
145
-
146
- // Kasa için kayıt
147
- await addKasaLog(req.uid, customerData.username, customerUid);
216
+ // DETAYLI LOG KAYDI
217
+ const customerData = await user.getUserFields(customerUid, ['username', 'picture']);
148
218
 
149
- 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
+ }
150
229
  });
151
230
 
152
231
  // SAYFA ROTASI
@@ -168,4 +247,4 @@ Plugin.addNavigation = async function (nav) {
168
247
  return nav;
169
248
  };
170
249
 
171
- 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.14",
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": {