diff --git a/api/config.php b/api/config.php index ed6c8eb..67b6144 100644 --- a/api/config.php +++ b/api/config.php @@ -2,415 +2,351 @@ // Session configuration session_start(); -// Database configuration -define('DB_PATH', __DIR__ . '/../db/markers.db'); +// MySQL Database configuration +define('DB_HOST', ' '); +define('DB_PORT', ' '); +define('DB_NAME', ' '); +define('DB_USER', ' '); +define('DB_PASS', ' '); -// Create db directory if it doesn't exist -if (!is_dir(__DIR__ . '/../db')) { - mkdir(__DIR__ . '/../db', 0755, true); -} - -// Connect to SQLite database +// Connect to MySQL database function getDB() { + static $db = null; + if ($db !== null) { + return $db; + } + try { - $db = new PDO('sqlite:' . DB_PATH); - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $db->exec('PRAGMA foreign_keys = ON'); + $dsn = 'mysql:host=' . DB_HOST . ';port=' . DB_PORT . ';dbname=' . DB_NAME . ';charset=utf8mb4'; + $db = new PDO($dsn, DB_USER, DB_PASS, [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4' + ]); // Initialize database schema initializeDB($db); return $db; } catch (PDOException $e) { + http_response_code(500); die(json_encode(['error' => 'Database connection failed: ' . $e->getMessage()])); } } -// Initialize database schema +// Initialize database schema (MySQL version) function initializeDB($db) { // Create markers table for points $db->exec(' CREATE TABLE IF NOT EXISTS markers ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, - nomor_spbu VARCHAR(100), - latitude REAL NOT NULL, - longitude REAL NOT NULL, - open_24_hours BOOLEAN DEFAULT 0, + nomor_spbu VARCHAR(100) DEFAULT NULL, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + open_24_hours TINYINT(1) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); - // Add nomor_spbu column to existing databases if it doesn't exist - try { - $db->exec('PRAGMA table_info(markers)'); - $columns = $db->query('PRAGMA table_info(markers)')->fetchAll(PDO::FETCH_ASSOC); - $hasNomorSpbu = false; - - foreach ($columns as $col) { - if ($col['name'] === 'nomor_spbu') { - $hasNomorSpbu = true; - break; - } - } - - if (!$hasNomorSpbu) { - $db->exec('ALTER TABLE markers ADD COLUMN nomor_spbu VARCHAR(100)'); - } - } catch (PDOException $e) { - // Table already has the column or other non-critical error - } - // Create roads table for polylines $db->exec(' CREATE TABLE IF NOT EXISTS roads ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, - road_type VARCHAR(50) NOT NULL CHECK(road_type IN (\'Nasional\', \'Provinsi\', \'Kabupaten\')), - coordinates TEXT NOT NULL, - length_meters REAL NOT NULL, + road_type ENUM(\'Nasional\', \'Provinsi\', \'Kabupaten\') NOT NULL, + coordinates LONGTEXT NOT NULL, + length_meters DOUBLE NOT NULL, + condition_status ENUM(\'normal\', \'minor_damage\', \'major_damage\') DEFAULT \'normal\', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); // Create indexes for roads table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_roads_road_type ON roads(road_type)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_roads_created_at ON roads(created_at)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_roads_road_type ON roads(road_type)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_roads_created_at ON roads(created_at)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create land_parcels table for polygons $db->exec(' CREATE TABLE IF NOT EXISTS land_parcels ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, - certificate_type VARCHAR(50) NOT NULL CHECK(certificate_type IN (\'SHM\', \'HGB\', \'HGU\', \'HP\')), - coordinates TEXT NOT NULL, - area_sqm REAL NOT NULL, + certificate_type ENUM(\'SHM\', \'HGB\', \'HGU\', \'HP\') NOT NULL, + coordinates LONGTEXT NOT NULL, + area_sqm DOUBLE NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); // Create indexes for land_parcels table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_parcels_cert_type ON land_parcels(certificate_type)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_parcels_created_at ON land_parcels(created_at)'); - } catch (PDOException $e) { - // Indexes may already exist - } - - // Add condition_status column to roads table if it doesn't exist + $db->exec('CREATE INDEX idx_parcels_cert_type ON land_parcels(certificate_type)'); + } catch (PDOException $e) { /* Index may already exist */ } try { - $columns = $db->query('PRAGMA table_info(roads)')->fetchAll(PDO::FETCH_ASSOC); - $hasConditionStatus = false; - - foreach ($columns as $col) { - if ($col['name'] === 'condition_status') { - $hasConditionStatus = true; - break; - } - } - - if (!$hasConditionStatus) { - $db->exec('ALTER TABLE roads ADD COLUMN condition_status VARCHAR(20) DEFAULT \'normal\' CHECK(condition_status IN (\'normal\', \'minor_damage\', \'major_damage\'))'); - } - } catch (PDOException $e) { - // Column may already exist - } + $db->exec('CREATE INDEX idx_parcels_created_at ON land_parcels(created_at)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create pontianak_areas table for choropleth boundaries $db->exec(' CREATE TABLE IF NOT EXISTS pontianak_areas ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, - area_type VARCHAR(50) NOT NULL CHECK(area_type IN (\'kelurahan\', \'kecamatan\', \'city\')), - geometry TEXT NOT NULL, + area_type ENUM(\'kelurahan\', \'kecamatan\', \'city\') NOT NULL, + geometry LONGTEXT NOT NULL, population INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - UNIQUE(name, area_type) - ) + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uq_area_name_type (name, area_type) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); // Create indexes for pontianak_areas table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_pontianak_areas_type ON pontianak_areas(area_type)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_pontianak_areas_name ON pontianak_areas(name)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_pontianak_areas_type ON pontianak_areas(area_type)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_pontianak_areas_name ON pontianak_areas(name)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create area_statistics table for computed densities $db->exec(' CREATE TABLE IF NOT EXISTS area_statistics ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - pontianak_area_id INTEGER NOT NULL, - statistic_type VARCHAR(50) NOT NULL CHECK(statistic_type IN (\'population_density\', \'road_density\', \'damaged_roads_density\')), - value REAL NOT NULL, + id INT AUTO_INCREMENT PRIMARY KEY, + pontianak_area_id INT NOT NULL, + statistic_type ENUM(\'population_density\', \'road_density\', \'damaged_roads_density\') NOT NULL, + value DOUBLE NOT NULL, data_date DATE NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(pontianak_area_id) REFERENCES pontianak_areas(id) ON DELETE CASCADE, - UNIQUE(pontianak_area_id, statistic_type, data_date) - ) + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uq_area_stat_date (pontianak_area_id, statistic_type, data_date), + FOREIGN KEY (pontianak_area_id) REFERENCES pontianak_areas(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); // Create indexes for area_statistics table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_stats_area_type ON area_statistics(pontianak_area_id, statistic_type)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_stats_area_type ON area_statistics(pontianak_area_id, statistic_type)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create masjids table $db->exec(' CREATE TABLE IF NOT EXISTS masjids ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, - latitude REAL NOT NULL, - longitude REAL NOT NULL, - radius_meters REAL NOT NULL DEFAULT 500, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + radius_meters DOUBLE NOT NULL DEFAULT 500, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); // Create indexes for masjids table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_masjids_created_at ON masjids(created_at)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_masjids_name ON masjids(name)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_masjids_created_at ON masjids(created_at)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_masjids_name ON masjids(name)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create special needs points table $db->exec(' CREATE TABLE IF NOT EXISTS need_points ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, - latitude REAL NOT NULL, - longitude REAL NOT NULL, - masjid_id INTEGER NOT NULL, - household_name VARCHAR(255), - head_name VARCHAR(255), - economic_status VARCHAR(50), - is_verified INTEGER NOT NULL DEFAULT 0, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + masjid_id INT NOT NULL, + household_name VARCHAR(255) DEFAULT NULL, + head_name VARCHAR(255) DEFAULT NULL, + economic_status VARCHAR(50) DEFAULT NULL, + is_verified TINYINT(1) NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(masjid_id) REFERENCES masjids(id) ON DELETE CASCADE - ) + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (masjid_id) REFERENCES masjids(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); // Create indexes for need_points table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_need_points_masjid_id ON need_points(masjid_id)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_need_points_created_at ON need_points(created_at)'); - } catch (PDOException $e) { - // Indexes may already exist - } - - // Add new columns to existing need_points table if they don't exist + $db->exec('CREATE INDEX idx_need_points_masjid_id ON need_points(masjid_id)'); + } catch (PDOException $e) { /* Index may already exist */ } try { - $columns = $db->query('PRAGMA table_info(need_points)')->fetchAll(PDO::FETCH_ASSOC); - $hasHouseholdName = false; - $hasHeadName = false; - $hasEconomic = false; - $hasVerified = false; - foreach ($columns as $col) { - if ($col['name'] === 'household_name') $hasHouseholdName = true; - if ($col['name'] === 'head_name') $hasHeadName = true; - if ($col['name'] === 'economic_status') $hasEconomic = true; - if ($col['name'] === 'is_verified') $hasVerified = true; - } - if (!$hasHouseholdName) { - $db->exec('ALTER TABLE need_points ADD COLUMN household_name VARCHAR(255)'); - } - if (!$hasHeadName) { - $db->exec('ALTER TABLE need_points ADD COLUMN head_name VARCHAR(255)'); - } - if (!$hasEconomic) { - $db->exec('ALTER TABLE need_points ADD COLUMN economic_status VARCHAR(50)'); - } - if (!$hasVerified) { - $db->exec('ALTER TABLE need_points ADD COLUMN is_verified INTEGER NOT NULL DEFAULT 0'); - } - } catch (PDOException $e) { - // non-critical - } + $db->exec('CREATE INDEX idx_need_points_created_at ON need_points(created_at)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create households table for poverty alleviation base data - $db->exec(' + $db->exec(" CREATE TABLE IF NOT EXISTS households ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, household_code VARCHAR(100) NOT NULL UNIQUE, head_name VARCHAR(255) NOT NULL, - nik_hash VARCHAR(255), - latitude REAL NOT NULL, - longitude REAL NOT NULL, - need_point_id INTEGER UNIQUE, - masjid_id INTEGER, - pontianak_area_id INTEGER, - monthly_income REAL NOT NULL DEFAULT 0, - dependents_count INTEGER NOT NULL DEFAULT 0, - housing_condition_score INTEGER NOT NULL DEFAULT 3 CHECK(housing_condition_score BETWEEN 1 AND 5), - employment_status VARCHAR(50) NOT NULL DEFAULT \'unknown\' CHECK(employment_status IN (\'formal\', \'informal\', \'unemployed\', \'daily_worker\', \'micro_business\', \'retired\', \'unknown\')), - economic_status VARCHAR(50) NOT NULL DEFAULT \'unknown\' CHECK(economic_status IN (\'sangat_miskin\', \'miskin\', \'rentan\', \'cukup\', \'baik\', \'unknown\')), - verification_status VARCHAR(30) NOT NULL DEFAULT \'unverified\' CHECK(verification_status IN (\'unverified\', \'field_verified\', \'admin_approved\', \'rejected\', \'needs_review\')), - vulnerability_notes TEXT, + nik_hash VARCHAR(255) DEFAULT NULL, + latitude DOUBLE NOT NULL, + longitude DOUBLE NOT NULL, + need_point_id INT DEFAULT NULL UNIQUE, + masjid_id INT DEFAULT NULL, + pontianak_area_id INT DEFAULT NULL, + monthly_income DOUBLE NOT NULL DEFAULT 0, + dependents_count INT NOT NULL DEFAULT 0, + housing_condition_score TINYINT NOT NULL DEFAULT 3, + employment_status ENUM('formal', 'informal', 'unemployed', 'daily_worker', 'micro_business', 'retired', 'unknown') NOT NULL DEFAULT 'unknown', + economic_status ENUM('sangat_miskin', 'miskin', 'rentan', 'cukup', 'baik', 'unknown') NOT NULL DEFAULT 'unknown', + verification_status ENUM('unverified', 'field_verified', 'admin_approved', 'rejected', 'needs_review') NOT NULL DEFAULT 'unverified', + vulnerability_notes TEXT DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(need_point_id) REFERENCES need_points(id) ON DELETE SET NULL, - FOREIGN KEY(masjid_id) REFERENCES masjids(id) ON DELETE SET NULL, - FOREIGN KEY(pontianak_area_id) REFERENCES pontianak_areas(id) ON DELETE SET NULL - ) - '); + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (need_point_id) REFERENCES need_points(id) ON DELETE SET NULL, + FOREIGN KEY (masjid_id) REFERENCES masjids(id) ON DELETE SET NULL, + FOREIGN KEY (pontianak_area_id) REFERENCES pontianak_areas(id) ON DELETE SET NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + "); // Create indexes for households table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_households_code ON households(household_code)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_households_verification_status ON households(verification_status)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_households_masjid_id ON households(masjid_id)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_households_area_id ON households(pontianak_area_id)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_households_created_at ON households(created_at)'); - } catch (PDOException $e) { - // Indexes may already exist - } - - // Add economic_status column to existing households table if it doesn't exist + $db->exec('CREATE INDEX idx_households_code ON households(household_code)'); + } catch (PDOException $e) { /* Index may already exist */ } try { - $columns = $db->query('PRAGMA table_info(households)')->fetchAll(PDO::FETCH_ASSOC); - $hasEconomicStatus = false; - foreach ($columns as $col) { - if ($col['name'] === 'economic_status') { - $hasEconomicStatus = true; - break; - } - } - if (!$hasEconomicStatus) { - $db->exec("ALTER TABLE households ADD COLUMN economic_status VARCHAR(50) NOT NULL DEFAULT 'unknown'"); - } - } catch (PDOException $e) { - // non-critical - } + $db->exec('CREATE INDEX idx_households_verification_status ON households(verification_status)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_households_masjid_id ON households(masjid_id)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_households_area_id ON households(pontianak_area_id)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_households_created_at ON households(created_at)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create assistance distributions table - $db->exec(' + $db->exec(" CREATE TABLE IF NOT EXISTS assistance_distributions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - household_id INTEGER NOT NULL, - assistance_type VARCHAR(50) NOT NULL CHECK(assistance_type IN (\'pangan\', \'tunai\', \'kesehatan\', \'pendidikan\', \'usaha_mikro\', \'lainnya\')), - assistance_value REAL NOT NULL DEFAULT 0, - assistance_frequency VARCHAR(30) NOT NULL DEFAULT \'sekali\' CHECK(assistance_frequency IN (\'sekali\', \'mingguan\', \'bulanan\', \'triwulanan\', \'tahunan\', \'insidental\')), + id INT AUTO_INCREMENT PRIMARY KEY, + household_id INT NOT NULL, + assistance_type ENUM('pangan', 'tunai', 'kesehatan', 'pendidikan', 'usaha_mikro', 'lainnya') NOT NULL, + assistance_value DOUBLE NOT NULL DEFAULT 0, + assistance_frequency ENUM('sekali', 'mingguan', 'bulanan', 'triwulanan', 'tahunan', 'insidental') NOT NULL DEFAULT 'sekali', distribution_date DATE NOT NULL, - delivery_status VARCHAR(30) NOT NULL DEFAULT \'pending\' CHECK(delivery_status IN (\'pending\', \'delivered\', \'failed\')), - delivered_by VARCHAR(255), - notes TEXT, + delivery_status ENUM('pending', 'delivered', 'failed') NOT NULL DEFAULT 'pending', + delivered_by VARCHAR(255) DEFAULT NULL, + notes TEXT DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(household_id) REFERENCES households(id) ON DELETE CASCADE - ) - '); + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + "); // Create indexes for assistance distributions table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_assistance_household_id ON assistance_distributions(household_id)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_assistance_date ON assistance_distributions(distribution_date)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_assistance_status ON assistance_distributions(delivery_status)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_assistance_household_id ON assistance_distributions(household_id)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_assistance_date ON assistance_distributions(distribution_date)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_assistance_status ON assistance_distributions(delivery_status)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create verification logs table - $db->exec(' + $db->exec(" CREATE TABLE IF NOT EXISTS verification_logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - household_id INTEGER NOT NULL, - verification_status VARCHAR(30) NOT NULL CHECK(verification_status IN (\'field_verified\', \'admin_approved\', \'rejected\', \'needs_review\')), + id INT AUTO_INCREMENT PRIMARY KEY, + household_id INT NOT NULL, + verification_status ENUM('field_verified', 'admin_approved', 'rejected', 'needs_review') NOT NULL, verifier_name VARCHAR(255) NOT NULL, - verifier_role VARCHAR(100), - field_note TEXT, - evidence_photo_count INTEGER NOT NULL DEFAULT 0, - data_confidence_score REAL, + verifier_role VARCHAR(100) DEFAULT NULL, + field_note TEXT DEFAULT NULL, + evidence_photo_count INT NOT NULL DEFAULT 0, + data_confidence_score DOUBLE DEFAULT NULL, verified_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(household_id) REFERENCES households(id) ON DELETE CASCADE - ) - '); + FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + "); // Create indexes for verification logs table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_verification_household_id ON verification_logs(household_id)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_verification_status ON verification_logs(verification_status)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_verification_verified_at ON verification_logs(verified_at)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_verification_household_id ON verification_logs(household_id)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_verification_status ON verification_logs(verification_status)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_verification_verified_at ON verification_logs(verified_at)'); + } catch (PDOException $e) { /* Index may already exist */ } - // Create household scores table (Option B) - $db->exec(' + // Create household scores table + $db->exec(" CREATE TABLE IF NOT EXISTS household_scores ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - household_id INTEGER NOT NULL UNIQUE, - skr REAL NOT NULL DEFAULT 0, - sag REAL NOT NULL DEFAULT 0, - spi REAL NOT NULL DEFAULT 0, - priority_level VARCHAR(30) NOT NULL DEFAULT \'low\' CHECK(priority_level IN (\'very_high\', \'high\', \'medium\', \'low\')), - score_version VARCHAR(30) NOT NULL DEFAULT \'v1\', - components_json TEXT, + id INT AUTO_INCREMENT PRIMARY KEY, + household_id INT NOT NULL UNIQUE, + skr DOUBLE NOT NULL DEFAULT 0, + sag DOUBLE NOT NULL DEFAULT 0, + spi DOUBLE NOT NULL DEFAULT 0, + priority_level ENUM('very_high', 'high', 'medium', 'low') NOT NULL DEFAULT 'low', + score_version VARCHAR(30) NOT NULL DEFAULT 'v1', + components_json TEXT DEFAULT NULL, computed_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(household_id) REFERENCES households(id) ON DELETE CASCADE - ) - '); + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + "); // Create indexes for household_scores table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_household_scores_spi ON household_scores(spi DESC)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_household_scores_priority ON household_scores(priority_level)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_household_scores_computed_at ON household_scores(computed_at)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_household_scores_spi ON household_scores(spi)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_household_scores_priority ON household_scores(priority_level)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_household_scores_computed_at ON household_scores(computed_at)'); + } catch (PDOException $e) { /* Index may already exist */ } // Create score metadata table for versioning/tuning $db->exec(' CREATE TABLE IF NOT EXISTS score_metadata ( key_name VARCHAR(100) PRIMARY KEY, key_value TEXT NOT NULL, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 '); // Create users table for authentication - $db->exec(' + $db->exec(" CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, - role VARCHAR(50) NOT NULL DEFAULT \'Petugas\' CHECK(role IN (\'Admin\', \'Petugas\', \'Pimpinan\')), - is_active BOOLEAN NOT NULL DEFAULT 1, + role ENUM('Admin', 'Petugas', 'Pimpinan') NOT NULL DEFAULT 'Petugas', + is_active TINYINT(1) NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ) - '); + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + "); // Create indexes for users table try { - $db->exec('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)'); - $db->exec('CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)'); - } catch (PDOException $e) { - // Indexes may already exist - } + $db->exec('CREATE INDEX idx_users_email ON users(email)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_users_username ON users(username)'); + } catch (PDOException $e) { /* Index may already exist */ } + try { + $db->exec('CREATE INDEX idx_users_role ON users(role)'); + } catch (PDOException $e) { /* Index may already exist */ } // Initialize default admin user try { - $adminCount = $db->query('SELECT COUNT(*) as cnt FROM users WHERE role = \'Admin\'')->fetch(PDO::FETCH_ASSOC); + $adminCount = $db->query("SELECT COUNT(*) as cnt FROM users WHERE role = 'Admin'")->fetch(PDO::FETCH_ASSOC); if ($adminCount['cnt'] == 0) { $adminPassword = password_hash('admin123', PASSWORD_BCRYPT, ['cost' => 12]); $stmt = $db->prepare('INSERT INTO users (username, email, password_hash, role, is_active) VALUES (?, ?, ?, ?, 1)'); @@ -433,7 +369,7 @@ function initializeDB($db) { 'population_reference' => '288315899' ]; - $metaStmt = $db->prepare('INSERT OR IGNORE INTO score_metadata (key_name, key_value) VALUES (?, ?)'); + $metaStmt = $db->prepare('INSERT IGNORE INTO score_metadata (key_name, key_value) VALUES (?, ?)'); foreach ($defaultMeta as $key => $value) { $metaStmt->execute([$key, $value]); } @@ -895,15 +831,55 @@ function syncNeedPointsToHouseholds($db) { $updatedCount = 0; $insertedCount = 0; - $updateStmt = $db->prepare("\n UPDATE households\n SET economic_status = (\n SELECT COALESCE(NULLIF(TRIM(np.economic_status), ''), 'unknown')\n FROM need_points np\n WHERE np.id = households.need_point_id\n ),\n updated_at = CURRENT_TIMESTAMP\n WHERE need_point_id IS NOT NULL\n AND (economic_status IS NULL OR TRIM(economic_status) = '')\n "); + $updateStmt = $db->prepare(" + UPDATE households + SET economic_status = ( + SELECT COALESCE(NULLIF(TRIM(np.economic_status), ''), 'unknown') + FROM need_points np + WHERE np.id = households.need_point_id + ), + updated_at = CURRENT_TIMESTAMP + WHERE need_point_id IS NOT NULL + AND (economic_status IS NULL OR TRIM(economic_status) = '') + "); $updateStmt->execute(); $updatedCount += $updateStmt->rowCount(); - $missingStmt = $db->query("\n SELECT\n np.id,\n np.name,\n np.latitude,\n np.longitude,\n np.masjid_id,\n np.head_name,\n np.economic_status,\n np.is_verified\n FROM need_points np\n LEFT JOIN households h ON h.need_point_id = np.id\n WHERE h.id IS NULL\n ORDER BY np.id ASC\n "); + $missingStmt = $db->query(" + SELECT + np.id, + np.name, + np.latitude, + np.longitude, + np.masjid_id, + np.head_name, + np.economic_status, + np.is_verified + FROM need_points np + LEFT JOIN households h ON h.need_point_id = np.id + WHERE h.id IS NULL + ORDER BY np.id ASC + "); $missingRows = $missingStmt->fetchAll(PDO::FETCH_ASSOC); if (!empty($missingRows)) { - $insertStmt = $db->prepare("\n INSERT INTO households (\n household_code,\n head_name,\n latitude,\n longitude,\n need_point_id,\n masjid_id,\n monthly_income,\n dependents_count,\n housing_condition_score,\n employment_status,\n economic_status,\n verification_status,\n vulnerability_notes\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n "); + $insertStmt = $db->prepare(" + INSERT INTO households ( + household_code, + head_name, + latitude, + longitude, + need_point_id, + masjid_id, + monthly_income, + dependents_count, + housing_condition_score, + employment_status, + economic_status, + verification_status, + vulnerability_notes + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "); foreach ($missingRows as $row) { $pointId = (int)$row['id']; @@ -945,7 +921,13 @@ function syncNeedPointsToHouseholds($db) { } function ensureHouseholdScoresForAllHouseholds($db) { - $missingStmt = $db->query("\n SELECT h.*\n FROM households h\n LEFT JOIN household_scores hs ON hs.household_id = h.id\n WHERE hs.household_id IS NULL\n ORDER BY h.id ASC\n "); + $missingStmt = $db->query(" + SELECT h.* + FROM households h + LEFT JOIN household_scores hs ON hs.household_id = h.id + WHERE hs.household_id IS NULL + ORDER BY h.id ASC + "); $missingHouseholds = $missingStmt->fetchAll(PDO::FETCH_ASSOC); $computed = 0; @@ -1160,14 +1142,13 @@ function upsertHouseholdScore($db, $householdId, $scoreData) { updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON CONFLICT(household_id) - DO UPDATE SET - skr = excluded.skr, - sag = excluded.sag, - spi = excluded.spi, - priority_level = excluded.priority_level, - score_version = excluded.score_version, - components_json = excluded.components_json, + ON DUPLICATE KEY UPDATE + skr = VALUES(skr), + sag = VALUES(sag), + spi = VALUES(spi), + priority_level = VALUES(priority_level), + score_version = VALUES(score_version), + components_json = VALUES(components_json), computed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP '); diff --git a/api/login.php b/api/login.php index 77f3d0f..aafea76 100644 --- a/api/login.php +++ b/api/login.php @@ -13,11 +13,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { } if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $data = json_decode(file_get_contents('php://input'), true); + $rawData = file_get_contents('php://input'); + $data = json_decode($rawData, true); - if (!isset($data['email']) || !isset($data['password'])) { + if (!$data) { + $data = $_POST; + } + + if (empty($data['email']) || empty($data['password'])) { http_response_code(400); - die(json_encode(['error' => 'Email and password are required'])); + die(json_encode([ + 'error' => 'Email and password are required', + 'debug' => ['raw' => $rawData, 'post' => $_POST] + ])); } $result = loginUser($data['email'], $data['password']); diff --git a/api/save_configuration.php b/api/save_configuration.php index 07a6edd..886028c 100644 --- a/api/save_configuration.php +++ b/api/save_configuration.php @@ -35,7 +35,7 @@ try { } $db = getDB(); - $stmt = $db->prepare('INSERT INTO score_metadata (key_name, key_value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(key_name) DO UPDATE SET key_value = excluded.key_value, updated_at = CURRENT_TIMESTAMP'); + $stmt = $db->prepare('INSERT INTO score_metadata (key_name, key_value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE key_value = VALUES(key_value), updated_at = CURRENT_TIMESTAMP'); $stmt->execute(['target_nasional', number_format($targetNasional, 2, '.', '')]); $stmt->execute(['population_reference', (string)$populationReference]); diff --git a/api/seed_pontianak_data.php b/api/seed_pontianak_data.php index dc80620..c10f1ca 100644 --- a/api/seed_pontianak_data.php +++ b/api/seed_pontianak_data.php @@ -77,13 +77,13 @@ try { // Insert Pontianak areas $stmt = $db->prepare(' - INSERT OR IGNORE INTO pontianak_areas (name, area_type, geometry, population) + INSERT IGNORE INTO pontianak_areas (name, area_type, geometry, population) VALUES (?, ?, ?, ?) '); $today = date('Y-m-d'); $stats_stmt = $db->prepare(' - INSERT OR IGNORE INTO area_statistics (pontianak_area_id, statistic_type, value, data_date) + INSERT IGNORE INTO area_statistics (pontianak_area_id, statistic_type, value, data_date) VALUES (?, ?, ?, ?) ');