feat(qr-custom): hapus satu & reset histori dengan password admin
All checks were successful
CI / test (push) Successful in 1m47s
All checks were successful
CI / test (push) Successful in 1m47s
- endpoint delete/reset, verifikasi admin_password - tombol hapus di detail + reset di header histori - versi 0.6.5
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
|
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [0.6.5] — 2026-11-06 — "qr-custom-delete"
|
||||||
|
|
||||||
|
### Ditambahkan
|
||||||
|
- **Hapus satu QR custom** dari detail (wajib password admin).
|
||||||
|
- **Reset seluruh histori QR custom** dari header tabel (wajib password admin, konfirmasi).
|
||||||
|
|
||||||
## [0.6.4] — 2026-11-06 — "qr-custom-ux"
|
## [0.6.4] — 2026-11-06 — "qr-custom-ux"
|
||||||
|
|
||||||
### Diperbaiki
|
### Diperbaiki
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
|
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
|
||||||
<img src="https://img.shields.io/badge/versi-0.6.4_qr--ux-ADFF2F" alt="Versi 0.6.4 (qr-custom-ux)">
|
<img src="https://img.shields.io/badge/versi-0.6.5_qr--delete-ADFF2F" alt="Versi 0.6.5 (qr-custom-delete)">
|
||||||
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
|
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
|
||||||
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker">
|
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker">
|
||||||
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>
|
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "open-isp",
|
"name": "open-isp",
|
||||||
"version": "0.6.4",
|
"version": "0.6.5",
|
||||||
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
|
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
|
||||||
"main": "app-customer.js",
|
"main": "app-customer.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -4062,6 +4062,30 @@ router.post('/custom-qr/:id/manual-paid', requireAdminSession, requireSidebarMen
|
|||||||
res.redirect('/admin/custom-qr?id=' + Number(req.params.id));
|
res.redirect('/admin/custom-qr?id=' + Number(req.params.id));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function _checkAdminPassword(req) {
|
||||||
|
const pass = String((req.body && req.body.password) || '');
|
||||||
|
const expected = String(getSetting('admin_password', 'demo-isp123') || 'demo-isp123');
|
||||||
|
return !!expected && pass === expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.post('/custom-qr/:id/delete', requireAdminSession, restrictToAdmin, requireSidebarMenuAccess('custom_qr'), express.urlencoded({ extended: true }), (req, res) => {
|
||||||
|
try {
|
||||||
|
if (!_checkAdminPassword(req)) throw new Error('Password admin salah.');
|
||||||
|
const n = require('../services/customQrService').remove(req.params.id);
|
||||||
|
req.session._msg = n ? { type: 'success', text: 'QR custom dihapus.' } : { type: 'error', text: 'Data tidak ditemukan.' };
|
||||||
|
} catch (e) { req.session._msg = { type: 'error', text: e.message }; }
|
||||||
|
res.redirect('/admin/custom-qr');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/custom-qr/reset', requireAdminSession, restrictToAdmin, requireSidebarMenuAccess('custom_qr'), express.urlencoded({ extended: true }), (req, res) => {
|
||||||
|
try {
|
||||||
|
if (!_checkAdminPassword(req)) throw new Error('Password admin salah.');
|
||||||
|
const n = require('../services/customQrService').clearAll();
|
||||||
|
req.session._msg = { type: 'success', text: `Histori QR custom direset (${n} data dihapus).` };
|
||||||
|
} catch (e) { req.session._msg = { type: 'error', text: e.message }; }
|
||||||
|
res.redirect('/admin/custom-qr');
|
||||||
|
});
|
||||||
|
|
||||||
router.post('/custom-qr/:id/regenerate', requireAdminSession, requireSidebarMenuAccess('custom_qr'), express.urlencoded({ extended: true }), (req, res) => {
|
router.post('/custom-qr/:id/regenerate', requireAdminSession, requireSidebarMenuAccess('custom_qr'), express.urlencoded({ extended: true }), (req, res) => {
|
||||||
try {
|
try {
|
||||||
const row = require('../services/customQrService').regenerate(req.params.id, resolvePaidByName(req, 'Admin'));
|
const row = require('../services/customQrService').regenerate(req.params.id, resolvePaidByName(req, 'Admin'));
|
||||||
|
|||||||
@@ -118,6 +118,16 @@ function acceptWarning(id, actor, note) {
|
|||||||
return getById(id);
|
return getById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function remove(id) {
|
||||||
|
const r = db.prepare('DELETE FROM custom_qr_payments WHERE id=?').run(Number(id) || 0);
|
||||||
|
return r.changes || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAll() {
|
||||||
|
const r = db.prepare('DELETE FROM custom_qr_payments').run();
|
||||||
|
return r.changes || 0;
|
||||||
|
}
|
||||||
|
|
||||||
function regenerate(id, actor) {
|
function regenerate(id, actor) {
|
||||||
const old = getById(id);
|
const old = getById(id);
|
||||||
if (!old) throw new Error('Data tidak ditemukan.');
|
if (!old) throw new Error('Data tidak ditemukan.');
|
||||||
@@ -196,6 +206,8 @@ module.exports = {
|
|||||||
markManualPaid,
|
markManualPaid,
|
||||||
acceptWarning,
|
acceptWarning,
|
||||||
regenerate,
|
regenerate,
|
||||||
|
remove,
|
||||||
|
clearAll,
|
||||||
reject,
|
reject,
|
||||||
markPaidFromWebhook,
|
markPaidFromWebhook,
|
||||||
markWarningFromWebhook,
|
markWarningFromWebhook,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.6.4
|
0.6.5
|
||||||
@@ -110,13 +110,23 @@
|
|||||||
</form>
|
</form>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
<form method="POST" action="/admin/custom-qr/<%= detail.id %>/delete" onsubmit="return confirm('Hapus QR ini dari histori?')" style="display:flex;gap:6px;margin-top:12px;flex-wrap:wrap">
|
||||||
|
<input class="fc" type="password" name="password" required placeholder="Password admin" style="max-width:200px">
|
||||||
|
<button class="btn btn-g btn-sm"><i class="bi bi-trash"></i> Hapus QR ini</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-hd"><h6><i class="bi bi-clock-history"></i> Histori QR Custom</h6></div>
|
<div class="card-hd" style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap">
|
||||||
|
<h6 style="margin:0"><i class="bi bi-clock-history"></i> Histori QR Custom</h6>
|
||||||
|
<form method="POST" action="/admin/custom-qr/reset" onsubmit="return confirm('Reset SEMUA histori QR custom? Tindakan ini tidak bisa dibatalkan.')" style="display:flex;gap:6px;align-items:center">
|
||||||
|
<input class="fc" type="password" name="password" required placeholder="Password admin" style="max-width:180px">
|
||||||
|
<button class="btn btn-g btn-sm"><i class="bi bi-trash3"></i> Reset Histori</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<div class="card-bd" style="overflow:auto">
|
<div class="card-bd" style="overflow:auto">
|
||||||
<table class="table" style="width:100%;font-size:12.5px">
|
<table class="table" style="width:100%;font-size:12.5px">
|
||||||
<thead><tr><th>Judul</th><th>Kategori</th><th>Nominal</th><th>Status</th><th>Dibuat</th><th>Kedaluwarsa</th><th>Aksi</th></tr></thead>
|
<thead><tr><th>Judul</th><th>Kategori</th><th>Nominal</th><th>Status</th><th>Dibuat</th><th>Kedaluwarsa</th><th>Aksi</th></tr></thead>
|
||||||
|
|||||||
Reference in New Issue
Block a user