GAS 14

// ============================================================================ // TRIPOD DATA - OPTIMIZED V14 (STABLE) // ============================================================================ function doGet(e) { return handleRequest(e); } function doPost(e) { return handleRequest(e); } function handleRequest(e) { // LockService giúp ngăn chặn 2 người cùng lưu 1 lúc gây lỗi dữ liệu var lock = LockService.getScriptLock(); if (lock.tryLock(10000)) { // Đợi tối đa 10s try { var params = e.parameter; var action = params.action; var ss = SpreadsheetApp.getActiveSpreadsheet(); var appPrefix = params.appPrefix || "Target"; // --- XỬ LÝ ĐĂNG NHẬP / ĐĂNG KÝ --- if (action === 'login') return responseJSON(loginUser(ss, params.username, params.password, appPrefix, params.headerNames)); if (action === 'register') return responseJSON(registerUser(ss, params.username, params.password, appPrefix, params.headerNames)); var sheetName = params.targetSheetName; if (!sheetName) throw new Error("Missing Sheet Name"); // --- TẢI DỮ LIỆU (READ) --- if (action === 'loadData') { var sheet = ss.getSheetByName(sheetName); if (!sheet) return responseJSON([]); var data = sheet.getDataRange().getValues(); if (data.length > 1) data.shift(); else data = []; // Bỏ dòng Header return responseJSON(data); } // --- CÁC HÀM GHI (WRITE) --- var sheet = getOrCreateSheet(ss, sheetName, params.headerNames); // 1. INSERT (THÊM MỚI) if (action === 'insert') { var rowData = parseGenericRowData(params); var inputId = rowData[0].toString(); // Nếu ID rỗng hoặc là ID tạm (TEMP_), tạo ID thật (UUID) var realId = (!inputId || inputId.startsWith("'TEMP_") || inputId === "'") ? "'" + Utilities.getUuid() : inputId; rowData[0] = realId; // Gán ID thật vào dữ liệu sheet.appendRow(rowData); // Trả về ID thật để Web cập nhật lại return responseJSON({ result: 'success', id: realId }); } // 2. UPDATE (CẬP NHẬT) else if (action === 'update') { var id = params.id; if (!id) throw new Error("Missing ID for update"); // Tìm dòng cần sửa (Tối ưu: Chỉ tìm trong cột A) var rowIndex = findRowIndexByIdOptimized(sheet, id); if (rowIndex == -1) throw new Error("ID not found on Server: " + id); var rowData = parseGenericRowData(params); rowData[0] = forceText(id); // Giữ nguyên ID cũ // Ghi đè dữ liệu mới vào dòng đó sheet.getRange(rowIndex, 1, 1, rowData.length).setValues([rowData]); return responseJSON({ result: 'success' }); } // 3. DELETE (XÓA) else if (action === 'delete') { var id = params.id; if (!id) throw new Error("Missing ID for delete"); var rowIndex = findRowIndexByIdOptimized(sheet, id); if (rowIndex == -1) { // Nếu không tìm thấy, báo success giả để web không báo lỗi (vì đằng nào cũng muốn xóa) return responseJSON({ result: 'success', message: 'ID not found but ignored' }); } sheet.deleteRow(rowIndex); return responseJSON({ result: 'success' }); } } catch (e) { return responseJSON({ result: 'error', message: e.toString() }); } finally { lock.releaseLock(); } } else { return responseJSON({ result: 'error', message: 'Server is busy, try again.' }); } } // === CÁC HÀM HỖ TRỢ ĐÃ TỐI ƯU === // Hàm tìm dòng nhanh: Chỉ đọc cột A thay vì đọc cả bảng function findRowIndexByIdOptimized(sheet, id) { var cleanId = cleanText(id); // Lấy dữ liệu cột A (Cột ID) var lastRow = sheet.getLastRow(); if (lastRow < 2) return -1; var idColumn = sheet.getRange(1, 1, lastRow, 1).getValues(); // Mảng 2 chiều [[id1], [id2]...] // Duyệt ngược từ dưới lên (thường dữ liệu mới ở dưới, tìm sẽ nhanh hơn) for (var i = idColumn.length - 1; i >= 1; i--) { if (cleanText(idColumn[i][0]) == cleanId) { return i + 1; // Vì index mảng bắt đầu từ 0, dòng sheet bắt đầu từ 1 } } return -1; } function cleanText(val) { if (val == null) return ""; var str = val.toString(); // Xóa dấu nháy đơn ở đầu nếu có (để so sánh chính xác) return str.startsWith("'") ? str.substring(1) : str; } function forceText(val) { // Thêm dấu nháy đơn để ép Google Sheet hiểu là Text (tránh lỗi định dạng ngày tháng/số) return "'" + cleanText(val); } function parseGenericRowData(params) { if (!params.fieldNames) return []; var fields = params.fieldNames.split(','); return fields.map(function(f) { // ID (cột đầu tiên) xử lý riêng ở hàm main, ở đây cứ lấy value var val = params[f]; return (val === undefined || val === null) ? "" : forceText(val); }); } function responseJSON(obj) { return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON); } function getOrCreateSheet(ss, name, headers) { var sheet = ss.getSheetByName(name); if (!sheet) { sheet = ss.insertSheet(name); if (headers) { var h = headers.split(','); sheet.appendRow(h); sheet.getRange(1,1,1,h.length).setFontWeight("bold").setBackground("#f3f3f3"); sheet.setFrozenRows(1); } } return sheet; } // Giữ nguyên logic User function loginUser(ss, u, p, prefix, headers) { var userSheet = getOrCreateSheet(ss, "Users", "Username,Password,CreatedDate"); var data = userSheet.getDataRange().getValues(); var cU = cleanText(u), cP = cleanText(p); for(var i=1; i