const db = require('../db/models'); const ValidationError = require('./notifications/errors/validation'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; /** * @param {string} permission * @param {object} currentUser */ async function checkPermissions(permission, currentUser) { if (!currentUser) { throw new ValidationError('auth.unauthorized'); } const userPermission = currentUser.custom_permissions.find( (cp) => cp.name === permission, ); if (userPermission) { return true; } try { if (!currentUser.app_role) { throw new ValidationError('auth.forbidden'); } const permissions = await currentUser.app_role.getPermissions(); return !!permissions.find((p) => p.name === permission); } catch (e) { throw e; } } module.exports = class SearchService { static async search(searchQuery, currentUser ) { try { if (!searchQuery) { throw new ValidationError('iam.errors.searchQueryRequired'); } const tableColumns = { "users": [ "firstName", "lastName", "phoneNumber", "email", ], "kelas": [ "nama_kelas", "tingkat", "tahun_ajaran", ], "guru": [ "nip", "nama", "jenis_kelamin", "tempat_lahir", "alamat", "no_hp", ], "siswa": [ "nisn", "nis", "nama", "jenis_kelamin", "tempat_lahir", "alamat", "nama_ortu", "no_hp_ortu", ], "mata_pelajaran": [ "kode_mapel", "nama_mapel", ], "kktp": [ "fase", "deskripsi_umum", ], "kktp_detail": [ "deskripsi", ], "nilai": [ "judul_penilaian", "deskripsi_kktp", ], "sikap": [ "tahun_ajaran", "catatan_wali_kelas", ], "materi": [ "judul", "deskripsi", "url_video", ], "tugas": [ "judul", "instruksi", ], "pengumpulan_tugas": [ "jawaban_teks", "feedback_guru", ], "absensi": [ "keterangan", ], "pengumuman": [ "judul", "isi", ], "notifikasi": [ "judul", "pesan", ], "ai_insights": [ "ringkasan", "rekomendasi", ], "raport_descriptions": [ "tahun_ajaran", "deskripsi_kktp", "ai_prompt", "ai_output", ], "audit_logs": [ "entity_name", "entity_key", "summary", "ip_address", "user_agent", ], "import_jobs": [ "error_report", ], }; const columnsInt = { "mata_pelajaran": [ "urutan", ], "kktp": [ "nilai_minimum", ], "kktp_detail": [ "nilai_min", "nilai_max", ], "nilai": [ "skor", ], "tugas": [ "maks_nilai", ], "pengumpulan_tugas": [ "nilai", ], "ai_insights": [ "skor_keyakinan", ], "raport_descriptions": [ "nilai_akhir", "ranking_kelas", "rata_rata_kelas", ], "import_jobs": [ "total_rows", "success_rows", "failed_rows", ], }; let allFoundRecords = []; for (const tableName in tableColumns) { if (tableColumns.hasOwnProperty(tableName)) { const attributesToSearch = tableColumns[tableName]; const attributesIntToSearch = columnsInt[tableName] || []; const whereCondition = { [Op.or]: [ ...attributesToSearch.map(attribute => ({ [attribute]: { [Op.iLike] : `%${searchQuery}%`, }, })), ...attributesIntToSearch.map(attribute => ( Sequelize.where( Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'), { [Op.iLike]: `%${searchQuery}%` } ) )), ], }; const hasPermission = await checkPermissions(`READ_${tableName.toUpperCase()}`, currentUser); if (!hasPermission) { continue; } const foundRecords = await db[tableName].findAll({ where: whereCondition, attributes: [...tableColumns[tableName], 'id', ...attributesIntToSearch], }); const modifiedRecords = foundRecords.map((record) => { const matchAttribute = []; for (const attribute of attributesToSearch) { if (record[attribute]?.toLowerCase()?.includes(searchQuery.toLowerCase())) { matchAttribute.push(attribute); } } for (const attribute of attributesIntToSearch) { const castedValue = String(record[attribute]); if (castedValue && castedValue.toLowerCase().includes(searchQuery.toLowerCase())) { matchAttribute.push(attribute); } } return { ...record.get(), matchAttribute, tableName, }; }); allFoundRecords = allFoundRecords.concat(modifiedRecords); } } return allFoundRecords; } catch (error) { throw error; } } }