nodebb-plugin-niki-loyalty 1.0.8 → 1.0.10

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 +135 -116
  2. package/package.json +1 -1
package/library.js CHANGED
@@ -1,128 +1,147 @@
1
1
  'use strict';
2
2
 
3
- $(document).ready(function () {
4
- // --- AYARLAR ---
5
- // 1. Logo Ayarı (Senin çalışan linkin)
6
- const NIKI_LOGO_URL = "https://i.ibb.co/nZvtpss/logo-placeholder.png";
7
-
8
- // Widget HTML Şablonu
9
- const widgetHtml = `
10
- <div id="niki-floating-widget" class="niki-hidden">
11
- <div class="niki-widget-content" onclick="ajaxify.go('niki-wallet')">
12
- <img src="${NIKI_LOGO_URL}" class="niki-widget-logo" alt="Niki">
13
- <div class="niki-widget-text">
14
- <span class="niki-lbl">PUANIM</span>
15
- <span class="niki-val" id="niki-live-points">...</span>
16
- </div>
17
- </div>
18
- </div>
19
- `;
20
-
21
- // 1. Widget Başlatma ve Veri Yönetimi
22
- function initNikiWidget() {
23
- if (!app.user.uid || app.user.uid <= 0) return;
24
-
25
- // Widget yoksa ekle
26
- if ($('#niki-floating-widget').length === 0) {
27
- $('body').append(widgetHtml);
28
- }
3
+ const db = require.main.require('./src/database');
4
+ const user = require.main.require('./src/user');
5
+ const routeHelpers = require.main.require('./src/controllers/helpers');
6
+
7
+ const Plugin = {};
8
+
9
+ const SETTINGS = {
10
+ pointsPerHeartbeat: 5,
11
+ dailyCap: 250,
12
+ coffeeCost: 250
13
+ };
14
+
15
+ // --- YARDIMCI: İŞLEM GEÇMİŞİNE EKLE ---
16
+ async function addLog(uid, type, amount, description) {
17
+ const logEntry = {
18
+ timestamp: Date.now(),
19
+ type: type, // 'earn' veya 'spend'
20
+ amount: amount,
21
+ desc: description
22
+ };
23
+ // Listeye ekle (Sondan ekler)
24
+ await db.listAppend(`niki:activity:${uid}`, logEntry);
25
+
26
+ // Son 50 işlemi tut, gerisini sil (Veritabanı şişmesin)
27
+ await db.listTrim(`niki:activity:${uid}`, -50, -1);
28
+ }
29
+
30
+ Plugin.init = async function (params) {
31
+ const router = params.router;
32
+ const middleware = params.middleware;
33
+
34
+ // 1. HEARTBEAT (Puan Kazanma + LOG)
35
+ router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
36
+ const uid = req.uid;
37
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
38
+ const dailyKey = `niki:daily:${uid}:${today}`;
39
+
40
+ const currentDailyScore = await db.getObjectField(dailyKey, 'score') || 0;
29
41
 
30
- // --- HIZLI YÜKLEME (CACHE) ---
31
- // Önce hafızadaki son puanı hemen göster (Bekletme yapmaz)
32
- const cachedPoints = localStorage.getItem('niki_last_points');
33
- if (cachedPoints !== null) {
34
- $('#niki-live-points').text(cachedPoints);
35
- $('#niki-floating-widget').removeClass('niki-hidden');
42
+ if (parseInt(currentDailyScore) >= SETTINGS.dailyCap) {
43
+ return res.json({ earned: false, reason: 'daily_cap' });
36
44
  }
37
45
 
38
- // Logo Kontrolü (Garanti olsun)
39
- fixLogo();
40
-
41
- // --- GÜNCEL VERİ ÇEKME ---
42
- // Arka planda sunucuya sor: "Puan değişti mi?"
43
- $.get('/api/niki-loyalty/wallet-data', function(data) {
44
- const freshPoints = data.points || 0;
45
-
46
- // Puanı güncelle
47
- $('#niki-live-points').text(freshPoints);
48
- $('#niki-floating-widget').removeClass('niki-hidden'); // İlk kez açılıyorsa göster
49
-
50
- // Yeni puanı hafızaya at (Bir sonraki giriş için)
51
- localStorage.setItem('niki_last_points', freshPoints);
52
-
53
- // Logoyu tekrar kontrol et (Resim geç yüklendiyse)
54
- fixLogo();
55
- }).fail(function() {
56
- // Hata olursa ve cache yoksa 0 yaz
57
- if (cachedPoints === null) {
58
- $('#niki-live-points').text('0');
59
- $('#niki-floating-widget').removeClass('niki-hidden');
60
- }
46
+ await user.incrementUserFieldBy(uid, 'niki_points', SETTINGS.pointsPerHeartbeat);
47
+ await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
48
+
49
+ // LOG EKLEME (Her 5 puan için log şişirmemek adına, sadece veritabanına sessizce işleyelim)
50
+ // Ancak kullanıcı her saniye log görmesin diye buraya eklemiyoruz.
51
+ // İstersen "Ders Çalışma Oturumu" bittiğinde toplu ekleyebiliriz ama şimdilik basit tutalım.
52
+
53
+ const newBalance = await user.getUserField(uid, 'niki_points');
54
+ return res.json({ earned: true, points: SETTINGS.pointsPerHeartbeat, total: newBalance });
55
+ });
56
+
57
+ // 2. WALLET DATA + GEÇMİŞİ ÇEKME
58
+ router.get('/api/niki-loyalty/wallet-data', middleware.ensureLoggedIn, async (req, res) => {
59
+ const uid = req.uid;
60
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
61
+
62
+ const [userData, dailyData, history] = await Promise.all([
63
+ user.getUserFields(uid, ['niki_points']),
64
+ db.getObject(`niki:daily:${uid}:${today}`),
65
+ db.getListRange(`niki:activity:${uid}`, 0, -1) // Tüm geçmişi çek
66
+ ]);
67
+
68
+ // Geçmişi ters çevir (En yeni en üstte)
69
+ const recentActivity = (history || []).reverse();
70
+
71
+ const currentPoints = parseInt(userData.niki_points) || 0;
72
+ const dailyScore = parseInt(dailyData ? dailyData.score : 0) || 0;
73
+ let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
74
+ if (dailyPercent > 100) dailyPercent = 100;
75
+
76
+ res.json({
77
+ points: currentPoints,
78
+ dailyScore: dailyScore,
79
+ dailyCap: SETTINGS.dailyCap,
80
+ dailyPercent: dailyPercent,
81
+ history: recentActivity // Frontend'e gönder
61
82
  });
62
- }
83
+ });
63
84
 
64
- // Logo Düzeltici (Senin çalışan kodun entegresi)
65
- function fixLogo() {
66
- const img = document.querySelector("img.niki-widget-logo");
67
- if (img && img.src !== NIKI_LOGO_URL) {
68
- img.src = NIKI_LOGO_URL;
69
- }
70
- }
85
+ // 3. QR TOKEN ÜRET
86
+ router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
87
+ const uid = req.uid;
88
+ const points = parseInt(await user.getUserField(uid, 'niki_points')) || 0;
89
+
90
+ if (points < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Yetersiz Puan' });
71
91
 
72
- // Başlat
73
- initNikiWidget();
92
+ const token = Math.random().toString(36).substring(2) + Date.now().toString(36);
93
+ await db.set(`niki:qr:${token}`, uid);
94
+ await db.expire(`niki:qr:${token}`, 120);
74
95
 
75
- // Sayfa Geçişlerinde Tekrar Çalıştır
76
- $(window).on('action:ajaxify.end', function () {
77
- initNikiWidget();
78
- setTimeout(fixLogo, 500); // 0.5sn sonra son bir kontrol
96
+ return res.json({ success: true, token: token });
79
97
  });
80
98
 
81
- // --- AKTİFLİK SİSTEMİ (Heartbeat) ---
82
- let activeSeconds = 0;
83
- let isUserActive = false;
84
- let idleTimer;
99
+ // 4. QR OKUTMA (Harcama + LOG)
100
+ router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
101
+ const { token } = req.body;
102
+
103
+ const isAdmin = await user.isAdministrator(req.uid);
104
+ const isMod = await user.isGlobalModerator(req.uid);
105
+ if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
106
+
107
+ const customerUid = await db.get(`niki:qr:${token}`);
108
+ if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
109
+
110
+ const pts = parseInt(await user.getUserField(customerUid, 'niki_points')) || 0;
111
+ if (pts < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Bakiye Yetersiz' });
112
+
113
+ // Puan Düş
114
+ await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
115
+ await db.delete(`niki:qr:${token}`);
116
+
117
+ // LOG KAYDET: Harcama
118
+ await addLog(customerUid, 'spend', SETTINGS.coffeeCost, 'Kahve Harcaması');
119
+
120
+ const customerData = await user.getUserFields(customerUid, ['username', 'picture']);
121
+ return res.json({ success: true, customer: customerData, message: 'Onaylandı!' });
122
+ });
123
+
124
+ // ** Manuel Puan Ekleme API'si (Test veya Bonus İçin) **
125
+ // Buraya "Kazanma Logu" ekleyelim ki tabloda görünsün.
126
+ // Client tarafındaki "heartbeat" çok sık çalıştığı için veritabanını şişirmemek adına
127
+ // oraya log koymadık. Ama günlük hedefe ulaşınca veya manuel eklenince log düşebiliriz.
85
128
 
86
- function resetIdleTimer() {
87
- isUserActive = true;
88
- clearTimeout(idleTimer);
89
- idleTimer = setTimeout(() => { isUserActive = false; }, 30000);
90
- }
91
- $(window).on('mousemove scroll keydown click touchstart', resetIdleTimer);
92
-
93
- setInterval(() => {
94
- if (ajaxify.data.template.topic && document.visibilityState === 'visible' && isUserActive) {
95
- activeSeconds++;
96
- }
97
- if (activeSeconds >= 60) {
98
- sendHeartbeat();
99
- activeSeconds = 0;
100
- }
101
- }, 1000);
102
-
103
- function sendHeartbeat() {
104
- $.post('/api/niki-loyalty/heartbeat', { _csrf: config.csrf_token }, function(res) {
105
- if (res.earned) {
106
- // Puanı güncelle
107
- $('#niki-live-points').text(res.total);
108
- // Hafızayı da güncelle
109
- localStorage.setItem('niki_last_points', res.total);
110
-
111
- showNikiToast(`+${res.points} Puan Kazandın! ☕`);
112
- $('#niki-floating-widget').addClass('niki-bounce');
113
- setTimeout(() => $('#niki-floating-widget').removeClass('niki-bounce'), 500);
114
- }
115
- });
116
- }
117
-
118
- function showNikiToast(msg) {
119
- $('.niki-toast').remove();
120
- const toast = $(`<div class="niki-toast"><i class="fa fa-paw"></i> ${msg}</div>`);
121
- $('body').append(toast);
122
- setTimeout(() => { toast.addClass('show'); }, 100);
123
- setTimeout(() => {
124
- toast.removeClass('show');
125
- setTimeout(() => toast.remove(), 3000);
126
- }, 3000);
127
- }
128
- });
129
+ // NIKI KASA SAYFASI
130
+ routeHelpers.setupPageRoute(router, '/niki-kasa', middleware, [], async (req, res) => {
131
+ const isAdmin = await user.isAdministrator(req.uid);
132
+ if (!isAdmin) return res.render('403', {});
133
+ res.render('niki-kasa', { title: 'Niki Kasa' });
134
+ });
135
+ };
136
+
137
+ Plugin.addScripts = async function (scripts) {
138
+ scripts.push('plugins/nodebb-plugin-niki-loyalty/static/lib/client.js');
139
+ return scripts;
140
+ };
141
+
142
+ Plugin.addNavigation = async function (nav) {
143
+ nav.push({ route: "/niki-wallet", title: "Niki Cüzdan", enabled: true, iconClass: "fa-coffee", text: "Niki Cüzdan" });
144
+ return nav;
145
+ };
146
+
147
+ module.exports = Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-niki-loyalty",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {