OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana
Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor, admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS. Kredit & lisensi (ISC): - Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak atas base kode dan repo yang bermanfaat. - Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder. - Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
This commit is contained in:
commit
eaf05f2811
280 files changed
+123975
No files matched your search
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* Settings Validation Schema
|
||||
* Validate setiap field sebelum di-save ke settings.json
|
||||
*/
|
||||
const { logger } = require('./logger');
|
||||
|
||||
// Validation rules untuk setiap field
|
||||
const VALIDATION_RULES = {
|
||||
// Server Configuration
|
||||
server_port: {
|
||||
type: 'number',
|
||||
min: 1024,
|
||||
max: 65535,
|
||||
required: true,
|
||||
description: 'Port server (1024-65535)'
|
||||
},
|
||||
server_host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Host server'
|
||||
},
|
||||
session_secret: {
|
||||
type: 'string',
|
||||
minLength: 32,
|
||||
required: true,
|
||||
description: 'Session secret (min 32 karakter)'
|
||||
},
|
||||
|
||||
// Company Information
|
||||
company_header: {
|
||||
type: 'string',
|
||||
maxLength: 100,
|
||||
required: true,
|
||||
description: 'Nama perusahaan'
|
||||
},
|
||||
public_base_url: {
|
||||
type: 'string',
|
||||
pattern: /^https?:\/\/.+/,
|
||||
description: 'URL publik aplikasi (http://... atau https://...)'
|
||||
},
|
||||
company_manager: {
|
||||
type: 'string',
|
||||
maxLength: 100,
|
||||
description: 'Nama manager'
|
||||
},
|
||||
company_phone: {
|
||||
type: 'string',
|
||||
pattern: /^[0-9\-\+\s]+$/,
|
||||
description: 'Nomor telepon perusahaan'
|
||||
},
|
||||
company_email: {
|
||||
type: 'string',
|
||||
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
||||
description: 'Email perusahaan'
|
||||
},
|
||||
company_address: {
|
||||
type: 'string',
|
||||
maxLength: 500,
|
||||
description: 'Alamat perusahaan'
|
||||
},
|
||||
footer_info: {
|
||||
type: 'string',
|
||||
maxLength: 200,
|
||||
description: 'Footer info'
|
||||
},
|
||||
operational_hours: {
|
||||
type: 'string',
|
||||
maxLength: 200,
|
||||
description: 'Jam operasional'
|
||||
},
|
||||
|
||||
// Admin Credentials
|
||||
admin_username: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
maxLength: 50,
|
||||
required: true,
|
||||
description: 'Username admin'
|
||||
},
|
||||
admin_password: {
|
||||
type: 'string',
|
||||
minLength: 6,
|
||||
required: true,
|
||||
description: 'Password admin (min 6 karakter)'
|
||||
},
|
||||
admin_api_key: {
|
||||
type: 'string',
|
||||
minLength: 10,
|
||||
description: 'API key admin'
|
||||
},
|
||||
|
||||
// GenieACS Configuration
|
||||
genieacs_url: {
|
||||
type: 'string',
|
||||
pattern: /^https?:\/\/.+/,
|
||||
description: 'URL GenieACS (http://...)'
|
||||
},
|
||||
genieacs_username: {
|
||||
type: 'string',
|
||||
description: 'Username GenieACS'
|
||||
},
|
||||
genieacs_password: {
|
||||
type: 'string',
|
||||
description: 'Password GenieACS'
|
||||
},
|
||||
|
||||
// MikroTik Configuration
|
||||
mikrotik_host: {
|
||||
type: 'string',
|
||||
pattern: /^(\d{1,3}\.){3}\d{1,3}$|^[a-zA-Z0-9\-\.]+$/,
|
||||
description: 'Host MikroTik (IP atau hostname)'
|
||||
},
|
||||
mikrotik_user: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
description: 'Username MikroTik'
|
||||
},
|
||||
mikrotik_password: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
description: 'Password MikroTik'
|
||||
},
|
||||
mikrotik_port: {
|
||||
type: 'number',
|
||||
min: 1024,
|
||||
max: 65535,
|
||||
description: 'Port MikroTik'
|
||||
},
|
||||
isolir_day: {
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 365,
|
||||
description: 'Hari isolir (1-365)'
|
||||
},
|
||||
|
||||
// WhatsApp Configuration
|
||||
whatsapp_enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Enable WhatsApp'
|
||||
},
|
||||
whatsapp_auth_folder: {
|
||||
type: 'string',
|
||||
description: 'Folder auth WhatsApp'
|
||||
},
|
||||
whatsapp_broadcast_delay: {
|
||||
type: 'number',
|
||||
min: 10,
|
||||
max: 5000,
|
||||
description: 'Delay broadcast WhatsApp (ms)'
|
||||
},
|
||||
|
||||
// Telegram Configuration
|
||||
telegram_enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Enable Telegram'
|
||||
},
|
||||
telegram_bot_token: {
|
||||
type: 'string',
|
||||
description: 'Telegram bot token'
|
||||
},
|
||||
telegram_admin_id: {
|
||||
type: 'string',
|
||||
pattern: /^\d+$/,
|
||||
description: 'Telegram admin ID (numeric)'
|
||||
},
|
||||
|
||||
// Tripay Configuration
|
||||
office_lat: {
|
||||
type: 'string',
|
||||
pattern: /^-?\d+(\.\d+)?$|^$/,
|
||||
description: 'Latitude kantor'
|
||||
},
|
||||
office_lng: {
|
||||
type: 'string',
|
||||
pattern: /^-?\d+(\.\d+)?$|^$/,
|
||||
description: 'Longitude kantor'
|
||||
},
|
||||
|
||||
// Other
|
||||
|
||||
auto_backup_enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Enable auto backup'
|
||||
},
|
||||
login_otp_enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Enable OTP login'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate single value
|
||||
*/
|
||||
function validateValue(field, value, rule) {
|
||||
// Trim string values
|
||||
let trimmedValue = value;
|
||||
if (typeof value === 'string') {
|
||||
trimmedValue = value.trim();
|
||||
}
|
||||
|
||||
// Check type
|
||||
if (rule.type === 'number') {
|
||||
if (typeof trimmedValue !== 'number') return `${field} harus berupa angka`;
|
||||
if (rule.min !== undefined && trimmedValue < rule.min) return `${field} minimal ${rule.min}`;
|
||||
if (rule.max !== undefined && trimmedValue > rule.max) return `${field} maksimal ${rule.max}`;
|
||||
}
|
||||
|
||||
if (rule.type === 'string') {
|
||||
if (typeof trimmedValue !== 'string') return `${field} harus berupa string`;
|
||||
if (rule.minLength && trimmedValue.length < rule.minLength) return `${field} minimal ${rule.minLength} karakter`;
|
||||
if (rule.maxLength && trimmedValue.length > rule.maxLength) return `${field} maksimal ${rule.maxLength} karakter`;
|
||||
if (rule.pattern && !rule.pattern.test(trimmedValue)) return `${field} format tidak valid`;
|
||||
if (rule.enum && !rule.enum.includes(trimmedValue)) return `${field} harus salah satu dari: ${rule.enum.join(', ')}`;
|
||||
}
|
||||
|
||||
if (rule.type === 'boolean') {
|
||||
if (typeof trimmedValue !== 'boolean') return `${field} harus berupa boolean`;
|
||||
}
|
||||
|
||||
return null; // Valid
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate settings object
|
||||
*/
|
||||
function validateSettings(settings) {
|
||||
const errors = [];
|
||||
|
||||
Object.keys(settings).forEach(field => {
|
||||
const value = settings[field];
|
||||
const rule = VALIDATION_RULES[field];
|
||||
|
||||
// Skip jika tidak ada rule (field baru atau optional)
|
||||
if (!rule) return;
|
||||
|
||||
// Check required
|
||||
if (rule.required && (value === undefined || value === null || value === '')) {
|
||||
errors.push(`${field} wajib diisi`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip validation jika value kosong dan tidak required
|
||||
if (!rule.required && (value === undefined || value === null || value === '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate value
|
||||
const error = validateValue(field, value, rule);
|
||||
if (error) errors.push(error);
|
||||
});
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rule untuk field
|
||||
*/
|
||||
function getFieldRule(field) {
|
||||
return VALIDATION_RULES[field] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all validation rules
|
||||
*/
|
||||
function getAllRules() {
|
||||
return VALIDATION_RULES;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateValue,
|
||||
validateSettings,
|
||||
getFieldRule,
|
||||
getAllRules,
|
||||
VALIDATION_RULES
|
||||
};
|
||||
Reference in new issue
Block a user