diff --git a/api.php b/api.php new file mode 100644 index 0000000..a464eb8 --- /dev/null +++ b/api.php @@ -0,0 +1,148 @@ + false, 'error' => 'Authentication required.']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + echo json_encode(['success' => false, 'error' => 'Invalid request method.']); + exit; +} + +$input = json_decode(file_get_contents('php://input'), true); +if (!$input || !isset($input['action'])) { + echo json_encode(['success' => false, 'error' => 'Invalid or missing action.']); + exit; +} + +$action = $input['action']; +$user_id = $_SESSION['user_id']; +$role = $_SESSION['role']; + +switch ($action) { + case 'update_task_status': + handle_update_task_status($input, $user_id, $role); + break; + case 'create_task': + handle_create_task($input, $user_id, $role); + break; + case 'create_user': + case 'update_user': + case 'delete_user': + handle_user_management($action, $input, $role); + break; + default: + echo json_encode(['success' => false, 'error' => 'Unknown action.']); + break; +} + +function handle_update_task_status($input, $user_id, $role) { + if ($role !== 'siswa') { + echo json_encode(['success' => false, 'error' => 'Permission denied.']); + exit; + } + if (!isset($input['assignment_id']) || !isset($input['status'])) { + echo json_encode(['success' => false, 'error' => 'Missing parameters.']); + return; + } + try { + $pdo = db(); + $stmt = $pdo->prepare("UPDATE task_assignments SET status = ?, completed_at = ? WHERE id = ? AND assigned_to_user_id = ?"); + $completed_at = ($input['status'] === 'done') ? date('Y-m-d H:i:s') : null; + $stmt->execute([$input['status'], $completed_at, $input['assignment_id'], $user_id]); + echo json_encode(['success' => $stmt->rowCount() > 0]); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Database error.']); + } +} + +function handle_create_task($input, $user_id, $role) { + if ($role !== 'guru') { + echo json_encode(['success' => false, 'error' => 'Permission denied.']); + exit; + } + if (empty($input['title']) || empty($input['student_ids'])) { + echo json_encode(['success' => false, 'error' => 'Title and at least one student are required.']); + return; + } + + $pdo = db(); + try { + $pdo->beginTransaction(); + $stmt = $pdo->prepare("INSERT INTO tasks (title, description, created_by_user_id, due_date) VALUES (?, ?, ?, ?)"); + $stmt->execute([$input['title'], $input['description'], $user_id, $input['due_date']]); + $task_id = $pdo->lastInsertId(); + $stmt = $pdo->prepare("INSERT INTO task_assignments (task_id, assigned_to_user_id) VALUES (?, ?)"); + foreach ($input['student_ids'] as $student_id) { + $stmt->execute([$task_id, $student_id]); + } + $pdo->commit(); + echo json_encode(['success' => true]); + } catch (PDOException $e) { + $pdo->rollBack(); + echo json_encode(['success' => false, 'error' => 'Database error during task creation.']); + } +} + +function handle_user_management($action, $input, $role) { + if ($role !== 'admin') { + echo json_encode(['success' => false, 'error' => 'Permission denied.']); + exit; + } + + $pdo = db(); + try { + switch ($action) { + case 'create_user': + if (empty($input['username']) || empty($input['password']) || empty($input['role'])) { + echo json_encode(['success' => false, 'error' => 'Username, password, and role are required.']); + return; + } + $hashed_password = password_hash($input['password'], PASSWORD_DEFAULT); + $stmt = $pdo->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)"); + $stmt->execute([$input['username'], $hashed_password, $input['role']]); + break; + + case 'update_user': + if (empty($input['user_id']) || empty($input['username']) || empty($input['role'])) { + echo json_encode(['success' => false, 'error' => 'User ID, username, and role are required.']); + return; + } + if (!empty($input['password'])) { + $hashed_password = password_hash($input['password'], PASSWORD_DEFAULT); + $stmt = $pdo->prepare("UPDATE users SET username = ?, password = ?, role = ? WHERE id = ?"); + $stmt->execute([$input['username'], $hashed_password, $input['role'], $input['user_id']]); + } else { + $stmt = $pdo->prepare("UPDATE users SET username = ?, role = ? WHERE id = ?"); + $stmt->execute([$input['username'], $input['role'], $input['user_id']]); + } + break; + + case 'delete_user': + if (empty($input['user_id'])) { + echo json_encode(['success' => false, 'error' => 'User ID is required.']); + return; + } + // Prevent admin from deleting themselves + if ($input['user_id'] == $_SESSION['user_id']) { + echo json_encode(['success' => false, 'error' => 'You cannot delete your own account.']); + return; + } + $stmt = $pdo->prepare("DELETE FROM users WHERE id = ?"); + $stmt->execute([$input['user_id']]); + break; + } + echo json_encode(['success' => true]); + } catch (PDOException $e) { + // Check for duplicate username error + if ($e->errorInfo[1] == 1062) { + echo json_encode(['success' => false, 'error' => 'Username already exists.']); + } else { + echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]); + } + } +} diff --git a/assets/pasted-20251008-084914-8d794eeb.png b/assets/pasted-20251008-084914-8d794eeb.png new file mode 100644 index 0000000..1592038 Binary files /dev/null and b/assets/pasted-20251008-084914-8d794eeb.png differ diff --git a/auth.php b/auth.php new file mode 100644 index 0000000..547e70d --- /dev/null +++ b/auth.php @@ -0,0 +1,224 @@ + false, 'message' => 'Invalid action']); + break; +} + +function handle_login() { + $username = $_POST['username'] ?? ''; + $password = $_POST['password'] ?? ''; + $remember = isset($_POST['remember']); + $demo_users = ['admin', 'guru', 'siswa']; + + if (empty($username) || empty($password)) { + echo json_encode(['success' => false, 'message' => 'Username and password are required.']); + return; + } + + try { + $pdo = db(); + $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username"); + $stmt->execute(['username' => $username]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + // For demo users, we don't regenerate the session ID to allow multiple logins. + // For regular users, we do it for security. + if (!in_array($username, $demo_users)) { + session_regenerate_id(true); + } + + $_SESSION['loggedin'] = true; + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['role'] = $user['role']; + + if ($remember) { + setRememberMeCookie($user['id']); + } + + echo json_encode([ + 'success' => true, + 'message' => 'Login successful!', + 'user' => [ + 'username' => $user['username'], + 'role' => $user['role'] + ] + ]); + } else { + echo json_encode(['success' => false, 'message' => 'Invalid username or password.']); + } + } catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]); + } +} + +function handle_register() { + $username = $_POST['username'] ?? ''; + $password = $_POST['password'] ?? ''; + + if (empty($username) || empty($password)) { + echo json_encode(['success' => false, 'message' => 'Username dan password tidak boleh kosong.']); + return; + } + + if (strlen($password) < 6) { + echo json_encode(['success' => false, 'message' => 'Password minimal harus 6 karakter.']); + return; + } + + if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) { + echo json_encode(['success' => false, 'message' => 'Username hanya boleh berisi huruf, angka, dan underscore.']); + return; + } + + try { + $pdo = db(); + + $stmt = $pdo->prepare("SELECT id FROM users WHERE username = :username"); + $stmt->execute(['username' => $username]); + if ($stmt->fetch()) { + echo json_encode(['success' => false, 'message' => 'Username sudah digunakan. Silakan pilih yang lain.']); + return; + } + + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $stmt = $pdo->prepare("INSERT INTO users (username, password, role) VALUES (:username, :password, 'siswa')"); + $stmt->execute([ + 'username' => $username, + 'password' => $hashed_password + ]); + + echo json_encode(['success' => true, 'message' => 'Registrasi berhasil! Anda akan dialihkan ke halaman login.']); + + } catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => 'Terjadi kesalahan pada database.']); + } +} + +function handle_logout() { + if (isset($_SESSION['user_id'])) { + clearRememberMeCookie($_SESSION['user_id']); + } + session_unset(); + session_destroy(); + echo json_encode(['success' => true, 'message' => 'Logged out successfully.']); +} + +function check_session() { + if (isset($_SESSION['user_id'])) { + echo json_encode([ + 'success' => true, + 'loggedIn' => true, + 'user' => [ + 'username' => $_SESSION['username'], + 'role' => $_SESSION['role'] + ] + ]); + } else { + echo json_encode(['success' => true, 'loggedIn' => false]); + } +} + +// --- Remember Me Helper Functions --- + +function setRememberMeCookie($userId) { + try { + $token = bin2hex(random_bytes(32)); + $hashed_token = hash('sha256', $token); + + $pdo = db(); + $stmt = $pdo->prepare("UPDATE users SET remember_token = :token WHERE id = :id"); + $stmt->execute(['token' => $hashed_token, 'id' => $userId]); + + $cookie_value = $userId . ':' . $token; + $expiry = time() + (86400 * 30); // 30 days + + setcookie('remember_me', $cookie_value, [ + 'expires' => $expiry, + 'path' => '/', + 'domain' => '', // current domain + 'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', + 'httponly' => true, + 'samesite' => 'Lax' + ]); + } catch (Exception $e) { + // Silently fail on cookie setting error + } +} + +function clearRememberMeCookie($userId) { + try { + $pdo = db(); + $stmt = $pdo->prepare("UPDATE users SET remember_token = NULL WHERE id = :id"); + $stmt->execute(['id' => $userId]); + + if (isset($_COOKIE['remember_me'])) { + unset($_COOKIE['remember_me']); + setcookie('remember_me', '', time() - 3600, '/'); + } + } catch (Exception $e) { + // Silently fail + } +} + +function loginViaCookie() { + $cookie = $_COOKIE['remember_me'] ?? ''; + if (!$cookie) return; + + $parts = explode(':', $cookie); + if (count($parts) !== 2) return; + + list($userId, $token) = $parts; + if (empty($userId) || empty($token)) return; + + try { + $pdo = db(); + $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id"); + $stmt->execute(['id' => $userId]); + $user = $stmt->fetch(); + + if ($user && !empty($user['remember_token'])) { + $hashed_token_from_cookie = hash('sha256', $token); + if (hash_equals($user['remember_token'], $hashed_token_from_cookie)) { + // Log user in + session_regenerate_id(true); + $_SESSION['loggedin'] = true; + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['role'] = $user['role']; + + // Security: Rotate token + setRememberMeCookie($user['id']); + } + } + } catch (Exception $e) { + // Silently fail + } +} diff --git a/callback-google.php b/callback-google.php new file mode 100644 index 0000000..8064e3a --- /dev/null +++ b/callback-google.php @@ -0,0 +1,132 @@ +"; + echo "
Proses login dengan Google gagal. Silakan coba lagi.
"; + echo "Detail: " . htmlspecialchars($message) . "
"; + echo "Kembali ke Halaman Utama"; + echo ""; + exit; +} + +// 1. Pastikan Client ID dan Secret sudah diisi +if (GOOGLE_CLIENT_ID === 'MASUKKAN_CLIENT_ID_ANDA_DISINI' || GOOGLE_CLIENT_SECRET === 'MASUKKAN_CLIENT_SECRET_ANDA_DISINI') { + showErrorPage('Konfigurasi Google OAuth belum diatur. Silakan hubungi administrator.'); +} + +// 2. Ambil authorization code dari Google +if (!isset($_GET['code'])) { + showErrorPage('Authorization code tidak ditemukan.'); +} +$code = $_GET['code']; + +// 3. Tukarkan code dengan access token +try { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://oauth2.googleapis.com/token'); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'client_id' => GOOGLE_CLIENT_ID, + 'client_secret' => GOOGLE_CLIENT_SECRET, + 'code' => $code, + 'redirect_uri' => GOOGLE_REDIRECT_URI, + 'grant_type' => 'authorization_code' + ])); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + + $token_data = json_decode($response, true); + if (!isset($token_data['access_token'])) { + showErrorPage('Gagal mendapatkan access token dari Google. ' . ($token_data['error_description'] ?? '')); + } + $access_token = $token_data['access_token']; + + // 4. Gunakan access token untuk mengambil data profil pengguna + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json'); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $access_token]); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $profile_response = curl_exec($ch); + curl_close($ch); + + $profile_data = json_decode($profile_response, true); + if (!isset($profile_data['id'])) { + showErrorPage('Gagal mengambil data profil dari Google.'); + } + + // 5. Proses data pengguna (Login atau Register) + $google_id = $profile_data['id']; + $email = $profile_data['email']; + $username = $profile_data['name']; + $avatar_url = $profile_data['picture']; + + $pdo = db(); + + // Cek apakah pengguna sudah ada berdasarkan google_id + $stmt = $pdo->prepare("SELECT * FROM users WHERE google_id = :google_id"); + $stmt->execute(['google_id' => $google_id]); + $user = $stmt->fetch(); + + if ($user) { + // Pengguna sudah ada, langsung login + $user_id = $user['id']; + // Mungkin update avatar jika berubah + $update_stmt = $pdo->prepare("UPDATE users SET avatar_url = :avatar_url WHERE id = :id"); + $update_stmt->execute(['avatar_url' => $avatar_url, 'id' => $user_id]); + } else { + // Pengguna baru, buat akun + // Cek dulu apakah email sudah terdaftar (untuk linking akun) + $stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email"); + $stmt->execute(['email' => $email]); + $existing_user_by_email = $stmt->fetch(); + + if ($existing_user_by_email) { + // Email sudah ada, tautkan akun ini dengan google_id + $user_id = $existing_user_by_email['id']; + $update_stmt = $pdo->prepare("UPDATE users SET google_id = :google_id, avatar_url = :avatar_url WHERE id = :id"); + $update_stmt->execute(['google_id' => $google_id, 'avatar_url' => $avatar_url, 'id' => $user_id]); + } else { + // Buat pengguna baru + $insert_stmt = $pdo->prepare( + "INSERT INTO users (username, email, google_id, avatar_url, role) VALUES (:username, :email, :google_id, :avatar_url, 'siswa')" + ); + $insert_stmt->execute([ + 'username' => $username, + 'email' => $email, + 'google_id' => $google_id, + 'avatar_url' => $avatar_url + ]); + $user_id = $pdo->lastInsertId(); + } + // Ambil data pengguna yang baru dibuat/diupdate + $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id"); + $stmt->execute(['id' => $user_id]); + $user = $stmt->fetch(); + } + + // 6. Buat sesi login + session_regenerate_id(true); + $_SESSION['loggedin'] = true; + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $user['username']; + $_SESSION['role'] = $user['role']; + + // 7. Redirect ke dashboard + header('Location: dashboard.php'); + exit(); + +} catch (Exception $e) { + // Tangkap semua jenis error (PDO, cURL, dll) + showErrorPage($e->getMessage()); +} +?> diff --git a/dashboard.php b/dashboard.php new file mode 100644 index 0000000..712db87 --- /dev/null +++ b/dashboard.php @@ -0,0 +1,533 @@ +prepare( + "SELECT t.id, t.title, t.description, t.due_date, ta.status, ta.id as assignment_id + FROM tasks t + JOIN task_assignments ta ON t.id = ta.task_id + WHERE ta.assigned_to_user_id = ? + ORDER BY t.due_date ASC" + ); + $stmt->execute([$user_id]); + $tasks = $stmt->fetchAll(); + + $dashboard_content = 'Belum ada tugas untuk Anda. Selamat beristirahat!
'; + } else { + $dashboard_content .= '' . htmlspecialchars($task['description']) . '
+ Tenggat: ' . $due_date . ' +| Judul | +Tenggat Waktu | +Dibuat pada | +Aksi | +
|---|---|---|---|
| Anda belum membuat tugas apapun. | |||
| ' . htmlspecialchars($task['title']) . ' | +' . ($task['due_date'] ? date('d M Y', strtotime($task['due_date'])) : '-') . ' | +' . date('d M Y', strtotime($task['created_at'])) . ' | ++ + + | +
| ID | +Username | +Peran | +Bergabung pada | +Aksi | +
|---|---|---|---|---|
| Tidak ada pengguna yang ditemukan. | ||||
| ' . htmlspecialchars($user['id']) . ' | +' . htmlspecialchars($user['username']) . ' | +' . htmlspecialchars($user['role']) . ' | +' . date('d M Y', strtotime($user['created_at'])) . ' | ++ + + | +
Dashboard Anda sudah siap.
'; + break; + } +} catch (PDOException $e) { + $dashboard_content = 'Gagal memuat data. Silakan coba lagi nanti.
'; +} +?> + + + + + += ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-This page will update automatically as the plan is implemented.
-Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
Platform modern untuk guru dan siswa mengelola tugas, deadline, dan penilaian secara efisien. Fokus belajar, bukan administrasi.
+ Mulai Sekarang Gratis +Buat, bagikan, dan nilai tugas dengan mudah. Pantau progres siswa dalam satu kalender terintegrasi.
+Lihat semua tugas dalam satu tempat, kumpulkan pekerjaan, dan dapatkan notifikasi deadline. Jangan terlambat lagi!
+Kelola pengguna, pantau statistik, dan pastikan sistem berjalan lancar untuk semua sekolah.