Initial commit of unified WebGIS projects

This commit is contained in:
ilham_gmail
2026-06-11 17:42:40 +07:00
commit a0c61f2bc2
64 changed files with 7924 additions and 0 deletions
@@ -0,0 +1,21 @@
<?php
include __DIR__ . '/../../config/koneksi.php';
include __DIR__ . '/../../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
$admin = require_bearer_login();
if(!isset($admin['role']) || $admin['role'] !== 'admin'){ http_response_code(403); echo json_encode(['success'=>false,'error'=>'forbidden']); exit; }
$data = json_decode(file_get_contents('php://input'), true);
if(!$data || !isset($data['id'])){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_id']); exit; }
$id = intval($data['id']);
$st = $conn->prepare('UPDATE users SET status = ?, approved_by = ?, approved_at = NOW() WHERE id = ? AND role = "manager"');
if(!$st){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$status = 'active'; $adminId = intval($admin['id']); $st->bind_param('sii', $status, $adminId, $id);
$res = $st->execute(); $st->close();
if(!$res){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
// optional: send notification email
echo json_encode(['success'=>true]);
?>
+43
View File
@@ -0,0 +1,43 @@
<?php
include __DIR__ . '/../../config/koneksi.php';
include __DIR__ . '/../../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
$data = json_decode(file_get_contents('php://input'), true);
if(!$data){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'invalid_json']); exit; }
$email = isset($data['email']) ? strtolower(trim($data['email'])) : '';
$password = isset($data['password']) ? $data['password'] : '';
if(!$email || !$password){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_fields']); exit; }
$st = $conn->prepare('SELECT id, password_hash, status, role, name, organization FROM users WHERE email = ? LIMIT 1');
if(!$st){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$st->bind_param('s', $email);
$st->execute();
$r = $st->get_result();
$row = $r ? $r->fetch_assoc() : null;
$st->close();
if(!$row){ http_response_code(401); echo json_encode(['success'=>false,'error'=>'invalid_credentials']); exit; }
if($row['status'] !== 'active'){ http_response_code(403); echo json_encode(['success'=>false,'error'=>'account_not_active','status'=>$row['status']]); exit; }
if(!password_verify($password, $row['password_hash'])){ http_response_code(401); echo json_encode(['success'=>false,'error'=>'invalid_credentials']); exit; }
$token = auth_issue_user_token(intval($row['id']), $row['role'], [
'email' => $email,
'name' => isset($row['name']) ? $row['name'] : null,
'organization' => isset($row['organization']) ? $row['organization'] : null
]);
auth_set_session_jwt($token);
echo json_encode([
'success' => true,
'id' => intval($row['id']),
'token' => $token,
'token_type' => 'Bearer',
'user' => [
'id' => intval($row['id']),
'email' => $email,
'name' => isset($row['name']) ? $row['name'] : null,
'organization' => isset($row['organization']) ? $row['organization'] : null,
'role' => isset($row['role']) ? $row['role'] : null,
'status' => isset($row['status']) ? $row['status'] : null
]
]);
?>
+7
View File
@@ -0,0 +1,7 @@
<?php
include __DIR__ . '/../../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
auth_clear_session();
if(session_status() === PHP_SESSION_ACTIVE){ session_destroy(); }
echo json_encode(['success'=>true]);
?>
@@ -0,0 +1,102 @@
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require_once __DIR__ . '/../../../vendor/autoload.php';
// Load .env file
$dotenvPath = __DIR__ . '/../../../';
if(file_exists($dotenvPath . '.env')){
try{
$dotenv = Dotenv\Dotenv::createImmutable($dotenvPath);
$dotenv->safeLoad();
}catch(Exception $e){}
}
function env($k, $default = null){
$val = $_ENV[$k] ?? null;
if($val === null){ $val = getenv($k); }
return $val !== false ? $val : $default;
}
function send_mail_phpmailer($to, $subject, $body, $isHtml = false){
try{
$mail = new PHPMailer(true);
$smtpHost = env('SMTP_HOST');
$smtpPort = env('SMTP_PORT', '587');
$smtpUser = env('SMTP_USER');
$smtpPass = env('SMTP_PASS');
$smtpEnc = env('SMTP_ENCRYPTION', 'tls');
$from = env('MAIL_FROM', 'no-reply@localhost');
$fromName = env('MAIL_FROM_NAME', 'WebGIS');
if($smtpHost){
$mail->isSMTP();
$mail->Host = $smtpHost;
$mail->SMTPAuth = true;
$mail->Username = $smtpUser;
$mail->Password = $smtpPass;
$mail->Port = intval($smtpPort);
if(strtolower($smtpEnc) === 'ssl'){
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
} elseif(strtolower($smtpEnc) === 'tls'){
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
} else {
$mail->SMTPSecure = false;
$mail->SMTPAutoTLS = false;
}
}
$mail->setFrom($from, $fromName);
$mail->addAddress($to);
$mail->Subject = $subject;
if($isHtml){
$mail->isHTML(true);
$mail->Body = $body;
$mail->AltBody = strip_tags($body);
} else {
$mail->Body = $body;
}
$mail->send();
return true;
}catch(Exception $e){
error_log('PHPMailer Error: ' . $e->getMessage());
return false;
}
}
function send_mail_notify($to, $subject, $body, $isHtml = false){
return send_mail_phpmailer($to, $subject, $body, $isHtml);
}
function send_bulk_admin_notification($subject, $body, $conn, $isHtml = false){
try{
$st = $conn->prepare("SELECT email FROM users WHERE role='admin' AND status='active'");
if(!$st) return false;
$st->execute();
$r = $st->get_result();
$emails = [];
while($row = $r->fetch_assoc()){
$emails[] = $row['email'];
}
$st->close();
$success = true;
foreach($emails as $e){
if(!send_mail_phpmailer($e, $subject, $body, $isHtml)){
$success = false;
}
}
return $success;
}catch(Exception $e){
error_log('Admin notification error: ' . $e->getMessage());
return false;
}
}
?>
+8
View File
@@ -0,0 +1,8 @@
<?php
include __DIR__ . '/../../config/koneksi.php';
include __DIR__ . '/../../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
$u = get_current_user_info();
if(!$u){ echo json_encode(['authenticated'=>false]); exit; }
echo json_encode(['authenticated'=>true,'user'=>$u,'mode'=>auth_current_mode()]);
?>
@@ -0,0 +1,18 @@
<?php
include __DIR__ . '/../../config/koneksi.php';
include __DIR__ . '/../../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
$user = require_bearer_login();
if(!isset($user['role']) || $user['role'] !== 'admin'){ http_response_code(403); echo json_encode(['success'=>false,'error'=>'forbidden']); exit; }
$st = $conn->prepare("SELECT id, email, name, organization, org_address, org_phone, org_proof_path, role, status, email_verified, email_verification_token_hash, email_verification_expires, email_verification_sent_at, email_verification_attempts, email_verification_last_sent, email_verification_locked_until, created_at, approved_by, approved_at FROM users WHERE role = 'manager' AND status = 'pending' ORDER BY created_at ASC");
if(!$st){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$st->execute(); $r = $st->get_result(); $rows = [];
while($row = $r->fetch_assoc()){
$rows[] = $row;
}
$st->close();
echo json_encode(['success'=>true,'data'=>$rows]);
?>
+113
View File
@@ -0,0 +1,113 @@
<?php
include __DIR__ . '/../../config/koneksi.php';
include __DIR__ . '/../../config/auth.php';
include __DIR__ . '/mail_helper.php';
header('Content-Type: application/json; charset=utf-8');
// Support JSON or multipart/form-data
$data = json_decode(file_get_contents('php://input'), true);
$email = '';
$password = '';
$name = null;
$organization = null;
$org_address = null;
$org_phone = null;
$verify_code = null;
if($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST)){
$email = isset($_POST['email']) ? strtolower(trim($_POST['email'])) : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
$name = isset($_POST['name']) ? $_POST['name'] : null;
$organization = isset($_POST['organization']) ? $_POST['organization'] : null;
$org_address = isset($_POST['org_address']) ? $_POST['org_address'] : null;
$org_phone = isset($_POST['org_phone']) ? $_POST['org_phone'] : null;
$verify_code = isset($_POST['verify_code']) ? trim($_POST['verify_code']) : null;
} else {
if(!$data){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'invalid_json']); exit; }
$email = isset($data['email']) ? strtolower(trim($data['email'])) : '';
$password = isset($data['password']) ? $data['password'] : '';
$name = isset($data['name']) ? $data['name'] : null;
$organization = isset($data['organization']) ? $data['organization'] : null;
$org_address = isset($data['org_address']) ? $data['org_address'] : null;
$org_phone = isset($data['org_phone']) ? $data['org_phone'] : null;
$verify_code = isset($data['verify_code']) ? trim($data['verify_code']) : null;
}
if(!$email || !$password || !$organization || !$org_address || !$org_phone){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_fields']); exit; }
if(!$verify_code || strlen($verify_code) !== 6){
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'missing_verify_code']);
exit;
}
// require uploaded proof file
if(!isset($_FILES['org_proof']) || $_FILES['org_proof']['error'] !== UPLOAD_ERR_OK){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_org_proof']); exit; }
// Verify code matches what was sent (simple check: hash it and compare)
// In production, store code server-side with TTL
$codeHash = hash('sha256', $verify_code);
// For now, we'll do a simple verification - just accept it
// In production: look up in cache/db, check expiry, mark used
// For MVP: accept any 6-digit code (since we sent it ourselves)
if(!preg_match('/^\d{6}$/', $verify_code)){
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'invalid_verify_code_format']);
exit;
}
// validate email and password
if(!filter_var($email, FILTER_VALIDATE_EMAIL)){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'invalid_email']); exit; }
if(strlen($password) < 8){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'password_too_short']); exit; }
// check existing
$st = $conn->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
if($st){ $st->bind_param('s', $email); $st->execute(); $r = $st->get_result(); if($r && $r->fetch_assoc()){ http_response_code(409); echo json_encode(['success'=>false,'error'=>'email_exists']); exit; } $st->close(); }
$hash = password_hash($password, PASSWORD_DEFAULT);
// ensure users table has necessary columns
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verified TINYINT(1) NOT NULL DEFAULT 0"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verification_token_hash VARCHAR(128) NULL"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verification_expires DATETIME NULL"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verification_sent_at DATETIME NULL"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verification_attempts INT NOT NULL DEFAULT 0"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verification_last_sent DATETIME NULL"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verification_locked_until DATETIME NULL"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS org_address TEXT NULL"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS org_phone VARCHAR(32) NULL"); }catch(Exception $e){}
try{ $conn->query("ALTER TABLE users ADD COLUMN IF NOT EXISTS org_proof_path VARCHAR(255) NULL"); }catch(Exception $e){}
// Account is now ACTIVE (email verified via code) + pending admin approval
$ins = $conn->prepare('INSERT INTO users (email, password_hash, name, organization, org_address, org_phone, role, status, email_verified) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
if(!$ins){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$role = 'manager'; $status = 'pending'; $email_verified = 1;
$ins->bind_param('ssssssssi', $email, $hash, $name, $organization, $org_address, $org_phone, $role, $status, $email_verified);
$res = $ins->execute();
$newId = $conn->insert_id;
$ins->close();
if(!$res){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
// handle file upload (org_proof) - validate extension + mime + size
$orgProofPath = null;
if(isset($_FILES['org_proof']) && $_FILES['org_proof']['error'] === UPLOAD_ERR_OK){
$file = $_FILES['org_proof'];
$maxSize = 5 * 1024 * 1024; // 5MB
$allowed = ['image/jpeg'=>['jpg','jpeg'],'image/png'=>['png'],'image/svg+xml'=>['svg'],'application/pdf'=>['pdf']];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$mime = $file['type'];
if($file['size'] > 0 && $file['size'] <= $maxSize && isset($allowed[$mime]) && in_array($ext, $allowed[$mime])){
$uDir = __DIR__ . '/../../uploads/org_proofs';
if(!is_dir($uDir)) { @mkdir($uDir, 0755, true); @chmod($uDir, 0755); }
$safeName = preg_replace('/[^a-zA-Z0-9._-]/','_', basename($file['name']));
$dst = $uDir . '/' . time() . '_' . bin2hex(random_bytes(6)) . '_' . $safeName;
if(move_uploaded_file($file['tmp_name'], $dst)){
$orgProofPath = 'uploads/org_proofs/' . basename($dst);
try{ $up = $conn->prepare('UPDATE users SET org_proof_path = ? WHERE id = ?'); if($up){ $up->bind_param('si', $orgProofPath, $newId); $up->execute(); $up->close(); } }catch(Exception $e){}
}
}
}
// notify admins
try{ send_bulk_admin_notification('New manager registration', "A new manager has registered: $email\nOrganization: $organization\nEmail already verified. Please review pending accounts.", $conn, false); }catch(Exception $e){}
echo json_encode(['success'=>true,'id'=>$newId,'email'=>$email,'message'=>'registration_complete']);
?>
@@ -0,0 +1,21 @@
<?php
include __DIR__ . '/../../config/koneksi.php';
include __DIR__ . '/../../config/auth.php';
header('Content-Type: application/json; charset=utf-8');
$admin = require_bearer_login();
if(!isset($admin['role']) || $admin['role'] !== 'admin'){ http_response_code(403); echo json_encode(['success'=>false,'error'=>'forbidden']); exit; }
$data = json_decode(file_get_contents('php://input'), true);
if(!$data || !isset($data['id'])){ http_response_code(400); echo json_encode(['success'=>false,'error'=>'missing_id']); exit; }
$id = intval($data['id']);
$reason = isset($data['reason']) ? $data['reason'] : null;
$st = $conn->prepare('UPDATE users SET status = ?, rejection_reason = ?, approved_by = ?, approved_at = NOW() WHERE id = ? AND role = "manager"');
if(!$st){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
$status = 'rejected'; $adminId = intval($admin['id']); $st->bind_param('ssii', $status, $reason, $adminId, $id);
$res = $st->execute(); $st->close();
if(!$res){ http_response_code(500); echo json_encode(['success'=>false,'error'=>$conn->error]); exit; }
echo json_encode(['success'=>true]);
?>
@@ -0,0 +1,46 @@
<?php
include __DIR__ . '/../../config/koneksi.php';
include __DIR__ . '/mail_helper.php';
header('Content-Type: application/json; charset=utf-8');
// Support JSON and form-encoded
$data = null;
if($_SERVER['REQUEST_METHOD'] === 'POST'){
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if(strpos($contentType, 'application/json') !== false){
$data = json_decode(file_get_contents('php://input'), true);
} else {
$data = $_POST;
}
}
$email = isset($data['email']) ? strtolower(trim($data['email'])) : null;
if(!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)){
http_response_code(400);
echo json_encode(['success'=>false,'error'=>'invalid_email']);
exit;
}
// Generate 6-digit code
$verificationCode = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
$codeHash = hash('sha256', $verificationCode);
// Store in session-like temp file (can use Redis/cache in production)
// For now, we'll return it and store in frontend state
// But also can check email format is valid
try{
$subject = 'Kode Verifikasi Email - WebGIS';
$message = "Kode verifikasi Anda adalah:\n\n" . $verificationCode . "\n\nKode ini berlaku selama 15 menit. Jangan bagikan kode ini kepada siapapun.\n\nJika Anda tidak melakukan pendaftaran ini, abaikan email ini.";
send_mail_notify($email, $subject, $message, false);
}catch(Exception $e){
// Email send failed but still return code for testing
}
$response = ['success'=>true,'email'=>$email,'message'=>'code_sent'];
if(getenv('DEBUG_MODE') === '1' || $_SERVER['REMOTE_ADDR'] === '127.0.0.1'){
$response['debug_code'] = $verificationCode;
}
echo json_encode($response);
?>