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.
- package/library.js +168 -92
- 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:
|
|
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 = {
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
206
|
+
const customerUid = await db.get(`niki:qr:${token}`);
|
|
207
|
+
if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
|
|
135
208
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
212
|
+
// İŞLEM
|
|
213
|
+
await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
|
|
214
|
+
await db.delete(`niki:qr:${token}`);
|
|
142
215
|
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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;
|