Autosave: 20260215-213226
This commit is contained in:
parent
b710d6acf5
commit
516273c8b4
BIN
assets/pasted-20260215-205745-40292c52.png
Normal file
BIN
assets/pasted-20260215-205745-40292c52.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
318
ballot.php
318
ballot.php
@ -30,121 +30,273 @@ if ($check->fetchColumn() > 0) {
|
||||
$positions = $pdo->prepare("SELECT * FROM positions WHERE election_id = ? ORDER BY sort_order ASC");
|
||||
$positions->execute([$id]);
|
||||
$positions = $positions->fetchAll();
|
||||
|
||||
$endTime = strtotime($election['end_date_and_time']) * 1000;
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Vote: <?= htmlspecialchars($election['title']) ?></title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?= time() ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Ballot: <?= htmlspecialchars($election['title']) ?></title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<style>
|
||||
body { background: #f8fafc; color: #1e293b; font-family: 'Inter', sans-serif; }
|
||||
.ballot-container { max-width: 800px; margin: 40px auto; padding: 0 20px; }
|
||||
.ballot-header { text-align: center; margin-bottom: 48px; }
|
||||
.ballot-header h1 { font-size: 2.5rem; font-weight: 800; color: #1e293b; margin-bottom: 12px; letter-spacing: -0.025em; }
|
||||
.ballot-header p { color: #64748b; font-size: 1.125rem; }
|
||||
|
||||
.position-group { margin-bottom: 40px; background: white; border-radius: 24px; border: 1px solid #e2e8f0; padding: 32px; box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); }
|
||||
.position-title { font-size: 1.25rem; font-weight: 700; color: #1e293b; margin-bottom: 24px; display: flex; align-items: center; gap: 12px; }
|
||||
.position-title i { color: #4f46e5; }
|
||||
|
||||
.candidates-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
||||
.candidate-label { cursor: pointer; display: block; }
|
||||
:root {
|
||||
--primary: #6366f1;
|
||||
--primary-hover: #4f46e5;
|
||||
--bg: #f8fafc;
|
||||
--text: #0f172a;
|
||||
--text-muted: #64748b;
|
||||
--glass: rgba(255, 255, 255, 0.7);
|
||||
--glass-border: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||
color: var(--text);
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.ballot-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px 100px;
|
||||
}
|
||||
|
||||
.timer-banner {
|
||||
background: var(--text);
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border-radius: 100px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-weight: 700;
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 32px;
|
||||
box-shadow: 0 10px 25px -5px rgba(15, 23, 42, 0.2);
|
||||
}
|
||||
|
||||
#countdown {
|
||||
color: #fbbf24;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.ballot-header {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.ballot-header h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: var(--text);
|
||||
margin: 0 0 16px 0;
|
||||
letter-spacing: -0.05em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ballot-header p {
|
||||
color: var(--text-muted);
|
||||
font-size: 1.25rem;
|
||||
max-width: 500px;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.position-group {
|
||||
margin-bottom: 48px;
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-radius: 32px;
|
||||
border: 1px solid var(--glass-border);
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 40px -15px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.position-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
color: var(--text);
|
||||
margin-bottom: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.position-title i {
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 14px;
|
||||
color: var(--primary);
|
||||
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.candidates-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.candidate-label {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.candidate-card {
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 24px;
|
||||
padding: 24px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: 20px;
|
||||
background: white;
|
||||
position: relative;
|
||||
}
|
||||
.candidate-card:hover { border-color: #cbd5e1; background: #f8fafc; }
|
||||
|
||||
.candidate-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 20px -8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + .candidate-card,
|
||||
input[type="checkbox"]:checked + .candidate-card {
|
||||
border-color: #4f46e5;
|
||||
border-color: var(--primary);
|
||||
background: #f5f3ff;
|
||||
box-shadow: 0 0 0 1px #4f46e5;
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + .candidate-card .check-icon,
|
||||
input[type="checkbox"]:checked + .candidate-card .check-icon {
|
||||
background: #4f46e5;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
input[type="radio"]:checked + .candidate-card .avatar-placeholder {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: #4f46e5;
|
||||
}
|
||||
|
||||
input[type="radio"], input[type="checkbox"] { display: none; }
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 14px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
color: #4f46e5;
|
||||
font-size: 1.25rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
font-weight: 800;
|
||||
color: var(--primary);
|
||||
font-size: 1.5rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.candidate-info h3 { margin: 0; font-size: 1.125rem; font-weight: 700; color: #1e293b; }
|
||||
.candidate-info p { margin: 4px 0 0 0; font-size: 0.875rem; color: #64748b; font-weight: 500; }
|
||||
.candidate-info h3 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.candidate-info p {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 1rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
margin-left: auto;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: transparent;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.submit-bar {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: calc(100% - 40px);
|
||||
max-width: 800px;
|
||||
background: var(--text);
|
||||
padding: 20px 40px;
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: white;
|
||||
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5);
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.submit-bar p { margin: 0; font-size: 0.875rem; color: #94a3b8; }
|
||||
.submit-bar h4 { margin: 4px 0 0 0; font-size: 1.125rem; font-weight: 700; }
|
||||
|
||||
.btn-submit {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 14px 32px;
|
||||
border-radius: 16px;
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.submit-section { margin-top: 64px; text-align: center; padding: 48px; background: #1e293b; border-radius: 24px; color: white; }
|
||||
.btn-submit {
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 16px 48px;
|
||||
border-radius: 12px;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.4);
|
||||
.btn-submit:hover {
|
||||
background: var(--primary-hover);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.submit-bar {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
}
|
||||
.btn-submit { width: 100%; }
|
||||
.ballot-header h1 { font-size: 2.25rem; }
|
||||
}
|
||||
.btn-submit:hover { transform: translateY(-2px); background: #4338ca; }
|
||||
.btn-submit:active { transform: translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ballot-container">
|
||||
<div class="ballot-header animate-fade-in">
|
||||
<div style="display: inline-flex; align-items: center; gap: 8px; background: #e0e7ff; color: #4338ca; padding: 6px 16px; border-radius: 100px; font-size: 0.875rem; font-weight: 700; margin-bottom: 16px;">
|
||||
<i data-lucide="vote" style="width: 16px;"></i> OFFICIAL BALLOT
|
||||
<div style="text-align: center;">
|
||||
<div class="timer-banner">
|
||||
<i data-lucide="clock" style="width: 18px;"></i>
|
||||
<span>TIME REMAINING:</span>
|
||||
<span id="countdown">00:00:00</span>
|
||||
</div>
|
||||
<h1><?= htmlspecialchars($election['title']) ?></h1>
|
||||
<p>Your vote is secure and anonymous. Choose your representatives below.</p>
|
||||
</div>
|
||||
|
||||
<form action="api/submit_vote.php" method="POST" onsubmit="return confirm('Are you sure you want to cast your vote? This action cannot be undone.')">
|
||||
<div class="ballot-header">
|
||||
<h1><?= htmlspecialchars($election['title']) ?></h1>
|
||||
<p>Your choice matters. Review the candidates carefully and cast your secure vote below.</p>
|
||||
</div>
|
||||
|
||||
<form id="ballotForm" action="api/submit_vote.php" method="POST">
|
||||
<input type="hidden" name="election_id" value="<?= $id ?>">
|
||||
|
||||
<?php foreach ($positions as $index => $pos): ?>
|
||||
<div class="position-group animate-stagger" style="--order: <?= $index ?>">
|
||||
<div class="position-group">
|
||||
<div class="position-title">
|
||||
<i data-lucide="award"></i>
|
||||
<i data-lucide="shield-check"></i>
|
||||
<?= htmlspecialchars($pos['name']) ?>
|
||||
</div>
|
||||
|
||||
@ -163,8 +315,8 @@ $positions = $positions->fetchAll();
|
||||
?>
|
||||
|
||||
<?php if (empty($candidates)): ?>
|
||||
<div style="padding: 24px; background: #f8fafc; border-radius: 12px; text-align: center; border: 1px dashed #cbd5e1;">
|
||||
<p style="margin: 0; color: #64748b; font-size: 0.875rem;">No candidates for this position.</p>
|
||||
<div style="padding: 32px; background: white; border-radius: 20px; text-align: center; border: 2px dashed #e2e8f0;">
|
||||
<p style="margin: 0; color: var(--text-muted); font-weight: 600;">No candidates available for your track.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="candidates-grid">
|
||||
@ -180,7 +332,7 @@ $positions = $positions->fetchAll();
|
||||
<p><?= htmlspecialchars($cand['party_name'] ?: 'Independent') ?></p>
|
||||
</div>
|
||||
<div class="check-icon">
|
||||
<i data-lucide="check" style="width: 14px;"></i>
|
||||
<i data-lucide="check" style="width: 16px;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@ -190,21 +342,51 @@ $positions = $positions->fetchAll();
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="submit-section animate-fade-in">
|
||||
<h2 style="margin: 0 0 12px 0; font-size: 1.5rem;">Ready to submit?</h2>
|
||||
<p style="margin: 0 0 32px 0; color: #94a3b8; font-size: 1rem;">Please review your selections before casting your vote.</p>
|
||||
<div class="submit-bar">
|
||||
<div>
|
||||
<p>READY TO SUBMIT?</p>
|
||||
<h4>Review your selections</h4>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit">
|
||||
Cast My Vote
|
||||
</button>
|
||||
<div style="margin-top: 24px; display: flex; align-items: center; justify-content: center; gap: 8px; color: #64748b; font-size: 0.875rem;">
|
||||
<i data-lucide="shield-check" style="width: 16px; color: #10b981;"></i> Verified Secure Election
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
|
||||
// Countdown Timer
|
||||
const endTime = <?= $endTime ?>;
|
||||
|
||||
function updateCountdown() {
|
||||
const now = new Date().getTime();
|
||||
const distance = endTime - now;
|
||||
|
||||
if (distance < 0) {
|
||||
document.getElementById("countdown").innerHTML = "EXPIRED";
|
||||
document.getElementById("ballotForm").style.opacity = "0.5";
|
||||
document.getElementById("ballotForm").style.pointerEvents = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
|
||||
|
||||
document.getElementById("countdown").innerHTML =
|
||||
(hours < 10 ? "0" : "") + hours + ":" +
|
||||
(minutes < 10 ? "0" : "") + minutes + ":" +
|
||||
(seconds < 10 ? "0" : "") + seconds;
|
||||
}
|
||||
|
||||
setInterval(updateCountdown, 1000);
|
||||
updateCountdown();
|
||||
|
||||
document.getElementById('ballotForm').onsubmit = function() {
|
||||
return confirm('Are you sure you want to cast your vote? This action is permanent.');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@ -6,16 +6,39 @@ define('DB_USER', 'app_38458');
|
||||
define('DB_PASS', 'c217529c-a428-4a97-8f31-773c420377a7');
|
||||
|
||||
// Supabase Configuration - Provide your project URL and Service Role Key
|
||||
define('SUPABASE_URL', getenv('SUPABASE_URL') ?: 'https://your-project.supabase.co');
|
||||
define('SUPABASE_SERVICE_ROLE_KEY', getenv('SUPABASE_SERVICE_ROLE_KEY') ?: 'your-service-role-key');
|
||||
define('SUPABASE_URL', getenv('SUPABASE_URL') ?: 'https://siqeqnizegizxemrfgkf.supabase.co');
|
||||
define('SUPABASE_SERVICE_ROLE_KEY', getenv('SUPABASE_SERVICE_ROLE_KEY') ?: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpcWVxbml6ZWdpenhlbXJmZ2tmIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc3MDEyMjgzMywiZXhwIjoyMDg1Njk4ODMzfQ.8K66Y3hSfSq5mRDeU8YT6pH9tqPVngxA9dEwCgQUCl0');
|
||||
define('SUPABASE_DB_PASS', getenv('SUPABASE_DB_PASS') ?: 'gA82h8K80T5QUAwi'); // Set your DB password here for PostgreSQL migration
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
if (defined('SUPABASE_DB_PASS') && !empty(SUPABASE_DB_PASS)) {
|
||||
// Use Supabase PostgreSQL
|
||||
$host = 'aws-1-ap-southeast-1.pooler.supabase.com';
|
||||
$port = '6543';
|
||||
$dbname = 'postgres';
|
||||
$user = 'postgres.siqeqnizegizxemrfgkf';
|
||||
$pass = SUPABASE_DB_PASS;
|
||||
try {
|
||||
$pdo = new PDO("pgsql:host=$host;port=$port;dbname=$dbname", $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
} catch (PDOException $e) {
|
||||
// Fallback to local MariaDB if PostgreSQL fails
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// Use local MariaDB
|
||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
}
|
||||
}
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
29
find_region.php
Normal file
29
find_region.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
$regions = [
|
||||
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
|
||||
'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2',
|
||||
'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3',
|
||||
'sa-east-1', 'ca-central-1', 'me-south-1', 'af-south-1'
|
||||
];
|
||||
|
||||
$pass = 'gA82h8K80T5QUAwi';
|
||||
$projectRef = 'siqeqnizegizxemrfgkf';
|
||||
|
||||
foreach ($regions as $region) {
|
||||
$host = "aws-0-$region.pooler.supabase.com";
|
||||
$user = "postgres.$projectRef";
|
||||
echo "Testing $region ($host)... ";
|
||||
try {
|
||||
$dsn = "pgsql:host=$host;port=6543;dbname=postgres;connect_timeout=5";
|
||||
$pdo = new PDO($dsn, $user, $pass);
|
||||
echo "SUCCESS!\n";
|
||||
exit(0);
|
||||
} catch (Exception $e) {
|
||||
if (strpos($e->getMessage(), 'Tenant or user not found') !== false) {
|
||||
echo "Not here.\n";
|
||||
} else {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
36
index.php
36
index.php
@ -13,7 +13,41 @@ if (in_array($user['role'], ['Admin', 'Adviser', 'Officer'])) {
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$elections = $pdo->query("SELECT * FROM elections WHERE archived = FALSE ORDER BY created_at DESC")->fetchAll();
|
||||
|
||||
// Voter redirection logic
|
||||
if ($user['role'] === 'Voter') {
|
||||
// Find ongoing elections that this voter is assigned to
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT e.* FROM elections e
|
||||
JOIN election_assignments ea ON e.id = ea.election_id
|
||||
WHERE ea.user_id = ?
|
||||
AND e.status = 'Ongoing'
|
||||
AND e.archived = FALSE
|
||||
AND e.end_date_and_time > CURRENT_TIMESTAMP
|
||||
");
|
||||
$stmt->execute([$user['id']]);
|
||||
$activeElections = $stmt->fetchAll();
|
||||
|
||||
// Filter out elections where the user has already voted
|
||||
$votedElectionsStmt = $pdo->prepare("SELECT election_id FROM votes WHERE voter_id = ?");
|
||||
$votedElectionsStmt->execute([$user['id']]);
|
||||
$votedIds = $votedElectionsStmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$eligibleElections = array_filter($activeElections, function($e) use ($votedIds) {
|
||||
return !in_array($e['id'], $votedIds);
|
||||
});
|
||||
|
||||
if (count($eligibleElections) === 1) {
|
||||
$singleElection = reset($eligibleElections);
|
||||
header("Location: ballot.php?id=" . $singleElection['id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// For voters, only show their assigned elections in the list
|
||||
$elections = $activeElections;
|
||||
} else {
|
||||
$elections = $pdo->query("SELECT * FROM elections WHERE archived = FALSE ORDER BY created_at DESC")->fetchAll();
|
||||
}
|
||||
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Online Election System for Senior High School';
|
||||
?>
|
||||
|
||||
163
supabase_migration.php
Normal file
163
supabase_migration.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// This script migrates the local MariaDB database to Supabase PostgreSQL.
|
||||
// It requires the Supabase Database Password.
|
||||
|
||||
if (php_sapi_name() !== 'cli') {
|
||||
die("This script must be run from the command line.");
|
||||
}
|
||||
|
||||
$dbPassword = $argv[1] ?? '';
|
||||
if (!$dbPassword) {
|
||||
echo "Usage: php supabase_migration.php [SUPABASE_DB_PASSWORD]\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$supabaseHost = "aws-1-ap-southeast-1.pooler.supabase.com";
|
||||
$supabaseUser = "postgres.siqeqnizegizxemrfgkf";
|
||||
$supabaseDb = "postgres";
|
||||
$supabasePort = "6543";
|
||||
|
||||
try {
|
||||
echo "Connecting to local MariaDB...\n";
|
||||
$localPdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
|
||||
echo "Connecting to Supabase PostgreSQL...\n";
|
||||
$dsn = "pgsql:host=$supabaseHost;port=$supabasePort;dbname=$supabaseDb";
|
||||
$supabasePdo = new PDO($dsn, $supabaseUser, $dbPassword, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||
]);
|
||||
|
||||
echo "Converting and creating tables in Supabase...\n";
|
||||
|
||||
// Define tables and their PostgreSQL schemas
|
||||
$schemas = [
|
||||
"users" => "CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
supabase_uid VARCHAR(255),
|
||||
student_id VARCHAR(10) UNIQUE NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255),
|
||||
grade_level INTEGER,
|
||||
track VARCHAR(100),
|
||||
section VARCHAR(100),
|
||||
role VARCHAR(50) DEFAULT 'Voter',
|
||||
access_level INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
)",
|
||||
"elections" => "CREATE TABLE IF NOT EXISTS elections (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
status VARCHAR(50) DEFAULT 'Preparing',
|
||||
start_date_and_time TIMESTAMP NOT NULL,
|
||||
end_date_and_time TIMESTAMP NOT NULL,
|
||||
created_by VARCHAR(255) REFERENCES users(id),
|
||||
archived BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)",
|
||||
"positions" => "CREATE TABLE IF NOT EXISTS positions (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(50) DEFAULT 'Uniform',
|
||||
max_votes INTEGER DEFAULT 1,
|
||||
sort_order INTEGER DEFAULT 0
|
||||
)",
|
||||
"candidates" => "CREATE TABLE IF NOT EXISTS candidates (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
|
||||
position_id VARCHAR(255) REFERENCES positions(id) ON DELETE CASCADE,
|
||||
user_id VARCHAR(255) REFERENCES users(id) ON DELETE CASCADE,
|
||||
party_name VARCHAR(255),
|
||||
manifesto TEXT,
|
||||
approved BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)",
|
||||
"votes" => "CREATE TABLE IF NOT EXISTS votes (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
election_id VARCHAR(255) REFERENCES elections(id),
|
||||
position_id VARCHAR(255) REFERENCES positions(id),
|
||||
candidate_id VARCHAR(255) REFERENCES candidates(id),
|
||||
voter_id VARCHAR(255) REFERENCES users(id),
|
||||
casted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
UNIQUE (election_id, position_id, voter_id)
|
||||
)",
|
||||
"election_assignments" => "CREATE TABLE IF NOT EXISTS election_assignments (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
|
||||
user_id VARCHAR(255) REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_in_election VARCHAR(50) DEFAULT 'Voter',
|
||||
assigned_by VARCHAR(255) REFERENCES users(id),
|
||||
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)",
|
||||
"parties" => "CREATE TABLE IF NOT EXISTS parties (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
logo_url TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)",
|
||||
"audit_logs" => "CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
user_id VARCHAR(255) REFERENCES users(id),
|
||||
action VARCHAR(255) NOT NULL,
|
||||
details TEXT,
|
||||
table_name VARCHAR(100),
|
||||
record_id VARCHAR(255),
|
||||
old_values TEXT,
|
||||
new_values TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
election_id VARCHAR(255) REFERENCES elections(id) ON DELETE CASCADE
|
||||
)"
|
||||
];
|
||||
|
||||
foreach ($schemas as $tableName => $sql) {
|
||||
echo "Creating table: $tableName...\n";
|
||||
$supabasePdo->exec($sql);
|
||||
}
|
||||
|
||||
echo "Migrating data...\n";
|
||||
|
||||
$tables = array_keys($schemas);
|
||||
// Order matters for foreign keys: users, elections, positions, candidates, assignments, votes, audit_logs
|
||||
$orderedTables = ["users", "elections", "positions", "election_assignments", "parties", "candidates", "votes", "audit_logs"];
|
||||
|
||||
foreach ($orderedTables as $table) {
|
||||
echo "Migrating data for $table...\n";
|
||||
$stmt = $localPdo->query("SELECT * FROM $table");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($rows)) {
|
||||
echo "No data for $table.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns = array_keys($rows[0]);
|
||||
$placeholders = implode(',', array_fill(0, count($columns), '?'));
|
||||
$insertSql = "INSERT INTO $table (" . implode(',', $columns) . ") VALUES ($placeholders) ON CONFLICT (id) DO NOTHING";
|
||||
|
||||
$insertStmt = $supabasePdo->prepare($insertSql);
|
||||
$count = 0;
|
||||
foreach ($rows as $row) {
|
||||
$insertStmt->execute(array_values($row));
|
||||
$count++;
|
||||
}
|
||||
echo "Migrated $count rows for $table.\n";
|
||||
}
|
||||
|
||||
echo "Migration completed successfully!\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "Error: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user