afterId). * Role-aware filtering: ADMIN (all), WALI_KELAS (by assigned class), GURU_MAPEL (by teacher), ORANG_TUA (by own children). */ class DashboardRealtimeService { /** Scope type: no filter */ public const SCOPE_ADMIN = 'admin'; /** Scope type: filter by class_id(s) where user is wali */ public const SCOPE_WALI_KELAS = 'wali_kelas'; /** Scope type: filter by schedules taught by this teacher (teacher_user_id) */ public const SCOPE_GURU_MAPEL = 'guru_mapel'; /** Scope type: filter by student_ids linked to parent */ public const SCOPE_ORANG_TUA = 'orang_tua'; /** * Resolve user scope for realtime filtering from current user (with roles). * * @param array $user { id, name, email, roles: [ { role_code, role_name } ] } * @return array{ type: string, class_ids?: int[], teacher_user_id?: int, student_ids?: int[] } */ public function resolveUserScope(array $user): array { $roles = $user['roles'] ?? []; $roleCodes = array_column($roles, 'role_code'); if (in_array(Role::CODE_ADMIN, $roleCodes, true)) { return ['type' => self::SCOPE_ADMIN]; } if (in_array(Role::CODE_WALI_KELAS, $roleCodes, true)) { $classIds = $this->getClassIdsForWali($user['id']); if ($classIds !== []) { return ['type' => self::SCOPE_WALI_KELAS, 'class_ids' => $classIds]; } } if (in_array(Role::CODE_GURU_MAPEL, $roleCodes, true)) { return ['type' => self::SCOPE_GURU_MAPEL, 'teacher_user_id' => (int) ($user['id'] ?? 0)]; } if (in_array(Role::CODE_ORANG_TUA, $roleCodes, true)) { $studentIds = $this->getStudentIdsForParent($user['id']); if ($studentIds !== []) { return ['type' => self::SCOPE_ORANG_TUA, 'student_ids' => $studentIds]; } } return ['type' => self::SCOPE_ADMIN]; } /** * Get class IDs where user is wali (classes.wali_user_id = userId). */ protected function getClassIdsForWali(int $userId): array { $db = \Config\Database::connect(); $rows = $db->table('classes')->select('id')->where('wali_user_id', $userId)->get()->getResultArray(); return array_map('intval', array_column($rows, 'id')); } /** * Get student IDs linked to parent (parent.user_id = userId via student_parents). */ protected function getStudentIdsForParent(int $userId): array { $db = \Config\Database::connect(); $rows = $db->table('student_parents AS sp') ->select('sp.student_id') ->join('parents AS p', 'p.id = sp.parent_id', 'inner') ->where('p.user_id', $userId) ->get() ->getResultArray(); return array_map('intval', array_column($rows, 'student_id')); } /** * Get attendance sessions after a given ID (for SSE incremental feed), with optional role scope. * * @param int $afterId Only return rows with id > afterId * @param int $limit Max rows to return * @param array|null $userContext Current user from session (id, name, email, roles). If null, no scope filter. * @return array */ public function getAttendanceSinceId(int $afterId = 0, int $limit = 100, ?array $userContext = null): array { $db = \Config\Database::connect(); $builder = $db->table('attendance_sessions AS a'); $builder->select(' a.id, s.name AS student_name, c.name AS class_name, COALESCE(sub.name, "-") AS subject, a.checkin_at, a.status '); $builder->join('students AS s', 's.id = a.student_id', 'left'); $builder->join('classes AS c', 'c.id = s.class_id', 'left'); $builder->join('schedules AS sch', 'sch.id = a.schedule_id', 'left'); $builder->join('subjects AS sub', 'sub.id = sch.subject_id', 'left'); $builder->where('a.id >', $afterId); if ($userContext !== null) { $this->applyScopeToBuilder($builder, $this->resolveUserScope($userContext)); } $builder->orderBy('a.id', 'ASC'); $builder->limit($limit); $rows = $builder->get()->getResultArray(); $out = []; foreach ($rows as $row) { $out[] = [ 'id' => (int) $row['id'], 'student_name' => (string) ($row['student_name'] ?? '-'), 'class_name' => (string) ($row['class_name'] ?? '-'), 'subject' => (string) ($row['subject'] ?? '-'), 'checkin_at' => (string) $row['checkin_at'], 'status' => (string) $row['status'], ]; } return $out; } /** * Apply scope conditions to the attendance query builder (no duplicate query). * * @param \CodeIgniter\Database\BaseBuilder $builder * @param array $scope From resolveUserScope() */ protected function applyScopeToBuilder($builder, array $scope): void { switch ($scope['type'] ?? '') { case self::SCOPE_WALI_KELAS: $classIds = $scope['class_ids'] ?? []; if ($classIds !== []) { $builder->whereIn('s.class_id', $classIds); } else { $builder->where('1 =', 0); } break; case self::SCOPE_GURU_MAPEL: $teacherUserId = (int) ($scope['teacher_user_id'] ?? 0); if ($teacherUserId > 0) { $builder->where('sch.teacher_user_id', $teacherUserId); } else { $builder->where('1 =', 0); } break; case self::SCOPE_ORANG_TUA: $studentIds = $scope['student_ids'] ?? []; if ($studentIds !== []) { $builder->whereIn('a.student_id', $studentIds); } else { $builder->where('1 =', 0); } break; case self::SCOPE_ADMIN: default: break; } } }