mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 02:01:40 +00:00
219 lines
7.7 KiB
Lua
219 lines
7.7 KiB
Lua
Bridge.Database.AfterMigration("sky_phone", function()
|
|
local reminder_minutes = {
|
|
[0] = true,
|
|
[10] = true,
|
|
[30] = true,
|
|
[60] = true,
|
|
[1440] = true,
|
|
}
|
|
|
|
local function text_length(value)
|
|
return type(value) == "string" and utf8.len(value) or nil
|
|
end
|
|
|
|
local function affected_rows(result)
|
|
if type(result) == "number" then
|
|
return result
|
|
end
|
|
return type(result) == "table" and tonumber(result.affectedRows) or 0
|
|
end
|
|
|
|
local function event_dto(row)
|
|
return {
|
|
id = row.id,
|
|
title = row.title,
|
|
note = row.note,
|
|
startsAt = (tonumber(row.starts_at_unix) or 0) * 1000,
|
|
endsAt = (tonumber(row.ends_at_unix) or 0) * 1000,
|
|
reminderMinutes = row.reminder_minutes and tonumber(row.reminder_minutes) or nil,
|
|
remindedAt = row.reminded_at_unix and tonumber(row.reminded_at_unix) * 1000 or nil,
|
|
revision = tonumber(row.revision) or 1,
|
|
}
|
|
end
|
|
|
|
local function list_events(account_id, starts_at, ends_at)
|
|
local rows = Bridge.Database.Query([[
|
|
SELECT `id`, `title`, `note`, `reminder_minutes`, `revision`,
|
|
UNIX_TIMESTAMP(`starts_at`) AS `starts_at_unix`,
|
|
UNIX_TIMESTAMP(`ends_at`) AS `ends_at_unix`,
|
|
UNIX_TIMESTAMP(`reminded_at`) AS `reminded_at_unix`
|
|
FROM `sky_phone_calendar_events`
|
|
WHERE `account_id` = ?
|
|
AND `ends_at` >= FROM_UNIXTIME(?)
|
|
AND `starts_at` < FROM_UNIXTIME(?)
|
|
ORDER BY `starts_at`, `ends_at`, `id`
|
|
LIMIT 500
|
|
]], { account_id, starts_at, ends_at })
|
|
local events = {}
|
|
for _, row in ipairs(rows) do
|
|
events[#events + 1] = event_dto(row)
|
|
end
|
|
return events
|
|
end
|
|
|
|
local function valid_range(starts_at, ends_at)
|
|
local now = os.time()
|
|
return starts_at >= now - Config.Calendar.PastEditSeconds
|
|
and starts_at <= now + Config.Calendar.FutureSeconds
|
|
and ends_at > starts_at
|
|
and ends_at - starts_at <= Config.Calendar.MaximumDurationSeconds
|
|
end
|
|
|
|
local function validate_event(data)
|
|
if type(data) ~= "table" then
|
|
return nil
|
|
end
|
|
local title_length = text_length(data.title)
|
|
local note_length = text_length(data.note)
|
|
local starts_at = math.floor(tonumber(data.startsAt) or 0)
|
|
local ends_at = math.floor(tonumber(data.endsAt) or 0)
|
|
local reminder = data.reminderMinutes
|
|
if not title_length
|
|
or title_length < 1
|
|
or title_length > Config.Calendar.TitleMaxLength
|
|
or not note_length
|
|
or note_length > Config.Calendar.NoteMaxLength
|
|
or not valid_range(starts_at, ends_at)
|
|
or (reminder ~= nil and not reminder_minutes[tonumber(reminder)])
|
|
then
|
|
return nil
|
|
end
|
|
return {
|
|
title = data.title,
|
|
note = data.note,
|
|
starts_at = starts_at,
|
|
ends_at = ends_at,
|
|
reminder_minutes = reminder == nil and nil or tonumber(reminder),
|
|
}
|
|
end
|
|
|
|
Bridge.Callbacks.Register("sky_phone:calendar:list", function(source, data)
|
|
local account, error_response = SkyPhone.RequireAccount(source)
|
|
if not account then
|
|
return error_response
|
|
end
|
|
local starts_at = math.floor(tonumber(data and data.startsAt) or 0)
|
|
local ends_at = math.floor(tonumber(data and data.endsAt) or 0)
|
|
if starts_at <= 0 or ends_at <= starts_at or ends_at - starts_at > Config.Calendar.MaximumQuerySeconds then
|
|
return { success = false, error = "invalid_range" }
|
|
end
|
|
return { success = true, data = list_events(account.id, starts_at, ends_at) }
|
|
end)
|
|
|
|
Bridge.Callbacks.Register("sky_phone:calendar:create", function(source, data)
|
|
if not SkyPhone.AllowOperation(source, "calendar_write", 60, 60) then
|
|
return { success = false, error = "rate_limited" }
|
|
end
|
|
local account, error_response = SkyPhone.RequireAccount(source)
|
|
if not account then
|
|
return error_response
|
|
end
|
|
local event = validate_event(data)
|
|
if not event then
|
|
return { success = false, error = "invalid_event" }
|
|
end
|
|
local ids = Bridge.Database.Query("SELECT UUID() AS `id`", {})
|
|
local id = ids[1] and ids[1].id
|
|
if type(id) ~= "string" then
|
|
error("[sky_phone] Database did not generate a calendar event id.")
|
|
end
|
|
Bridge.Database.Query([[
|
|
INSERT INTO `sky_phone_calendar_events`
|
|
(`id`, `account_id`, `title`, `note`, `starts_at`, `ends_at`, `reminder_minutes`)
|
|
VALUES (?, ?, ?, ?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?)
|
|
]], {
|
|
id,
|
|
account.id,
|
|
event.title,
|
|
event.note,
|
|
event.starts_at,
|
|
event.ends_at,
|
|
event.reminder_minutes,
|
|
})
|
|
return { success = true, data = { id = id } }
|
|
end)
|
|
|
|
Bridge.Callbacks.Register("sky_phone:calendar:update", function(source, data)
|
|
if not SkyPhone.AllowOperation(source, "calendar_write", 60, 60) then
|
|
return { success = false, error = "rate_limited" }
|
|
end
|
|
local account, error_response = SkyPhone.RequireAccount(source)
|
|
if not account then
|
|
return error_response
|
|
end
|
|
local event = validate_event(data)
|
|
if not event or type(data.id) ~= "string" then
|
|
return { success = false, error = "invalid_event" }
|
|
end
|
|
local revision = math.max(1, math.floor(tonumber(data.revision) or 0))
|
|
local result = Bridge.Database.Query([[
|
|
UPDATE `sky_phone_calendar_events`
|
|
SET `title` = ?, `note` = ?, `starts_at` = FROM_UNIXTIME(?),
|
|
`ends_at` = FROM_UNIXTIME(?), `reminder_minutes` = ?,
|
|
`reminded_at` = NULL, `revision` = `revision` + 1
|
|
WHERE `id` = ? AND `account_id` = ? AND `revision` = ?
|
|
]], {
|
|
event.title,
|
|
event.note,
|
|
event.starts_at,
|
|
event.ends_at,
|
|
event.reminder_minutes,
|
|
data.id,
|
|
account.id,
|
|
revision,
|
|
})
|
|
if affected_rows(result) ~= 1 then
|
|
return { success = false, error = "conflict" }
|
|
end
|
|
return { success = true }
|
|
end)
|
|
|
|
Bridge.Callbacks.Register("sky_phone:calendar:delete", function(source, data)
|
|
if not SkyPhone.AllowOperation(source, "calendar_write", 60, 60) then
|
|
return { success = false, error = "rate_limited" }
|
|
end
|
|
local account, error_response = SkyPhone.RequireAccount(source)
|
|
if not account then
|
|
return error_response
|
|
end
|
|
if type(data) ~= "table" or type(data.id) ~= "string" then
|
|
return { success = false, error = "invalid_event" }
|
|
end
|
|
Bridge.Database.Query(
|
|
"DELETE FROM `sky_phone_calendar_events` WHERE `id` = ? AND `account_id` = ?",
|
|
{ data.id, account.id }
|
|
)
|
|
return { success = true }
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(Config.Calendar.ReminderPollSeconds * 1000)
|
|
local due_events = Bridge.Database.Query([[
|
|
SELECT `id`, `account_id`, `title`, UNIX_TIMESTAMP(`starts_at`) AS `starts_at_unix`
|
|
FROM `sky_phone_calendar_events`
|
|
WHERE `reminder_minutes` IS NOT NULL
|
|
AND `reminded_at` IS NULL
|
|
AND DATE_SUB(`starts_at`, INTERVAL `reminder_minutes` MINUTE) <= CURRENT_TIMESTAMP
|
|
AND `starts_at` >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 DAY)
|
|
ORDER BY `starts_at`
|
|
LIMIT 100
|
|
]], {})
|
|
for _, event in ipairs(due_events) do
|
|
local result = Bridge.Database.Query([[
|
|
UPDATE `sky_phone_calendar_events`
|
|
SET `reminded_at` = CURRENT_TIMESTAMP
|
|
WHERE `id` = ? AND `reminded_at` IS NULL
|
|
]], { event.id })
|
|
if affected_rows(result) == 1 then
|
|
SkyPhone.NotifyAccountDevices(event.account_id, "sky_phone:calendar:reminder", {
|
|
eventId = event.id,
|
|
eventTitle = event.title,
|
|
startsAt = (tonumber(event.starts_at_unix) or 0) * 1000,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
end)
|