Files
wevads-platform/scripts/firebase-manager.php
2026-02-26 04:53:11 +01:00

444 lines
21 KiB
PHP
Executable File

<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
function getDB() {
static $pdo = null;
if ($pdo === null) {
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123",
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
}
return $pdo;
}
class FirebaseAPI {
private $projectId;
private $accessToken;
public function __construct($projectId) {
$this->projectId = $projectId;
$pdo = getDB();
$stmt = $pdo->prepare("SELECT service_account_json FROM admin.firebase_projects WHERE project_id = ?");
$stmt->execute([$projectId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row && $row['service_account_json']) {
$this->accessToken = $this->getAccessToken(json_decode($row['service_account_json'], true));
}
}
private function getAccessToken($saData) {
if (!$saData) return null;
$now = time();
$header = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));
$claims = base64_encode(json_encode([
'iss' => $saData['client_email'],
'scope' => 'https://www.googleapis.com/auth/firebase.messaging https://www.googleapis.com/auth/identitytoolkit',
'aud' => 'https://oauth2.googleapis.com/token',
'iat' => $now, 'exp' => $now + 3600
]));
$signature = '';
openssl_sign("$header.$claims", $signature, $saData['private_key'], 'SHA256');
$jwt = "$header.$claims." . base64_encode($signature);
$ch = curl_init('https://oauth2.googleapis.com/token');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(['grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion' => $jwt])]);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);
return $resp['access_token'] ?? null;
}
public function sendPush($token, $title, $body) {
return $this->apiPost("https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send",
['message' => ['token' => $token, 'notification' => ['title' => $title, 'body' => $body]]]);
}
private function apiPost($url, $data) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->accessToken, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($data)
]);
$resp = curl_exec($ch);
curl_close($ch);
return json_decode($resp, true);
}
}
function ensureTables() {
$pdo = getDB();
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.firebase_projects (
id SERIAL PRIMARY KEY, project_id VARCHAR(255) UNIQUE, project_name VARCHAR(255),
service_account_json TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.firebase_tokens (
id SERIAL PRIMARY KEY, project_id VARCHAR(255), token TEXT, device_type VARCHAR(50) DEFAULT 'android',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.firebase_notifications (
id SERIAL PRIMARY KEY, project_id VARCHAR(255), title VARCHAR(255), body TEXT,
target_type VARCHAR(50), tokens_count INTEGER DEFAULT 0, sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
}
if (isset($_GET['action']) || isset($_POST['action'])) {
header('Content-Type: application/json');
ensureTables();
$action = $_GET['action'] ?? $_POST['action'];
$pdo = getDB();
try {
switch ($action) {
case 'stats':
echo json_encode(['success' => true, 'stats' => [
'projects' => $pdo->query("SELECT COUNT(*) FROM admin.firebase_projects")->fetchColumn(),
'tokens' => $pdo->query("SELECT COUNT(*) FROM admin.firebase_tokens")->fetchColumn(),
'notifications' => $pdo->query("SELECT COUNT(*) FROM admin.firebase_notifications WHERE sent_at > NOW() - INTERVAL '24 hours'")->fetchColumn()
]]);
break;
case 'list_projects':
$projects = $pdo->query("SELECT id, project_id, project_name, created_at FROM admin.firebase_projects ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($projects);
break;
case 'add_project':
$projectId = $_POST['project_id'] ?? '';
$projectName = $_POST['project_name'] ?? $projectId;
$serviceAccount = $_POST['service_account'] ?? '';
if ($projectId) {
$stmt = $pdo->prepare("INSERT INTO admin.firebase_projects (project_id, project_name, service_account_json) VALUES (?, ?, ?) ON CONFLICT (project_id) DO UPDATE SET project_name = ?, service_account_json = ?");
$stmt->execute([$projectId, $projectName, $serviceAccount, $projectName, $serviceAccount]);
}
echo json_encode(['success' => true]);
break;
case 'delete_project':
$id = $_POST['id'] ?? 0;
$pdo->exec("DELETE FROM admin.firebase_projects WHERE id = " . intval($id));
echo json_encode(['success' => true]);
break;
case 'list_tokens':
$projectId = $_GET['project_id'] ?? '';
$where = $projectId ? "WHERE project_id = " . $pdo->quote($projectId) : "";
$tokens = $pdo->query("SELECT * FROM admin.firebase_tokens $where ORDER BY created_at DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($tokens);
break;
case 'add_token':
$projectId = $_POST['project_id'] ?? '';
$token = $_POST['token'] ?? '';
$deviceType = $_POST['device_type'] ?? 'android';
if ($projectId && $token) {
$stmt = $pdo->prepare("INSERT INTO admin.firebase_tokens (project_id, token, device_type) VALUES (?, ?, ?)");
$stmt->execute([$projectId, $token, $deviceType]);
}
echo json_encode(['success' => true]);
break;
case 'send_push':
$projectId = $_POST['project_id'] ?? '';
$title = $_POST['title'] ?? '';
$body = $_POST['body'] ?? '';
$tokenIds = json_decode($_POST['token_ids'] ?? '[]', true);
$sent = 0;
if ($projectId && $title && !empty($tokenIds)) {
$api = new FirebaseAPI($projectId);
$placeholders = implode(',', array_fill(0, count($tokenIds), '?'));
$stmt = $pdo->prepare("SELECT token FROM admin.firebase_tokens WHERE id IN ($placeholders)");
$stmt->execute($tokenIds);
$tokens = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($tokens as $token) {
$result = $api->sendPush($token, $title, $body);
if (!isset($result['error'])) $sent++;
}
$stmt = $pdo->prepare("INSERT INTO admin.firebase_notifications (project_id, title, body, target_type, tokens_count) VALUES (?, ?, ?, 'selected', ?)");
$stmt->execute([$projectId, $title, $body, $sent]);
}
echo json_encode(['success' => true, 'sent' => $sent]);
break;
default:
echo json_encode(['success' => false, 'error' => 'Unknown action']);
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
exit;
}
ensureTables();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firebase Manager - WEVAL SEND</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root{--bg:#0a0a0f;--card:#12121a;--card2:#1a1a25;--border:#2a2a3a;--text:#e4e4e7;--text2:#9ca3af;--primary:#6366f1;--success:#10b981;--warning:#f59e0b;--danger:#ef4444;--firebase:#ffca28}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
.sidebar{position:fixed;left:0;top:0;bottom:0;width:200px;background:var(--card);border-right:1px solid var(--border);padding:20px 0}
.logo{padding:0 20px 20px;border-bottom:1px solid var(--border);margin-bottom:20px;font-size:16px;font-weight:700;display:flex;align-items:center;gap:10px}
.logo i{color:var(--firebase)}
.nav-item{padding:12px 20px;cursor:pointer;display:flex;align-items:center;gap:10px;transition:all .2s}
.nav-item:hover,.nav-item.active{background:var(--card2);color:var(--primary)}
.main{margin-left:200px;padding:30px}
.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:30px}
.header h1{font-size:24px}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;margin-bottom:30px}
.stat-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px}
.stat-value{font-size:28px;font-weight:700;color:var(--primary)}
.stat-label{color:var(--text2);font-size:14px;margin-top:5px}
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:20px}
.card h3{margin-bottom:15px;display:flex;align-items:center;gap:10px}
.btn{padding:10px 20px;border:none;border-radius:8px;cursor:pointer;font-weight:600;display:inline-flex;align-items:center;gap:8px;transition:all .2s}
.btn-primary{background:var(--primary);color:white}
.btn-success{background:var(--success);color:white}
.btn-danger{background:var(--danger);color:white}
.btn:hover{opacity:.9;transform:translateY(-1px)}
.form-group{margin-bottom:15px}
.form-group label{display:block;margin-bottom:5px;color:var(--text2);font-size:14px}
.form-group input,.form-group select,.form-group textarea{width:100%;padding:10px 15px;background:var(--card2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px}
.form-group textarea{min-height:100px;resize:vertical}
table{width:100%;border-collapse:collapse}
th,td{padding:12px;text-align:left;border-bottom:1px solid var(--border)}
th{color:var(--text2);font-weight:500}
.badge{padding:4px 10px;border-radius:20px;font-size:12px}
.badge-android{background:rgba(16,185,129,.2);color:var(--success)}
.badge-ios{background:rgba(99,102,241,.2);color:var(--primary)}
.panel{display:none}
.panel.active{display:block}
.project-select{background:var(--card2);border:1px solid var(--border);color:var(--text);padding:8px 15px;border-radius:8px}
.empty{text-align:center;padding:40px;color:var(--text2)}
.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:100;align-items:center;justify-content:center}
.modal.active{display:flex}
.modal-content{background:var(--card);border-radius:12px;padding:25px;width:500px;max-width:90%}
.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}
.modal-close{background:none;border:none;color:var(--text2);font-size:20px;cursor:pointer}
</style>
</head>
<body>
<div class="sidebar">
<div class="logo"><i class="fas fa-fire"></i> Firebase</div>
<div class="nav-item active" data-panel="dashboard"><i class="fas fa-home"></i> Dashboard</div>
<div class="nav-item" data-panel="projects"><i class="fas fa-project-diagram"></i> Projects</div>
<div class="nav-item" data-panel="tokens"><i class="fas fa-mobile-alt"></i> FCM Tokens</div>
<div class="nav-item" data-panel="push"><i class="fas fa-bell"></i> Send Push</div>
</div>
<div class="main">
<div class="header">
<h1 id="pageTitle">Dashboard</h1>
<select class="project-select" id="currentProject">
<option value="">Select Project</option>
</select>
</div>
<div class="panel active" id="panel-dashboard">
<div class="stats">
<div class="stat-card"><div class="stat-value" id="statProjects">0</div><div class="stat-label">Projects</div></div>
<div class="stat-card"><div class="stat-value" id="statUsers">0</div><div class="stat-label">Users</div></div>
<div class="stat-card"><div class="stat-value" id="statTokens">0</div><div class="stat-label">Tokens</div></div>
<div class="stat-card"><div class="stat-value" id="statNotifs">0</div><div class="stat-label">Notifs 24h</div></div>
</div>
<div class="card">
<h3><i class="fas fa-plus-circle"></i> Quick Actions</h3>
<button class="btn btn-primary" onclick="showPanel('projects')"><i class="fas fa-plus"></i> Add Project</button>
<button class="btn btn-success" onclick="showPanel('push')"><i class="fas fa-bell"></i> Send Push</button>
</div>
</div>
<div class="panel" id="panel-projects">
<div class="card">
<h3><i class="fas fa-plus"></i> Add Project</h3>
<div class="form-group">
<label>Project ID</label>
<input type="text" id="projectId" placeholder="my-firebase-project">
</div>
<div class="form-group">
<label>Project Name</label>
<input type="text" id="projectName" placeholder="My Project">
</div>
<div class="form-group">
<label>Service Account JSON (optional)</label>
<textarea id="serviceAccount" placeholder='{"type":"service_account",...}'></textarea>
</div>
<button class="btn btn-primary" onclick="addProject()"><i class="fas fa-save"></i> Save Project</button>
</div>
<div class="card">
<h3><i class="fas fa-list"></i> Projects</h3>
<table>
<thead><tr><th>Project ID</th><th>Name</th><th>Created</th><th>Actions</th></tr></thead>
<tbody id="projectsList"></tbody>
</table>
</div>
</div>
<div class="panel" id="panel-tokens">
<div class="card">
<h3><i class="fas fa-plus"></i> Add Token</h3>
<div class="form-group">
<label>FCM Token</label>
<input type="text" id="newToken" placeholder="FCM device token...">
</div>
<div class="form-group">
<label>Device Type</label>
<select id="deviceType">
<option value="android">Android</option>
<option value="ios">iOS</option>
<option value="web">Web</option>
</select>
</div>
<button class="btn btn-primary" onclick="addToken()"><i class="fas fa-plus"></i> Add Token</button>
</div>
<div class="card">
<h3><i class="fas fa-list"></i> Tokens</h3>
<table>
<thead><tr><th><input type="checkbox" id="selectAllTokens"></th><th>Token</th><th>Device</th><th>Created</th></tr></thead>
<tbody id="tokensList"></tbody>
</table>
</div>
</div>
<div class="panel" id="panel-push">
<div class="card">
<h3><i class="fas fa-bell"></i> Send Push Notification</h3>
<div class="form-group">
<label>Title</label>
<input type="text" id="pushTitle" placeholder="Notification title">
</div>
<div class="form-group">
<label>Body</label>
<textarea id="pushBody" placeholder="Notification message..."></textarea>
</div>
<button class="btn btn-success" onclick="sendPush()"><i class="fas fa-paper-plane"></i> Send to Selected Tokens</button>
</div>
</div>
</div>
<script>
let projects = [];
let tokens = [];
document.querySelectorAll('.nav-item').forEach(item => {
item.onclick = () => showPanel(item.dataset.panel);
});
function showPanel(name) {
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.querySelector('[data-panel="'+name+'"]').classList.add('active');
document.getElementById('panel-'+name).classList.add('active');
document.getElementById('pageTitle').textContent = name.charAt(0).toUpperCase() + name.slice(1);
}
function loadStats() {
fetch('?action=stats').then(r=>r.json()).then(d=>{
if(d.success){
document.getElementById('statProjects').textContent = d.stats.projects;
document.getElementById('statTokens').textContent = d.stats.tokens;
document.getElementById('statNotifs').textContent = d.stats.notifications;
}
});
}
function loadProjects() {
fetch('?action=list_projects').then(r=>r.json()).then(data=>{
projects = data;
const sel = document.getElementById('currentProject');
sel.innerHTML = '<option value="">Select Project</option>' +
data.map(p=>'<option value="'+p.project_id+'">'+p.project_name+'</option>').join('');
document.getElementById('projectsList').innerHTML = data.length ? data.map(p=>
'<tr><td>'+p.project_id+'</td><td>'+p.project_name+'</td><td>'+new Date(p.created_at).toLocaleDateString()+'</td>'+
'<td><button class="btn btn-danger" onclick="deleteProject('+p.id+')"><i class="fas fa-trash"></i></button></td></tr>'
).join('') : '<tr><td colspan="4" class="empty">No projects yet</td></tr>';
});
}
function addProject() {
const fd = new FormData();
fd.append('action', 'add_project');
fd.append('project_id', document.getElementById('projectId').value);
fd.append('project_name', document.getElementById('projectName').value);
fd.append('service_account', document.getElementById('serviceAccount').value);
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d=>{
if(d.success){ loadProjects(); loadStats(); alert('Project added!'); }
});
}
function deleteProject(id) {
if(!confirm('Delete this project?')) return;
const fd = new FormData();
fd.append('action', 'delete_project');
fd.append('id', id);
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d=>{
if(d.success){ loadProjects(); loadStats(); }
});
}
function loadTokens() {
const projectId = document.getElementById('currentProject').value;
fetch('?action=list_tokens&project_id='+projectId).then(r=>r.json()).then(data=>{
tokens = data;
document.getElementById('tokensList').innerHTML = data.length ? data.map(t=>
'<tr><td><input type="checkbox" class="token-cb" value="'+t.id+'"></td>'+
'<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis">'+t.token+'</td>'+
'<td><span class="badge badge-'+t.device_type+'">'+t.device_type+'</span></td>'+
'<td>'+new Date(t.created_at).toLocaleDateString()+'</td></tr>'
).join('') : '<tr><td colspan="4" class="empty">No tokens yet</td></tr>';
});
}
function addToken() {
const projectId = document.getElementById('currentProject').value;
if(!projectId){ alert('Select a project first'); return; }
const fd = new FormData();
fd.append('action', 'add_token');
fd.append('project_id', projectId);
fd.append('token', document.getElementById('newToken').value);
fd.append('device_type', document.getElementById('deviceType').value);
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d=>{
if(d.success){ loadTokens(); loadStats(); document.getElementById('newToken').value=''; }
});
}
document.getElementById('selectAllTokens').onchange = function() {
document.querySelectorAll('.token-cb').forEach(cb => cb.checked = this.checked);
};
function sendPush() {
const projectId = document.getElementById('currentProject').value;
if(!projectId){ alert('Select a project first'); return; }
const selected = Array.from(document.querySelectorAll('.token-cb:checked')).map(cb=>cb.value);
if(!selected.length){ alert('Select at least one token'); return; }
const fd = new FormData();
fd.append('action', 'send_push');
fd.append('project_id', projectId);
fd.append('title', document.getElementById('pushTitle').value);
fd.append('body', document.getElementById('pushBody').value);
fd.append('token_ids', JSON.stringify(selected));
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d=>{
if(d.success){ alert('Sent to '+d.sent+' devices!'); loadStats(); }
});
}
document.getElementById('currentProject').onchange = loadTokens;
loadStats();
loadProjects();
loadTokens();
</script>
</body>
</html>