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.
- package/library.js +164 -85
- 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:
|
|
11
|
-
dailyCap:
|
|
12
|
-
coffeeCost:
|
|
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}`,
|
|
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',
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
58
|
-
await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
|
|
88
|
+
const currentDailyScore = await db.getObjectField(dailyKey, 'score') || 0;
|
|
59
89
|
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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İŞİ
|
|
145
|
+
// 3. KASA GEÇMİŞİ
|
|
90
146
|
router.get('/api/niki-loyalty/kasa-history', middleware.ensureLoggedIn, async (req, res) => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
206
|
+
const customerUid = await db.get(`niki:qr:${token}`);
|
|
207
|
+
if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
|
|
132
208
|
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
212
|
+
// İŞLEM
|
|
213
|
+
await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
|
|
214
|
+
await db.delete(`niki:qr:${token}`);
|
|
139
215
|
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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;
|