// Scheduled task to expire stale chats (older than 20 minutes with no recent activity) // Should be called every minute by a cron job or scheduled task function apiAbort(required struct payload) { writeOutput(serializeJSON(payload)); abort; } try { // Find open chat tasks that are stale // A chat is stale if: // 1. TaskTypeID = 2 (Chat) // 2. TaskCompletedOn IS NULL (not closed) // 3. No messages in the last 20 minutes AND task is older than 20 minutes staleChats = queryExecute(" SELECT t.TaskID, t.TaskAddedOn, (SELECT MAX(cm.CreatedOn) FROM ChatMessages cm WHERE cm.TaskID = t.TaskID) as LastMessageOn FROM Tasks t WHERE t.TaskTypeID = 2 AND t.TaskCompletedOn IS NULL AND t.TaskAddedOn < DATE_SUB(NOW(), INTERVAL 20 MINUTE) ", {}, { datasource: "payfrit" }); expiredCount = 0; expiredIds = []; for (chat in staleChats) { // Check last message time - if no messages or last message > 20 min ago, expire shouldExpire = false; if (isNull(chat.LastMessageOn) || !isDate(chat.LastMessageOn)) { // No messages at all - expire if task is old enough shouldExpire = true; } else { // Has messages - check if last one is older than 20 minutes lastMsgAge = dateDiff("n", chat.LastMessageOn, now()); if (lastMsgAge > 20) { shouldExpire = true; } } if (shouldExpire) { queryExecute(" UPDATE Tasks SET TaskCompletedOn = NOW() WHERE TaskID = :taskID ", { taskID: { value: chat.TaskID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); expiredCount++; arrayAppend(expiredIds, chat.TaskID); } } apiAbort({ "OK": true, "MESSAGE": "Expired #expiredCount# stale chat(s)", "EXPIRED_TASK_IDS": expiredIds, "CHECKED_COUNT": staleChats.recordCount }); } catch (any e) { apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": e.message }); }