382 lines
17 KiB
PHP
382 lines
17 KiB
PHP
<?php
|
|
// ============================================================
|
|
// database/migrate.php — Run via browser:
|
|
// http://localhost/webgis/03PovertyMapping/database/migrate.php
|
|
// ============================================================
|
|
declare(strict_types=1);
|
|
|
|
// ---- Configuration (mirror config/config.php) ---------------
|
|
$DB_HOST = getenv('DB_HOST') ?: 'localhost';
|
|
$DB_PORT = getenv('DB_PORT') ?: '3306';
|
|
$DB_NAME = 'poverty_mapping';
|
|
$DB_USER = getenv('DB_USER') ?: 'root';
|
|
$DB_PASS = getenv('DB_PASS') ?: '';
|
|
$DB_CHARSET = 'utf8mb4';
|
|
|
|
$results = [];
|
|
$hasError = false;
|
|
|
|
// ---- Helper -------------------------------------------------
|
|
function step(string $msg, bool $ok, string $detail = ''): void
|
|
{
|
|
global $results, $hasError;
|
|
$results[] = ['ok' => $ok, 'msg' => $msg, 'detail' => $detail];
|
|
if (!$ok) $hasError = true;
|
|
}
|
|
|
|
// ---- Connect WITHOUT selecting a database first -------------
|
|
try {
|
|
$dsn = "mysql:host={$DB_HOST};port={$DB_PORT};charset={$DB_CHARSET}";
|
|
$pdo = new PDO($dsn, $DB_USER, $DB_PASS, [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
]);
|
|
step('Connect to MySQL server', true, "{$DB_HOST}:{$DB_PORT}");
|
|
} catch (PDOException $e) {
|
|
step('Connect to MySQL server', false, $e->getMessage());
|
|
renderPage($results, true);
|
|
exit;
|
|
}
|
|
|
|
// ---- Create database ----------------------------------------
|
|
try {
|
|
$pdo->exec("CREATE DATABASE IF NOT EXISTS `{$DB_NAME}`
|
|
CHARACTER SET utf8mb4
|
|
COLLATE utf8mb4_unicode_ci");
|
|
step("Create database `{$DB_NAME}`", true);
|
|
} catch (PDOException $e) {
|
|
step("Create database `{$DB_NAME}`", false, $e->getMessage());
|
|
renderPage($results, true);
|
|
exit;
|
|
}
|
|
|
|
// ---- Select database ----------------------------------------
|
|
$pdo->exec("USE `{$DB_NAME}`");
|
|
step("USE `{$DB_NAME}`", true);
|
|
|
|
// ---- Enable FK checks off for migration ---------------------
|
|
$pdo->exec('SET FOREIGN_KEY_CHECKS = 0');
|
|
|
|
// ---- Table definitions (ordered: no FK before parent) -------
|
|
$tables = [];
|
|
|
|
// 1 — users
|
|
$tables['users'] = "
|
|
CREATE TABLE IF NOT EXISTS `users` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`name` VARCHAR(150) NOT NULL,
|
|
`email` VARCHAR(255) NOT NULL,
|
|
`password_hash` VARCHAR(255) NOT NULL,
|
|
`role` ENUM('admin','field_officer','viewer') NOT NULL DEFAULT 'viewer',
|
|
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
|
`last_login_at` TIMESTAMP NULL DEFAULT NULL,
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `uq_users_email` (`email`),
|
|
KEY `idx_users_role` (`role`),
|
|
KEY `idx_users_is_active` (`is_active`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// 2 — religious_centers
|
|
$tables['religious_centers'] = "
|
|
CREATE TABLE IF NOT EXISTS `religious_centers` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`name` VARCHAR(255) NOT NULL,
|
|
`worship_type` ENUM('masjid','gereja','klenteng','pura','vihara') NOT NULL DEFAULT 'masjid',
|
|
`address` TEXT NOT NULL,
|
|
`kelurahan` VARCHAR(100) NULL DEFAULT NULL,
|
|
`kecamatan` VARCHAR(100) NULL DEFAULT NULL,
|
|
`latitude` DECIMAL(10,7) NOT NULL,
|
|
`longitude` DECIMAL(10,7) NOT NULL,
|
|
`radius` INT UNSIGNED NOT NULL DEFAULT 300,
|
|
`contact_person` VARCHAR(150) NULL DEFAULT NULL,
|
|
`contact_phone` VARCHAR(20) NULL DEFAULT NULL,
|
|
`notes` TEXT NULL DEFAULT NULL,
|
|
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_rc_worship_type` (`worship_type`),
|
|
KEY `idx_rc_is_active` (`is_active`),
|
|
KEY `idx_rc_location` (`latitude`, `longitude`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// 3 — households
|
|
$tables['households'] = "
|
|
CREATE TABLE IF NOT EXISTS `households` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`rt` VARCHAR(10) NULL DEFAULT NULL,
|
|
`rw` VARCHAR(10) NULL DEFAULT NULL,
|
|
`kelurahan` VARCHAR(100) NULL DEFAULT NULL,
|
|
`kecamatan` VARCHAR(100) NULL DEFAULT NULL,
|
|
`full_address` TEXT NOT NULL,
|
|
`latitude` DECIMAL(10,7) NOT NULL,
|
|
`longitude` DECIMAL(10,7) NOT NULL,
|
|
`house_condition` ENUM('layak','tidak_layak') NOT NULL DEFAULT 'layak',
|
|
`land_ownership` ENUM('milik','sewa','numpang','lainnya') NOT NULL DEFAULT 'milik',
|
|
`house_photos` JSON NULL DEFAULT NULL,
|
|
`head_name` VARCHAR(150) NOT NULL,
|
|
`head_nik` VARCHAR(16) NULL DEFAULT NULL,
|
|
`head_gender` ENUM('male','female') NOT NULL DEFAULT 'male',
|
|
`head_date_of_birth` DATE NULL DEFAULT NULL,
|
|
`head_education` ENUM('tidak_sekolah','sd','smp','sma','diploma','sarjana','pascasarjana') NOT NULL DEFAULT 'sd',
|
|
`head_employment_status` VARCHAR(50) NOT NULL DEFAULT 'unemployed',
|
|
`head_job_name` VARCHAR(150) NULL DEFAULT NULL,
|
|
`head_institution_name` VARCHAR(200) NULL DEFAULT NULL,
|
|
`head_monthly_income` INT UNSIGNED NOT NULL DEFAULT 0,
|
|
`poverty_score` TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
|
`poverty_status` ENUM('terdata','rentan_miskin','miskin','sangat_miskin') NOT NULL DEFAULT 'terdata',
|
|
`aid_status` ENUM('not_yet','received') NOT NULL DEFAULT 'not_yet',
|
|
`managing_center_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
`notes` TEXT NULL DEFAULT NULL,
|
|
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_hh_poverty_status` (`poverty_status`),
|
|
KEY `idx_hh_aid_status` (`aid_status`),
|
|
KEY `idx_hh_house_condition` (`house_condition`),
|
|
KEY `idx_hh_managing_center` (`managing_center_id`),
|
|
KEY `idx_hh_is_active` (`is_active`),
|
|
KEY `idx_hh_location` (`latitude`, `longitude`),
|
|
KEY `idx_hh_head_nik` (`head_nik`),
|
|
KEY `idx_hh_created_at` (`created_at`),
|
|
CONSTRAINT `fk_hh_managing_center`
|
|
FOREIGN KEY (`managing_center_id`) REFERENCES `religious_centers` (`id`)
|
|
ON DELETE SET NULL ON UPDATE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// 4 — household_members
|
|
$tables['household_members'] = "
|
|
CREATE TABLE IF NOT EXISTS `household_members` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`household_id` INT UNSIGNED NOT NULL,
|
|
`name` VARCHAR(150) NOT NULL,
|
|
`nik` VARCHAR(16) NULL DEFAULT NULL,
|
|
`gender` ENUM('male','female') NOT NULL DEFAULT 'male',
|
|
`date_of_birth` DATE NULL DEFAULT NULL,
|
|
`education` ENUM('tidak_sekolah','sd','smp','sma','diploma','sarjana','pascasarjana') NOT NULL DEFAULT 'sd',
|
|
`relationship` VARCHAR(50) NOT NULL DEFAULT 'lainnya',
|
|
`employment_status` VARCHAR(50) NOT NULL DEFAULT 'unemployed',
|
|
`job_name` VARCHAR(150) NULL DEFAULT NULL,
|
|
`institution_name` VARCHAR(200) NULL DEFAULT NULL,
|
|
`monthly_income` INT UNSIGNED NOT NULL DEFAULT 0,
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_hm_household_id` (`household_id`),
|
|
KEY `idx_hm_nik` (`nik`),
|
|
CONSTRAINT `fk_hm_household`
|
|
FOREIGN KEY (`household_id`) REFERENCES `households` (`id`)
|
|
ON DELETE CASCADE ON UPDATE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// 5 — aid_history
|
|
$tables['aid_history'] = "
|
|
CREATE TABLE IF NOT EXISTS `aid_history` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`household_id` INT UNSIGNED NOT NULL,
|
|
`center_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
`aid_type` ENUM('sembako','pendanaan','pelatihan','sembako_pendanaan','sembako_pelatihan','pendanaan_pelatihan','lengkap') NOT NULL,
|
|
`aid_date` DATE NOT NULL,
|
|
`amount` INT UNSIGNED NULL DEFAULT NULL,
|
|
`description` TEXT NULL DEFAULT NULL,
|
|
`given_by` INT UNSIGNED NULL DEFAULT NULL,
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_ah_household_id` (`household_id`),
|
|
KEY `idx_ah_center_id` (`center_id`),
|
|
KEY `idx_ah_aid_date` (`aid_date`),
|
|
KEY `idx_ah_aid_type` (`aid_type`),
|
|
CONSTRAINT `fk_ah_household`
|
|
FOREIGN KEY (`household_id`) REFERENCES `households` (`id`)
|
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
|
CONSTRAINT `fk_ah_center`
|
|
FOREIGN KEY (`center_id`) REFERENCES `religious_centers` (`id`)
|
|
ON DELETE SET NULL ON UPDATE CASCADE,
|
|
CONSTRAINT `fk_ah_given_by`
|
|
FOREIGN KEY (`given_by`) REFERENCES `users` (`id`)
|
|
ON DELETE SET NULL ON UPDATE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// 6 — emergency_reports
|
|
$tables['emergency_reports'] = "
|
|
CREATE TABLE IF NOT EXISTS `emergency_reports` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`household_id` INT UNSIGNED NOT NULL,
|
|
`type` ENUM('sakit','kecelakaan','bencana','kehilangan_pekerjaan','kematian','lainnya') NOT NULL,
|
|
`severity` ENUM('ringan','sedang','berat','kritis') NOT NULL DEFAULT 'ringan',
|
|
`description` TEXT NOT NULL,
|
|
`status` ENUM('open','in_progress','resolved','closed') NOT NULL DEFAULT 'open',
|
|
`resolved_at` TIMESTAMP NULL DEFAULT NULL,
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_er_household_id` (`household_id`),
|
|
KEY `idx_er_status` (`status`),
|
|
KEY `idx_er_severity` (`severity`),
|
|
KEY `idx_er_created_at` (`created_at`),
|
|
CONSTRAINT `fk_er_household`
|
|
FOREIGN KEY (`household_id`) REFERENCES `households` (`id`)
|
|
ON DELETE CASCADE ON UPDATE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// 7 — public_reports
|
|
$tables['public_reports'] = "
|
|
CREATE TABLE IF NOT EXISTS `public_reports` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`reporter_name` VARCHAR(150) NOT NULL,
|
|
`reporter_phone` VARCHAR(20) NOT NULL,
|
|
`rt` VARCHAR(10) NOT NULL DEFAULT '',
|
|
`rw` VARCHAR(10) NOT NULL DEFAULT '',
|
|
`head_name` VARCHAR(150) NOT NULL,
|
|
`address` TEXT NOT NULL,
|
|
`kelurahan` VARCHAR(100) NOT NULL,
|
|
`kecamatan` VARCHAR(100) NOT NULL,
|
|
`latitude` DECIMAL(10,7) NOT NULL,
|
|
`longitude` DECIMAL(10,7) NOT NULL,
|
|
`description` TEXT NOT NULL,
|
|
`severity` ENUM('ringan','sedang','berat','kritis') NOT NULL DEFAULT 'ringan',
|
|
`urgent_need` TEXT NULL DEFAULT NULL,
|
|
`proof_photos` JSON NULL DEFAULT NULL,
|
|
`status` ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
|
|
`admin_notes` TEXT NULL DEFAULT NULL,
|
|
`reviewed_at` TIMESTAMP NULL DEFAULT NULL,
|
|
`converted_household_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
`ip_address` VARCHAR(45) NOT NULL DEFAULT '',
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_pr_status` (`status`),
|
|
KEY `idx_pr_severity` (`severity`),
|
|
KEY `idx_pr_ip_address` (`ip_address`),
|
|
KEY `idx_pr_created_at` (`created_at`),
|
|
KEY `idx_pr_converted` (`converted_household_id`),
|
|
CONSTRAINT `fk_pr_converted_household`
|
|
FOREIGN KEY (`converted_household_id`) REFERENCES `households` (`id`)
|
|
ON DELETE SET NULL ON UPDATE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// 8 — audit_logs
|
|
$tables['audit_logs'] = "
|
|
CREATE TABLE IF NOT EXISTS `audit_logs` (
|
|
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
`action` VARCHAR(100) NOT NULL,
|
|
`table_name` VARCHAR(100) NOT NULL,
|
|
`record_id` INT UNSIGNED NULL DEFAULT NULL,
|
|
`old_values` JSON NULL DEFAULT NULL,
|
|
`new_values` JSON NULL DEFAULT NULL,
|
|
`ip_address` VARCHAR(45) NOT NULL DEFAULT '',
|
|
`user_agent` TEXT NULL DEFAULT NULL,
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_al_table_name` (`table_name`),
|
|
KEY `idx_al_record_id` (`record_id`),
|
|
KEY `idx_al_action` (`action`),
|
|
KEY `idx_al_created_at` (`created_at`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
";
|
|
|
|
// ---- Execute each table -------------------------------------
|
|
foreach ($tables as $tableName => $sql) {
|
|
try {
|
|
$pdo->exec($sql);
|
|
step("CREATE TABLE IF NOT EXISTS `{$tableName}`", true);
|
|
} catch (PDOException $e) {
|
|
step("CREATE TABLE IF NOT EXISTS `{$tableName}`", false, $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// ---- Re-enable FK checks ------------------------------------
|
|
$pdo->exec('SET FOREIGN_KEY_CHECKS = 1');
|
|
step('Re-enable FOREIGN_KEY_CHECKS', true);
|
|
|
|
// ---- Verify: count created tables ---------------------------
|
|
try {
|
|
$count = (int)$pdo->query("SELECT COUNT(*) FROM information_schema.TABLES
|
|
WHERE TABLE_SCHEMA = '{$DB_NAME}'")->fetchColumn();
|
|
step("Verify: {$count} table(s) exist in `{$DB_NAME}`", $count >= 8,
|
|
$count < 8 ? "Expected at least 8 tables." : "");
|
|
} catch (PDOException $e) {
|
|
step('Verify table count', false, $e->getMessage());
|
|
}
|
|
|
|
renderPage($results, $hasError);
|
|
|
|
// ---- Render HTML --------------------------------------------
|
|
function renderPage(array $results, bool $hasError): void
|
|
{
|
|
$title = $hasError ? '⚠ Migration completed with errors' : '✅ Migration successful';
|
|
$color = $hasError ? '#c0392b' : '#27ae60';
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>DB Migration — poverty_mapping</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #0f1117; color: #e2e8f0; min-height: 100vh; padding: 2rem; }
|
|
.card { max-width: 900px; margin: 0 auto; background: #1a1d2e; border-radius: 12px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,.5); }
|
|
.header { padding: 2rem; background: <?= $color ?>22; border-bottom: 2px solid <?= $color ?>; }
|
|
.header h1 { font-size: 1.6rem; color: <?= $color ?>; }
|
|
.header p { margin-top: .4rem; color: #94a3b8; font-size: .9rem; }
|
|
.steps { padding: 1.5rem; display: flex; flex-direction: column; gap: .6rem; }
|
|
.step { display: flex; gap: 1rem; align-items: flex-start; padding: .7rem 1rem; border-radius: 8px; background: #252a3e; }
|
|
.step.ok { border-left: 3px solid #27ae60; }
|
|
.step.err { border-left: 3px solid #c0392b; }
|
|
.icon { font-size: 1.1rem; flex-shrink: 0; margin-top: .05rem; }
|
|
.msg { font-size: .9rem; flex: 1; }
|
|
.detail { font-size: .78rem; color: #f87171; margin-top: .2rem; font-family: monospace; }
|
|
.footer { padding: 1.5rem; border-top: 1px solid #2d3348; color: #64748b; font-size: .8rem; }
|
|
.next { margin-top: .8rem; }
|
|
.next a { color: #60a5fa; text-decoration: none; }
|
|
.next a:hover { text-decoration: underline; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<div class="header">
|
|
<h1><?= htmlspecialchars($title) ?></h1>
|
|
<p>Database: <strong>poverty_mapping</strong> | <?= date('Y-m-d H:i:s') ?></p>
|
|
</div>
|
|
<div class="steps">
|
|
<?php foreach ($results as $r): ?>
|
|
<div class="step <?= $r['ok'] ? 'ok' : 'err' ?>">
|
|
<span class="icon"><?= $r['ok'] ? '✅' : '❌' ?></span>
|
|
<div class="msg">
|
|
<?= htmlspecialchars($r['msg']) ?>
|
|
<?php if ($r['detail']): ?>
|
|
<div class="detail"><?= htmlspecialchars($r['detail']) ?></div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<div class="footer">
|
|
Migration script: <code>database/migrate.php</code>
|
|
<div class="next">
|
|
<?php if (!$hasError): ?>
|
|
✅ All done! You can now run the seeder:
|
|
<a href="seeder.php">database/seeder.php</a>
|
|
<?php else: ?>
|
|
⚠ Fix the errors above, then reload this page to re-run.
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
}
|