nodebb-plugin-niki-loyalty 1.0.8 → 1.0.9

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 +110 -116
  2. package/package.json +1 -1
package/library.js CHANGED
@@ -1,128 +1,122 @@
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
+ };
29
14
 
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');
15
+ Plugin.init = async function (params) {
16
+ const router = params.router;
17
+ const middleware = params.middleware;
18
+
19
+ // 1. HEARTBEAT (Puan Kazanma)
20
+ router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
21
+ const uid = req.uid;
22
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
23
+ const dailyKey = `niki:daily:${uid}:${today}`;
24
+
25
+ const currentDailyScore = await db.getObjectField(dailyKey, 'score') || 0;
26
+
27
+ if (parseInt(currentDailyScore) >= SETTINGS.dailyCap) {
28
+ return res.json({ earned: false, reason: 'daily_cap' });
36
29
  }
37
30
 
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
- }
31
+ await user.incrementUserFieldBy(uid, 'niki_points', SETTINGS.pointsPerHeartbeat);
32
+ await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
33
+
34
+ const newBalance = await user.getUserField(uid, 'niki_points');
35
+ return res.json({ earned: true, points: SETTINGS.pointsPerHeartbeat, total: newBalance });
36
+ });
37
+
38
+ // 2. WALLET DATA (Bilgi Çekme)
39
+ router.get('/api/niki-loyalty/wallet-data', middleware.ensureLoggedIn, async (req, res) => {
40
+ const uid = req.uid;
41
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
42
+
43
+ const [userData, dailyData] = await Promise.all([
44
+ user.getUserFields(uid, ['niki_points']),
45
+ db.getObject(`niki:daily:${uid}:${today}`)
46
+ ]);
47
+
48
+ const currentPoints = parseInt(userData.niki_points) || 0;
49
+ const dailyScore = parseInt(dailyData ? dailyData.score : 0) || 0;
50
+ let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
51
+ if (dailyPercent > 100) dailyPercent = 100;
52
+
53
+ res.json({
54
+ points: currentPoints,
55
+ dailyScore: dailyScore,
56
+ dailyCap: SETTINGS.dailyCap,
57
+ dailyPercent: dailyPercent
61
58
  });
62
- }
59
+ });
63
60
 
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;
61
+ // 3. QR TOKEN ÜRET (Öğrenci Butona Basınca)
62
+ router.post('/api/niki-loyalty/generate-qr', middleware.ensureLoggedIn, async (req, res) => {
63
+ const uid = req.uid;
64
+ const points = parseInt(await user.getUserField(uid, 'niki_points')) || 0;
65
+
66
+ if (points < SETTINGS.coffeeCost) {
67
+ return res.json({ success: false, message: 'Yetersiz Puan' });
69
68
  }
70
- }
71
69
 
72
- // Başlat
73
- initNikiWidget();
70
+ // Token Oluştur
71
+ const token = Math.random().toString(36).substring(2) + Date.now().toString(36);
72
+
73
+ // Kaydet (2 dakika geçerli)
74
+ await db.set(`niki:qr:${token}`, uid);
75
+ await db.expire(`niki:qr:${token}`, 120);
74
76
 
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
77
+ return res.json({ success: true, token: token });
79
78
  });
80
79
 
81
- // --- AKTİFLİK SİSTEMİ (Heartbeat) ---
82
- let activeSeconds = 0;
83
- let isUserActive = false;
84
- let idleTimer;
85
-
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
- });
80
+ // 4. QR OKUTMA (Personel Tarayınca)
81
+ router.post('/api/niki-loyalty/scan-qr', middleware.ensureLoggedIn, async (req, res) => {
82
+ const { token } = req.body;
83
+
84
+ // Sadece Admin/Mod yetkisi
85
+ const isAdmin = await user.isAdministrator(req.uid);
86
+ const isMod = await user.isGlobalModerator(req.uid);
87
+ if (!isAdmin && !isMod) return res.status(403).json({ success: false, message: 'Yetkisiz' });
88
+
89
+ const customerUid = await db.get(`niki:qr:${token}`);
90
+ if (!customerUid) return res.json({ success: false, message: 'Geçersiz Kod' });
91
+
92
+ // Puan Düş
93
+ const pts = parseInt(await user.getUserField(customerUid, 'niki_points')) || 0;
94
+ if (pts < SETTINGS.coffeeCost) return res.json({ success: false, message: 'Bakiye Yetersiz' });
95
+
96
+ await user.decrementUserFieldBy(customerUid, 'niki_points', SETTINGS.coffeeCost);
97
+ await db.delete(`niki:qr:${token}`);
98
+
99
+ const customerData = await user.getUserFields(customerUid, ['username', 'picture']);
100
+ return res.json({ success: true, customer: customerData, message: 'Onaylandı!' });
101
+ });
102
+
103
+ // 5. NIKI KASA SAYFASI (Erişim Kontrolü)
104
+ routeHelpers.setupPageRoute(router, '/niki-kasa', middleware, [], async (req, res) => {
105
+ const isAdmin = await user.isAdministrator(req.uid);
106
+ const isMod = await user.isGlobalModerator(req.uid);
107
+ if (!isAdmin && !isMod) return res.render('403', {});
108
+ res.render('niki-kasa', { title: 'Niki Kasa' });
109
+ });
110
+ };
111
+
112
+ Plugin.addScripts = async function (scripts) {
113
+ scripts.push('plugins/nodebb-plugin-niki-loyalty/static/lib/client.js');
114
+ return scripts;
115
+ };
116
+
117
+ Plugin.addNavigation = async function (nav) {
118
+ nav.push({ route: "/niki-wallet", title: "Niki Cüzdan", enabled: true, iconClass: "fa-coffee", text: "Niki Cüzdan" });
119
+ return nav;
120
+ };
121
+
122
+ 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.9",
4
4
  "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
5
  "main": "library.js",
6
6
  "nbbpm": {