mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 00:01:29 +00:00
ENH - complete Mail and Radio Sky UI
This commit is contained in:
@@ -259,6 +259,9 @@ Config.Mail = {
|
||||
MaxRecipients = 10,
|
||||
PageSize = 50,
|
||||
AuthAttemptsPerMinute = 5,
|
||||
MailboxNameMaxLength = 50,
|
||||
MaxMailboxes = 20,
|
||||
MailboxRequestsPerMinute = 12,
|
||||
DeleteBatchSize = 50,
|
||||
DeleteRequestsPerMinute = 12,
|
||||
}
|
||||
|
||||
@@ -998,11 +998,12 @@ Locales["en"] = {
|
||||
secondaryFrequency = "Secondary frequency", frequencyPlaceholder = "e.g. 120.5", optional = "Optional",
|
||||
mhz = "MHz", volume = "Volume", connect = "Connect", disconnect = "Disconnect",
|
||||
speaker = "Speaker", speakerDescription = "Play radio traffic through speaker mode",
|
||||
providerFeatureUnavailable = "Not supported by this voice service", notSupported = "Not supported",
|
||||
members = "Currently connected ({count})", noMembers = "No participants", history = "Recently connected",
|
||||
noHistory = "No history", badge = "Service number", badgePlaceholder = "e.g. 231",
|
||||
profileSaved = "Radio profile saved", otherSettings = "Other", autoRejoin = "Automatic rejoin",
|
||||
displayName = "Radio display name", displayNamePlaceholder = "Leave empty to use your character name",
|
||||
displayNameDescription = "This name is shown to other participants and in the built-in radio overlay.",
|
||||
displayName = "Radio display name", displayNamePlaceholder = "Character name",
|
||||
displayNameDescription = "Leave empty to use your character name. This name is shown to other participants and in the built-in radio overlay.",
|
||||
displayNameNotAllowed = "Your current job or grade is not allowed to change the radio display name.",
|
||||
autoRejoinDescription = "Reconnect after spawning or restarting the phone resource",
|
||||
radioNotifications = "Radio notifications", notificationsDescription = "Show joins and leaves on your current channel",
|
||||
@@ -1432,10 +1433,21 @@ Locales["en"] = {
|
||||
localPart = "Email address", email = "Email", password = "Password", confirmPassword = "Confirm password",
|
||||
accountEyebrow = "Your Sky Cloud account", emailPlaceholder = "name@ifruit.com", passwordPlaceholder = "Enter password",
|
||||
passwordWarning = "Use an in-character password. Do not reuse a real-world password.",
|
||||
mailboxes = "Mailboxes", inbox = "Inbox", sent = "Sent", drafts = "Drafts", trash = "Trash",
|
||||
mailboxes = "Mailboxes", editMailboxes = "Edit", newMailbox = "New Mailbox",
|
||||
mailboxName = "Name", mailboxNamePlaceholder = "Mailbox name", mailboxLocation = "Mailbox Location",
|
||||
deleteMailbox = "Delete {mailbox}", deleteMailboxTitle = "Delete Mailbox?",
|
||||
deleteMailboxBody = "Messages in {mailbox} will return to their original mailbox.",
|
||||
moveToMailbox = "Move to Mailbox", movedToMailbox = "Moved to {mailbox}.",
|
||||
inbox = "Inbox", sent = "Sent", drafts = "Drafts", trash = "Trash",
|
||||
compose = "New Message", recipients = "To", recipientHint = "Separate up to 10 addresses with commas.",
|
||||
recipientPlaceholder = "name@ifruit.com", subject = "Subject", subjectPlaceholder = "What's this about?",
|
||||
body = "Message", messagePlaceholder = "Write a message...", search = "Search mail", messages = "messages",
|
||||
toolbar = "Mail tools", filterTitle = "Filter", filteredBy = "Filtered by",
|
||||
filterActiveLabel = "Filtered by {filter}",
|
||||
filterStatus = "Status", filterAddressed = "Addressed", filterDirection = "Direction", filterOther = "Other",
|
||||
filterUnreadLabel = "Unread", filterReadLabel = "Read", filterReceived = "Received",
|
||||
filterSent = "Sent", filterToMe = "To Me", filterFromMe = "From Me",
|
||||
filterToday = "Today", filterMultiple = "{count} Filters",
|
||||
select = "Select", selectedCount = "{count} Selected", deleteSelected = "Delete Selected",
|
||||
selectionLimit = "You can select up to {count} messages at once.",
|
||||
noMessageContent = "No message content", noMail = "No Mail",
|
||||
@@ -1457,6 +1469,12 @@ Locales["en"] = {
|
||||
recipient_not_found = "One or more recipients do not exist.", invalid_draft = "This draft contains invalid content.",
|
||||
not_authenticated = "Your mail session has ended. Sign in again.",
|
||||
request_failed = "Mail is temporarily unavailable.", invalid_request = "The mail request was invalid.",
|
||||
invalid_mailbox = "Enter a mailbox name with up to 50 characters.",
|
||||
mailbox_exists = "A mailbox with that name already exists.",
|
||||
mailbox_limit = "You have reached the mailbox limit.",
|
||||
mailbox_not_found = "That mailbox is no longer available.",
|
||||
message_not_found = "That message is no longer available.",
|
||||
invalid_filter = "That mail filter is not available.",
|
||||
default = "The mail request failed.",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -43,6 +43,9 @@ local server_callbacks = {
|
||||
"mail:login",
|
||||
"mail:logout",
|
||||
"mail:counts",
|
||||
"mail:mailboxes",
|
||||
"mail:create-mailbox",
|
||||
"mail:delete-mailbox",
|
||||
"mail:list",
|
||||
"mail:get",
|
||||
"mail:get-draft",
|
||||
@@ -51,6 +54,7 @@ local server_callbacks = {
|
||||
"mail:delete-many",
|
||||
"mail:send",
|
||||
"mail:set-read",
|
||||
"mail:move",
|
||||
"mail:trash",
|
||||
"mail:restore",
|
||||
"mail:delete-forever",
|
||||
|
||||
@@ -57,6 +57,30 @@ local schema = {
|
||||
},
|
||||
tableOptions = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
},
|
||||
{
|
||||
name = "sky_phone_mailboxes",
|
||||
columns = {
|
||||
{ name = "id", type = "BIGINT UNSIGNED NOT NULL AUTO_INCREMENT" },
|
||||
{ name = "account_id", type = "BIGINT UNSIGNED NOT NULL" },
|
||||
{ name = "name", type = "VARCHAR(50) NOT NULL" },
|
||||
{ name = "sort_order", type = "SMALLINT UNSIGNED NOT NULL DEFAULT 0" },
|
||||
{ name = "created_at", type = "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP" },
|
||||
},
|
||||
primaryKey = "id",
|
||||
uniqueKeys = {
|
||||
{ name = "uniq_sky_phone_mailbox_name", columns = "(`account_id`, `name`)" },
|
||||
},
|
||||
indexes = {
|
||||
{ name = "idx_sky_phone_mailboxes", columns = "(`account_id`, `sort_order`, `id`)" },
|
||||
},
|
||||
foreignKeys = {
|
||||
{
|
||||
column = "account_id",
|
||||
references = "`sky_phone_accounts` (`id`) ON DELETE CASCADE",
|
||||
},
|
||||
},
|
||||
tableOptions = "ENGINE=InnoDB",
|
||||
},
|
||||
{
|
||||
name = "sky_phone_mail_entries",
|
||||
columns = {
|
||||
@@ -64,6 +88,7 @@ local schema = {
|
||||
{ name = "message_id", type = "CHAR(36) NOT NULL" },
|
||||
{ name = "account_id", type = "BIGINT UNSIGNED NOT NULL" },
|
||||
{ name = "folder", type = "ENUM('inbox', 'sent') NOT NULL" },
|
||||
{ name = "mailbox_id", type = "BIGINT UNSIGNED NULL" },
|
||||
{ name = "read_at", type = "DATETIME NULL" },
|
||||
{ name = "trashed_at", type = "DATETIME NULL" },
|
||||
},
|
||||
@@ -76,6 +101,7 @@ local schema = {
|
||||
},
|
||||
indexes = {
|
||||
{ name = "idx_sky_phone_mailbox", columns = "(`account_id`, `folder`, `trashed_at`, `id`)" },
|
||||
{ name = "idx_sky_phone_custom_mailbox", columns = "(`account_id`, `mailbox_id`, `trashed_at`, `id`)" },
|
||||
},
|
||||
foreignKeys = {
|
||||
{
|
||||
|
||||
@@ -67,6 +67,60 @@ local function validate_text(value, maximum)
|
||||
return length and length <= maximum
|
||||
end
|
||||
|
||||
local function normalize_mailbox_name(value)
|
||||
local name = trim(value)
|
||||
local length = text_length(name)
|
||||
if not length or length < 1 or length > Config.Mail.MailboxNameMaxLength or name:find("%c") then
|
||||
return nil
|
||||
end
|
||||
|
||||
return name
|
||||
end
|
||||
|
||||
local function normalize_numeric_id(value)
|
||||
local id = tonumber(value)
|
||||
if not id or id ~= id or id < 1 or id > MAX_SAFE_INTEGER or id ~= math.floor(id) then
|
||||
return nil
|
||||
end
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
local function mailbox_id_from_folder(folder)
|
||||
if type(folder) ~= "string" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local value = folder:match("^mailbox:(%d+)$")
|
||||
return value and normalize_numeric_id(value) or nil
|
||||
end
|
||||
|
||||
local function normalize_list_filters(value)
|
||||
if value == nil then
|
||||
return {
|
||||
address = "all",
|
||||
direction = "all",
|
||||
read = "all",
|
||||
today = false,
|
||||
}
|
||||
end
|
||||
if type(value) ~= "table"
|
||||
or (value.address ~= "all" and value.address ~= "from-me" and value.address ~= "to-me")
|
||||
or (value.direction ~= "all" and value.direction ~= "inbox" and value.direction ~= "sent")
|
||||
or (value.read ~= "all" and value.read ~= "read" and value.read ~= "unread")
|
||||
or type(value.today) ~= "boolean"
|
||||
then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
address = value.address,
|
||||
direction = value.direction,
|
||||
read = value.read,
|
||||
today = value.today,
|
||||
}
|
||||
end
|
||||
|
||||
local function normalize_recipients(values, strict)
|
||||
if type(values) ~= "table" or #values > Config.Mail.MaxRecipients then
|
||||
return nil
|
||||
@@ -192,9 +246,9 @@ end
|
||||
local function get_counts(account_id)
|
||||
local rows = Bridge.Database.Query([[
|
||||
SELECT
|
||||
SUM(CASE WHEN `folder` = 'inbox' AND `trashed_at` IS NULL AND `read_at` IS NULL THEN 1 ELSE 0 END) AS unread,
|
||||
SUM(CASE WHEN `folder` = 'inbox' AND `trashed_at` IS NULL THEN 1 ELSE 0 END) AS inbox,
|
||||
SUM(CASE WHEN `folder` = 'sent' AND `trashed_at` IS NULL THEN 1 ELSE 0 END) AS sent,
|
||||
SUM(CASE WHEN `folder` = 'inbox' AND `mailbox_id` IS NULL AND `trashed_at` IS NULL AND `read_at` IS NULL THEN 1 ELSE 0 END) AS unread,
|
||||
SUM(CASE WHEN `folder` = 'inbox' AND `mailbox_id` IS NULL AND `trashed_at` IS NULL THEN 1 ELSE 0 END) AS inbox,
|
||||
SUM(CASE WHEN `folder` = 'sent' AND `mailbox_id` IS NULL AND `trashed_at` IS NULL THEN 1 ELSE 0 END) AS sent,
|
||||
SUM(CASE WHEN `trashed_at` IS NOT NULL THEN 1 ELSE 0 END) AS trash
|
||||
FROM `sky_phone_mail_entries`
|
||||
WHERE `account_id` = ?
|
||||
@@ -214,6 +268,38 @@ local function get_counts(account_id)
|
||||
}
|
||||
end
|
||||
|
||||
local function get_mailboxes(account_id)
|
||||
local rows = Bridge.Database.Query([[
|
||||
SELECT mailbox.`id`, mailbox.`name`, mailbox.`sort_order`, COUNT(entry.`id`) AS `count`
|
||||
FROM `sky_phone_mailboxes` mailbox
|
||||
LEFT JOIN `sky_phone_mail_entries` entry
|
||||
ON entry.`mailbox_id` = mailbox.`id`
|
||||
AND entry.`account_id` = mailbox.`account_id`
|
||||
AND entry.`trashed_at` IS NULL
|
||||
WHERE mailbox.`account_id` = ?
|
||||
GROUP BY mailbox.`id`, mailbox.`name`, mailbox.`sort_order`
|
||||
ORDER BY mailbox.`sort_order` ASC, mailbox.`id` ASC
|
||||
]], { account_id })
|
||||
|
||||
for index = 1, #rows do
|
||||
rows[index].id = tonumber(rows[index].id)
|
||||
rows[index].sort_order = tonumber(rows[index].sort_order) or 0
|
||||
rows[index].count = tonumber(rows[index].count) or 0
|
||||
end
|
||||
|
||||
return rows
|
||||
end
|
||||
|
||||
local function find_owned_mailbox(account_id, mailbox_id)
|
||||
local rows = Bridge.Database.Query([[
|
||||
SELECT `id`, `name`, `sort_order`
|
||||
FROM `sky_phone_mailboxes`
|
||||
WHERE `id` = ? AND `account_id` = ?
|
||||
LIMIT 1
|
||||
]], { mailbox_id, account_id })
|
||||
return rows[1]
|
||||
end
|
||||
|
||||
local function broadcast_mailbox_changed(account_id, counts)
|
||||
notify_account(account_id, "sky_phone:mail:changed", {
|
||||
counts = counts or get_counts(account_id),
|
||||
@@ -229,6 +315,111 @@ Bridge.Callbacks.Register("sky_phone:mail:counts", function(source)
|
||||
return { success = true, data = get_counts(session.id) }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:mail:mailboxes", function(source)
|
||||
local session, error_response = require_session(source)
|
||||
if not session then
|
||||
return error_response
|
||||
end
|
||||
|
||||
return { success = true, data = { mailboxes = get_mailboxes(session.id) } }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:mail:create-mailbox", function(source, data)
|
||||
local session, error_response = require_session(source)
|
||||
if not session then
|
||||
return error_response
|
||||
end
|
||||
if not SkyPhone.AllowOperation(source, "mail_manage_mailboxes", Config.Mail.MailboxRequestsPerMinute, 60) then
|
||||
return { success = false, error = "rate_limited" }
|
||||
end
|
||||
|
||||
data = validate_payload(source, "create-mailbox", data)
|
||||
if not data then
|
||||
return { success = false, error = "invalid_request" }
|
||||
end
|
||||
|
||||
local name = normalize_mailbox_name(data.name)
|
||||
if not name then
|
||||
return { success = false, error = "invalid_mailbox" }
|
||||
end
|
||||
|
||||
local rows = Bridge.Database.Query([[
|
||||
SELECT COUNT(*) AS `count`,
|
||||
SUM(CASE WHEN LOWER(`name`) = LOWER(?) THEN 1 ELSE 0 END) AS `duplicate_count`,
|
||||
COALESCE(MAX(`sort_order`), -1) AS `maximum_sort_order`
|
||||
FROM `sky_phone_mailboxes`
|
||||
WHERE `account_id` = ?
|
||||
]], { name, session.id })
|
||||
local summary = rows[1] or {}
|
||||
if (tonumber(summary.duplicate_count) or 0) > 0 then
|
||||
return { success = false, error = "mailbox_exists" }
|
||||
end
|
||||
if (tonumber(summary.count) or 0) >= Config.Mail.MaxMailboxes then
|
||||
return { success = false, error = "mailbox_limit" }
|
||||
end
|
||||
|
||||
local sort_order = math.min(65535, (tonumber(summary.maximum_sort_order) or -1) + 1)
|
||||
Bridge.Database.Query([[
|
||||
INSERT INTO `sky_phone_mailboxes` (`account_id`, `name`, `sort_order`)
|
||||
VALUES (?, ?, ?)
|
||||
]], { session.id, name, sort_order })
|
||||
|
||||
local created = Bridge.Database.Query([[
|
||||
SELECT `id`, `name`, `sort_order`, 0 AS `count`
|
||||
FROM `sky_phone_mailboxes`
|
||||
WHERE `account_id` = ? AND LOWER(`name`) = LOWER(?)
|
||||
LIMIT 1
|
||||
]], { session.id, name })
|
||||
if not created[1] then
|
||||
error("[sky_phone] Created mail mailbox could not be loaded.")
|
||||
end
|
||||
|
||||
created[1].id = tonumber(created[1].id)
|
||||
created[1].sort_order = tonumber(created[1].sort_order) or 0
|
||||
created[1].count = 0
|
||||
broadcast_mailbox_changed(session.id)
|
||||
return { success = true, data = created[1] }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:mail:delete-mailbox", function(source, data)
|
||||
local session, error_response = require_session(source)
|
||||
if not session then
|
||||
return error_response
|
||||
end
|
||||
if not SkyPhone.AllowOperation(source, "mail_manage_mailboxes", Config.Mail.MailboxRequestsPerMinute, 60) then
|
||||
return { success = false, error = "rate_limited" }
|
||||
end
|
||||
|
||||
data = validate_payload(source, "delete-mailbox", data)
|
||||
if not data then
|
||||
return { success = false, error = "invalid_request" }
|
||||
end
|
||||
|
||||
local mailbox_id = normalize_numeric_id(data.id)
|
||||
if not mailbox_id or not find_owned_mailbox(session.id, mailbox_id) then
|
||||
return { success = false, error = "mailbox_not_found" }
|
||||
end
|
||||
|
||||
if not Bridge.Database.Transaction({
|
||||
{
|
||||
query = [[
|
||||
UPDATE `sky_phone_mail_entries` SET `mailbox_id` = NULL
|
||||
WHERE `account_id` = ? AND `mailbox_id` = ?
|
||||
]],
|
||||
params = { session.id, mailbox_id },
|
||||
},
|
||||
{
|
||||
query = "DELETE FROM `sky_phone_mailboxes` WHERE `id` = ? AND `account_id` = ?",
|
||||
params = { mailbox_id, session.id },
|
||||
},
|
||||
}) then
|
||||
return { success = false, error = "request_failed" }
|
||||
end
|
||||
|
||||
broadcast_mailbox_changed(session.id)
|
||||
return { success = true }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:mail:list", function(source, data)
|
||||
local session, error_response = require_session(source)
|
||||
if not session then
|
||||
@@ -241,9 +432,15 @@ Bridge.Callbacks.Register("sky_phone:mail:list", function(source, data)
|
||||
end
|
||||
|
||||
local folder = data.folder
|
||||
if folder ~= "inbox" and folder ~= "sent" and folder ~= "drafts" and folder ~= "trash" then
|
||||
local custom_mailbox_id = mailbox_id_from_folder(folder)
|
||||
if folder ~= "inbox" and folder ~= "sent" and folder ~= "drafts" and folder ~= "trash"
|
||||
and not custom_mailbox_id
|
||||
then
|
||||
return { success = false, error = "invalid_folder" }
|
||||
end
|
||||
if custom_mailbox_id and not find_owned_mailbox(session.id, custom_mailbox_id) then
|
||||
return { success = false, error = "mailbox_not_found" }
|
||||
end
|
||||
|
||||
local search = trim(data.search) or ""
|
||||
if not validate_text(search, 120) then
|
||||
@@ -251,9 +448,16 @@ Bridge.Callbacks.Register("sky_phone:mail:list", function(source, data)
|
||||
end
|
||||
local offset = math.max(0, math.min(100000, math.floor(tonumber(data.offset) or 0)))
|
||||
local limit = Config.Mail.PageSize + 1
|
||||
local filters = normalize_list_filters(data.filters)
|
||||
if not filters then
|
||||
return { success = false, error = "invalid_filter" }
|
||||
end
|
||||
local rows
|
||||
|
||||
if folder == "drafts" then
|
||||
if filters.address ~= "all" or filters.direction ~= "all" or filters.read ~= "all" or filters.today then
|
||||
return { success = false, error = "invalid_filter" }
|
||||
end
|
||||
local pattern = "%" .. search .. "%"
|
||||
rows = Bridge.Database.Query([[
|
||||
SELECT `id`, `recipients`, `subject`, LEFT(`body`, 180) AS `preview`, `updated_at` AS `created_at`
|
||||
@@ -265,13 +469,35 @@ Bridge.Callbacks.Register("sky_phone:mail:list", function(source, data)
|
||||
else
|
||||
local conditions
|
||||
local values = { session.id }
|
||||
if folder == "trash" then
|
||||
if custom_mailbox_id then
|
||||
conditions = "e.`mailbox_id` = ? AND e.`trashed_at` IS NULL"
|
||||
values[#values + 1] = custom_mailbox_id
|
||||
elseif folder == "trash" then
|
||||
conditions = "e.`trashed_at` IS NOT NULL"
|
||||
else
|
||||
conditions = "e.`folder` = ? AND e.`trashed_at` IS NULL"
|
||||
conditions = "e.`folder` = ? AND e.`mailbox_id` IS NULL AND e.`trashed_at` IS NULL"
|
||||
values[#values + 1] = folder
|
||||
end
|
||||
|
||||
if filters.read == "unread" then
|
||||
conditions = conditions .. " AND e.`folder` = 'inbox' AND e.`read_at` IS NULL"
|
||||
elseif filters.read == "read" then
|
||||
conditions = conditions .. " AND (e.`folder` = 'sent' OR e.`read_at` IS NOT NULL)"
|
||||
end
|
||||
if filters.direction ~= "all" then
|
||||
conditions = conditions .. " AND e.`folder` = ?"
|
||||
values[#values + 1] = filters.direction
|
||||
end
|
||||
if filters.address == "to-me" then
|
||||
conditions = conditions .. " AND m.`recipients` LIKE ?"
|
||||
values[#values + 1] = "%\"" .. session.email .. "\"%"
|
||||
elseif filters.address == "from-me" then
|
||||
conditions = conditions .. " AND m.`sender_account_id` = e.`account_id`"
|
||||
end
|
||||
if filters.today then
|
||||
conditions = conditions .. " AND m.`created_at` >= CURRENT_DATE() AND m.`created_at` < CURRENT_DATE() + INTERVAL 1 DAY"
|
||||
end
|
||||
|
||||
local pattern = "%" .. search .. "%"
|
||||
values[#values + 1] = search
|
||||
values[#values + 1] = pattern
|
||||
@@ -566,6 +792,57 @@ Bridge.Callbacks.Register("sky_phone:mail:set-read", function(source, data)
|
||||
return { success = true }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:mail:move", function(source, data)
|
||||
local session, error_response = require_session(source)
|
||||
if not session then
|
||||
return error_response
|
||||
end
|
||||
if not SkyPhone.AllowOperation(source, "mail_move", Config.Mail.MailboxRequestsPerMinute * 3, 60) then
|
||||
return { success = false, error = "rate_limited" }
|
||||
end
|
||||
|
||||
data = validate_payload(source, "move", data)
|
||||
if not data then
|
||||
return { success = false, error = "invalid_request" }
|
||||
end
|
||||
|
||||
local entry_id = normalize_numeric_id(data.id)
|
||||
local mailbox_id
|
||||
if data.mailboxId ~= 0 then
|
||||
mailbox_id = normalize_numeric_id(data.mailboxId)
|
||||
end
|
||||
if not entry_id then
|
||||
return { success = false, error = "message_not_found" }
|
||||
end
|
||||
if data.mailboxId ~= 0 and (not mailbox_id or not find_owned_mailbox(session.id, mailbox_id)) then
|
||||
return { success = false, error = "mailbox_not_found" }
|
||||
end
|
||||
|
||||
local entries = Bridge.Database.Query([[
|
||||
SELECT `id`
|
||||
FROM `sky_phone_mail_entries`
|
||||
WHERE `id` = ? AND `account_id` = ? AND `trashed_at` IS NULL
|
||||
LIMIT 1
|
||||
]], { entry_id, session.id })
|
||||
if not entries[1] then
|
||||
return { success = false, error = "message_not_found" }
|
||||
end
|
||||
|
||||
if mailbox_id then
|
||||
Bridge.Database.Query([[
|
||||
UPDATE `sky_phone_mail_entries` SET `mailbox_id` = ?
|
||||
WHERE `id` = ? AND `account_id` = ? AND `trashed_at` IS NULL
|
||||
]], { mailbox_id, entry_id, session.id })
|
||||
else
|
||||
Bridge.Database.Query([[
|
||||
UPDATE `sky_phone_mail_entries` SET `mailbox_id` = NULL
|
||||
WHERE `id` = ? AND `account_id` = ? AND `trashed_at` IS NULL
|
||||
]], { entry_id, session.id })
|
||||
end
|
||||
broadcast_mailbox_changed(session.id)
|
||||
return { success = true }
|
||||
end)
|
||||
|
||||
Bridge.Callbacks.Register("sky_phone:mail:trash", function(source, data)
|
||||
local session, error_response = require_session(source)
|
||||
if not session then
|
||||
@@ -658,9 +935,13 @@ Bridge.Callbacks.Register("sky_phone:mail:delete-many", function(source, data)
|
||||
end
|
||||
|
||||
local folder = data.folder
|
||||
if type(folder) ~= "string" or not DELETE_FOLDERS[folder] then
|
||||
local custom_mailbox_id = mailbox_id_from_folder(folder)
|
||||
if type(folder) ~= "string" or (not DELETE_FOLDERS[folder] and not custom_mailbox_id) then
|
||||
return { success = false, error = "invalid_folder" }
|
||||
end
|
||||
if custom_mailbox_id and not find_owned_mailbox(session.id, custom_mailbox_id) then
|
||||
return { success = false, error = "mailbox_not_found" }
|
||||
end
|
||||
|
||||
local ids = normalize_delete_ids(folder, data.ids)
|
||||
if not ids then
|
||||
@@ -680,7 +961,16 @@ Bridge.Callbacks.Register("sky_phone:mail:delete-many", function(source, data)
|
||||
append_values(parameters, ids)
|
||||
Bridge.Database.Query(([[
|
||||
UPDATE `sky_phone_mail_entries` SET `trashed_at` = CURRENT_TIMESTAMP
|
||||
WHERE `account_id` = ? AND `folder` = ? AND `trashed_at` IS NULL
|
||||
WHERE `account_id` = ? AND `folder` = ? AND `mailbox_id` IS NULL
|
||||
AND `trashed_at` IS NULL
|
||||
AND `id` IN (%s)
|
||||
]]):format(placeholders), parameters)
|
||||
elseif custom_mailbox_id then
|
||||
local parameters = { session.id, custom_mailbox_id }
|
||||
append_values(parameters, ids)
|
||||
Bridge.Database.Query(([[
|
||||
UPDATE `sky_phone_mail_entries` SET `trashed_at` = CURRENT_TIMESTAMP
|
||||
WHERE `account_id` = ? AND `mailbox_id` = ? AND `trashed_at` IS NULL
|
||||
AND `id` IN (%s)
|
||||
]]):format(placeholders), parameters)
|
||||
else
|
||||
|
||||
@@ -23,16 +23,30 @@ CREATE TABLE IF NOT EXISTS `sky_phone_mail_messages` (
|
||||
FOREIGN KEY (`sender_account_id`) REFERENCES `sky_phone_accounts` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sky_phone_mailboxes` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`account_id` BIGINT UNSIGNED NOT NULL,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_sky_phone_mailbox_name` (`account_id`, `name`),
|
||||
KEY `idx_sky_phone_mailboxes` (`account_id`, `sort_order`, `id`),
|
||||
FOREIGN KEY (`account_id`) REFERENCES `sky_phone_accounts` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `sky_phone_mail_entries` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`message_id` CHAR(36) NOT NULL,
|
||||
`account_id` BIGINT UNSIGNED NOT NULL,
|
||||
`folder` ENUM('inbox', 'sent') NOT NULL,
|
||||
`mailbox_id` BIGINT UNSIGNED NULL,
|
||||
`read_at` DATETIME NULL,
|
||||
`trashed_at` DATETIME NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_sky_phone_mail_entry` (`message_id`, `account_id`, `folder`),
|
||||
KEY `idx_sky_phone_mailbox` (`account_id`, `folder`, `trashed_at`, `id`),
|
||||
KEY `idx_sky_phone_custom_mailbox` (`account_id`, `mailbox_id`, `trashed_at`, `id`),
|
||||
FOREIGN KEY (`message_id`) REFERENCES `sky_phone_mail_messages` (`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`account_id`) REFERENCES `sky_phone_accounts` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
Reference in New Issue
Block a user