nodebb-plugin-niki-loyalty 1.0.25 → 1.0.28

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 CHANGED
@@ -8,157 +8,106 @@ const nconf = require.main.require('nconf');
8
8
  const Plugin = {};
9
9
 
10
10
  // =========================
11
- // AYARLAR
11
+ // SETTINGS & REWARDS
12
12
  // =========================
13
13
  const SETTINGS = {
14
14
  pointsPerHeartbeat: 5,
15
15
  dailyCap: 250,
16
-
17
- barMax: 250,
18
- rewards: [
19
- { id: 'cookie', at: 60, title: 'Ücretsiz Kurabiye', type: 'free_item', meta: { item: 'cookie' } },
20
- { id: 'c35', at: 120, title: '%35 İndirimli Kahve', type: 'discount', meta: { product: 'coffee', percent: 35 } },
21
- { id: 'c60', at: 180, title: '%60 İndirimli Kahve', type: 'discount', meta: { product: 'coffee', percent: 60 } },
22
- { id: 'coffee', at: 250, title: 'Ücretsiz Kahve', type: 'free_item', meta: { item: 'coffee' } },
23
- ],
24
-
25
- couponTTLSeconds: 10 * 60, // 10 dk
26
16
  };
27
17
 
18
+ // Rewards configuration (Ordered from highest cost to lowest)
19
+ const REWARDS = [
20
+ { cost: 250, name: "Ücretsiz Kahve ☕" },
21
+ { cost: 180, name: "%60 İndirimli Kahve" },
22
+ { cost: 120, name: "%30 İndirimli Kahve" },
23
+ { cost: 60, name: "1 Kurabiye 🍪" }
24
+ ];
28
25
 
29
- // TEST: sınırsız kullanım (puan kontrolünü kapatmak için true)
26
+ // TEST MODE (Set to true to bypass point checks)
30
27
  const TEST_MODE_UNLIMITED = false;
31
28
 
32
29
  // =========================
33
- // JSON SAFE HELPERS
30
+ // HELPER FUNCTIONS
34
31
  // =========================
35
32
  function safeParseMaybeJson(x) {
36
33
  if (x == null) return null;
37
34
  if (typeof x === 'object') return x;
38
-
39
35
  if (typeof x === 'string') {
40
- try {
41
- return JSON.parse(x);
42
- } catch (e) {
43
- return null;
44
- }
36
+ try { return JSON.parse(x); } catch (e) { return null; }
45
37
  }
46
38
  return null;
47
39
  }
48
40
 
49
41
  function safeStringify(obj) {
50
- try {
51
- return JSON.stringify(obj);
52
- } catch (e) {
53
- return null;
54
- }
55
- }
56
-
57
- function makeProfileUrl(userslug) {
58
- const rp = nconf.get('relative_path') || '';
59
- if (!userslug) return '';
60
- return `${rp}/user/${userslug}`;
61
- }
62
-
63
- function makeToken() {
64
- return Math.random().toString(36).substring(2) + Date.now().toString(36);
65
- }
66
-
67
- function getTodayKey() {
68
- return new Date().toISOString().slice(0, 10).replace(/-/g, '');
69
- }
70
-
71
- function findRewardById(id) {
72
- return SETTINGS.rewards.find(r => r.id === id) || null;
73
- }
74
-
75
- async function isStaff(uid) {
76
- const [isAdmin, isMod] = await Promise.all([
77
- user.isAdministrator(uid),
78
- user.isGlobalModerator(uid),
79
- ]);
80
- return isAdmin || isMod;
42
+ try { return JSON.stringify(obj); } catch (e) { return null; }
81
43
  }
82
44
 
83
45
  // =========================
84
- // LOG FONKSİYONLARI
46
+ // LOGGING
85
47
  // =========================
86
- async function addUserLog(uid, type, amount, desc, extra) {
48
+ async function addUserLog(uid, type, amount, desc) {
87
49
  const logEntry = {
88
50
  ts: Date.now(),
89
51
  type, // 'earn' | 'spend'
90
52
  amt: amount,
91
53
  txt: desc,
92
- ...(extra ? { extra } : {}),
93
54
  };
94
-
95
55
  const payload = safeStringify(logEntry);
96
56
  if (!payload) return;
97
-
98
57
  await db.listAppend(`niki:activity:${uid}`, payload);
99
58
  await db.listTrim(`niki:activity:${uid}`, -50, -1);
100
59
  }
101
60
 
102
- async function addKasaLog(staffUid, customerName, customerUid, amount, rewardId, rewardTitle) {
61
+ async function addKasaLog(staffUid, customerName, customerUid, rewardName, amount) {
103
62
  const logEntry = {
104
63
  ts: Date.now(),
105
64
  staff: staffUid,
106
65
  cust: customerName,
107
66
  cuid: customerUid,
108
67
  amt: amount,
109
- rewardId: rewardId || '',
110
- rewardTitle: rewardTitle || '',
68
+ reward: rewardName // Store the specific reward name
111
69
  };
112
-
113
70
  const payload = safeStringify(logEntry);
114
71
  if (!payload) return;
115
-
116
72
  await db.listAppend('niki:kasa:history', payload);
117
73
  await db.listTrim('niki:kasa:history', -100, -1);
118
74
  }
119
75
 
120
76
  // =========================
121
- // INIT
77
+ // PLUGIN INIT
122
78
  // =========================
123
79
  Plugin.init = async function (params) {
124
80
  const router = params.router;
125
81
  const middleware = params.middleware;
126
82
 
127
- // -------------------------
128
- // 1) HEARTBEAT (puan kazanma)
129
- // -------------------------
83
+ // 1) HEARTBEAT (Earn Points)
130
84
  router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
131
85
  try {
132
86
  const uid = req.uid;
133
- const today = getTodayKey();
87
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
134
88
  const dailyKey = `niki:daily:${uid}:${today}`;
135
89
 
136
90
  const currentDailyScore = parseInt((await db.getObjectField(dailyKey, 'score')) || 0, 10);
137
91
 
138
- if (!TEST_MODE_UNLIMITED && currentDailyScore >= SETTINGS.dailyCap) {
92
+ if (currentDailyScore >= SETTINGS.dailyCap) {
139
93
  return res.json({ earned: false, reason: 'daily_cap' });
140
94
  }
141
95
 
142
96
  await user.incrementUserFieldBy(uid, 'niki_points', SETTINGS.pointsPerHeartbeat);
143
97
  await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
144
98
 
145
- const newBalance = parseInt((await user.getUserField(uid, 'niki_points')) || 0, 10);
146
-
147
- await addUserLog(uid, 'earn', SETTINGS.pointsPerHeartbeat, 'Aktiflik Puanı ⚡');
148
-
99
+ const newBalance = await user.getUserField(uid, 'niki_points');
149
100
  return res.json({ earned: true, points: SETTINGS.pointsPerHeartbeat, total: newBalance });
150
101
  } catch (err) {
151
102
  return res.status(500).json({ earned: false, reason: 'server_error' });
152
103
  }
153
104
  });
154
105
 
155
- // -------------------------
156
- // 2) WALLET DATA (cüzdan + geçmiş + rewards)
157
- // -------------------------
106
+ // 2) WALLET DATA (Data + History + Rewards Info)
158
107
  router.get('/api/niki-loyalty/wallet-data', middleware.ensureLoggedIn, async (req, res) => {
159
108
  try {
160
109
  const uid = req.uid;
161
- const today = getTodayKey();
110
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
162
111
 
163
112
  const [userData, dailyData, historyRaw] = await Promise.all([
164
113
  user.getUserFields(uid, ['niki_points']),
@@ -172,144 +121,36 @@ Plugin.init = async function (params) {
172
121
  let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
173
122
  if (dailyPercent > 100) dailyPercent = 100;
174
123
 
175
- const barPercent = Math.min(100, (currentPoints / SETTINGS.barMax) * 100);
176
-
177
124
  const history = (historyRaw || [])
178
125
  .map(safeParseMaybeJson)
179
126
  .filter(Boolean)
180
127
  .reverse();
181
128
 
182
- const rewards = SETTINGS.rewards.map(r => ({
183
- id: r.id,
184
- at: r.at,
185
- title: r.title,
186
- type: r.type,
187
- meta: r.meta,
188
- unlocked: currentPoints >= r.at,
189
- }));
190
-
191
129
  return res.json({
192
130
  points: currentPoints,
193
-
194
131
  dailyScore,
195
132
  dailyCap: SETTINGS.dailyCap,
196
133
  dailyPercent,
197
-
198
- barMax: SETTINGS.barMax,
199
- barPercent,
200
-
201
- rewards,
202
134
  history,
135
+ rewards: REWARDS // Send reward tiers to frontend
203
136
  });
204
137
  } catch (err) {
205
- return res.status(500).json({
206
- points: 0,
207
- dailyScore: 0,
208
- dailyCap: SETTINGS.dailyCap,
209
- dailyPercent: 0,
210
- barMax: SETTINGS.barMax,
211
- barPercent: 0,
212
- rewards: SETTINGS.rewards.map(r => ({ id: r.id, at: r.at, title: r.title, type: r.type, meta: r.meta, unlocked: false })),
213
- history: [],
214
- });
138
+ return res.status(500).json({ points: 0, history: [] });
215
139
  }
216
140
  });
217
141
 
218
- // -------------------------
219
- // 3) CLAIM REWARD (Seçenek B: claim = puan düşer + kupon token üretir)
220
- // Client bu endpoint'i çağıracak.
221
- // -------------------------
222
- router.post('/api/niki-loyalty/claim', middleware.ensureLoggedIn, async (req, res) => {
223
- try {
224
- const uid = req.uid;
225
- const rewardId = (req.body && req.body.rewardId) ? String(req.body.rewardId) : '';
226
- const reward = findRewardById(rewardId);
227
- if (!reward) return res.json({ success: false, message: 'Geçersiz ödül' });
228
-
229
- const points = parseInt((await user.getUserField(uid, 'niki_points')) || 0, 10);
230
- if (!TEST_MODE_UNLIMITED && points < reward.at) {
231
- return res.json({ success: false, message: 'Yetersiz puan' });
232
- }
233
-
234
- // ✅ Aynı ödülden aktif kupon varsa tekrar üretme (spam engeli)
235
- const reserveKey = `niki:reserve:${uid}:${reward.id}`;
236
- const existingToken = await db.get(reserveKey);
237
- if (existingToken) {
238
- return res.json({
239
- success: true,
240
- token: existingToken,
241
- reward: { id: reward.id, at: reward.at, title: reward.title, type: reward.type, meta: reward.meta },
242
- message: 'Zaten aktif bir kuponun var. Kasada okutabilirsin.',
243
- });
244
- }
245
-
246
- // ✅ token oluştur
247
- const token = makeToken();
248
- const couponKey = `niki:coupon:${token}`;
249
-
250
- const couponPayload = safeStringify({
251
- token,
252
- ts: Date.now(),
253
- rewardId: reward.id,
254
- cost: reward.at, // ✅ puan düşülecek tutar
255
- title: reward.title,
256
- type: reward.type,
257
- meta: reward.meta,
258
- ownerUid: uid,
259
- });
260
-
261
- await db.set(couponKey, couponPayload);
262
- await db.expire(couponKey, SETTINGS.couponTTLSeconds);
263
-
264
- // ✅ rezervasyonu da tut
265
- await db.set(reserveKey, token);
266
- await db.expire(reserveKey, SETTINGS.couponTTLSeconds);
267
-
268
- // user log (claim logu - puan düşmeden)
269
- await addUserLog(uid, 'spend', 0, `Kupon oluşturuldu: ${reward.title}`, { rewardId: reward.id, token });
270
-
271
- return res.json({
272
- success: true,
273
- token,
274
- reward: { id: reward.id, at: reward.at, title: reward.title, type: reward.type, meta: reward.meta },
275
- message: 'Kupon hazır! Kasada okutunca puanın düşer.',
276
- });
277
- } catch (err) {
278
- return res.status(500).json({ success: false, message: 'Sunucu hatası' });
279
- }
280
- });
281
-
282
-
283
- // -------------------------
284
- // 4) KASA HISTORY (admin/mod)
285
- // -------------------------
142
+ // 3) KASA HISTORY
286
143
  router.get('/api/niki-loyalty/kasa-history', middleware.ensureLoggedIn, async (req, res) => {
287
144
  try {
288
- const staffOk = await isStaff(req.uid);
289
- if (!staffOk) return res.status(403).json([]);
145
+ const isAdmin = await user.isAdministrator(req.uid);
146
+ const isMod = await user.isGlobalModerator(req.uid);
147
+ if (!isAdmin && !isMod) return res.status(403).json([]);
290
148
 
291
149
  const raw = await db.getListRange('niki:kasa:history', 0, -1);
150
+ const rows = (raw || []).map(safeParseMaybeJson).filter(Boolean).reverse();
292
151
 
293
- const rows = (raw || [])
294
- .map((x) => {
295
- if (!x) return null;
296
- if (typeof x === 'object') return x;
297
- if (typeof x === 'string') {
298
- try { return JSON.parse(x); } catch (e) { return null; }
299
- }
300
- return null;
301
- })
302
- .filter(Boolean)
303
- .reverse();
304
-
305
- const uids = rows
306
- .map(r => parseInt(r.cuid, 10))
307
- .filter(n => Number.isFinite(n) && n > 0);
308
-
309
- const users = await user.getUsersFields(uids, [
310
- 'uid', 'username', 'userslug', 'picture', 'icon:bgColor',
311
- ]);
312
-
152
+ const uids = rows.map(r => parseInt(r.cuid, 10)).filter(n => Number.isFinite(n) && n > 0);
153
+ const users = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'icon:bgColor']);
313
154
  const userMap = {};
314
155
  (users || []).forEach(u => { userMap[u.uid] = u; });
315
156
 
@@ -319,13 +160,11 @@ Plugin.init = async function (params) {
319
160
  const uid = parseInt(r.cuid, 10);
320
161
  const u = userMap[uid];
321
162
  if (!u) return r;
322
-
323
163
  return {
324
164
  ...r,
325
165
  cust: u.username || r.cust || 'Bilinmeyen',
326
166
  userslug: u.userslug || r.userslug || '',
327
167
  picture: u.picture || r.picture || '',
328
- iconBg: u['icon:bgColor'] || r.iconBg || '#4b5563',
329
168
  profileUrl: (u.userslug ? `${rp}/user/${u.userslug}` : ''),
330
169
  };
331
170
  });
@@ -336,85 +175,20 @@ Plugin.init = async function (params) {
336
175
  }
337
176
  });
338
177
 
339
- // -------------------------
340
- // 5) KASA: COUPON SCAN (admin/mod) ✅ yeni akış
341
- // Claim ile üretilen token kasada okutulur.
342
- // -------------------------
343
- router.post('/api/niki-loyalty/scan-coupon', middleware.ensureLoggedIn, async (req, res) => {
344
- try {
345
- const token = (req.body && req.body.token) ? String(req.body.token) : '';
346
-
347
- const staffOk = await isStaff(req.uid);
348
- if (!staffOk) return res.status(403).json({ success: false, message: 'Yetkisiz' });
349
-
350
- const raw = await db.get(`niki:coupon:${token}`);
351
- if (!raw) return res.json({ success: false, message: 'Geçersiz / Süresi dolmuş kupon' });
352
-
353
- const coupon = safeParseMaybeJson(raw);
354
- if (!coupon || !coupon.ownerUid || !coupon.rewardId) {
355
- await db.delete(`niki:coupon:${token}`);
356
- return res.json({ success: false, message: 'Kupon bozuk' });
357
- }
358
-
359
- const customerUid = parseInt(coupon.ownerUid, 10);
360
- const cost = parseInt(coupon.cost || 0, 10);
361
-
362
- // ✅ puan yeter mi? (scan anında kontrol)
363
- const pts = parseInt((await user.getUserField(customerUid, 'niki_points')) || 0, 10);
364
- if (!TEST_MODE_UNLIMITED && pts < cost) {
365
- return res.json({ success: false, message: 'Yetersiz bakiye (puan değişmiş olabilir)' });
366
- }
367
-
368
- // ✅ puan düş (asıl düşüş burada)
369
- if (!TEST_MODE_UNLIMITED && cost > 0) {
370
- await user.decrementUserFieldBy(customerUid, 'niki_points', cost);
371
- }
372
-
373
- // ✅ tek kullanımlık: kuponu sil
374
- await db.delete(`niki:coupon:${token}`);
375
- // ✅ rezervasyonu da sil
376
- await db.delete(`niki:reserve:${customerUid}:${coupon.rewardId}`);
377
-
378
- const customerData = await user.getUserFields(customerUid, ['username', 'picture', 'userslug']);
379
-
380
- // user log (gerçek spend burada)
381
- await addUserLog(customerUid, 'spend', cost, `Kullanıldı: ${coupon.title}`, { rewardId: coupon.rewardId });
382
-
383
- // kasa log
384
- await addKasaLog(req.uid, customerData?.username || 'Bilinmeyen', customerUid, cost, coupon.rewardId, coupon.title);
385
-
386
- return res.json({
387
- success: true,
388
- customer: customerData,
389
- coupon: {
390
- rewardId: coupon.rewardId,
391
- title: coupon.title,
392
- type: coupon.type,
393
- meta: coupon.meta,
394
- cost,
395
- },
396
- message: 'Kupon onaylandı!',
397
- });
398
- } catch (err) {
399
- return res.status(500).json({ success: false, message: 'Sunucu hatası' });
400
- }
401
- });
402
-
403
- // -------------------------
404
- // 6) (ESKİ) QR OLUŞTUR / OKUT — opsiyonel uyumluluk
405
- // İstersen tamamen kaldırırız. Şimdilik 250 "Ücretsiz Kahve" gibi düşünebilirsin.
406
- // -------------------------
178
+ // 4) GENERATE QR (Check if user has enough for MINIMUM reward)
407
179
  router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
408
180
  try {
409
181
  const uid = req.uid;
410
182
  const points = parseInt((await user.getUserField(uid, 'niki_points')) || 0, 10);
183
+
184
+ // Get the cost of the cheapest reward
185
+ const minCost = REWARDS[REWARDS.length - 1].cost;
411
186
 
412
- if (!TEST_MODE_UNLIMITED && points < SETTINGS.coffeeCost) {
413
- return res.json({ success: false, message: 'Yetersiz Puan' });
187
+ if (!TEST_MODE_UNLIMITED && points < minCost) {
188
+ return res.json({ success: false, message: `En az ${minCost} puan gerekli.` });
414
189
  }
415
190
 
416
- const token = makeToken();
417
-
191
+ const token = Math.random().toString(36).substring(2) + Date.now().toString(36);
418
192
  await db.set(`niki:qr:${token}`, uid);
419
193
  await db.expire(`niki:qr:${token}`, 120);
420
194
 
@@ -423,75 +197,55 @@ router.post('/api/niki-loyalty/scan-coupon', middleware.ensureLoggedIn, async (r
423
197
  return res.status(500).json({ success: false, message: 'Sunucu hatası' });
424
198
  }
425
199
  });
426
- const TIERS = [60, 120, 180, 250];
427
-
428
- function pickTier(points) {
429
- // en büyük uygun tier
430
- let chosen = 0;
431
- for (const t of TIERS) if (points >= t) chosen = t;
432
- return chosen; // 0 ise yetersiz
433
- }
434
-
435
- router.post('/api/niki-loyalty/claim-auto', middleware.ensureLoggedIn, async (req, res) => {
436
- try {
437
- const uid = req.uid;
438
- const points = parseInt((await user.getUserField(uid, 'niki_points')) || 0, 10);
439
-
440
- if (TEST_MODE_UNLIMITED) {
441
- // testte 250 gibi davran
442
- const token = makeToken();
443
- await db.set(`niki:coupon:${token}`, safeStringify({ token, ts: Date.now(), ownerUid: uid, cost: 250 }));
444
- await db.expire(`niki:coupon:${token}`, 120);
445
- return res.json({ success: true, token, cost: 250 });
446
- }
447
-
448
- const cost = pickTier(points);
449
- if (!cost) return res.json({ success: false, message: 'En az 60 puan gerekli.' });
450
-
451
- const token = makeToken();
452
- await db.set(`niki:coupon:${token}`, safeStringify({
453
- token,
454
- ts: Date.now(),
455
- ownerUid: uid,
456
- cost, // ✅ kasada düşülecek miktar
457
- }));
458
- await db.expire(`niki:coupon:${token}`, 120); // ✅ 2 dk
459
-
460
- return res.json({ success: true, token, cost });
461
- } catch (e) {
462
- return res.status(500).json({ success: false, message: 'Sunucu hatası' });
463
- }
464
- });
465
200
 
201
+ // 5) SCAN QR (Determine Reward & Deduct Points)
466
202
  router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
467
203
  try {
468
204
  const token = (req.body && req.body.token) ? String(req.body.token) : '';
469
-
470
- const staffOk = await isStaff(req.uid);
471
- if (!staffOk) return res.status(403).json({ success: false, message: 'Yetkisiz' });
205
+ const isAdmin = await user.isAdministrator(req.uid);
206
+ const isMod = await user.isGlobalModerator(req.uid);
207
+ if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
472
208
 
473
209
  const customerUid = await db.get(`niki:qr:${token}`);
474
210
  if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
475
211
 
476
212
  const pts = parseInt((await user.getUserField(customerUid, 'niki_points')) || 0, 10);
477
- if (!TEST_MODE_UNLIMITED && pts < SETTINGS.coffeeCost) {
478
- return res.json({ success: false, message: 'Yetersiz Bakiye' });
213
+
214
+ // Calculate best possible reward
215
+ let selectedReward = null;
216
+ if (!TEST_MODE_UNLIMITED) {
217
+ for (const reward of REWARDS) {
218
+ if (pts >= reward.cost) {
219
+ selectedReward = reward;
220
+ break;
221
+ }
222
+ }
223
+ if (!selectedReward) {
224
+ return res.json({ success: false, message: 'Puan Yetersiz' });
225
+ }
226
+ } else {
227
+ // Default for test mode
228
+ selectedReward = REWARDS[0];
479
229
  }
480
230
 
231
+ // Deduct Points
481
232
  if (!TEST_MODE_UNLIMITED) {
482
- await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
233
+ await user.decrementUserFieldBy(customerUid, 'niki_points', selectedReward.cost);
483
234
  }
484
235
 
485
236
  await db.delete(`niki:qr:${token}`);
486
237
 
487
238
  const customerData = await user.getUserFields(customerUid, ['username', 'picture', 'userslug']);
488
239
 
489
- await addUserLog(customerUid, 'spend', SETTINGS.coffeeCost, 'Kahve Keyfi ☕');
490
- await addKasaLog(req.uid, customerData?.username || 'Bilinmeyen', customerUid, SETTINGS.coffeeCost, 'coffee', 'Ücretsiz Kahve (QR)');
240
+ // Logs
241
+ await addUserLog(customerUid, 'spend', selectedReward.cost, selectedReward.name);
242
+ await addKasaLog(req.uid, customerData?.username || 'Bilinmeyen', customerUid, selectedReward.name, selectedReward.cost);
491
243
 
492
244
  return res.json({
493
245
  success: true,
494
246
  customer: customerData,
247
+ rewardName: selectedReward.name,
248
+ cost: selectedReward.cost,
495
249
  message: 'Onaylandı!',
496
250
  });
497
251
  } catch (err) {
@@ -499,41 +253,23 @@ router.post('/api/niki-loyalty/claim-auto', middleware.ensureLoggedIn, async (re
499
253
  }
500
254
  });
501
255
 
502
- // -------------------------
503
- // 7) SAYFA ROTASI (kasa sayfası)
504
- // -------------------------
256
+ // 6) PAGE ROUTES
505
257
  routeHelpers.setupPageRoute(router, '/niki-kasa', middleware, [], async (req, res) => {
506
- const staffOk = await isStaff(req.uid);
507
- if (!staffOk) return res.render('403', {});
258
+ const isAdmin = await user.isAdministrator(req.uid);
259
+ const isMod = await user.isGlobalModerator(req.uid);
260
+ if (!isAdmin && !isMod) return res.render('403', {});
508
261
  return res.render('niki-kasa', { title: 'Niki Kasa' });
509
262
  });
510
263
  };
511
264
 
512
- // -------------------------
513
- // ✅ plugin.json'da action:topic.get -> checkTopicVisit vardı.
514
- // Method yoksa NodeBB şikayet eder. Şimdilik NO-OP koydum.
515
- // Sonra istersen gerçekten "topic view" ile puan ekleriz.
516
- // -------------------------
517
- Plugin.checkTopicVisit = async function () {
518
- return;
519
- };
520
-
521
- // client.js inject
522
265
  Plugin.addScripts = async function (scripts) {
523
266
  scripts.push('plugins/nodebb-plugin-niki-loyalty/static/lib/client.js');
524
267
  return scripts;
525
268
  };
526
269
 
527
- // navigation
528
270
  Plugin.addNavigation = async function (nav) {
529
- nav.push({
530
- route: '/niki-wallet',
531
- title: 'Niki Cüzdan',
532
- enabled: true,
533
- iconClass: 'fa-coffee',
534
- text: 'Niki Cüzdan',
535
- });
271
+ nav.push({ route: '/niki-wallet', title: 'Niki Cüzdan', enabled: true, iconClass: 'fa-coffee', text: 'Niki Cüzdan' });
536
272
  return nav;
537
273
  };
538
274
 
539
- module.exports = Plugin;
275
+ module.exports = Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-niki-loyalty",
3
- "version": "1.0.25",
3
+ "version": "1.0.28",
4
4
  "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {