diff --git a/.sqlx/query-017b3f73101926a3fe029ed75c412ec902aa74fee334c23fd75fe0cde1e324ca.json b/.sqlx/query-017b3f73101926a3fe029ed75c412ec902aa74fee334c23fd75fe0cde1e324ca.json new file mode 100644 index 0000000..1d153dc --- /dev/null +++ b/.sqlx/query-017b3f73101926a3fe029ed75c412ec902aa74fee334c23fd75fe0cde1e324ca.json @@ -0,0 +1,84 @@ +{ + "db_name": "MySQL", + "query": "\n SELECT id, channel, nudge, webhook_id, webhook_token, paused,\n paused_until\n FROM channels\n WHERE channel = ?\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "channel", + "type_info": { + "type": "LongLong", + "flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE", + "max_size": 20 + } + }, + { + "ordinal": 2, + "name": "nudge", + "type_info": { + "type": "Short", + "flags": "NOT_NULL", + "max_size": 6 + } + }, + { + "ordinal": 3, + "name": "webhook_id", + "type_info": { + "type": "LongLong", + "flags": "UNIQUE_KEY | UNSIGNED", + "max_size": 20 + } + }, + { + "ordinal": 4, + "name": "webhook_token", + "type_info": { + "type": "Blob", + "flags": "BLOB", + "max_size": 262140 + } + }, + { + "ordinal": 5, + "name": "paused", + "type_info": { + "type": "Tiny", + "flags": "NOT_NULL", + "max_size": 1 + } + }, + { + "ordinal": 6, + "name": "paused_until", + "type_info": { + "type": "Timestamp", + "flags": "BINARY", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + true, + true, + false, + true + ] + }, + "hash": "017b3f73101926a3fe029ed75c412ec902aa74fee334c23fd75fe0cde1e324ca" +} diff --git a/.sqlx/query-ecbea4acb61da864a4f83004b1163391068456a366fee95daff5f46413bcf494.json b/.sqlx/query-168e34b942db6025a5674dca06ac154309cc8f86341272e7c771abfad1b61003.json similarity index 69% rename from .sqlx/query-ecbea4acb61da864a4f83004b1163391068456a366fee95daff5f46413bcf494.json rename to .sqlx/query-168e34b942db6025a5674dca06ac154309cc8f86341272e7c771abfad1b61003.json index a2c694d..7947dac 100644 --- a/.sqlx/query-ecbea4acb61da864a4f83004b1163391068456a366fee95daff5f46413bcf494.json +++ b/.sqlx/query-168e34b942db6025a5674dca06ac154309cc8f86341272e7c771abfad1b61003.json @@ -1,19 +1,10 @@ { "db_name": "MySQL", - "query": "\n SELECT reminders.attachment,\n reminders.attachment_name,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?\n ", + "query": "\n SELECT\n reminders.attachment_name,\n reminders.avatar,\n channels.channel,\n reminders.message_json,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.restartable,\n reminders.tts,\n reminders.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?", "describe": { "columns": [ { "ordinal": 0, - "name": "attachment", - "type_info": { - "type": "Blob", - "flags": "BLOB | BINARY", - "max_size": 16777215 - } - }, - { - "ordinal": 1, "name": "attachment_name", "type_info": { "type": "VarString", @@ -21,8 +12,35 @@ "max_size": 1040 } }, + { + "ordinal": 1, + "name": "avatar", + "type_info": { + "type": "VarString", + "flags": "", + "max_size": 2048 + } + }, { "ordinal": 2, + "name": "channel", + "type_info": { + "type": "LongLong", + "flags": "UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE", + "max_size": 20 + } + }, + { + "ordinal": 3, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 4, "name": "content", "type_info": { "type": "VarString", @@ -31,7 +49,7 @@ } }, { - "ordinal": 3, + "ordinal": 5, "name": "embed_author", "type_info": { "type": "VarString", @@ -40,7 +58,7 @@ } }, { - "ordinal": 4, + "ordinal": 6, "name": "embed_author_url", "type_info": { "type": "VarString", @@ -49,7 +67,7 @@ } }, { - "ordinal": 5, + "ordinal": 7, "name": "embed_color", "type_info": { "type": "Long", @@ -58,7 +76,7 @@ } }, { - "ordinal": 6, + "ordinal": 8, "name": "embed_description", "type_info": { "type": "VarString", @@ -67,7 +85,7 @@ } }, { - "ordinal": 7, + "ordinal": 9, "name": "embed_footer", "type_info": { "type": "VarString", @@ -76,7 +94,7 @@ } }, { - "ordinal": 8, + "ordinal": 10, "name": "embed_footer_url", "type_info": { "type": "VarString", @@ -85,7 +103,7 @@ } }, { - "ordinal": 9, + "ordinal": 11, "name": "embed_image_url", "type_info": { "type": "VarString", @@ -94,7 +112,7 @@ } }, { - "ordinal": 10, + "ordinal": 12, "name": "embed_thumbnail_url", "type_info": { "type": "VarString", @@ -103,7 +121,7 @@ } }, { - "ordinal": 11, + "ordinal": 13, "name": "embed_title", "type_info": { "type": "VarString", @@ -112,7 +130,7 @@ } }, { - "ordinal": 12, + "ordinal": 14, "name": "embed_fields", "type_info": { "type": "Json", @@ -121,7 +139,7 @@ } }, { - "ordinal": 13, + "ordinal": 15, "name": "enabled", "type_info": { "type": "Tiny", @@ -130,7 +148,7 @@ } }, { - "ordinal": 14, + "ordinal": 16, "name": "expires", "type_info": { "type": "Datetime", @@ -139,7 +157,7 @@ } }, { - "ordinal": 15, + "ordinal": 17, "name": "interval_seconds", "type_info": { "type": "Long", @@ -148,7 +166,7 @@ } }, { - "ordinal": 16, + "ordinal": 18, "name": "interval_days", "type_info": { "type": "Long", @@ -157,7 +175,7 @@ } }, { - "ordinal": 17, + "ordinal": 19, "name": "interval_months", "type_info": { "type": "Long", @@ -166,7 +184,7 @@ } }, { - "ordinal": 18, + "ordinal": 20, "name": "name", "type_info": { "type": "VarString", @@ -175,7 +193,16 @@ } }, { - "ordinal": 19, + "ordinal": 21, + "name": "restartable", + "type_info": { + "type": "Tiny", + "flags": "NOT_NULL", + "max_size": 1 + } + }, + { + "ordinal": 22, "name": "tts", "type_info": { "type": "Tiny", @@ -184,7 +211,7 @@ } }, { - "ordinal": 20, + "ordinal": 23, "name": "uid", "type_info": { "type": "VarString", @@ -193,7 +220,16 @@ } }, { - "ordinal": 21, + "ordinal": 24, + "name": "username", + "type_info": { + "type": "VarString", + "flags": "", + "max_size": 128 + } + }, + { + "ordinal": 25, "name": "utc_time", "type_info": { "type": "Datetime", @@ -206,6 +242,8 @@ "Right": 1 }, "nullable": [ + true, + true, true, true, false, @@ -227,8 +265,10 @@ false, false, false, + false, + true, false ] }, - "hash": "ecbea4acb61da864a4f83004b1163391068456a366fee95daff5f46413bcf494" + "hash": "168e34b942db6025a5674dca06ac154309cc8f86341272e7c771abfad1b61003" } diff --git a/.sqlx/query-21b660ecbeffcac31ef283f59e420360a28b344759407024a11dc4a04da9341d.json b/.sqlx/query-21b660ecbeffcac31ef283f59e420360a28b344759407024a11dc4a04da9341d.json new file mode 100644 index 0000000..0314609 --- /dev/null +++ b/.sqlx/query-21b660ecbeffcac31ef283f59e420360a28b344759407024a11dc4a04da9341d.json @@ -0,0 +1,84 @@ +{ + "db_name": "MySQL", + "query": "\n SELECT id, channel, nudge, webhook_id, webhook_token, paused, paused_until\n FROM channels\n WHERE channel = ?\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": { + "type": "Long", + "flags": "NOT_NULL | PRIMARY_KEY | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT", + "max_size": 10 + } + }, + { + "ordinal": 1, + "name": "channel", + "type_info": { + "type": "LongLong", + "flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE", + "max_size": 20 + } + }, + { + "ordinal": 2, + "name": "nudge", + "type_info": { + "type": "Short", + "flags": "NOT_NULL", + "max_size": 6 + } + }, + { + "ordinal": 3, + "name": "webhook_id", + "type_info": { + "type": "LongLong", + "flags": "UNIQUE_KEY | UNSIGNED", + "max_size": 20 + } + }, + { + "ordinal": 4, + "name": "webhook_token", + "type_info": { + "type": "Blob", + "flags": "BLOB", + "max_size": 262140 + } + }, + { + "ordinal": 5, + "name": "paused", + "type_info": { + "type": "Tiny", + "flags": "NOT_NULL", + "max_size": 1 + } + }, + { + "ordinal": 6, + "name": "paused_until", + "type_info": { + "type": "Timestamp", + "flags": "BINARY", + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false, + true, + true, + false, + true + ] + }, + "hash": "21b660ecbeffcac31ef283f59e420360a28b344759407024a11dc4a04da9341d" +} diff --git a/.sqlx/query-f9d2d9f6809b6c852297e5c60b6c356af5176c6b81b86cd701c1b8af8212f194.json b/.sqlx/query-22ea7fbff927d2696e5c3dc78f714d1cb5dadcc560854e22f15def122c64fc0c.json similarity index 54% rename from .sqlx/query-f9d2d9f6809b6c852297e5c60b6c356af5176c6b81b86cd701c1b8af8212f194.json rename to .sqlx/query-22ea7fbff927d2696e5c3dc78f714d1cb5dadcc560854e22f15def122c64fc0c.json index 2a1c112..4b65cc2 100644 --- a/.sqlx/query-f9d2d9f6809b6c852297e5c60b6c356af5176c6b81b86cd701c1b8af8212f194.json +++ b/.sqlx/query-22ea7fbff927d2696e5c3dc78f714d1cb5dadcc560854e22f15def122c64fc0c.json @@ -1,12 +1,12 @@ { "db_name": "MySQL", - "query": "\n INSERT INTO reminders (\n `uid`,\n `channel_id`,\n `thread_id`,\n `utc_time`,\n `timezone`,\n `interval_seconds`,\n `interval_days`,\n `interval_months`,\n `expires`,\n `content`,\n `tts`,\n `attachment_name`,\n `attachment`,\n `set_by`\n ) VALUES (\n ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?\n )\n ", + "query": "\n INSERT INTO reminders (\n `uid`,\n `channel_id`,\n `thread_id`,\n `utc_time`,\n `timezone`,\n `interval_seconds`,\n `interval_days`,\n `interval_months`,\n `expires`,\n `message_json`,\n `attachment_name`,\n `attachment`,\n `set_by`\n ) VALUES (\n ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?\n )\n ", "describe": { "columns": [], "parameters": { - "Right": 14 + "Right": 13 }, "nullable": [] }, - "hash": "f9d2d9f6809b6c852297e5c60b6c356af5176c6b81b86cd701c1b8af8212f194" + "hash": "22ea7fbff927d2696e5c3dc78f714d1cb5dadcc560854e22f15def122c64fc0c" } diff --git a/.sqlx/query-67b970d7b85f7bf3e71a58af10bedf7281cf0d8601b149383037d604a22a64c7.json b/.sqlx/query-2753eb51d5f4b51771b696555752cc59d8d0c78737fc35210c5f6108d32f3540.json similarity index 82% rename from .sqlx/query-67b970d7b85f7bf3e71a58af10bedf7281cf0d8601b149383037d604a22a64c7.json rename to .sqlx/query-2753eb51d5f4b51771b696555752cc59d8d0c78737fc35210c5f6108d32f3540.json index 3d99f49..631de40 100644 --- a/.sqlx/query-67b970d7b85f7bf3e71a58af10bedf7281cf0d8601b149383037d604a22a64c7.json +++ b/.sqlx/query-2753eb51d5f4b51771b696555752cc59d8d0c78737fc35210c5f6108d32f3540.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.id = ?\n ", + "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.message_json,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.id = ?\n ", "describe": { "columns": [ { @@ -86,6 +86,15 @@ }, { "ordinal": 9, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 10, "name": "content", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_description", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "set_by", "type_info": { "type": "LongLong", @@ -125,10 +134,11 @@ true, true, false, + true, false, false, true ] }, - "hash": "67b970d7b85f7bf3e71a58af10bedf7281cf0d8601b149383037d604a22a64c7" + "hash": "2753eb51d5f4b51771b696555752cc59d8d0c78737fc35210c5f6108d32f3540" } diff --git a/.sqlx/query-d7f469dd1c7f37f1aca643e9018083b48da1cf988030b9efeae451e77e54a23d.json b/.sqlx/query-40ceeb037b5266e743a77a4a657d222b769e871b521d3a1745caed0292e9e6cc.json similarity index 82% rename from .sqlx/query-d7f469dd1c7f37f1aca643e9018083b48da1cf988030b9efeae451e77e54a23d.json rename to .sqlx/query-40ceeb037b5266e743a77a4a657d222b769e871b521d3a1745caed0292e9e6cc.json index 7ba0102..82c9cbe 100644 --- a/.sqlx/query-d7f469dd1c7f37f1aca643e9018083b48da1cf988030b9efeae451e77e54a23d.json +++ b/.sqlx/query-40ceeb037b5266e743a77a4a657d222b769e871b521d3a1745caed0292e9e6cc.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.uid = ?\n ", + "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.message_json,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.uid = ?\n ", "describe": { "columns": [ { @@ -86,6 +86,15 @@ }, { "ordinal": 9, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 10, "name": "content", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_description", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "set_by", "type_info": { "type": "LongLong", @@ -125,10 +134,11 @@ true, true, false, + true, false, false, true ] }, - "hash": "d7f469dd1c7f37f1aca643e9018083b48da1cf988030b9efeae451e77e54a23d" + "hash": "40ceeb037b5266e743a77a4a657d222b769e871b521d3a1745caed0292e9e6cc" } diff --git a/.sqlx/query-4cc3ff2bbdf8502f6e739a002c7aa16df8c4f24de0966b26b268efab97e205e3.json b/.sqlx/query-4cc3ff2bbdf8502f6e739a002c7aa16df8c4f24de0966b26b268efab97e205e3.json deleted file mode 100644 index 15c3449..0000000 --- a/.sqlx/query-4cc3ff2bbdf8502f6e739a002c7aa16df8c4f24de0966b26b268efab97e205e3.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "MySQL", - "query": "INSERT INTO reminders (\n uid,\n attachment,\n attachment_name,\n channel_id,\n avatar,\n content,\n embed_author,\n embed_author_url,\n embed_color,\n embed_description,\n embed_footer,\n embed_footer_url,\n embed_image_url,\n embed_thumbnail_url,\n embed_title,\n embed_fields,\n enabled,\n expires,\n interval_seconds,\n interval_days,\n interval_months,\n name,\n restartable,\n tts,\n username,\n `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 26 - }, - "nullable": [] - }, - "hash": "4cc3ff2bbdf8502f6e739a002c7aa16df8c4f24de0966b26b268efab97e205e3" -} diff --git a/.sqlx/query-45e555a2a0664662f55cc738f91abf8c51648fb687f9568c4dd5f48e9273253a.json b/.sqlx/query-5575971e2a460476adcc70147810caa91fb07f52d788af0f7ec319a15ee5020c.json similarity index 78% rename from .sqlx/query-45e555a2a0664662f55cc738f91abf8c51648fb687f9568c4dd5f48e9273253a.json rename to .sqlx/query-5575971e2a460476adcc70147810caa91fb07f52d788af0f7ec319a15ee5020c.json index 6a0fd27..09fd639 100644 --- a/.sqlx/query-45e555a2a0664662f55cc738f91abf8c51648fb687f9568c4dd5f48e9273253a.json +++ b/.sqlx/query-5575971e2a460476adcc70147810caa91fb07f52d788af0f7ec319a15ee5020c.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n channels.id = reminders.channel_id\n WHERE\n `status` = 'pending' AND\n channels.id = (SELECT dm_channel FROM users WHERE id = ?)\n ", + "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.message_json,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n channels.id = reminders.channel_id\n WHERE\n `status` = 'pending' AND\n channels.id = (SELECT dm_channel FROM users WHERE id = ?)\n ", "describe": { "columns": [ { @@ -86,6 +86,15 @@ }, { "ordinal": 9, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 10, "name": "content", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_description", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "set_by", "type_info": { "type": "LongLong", @@ -125,10 +134,11 @@ true, true, false, + true, false, false, true ] }, - "hash": "45e555a2a0664662f55cc738f91abf8c51648fb687f9568c4dd5f48e9273253a" + "hash": "5575971e2a460476adcc70147810caa91fb07f52d788af0f7ec319a15ee5020c" } diff --git a/.sqlx/query-5d7a787e3a6f487c3fe00bd39d308c9350c82e58b0ad1aae0b92824375786f02.json b/.sqlx/query-5d7a787e3a6f487c3fe00bd39d308c9350c82e58b0ad1aae0b92824375786f02.json new file mode 100644 index 0000000..c85539d --- /dev/null +++ b/.sqlx/query-5d7a787e3a6f487c3fe00bd39d308c9350c82e58b0ad1aae0b92824375786f02.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT IGNORE INTO channels (channel, guild_id) VALUES (?, (SELECT id FROM guilds WHERE guild = ?))", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + }, + "hash": "5d7a787e3a6f487c3fe00bd39d308c9350c82e58b0ad1aae0b92824375786f02" +} diff --git a/.sqlx/query-4dc78a4ebdad0d9fb721389013537093dd814c313bc8ecc80fc9767420afb798.json b/.sqlx/query-5efa565633215c62ae826b670140c2f36ccc019d56db7a0182ec00f1a2713562.json similarity index 77% rename from .sqlx/query-4dc78a4ebdad0d9fb721389013537093dd814c313bc8ecc80fc9767420afb798.json rename to .sqlx/query-5efa565633215c62ae826b670140c2f36ccc019d56db7a0182ec00f1a2713562.json index 1446786..3e8fdae 100644 --- a/.sqlx/query-4dc78a4ebdad0d9fb721389013537093dd814c313bc8ecc80fc9767420afb798.json +++ b/.sqlx/query-5efa565633215c62ae826b670140c2f36ccc019d56db7a0182ec00f1a2713562.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n LEFT JOIN\n channels\n ON\n channels.id = reminders.channel_id\n WHERE\n `status` = 'pending' AND\n channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)\n ", + "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.message_json,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n LEFT JOIN\n channels\n ON\n channels.id = reminders.channel_id\n WHERE\n `status` = 'pending' AND\n channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)\n ", "describe": { "columns": [ { @@ -86,6 +86,15 @@ }, { "ordinal": 9, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 10, "name": "content", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_description", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "set_by", "type_info": { "type": "LongLong", @@ -125,10 +134,11 @@ true, true, false, + true, false, false, true ] }, - "hash": "4dc78a4ebdad0d9fb721389013537093dd814c313bc8ecc80fc9767420afb798" + "hash": "5efa565633215c62ae826b670140c2f36ccc019d56db7a0182ec00f1a2713562" } diff --git a/.sqlx/query-a33ef7167746183766b6a9b4e31784dd3712fa2b4f72482d4b681e0a9fdbaf1d.json b/.sqlx/query-6cc6fafaa4dab75639c6e5348d2a836bc6be510e76534c36df709883a4694859.json similarity index 73% rename from .sqlx/query-a33ef7167746183766b6a9b4e31784dd3712fa2b4f72482d4b681e0a9fdbaf1d.json rename to .sqlx/query-6cc6fafaa4dab75639c6e5348d2a836bc6be510e76534c36df709883a4694859.json index cf85f9a..c3d7a2c 100644 --- a/.sqlx/query-a33ef7167746183766b6a9b4e31784dd3712fa2b4f72482d4b681e0a9fdbaf1d.json +++ b/.sqlx/query-6cc6fafaa4dab75639c6e5348d2a836bc6be510e76534c36df709883a4694859.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.`id` AS id,\n\n channels.`channel` AS channel_id,\n reminders.`thread_id` AS thread_id,\n channels.`webhook_id` AS webhook_id,\n channels.`webhook_token` AS webhook_token,\n\n channels.`paused` AS 'channel_paused',\n channels.`paused_until` AS 'channel_paused_until',\n reminders.`enabled` AS 'enabled',\n\n reminders.`tts` AS tts,\n reminders.`pin` AS pin,\n reminders.`content` AS content,\n reminders.`attachment` AS attachment,\n reminders.`attachment_name` AS attachment_name,\n\n reminders.`utc_time` AS 'utc_time',\n reminders.`timezone` AS timezone,\n reminders.`restartable` AS restartable,\n reminders.`expires` AS 'expires',\n reminders.`interval_seconds` AS 'interval_seconds',\n reminders.`interval_days` AS 'interval_days',\n reminders.`interval_months` AS 'interval_months',\n\n reminders.`avatar` AS avatar,\n reminders.`username` AS username\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.`status` = 'pending' AND\n reminders.`id` IN (\n SELECT\n MIN(id)\n FROM\n reminders\n WHERE\n reminders.`utc_time` <= NOW() AND\n `status` = 'pending' AND\n (\n reminders.`interval_seconds` IS NOT NULL\n OR reminders.`interval_months` IS NOT NULL\n OR reminders.`interval_days` IS NOT NULL\n OR reminders.enabled\n )\n GROUP BY channel_id\n )\n ", + "query": "\n SELECT\n reminders.`id` AS id,\n\n channels.`channel` AS channel_id,\n reminders.`thread_id` AS thread_id,\n channels.`webhook_id` AS webhook_id,\n channels.`webhook_token` AS webhook_token,\n\n channels.`paused` AS 'channel_paused',\n channels.`paused_until` AS 'channel_paused_until',\n reminders.`enabled` AS 'enabled',\n\n reminders.`tts` AS tts,\n reminders.`pin` AS pin,\n reminders.`content` AS content,\n reminders.`message_json` AS message_json,\n reminders.`attachment` AS attachment,\n reminders.`attachment_name` AS attachment_name,\n\n reminders.`utc_time` AS 'utc_time',\n reminders.`timezone` AS timezone,\n reminders.`restartable` AS restartable,\n reminders.`expires` AS 'expires',\n reminders.`interval_seconds` AS 'interval_seconds',\n reminders.`interval_days` AS 'interval_days',\n reminders.`interval_months` AS 'interval_months',\n\n reminders.`avatar` AS avatar,\n reminders.`username` AS username\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n reminders.`status` = 'pending' AND\n reminders.`id` IN (\n SELECT\n MIN(id)\n FROM\n reminders\n WHERE\n reminders.`utc_time` <= NOW() AND\n `status` = 'pending' AND\n (\n reminders.`interval_seconds` IS NOT NULL\n OR reminders.`interval_months` IS NOT NULL\n OR reminders.`interval_days` IS NOT NULL\n OR reminders.enabled\n )\n GROUP BY channel_id\n )\n ", "describe": { "columns": [ { @@ -104,6 +104,15 @@ }, { "ordinal": 11, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 12, "name": "attachment", "type_info": { "type": "Blob", @@ -112,7 +121,7 @@ } }, { - "ordinal": 12, + "ordinal": 13, "name": "attachment_name", "type_info": { "type": "VarString", @@ -121,7 +130,7 @@ } }, { - "ordinal": 13, + "ordinal": 14, "name": "utc_time", "type_info": { "type": "Datetime", @@ -130,7 +139,7 @@ } }, { - "ordinal": 14, + "ordinal": 15, "name": "timezone", "type_info": { "type": "String", @@ -139,7 +148,7 @@ } }, { - "ordinal": 15, + "ordinal": 16, "name": "restartable", "type_info": { "type": "Tiny", @@ -148,7 +157,7 @@ } }, { - "ordinal": 16, + "ordinal": 17, "name": "expires", "type_info": { "type": "Datetime", @@ -157,7 +166,7 @@ } }, { - "ordinal": 17, + "ordinal": 18, "name": "interval_seconds", "type_info": { "type": "Long", @@ -166,7 +175,7 @@ } }, { - "ordinal": 18, + "ordinal": 19, "name": "interval_days", "type_info": { "type": "Long", @@ -175,7 +184,7 @@ } }, { - "ordinal": 19, + "ordinal": 20, "name": "interval_months", "type_info": { "type": "Long", @@ -184,7 +193,7 @@ } }, { - "ordinal": 20, + "ordinal": 21, "name": "avatar", "type_info": { "type": "VarString", @@ -193,7 +202,7 @@ } }, { - "ordinal": 21, + "ordinal": 22, "name": "username", "type_info": { "type": "VarString", @@ -219,6 +228,7 @@ false, true, true, + true, false, false, false, @@ -230,5 +240,5 @@ true ] }, - "hash": "a33ef7167746183766b6a9b4e31784dd3712fa2b4f72482d4b681e0a9fdbaf1d" + "hash": "6cc6fafaa4dab75639c6e5348d2a836bc6be510e76534c36df709883a4694859" } diff --git a/.sqlx/query-7f44451c441507ccd7f8d41633fb156f95a28389fcc9c489fdb228446f88342e.json b/.sqlx/query-7f44451c441507ccd7f8d41633fb156f95a28389fcc9c489fdb228446f88342e.json deleted file mode 100644 index b742b65..0000000 --- a/.sqlx/query-7f44451c441507ccd7f8d41633fb156f95a28389fcc9c489fdb228446f88342e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "MySQL", - "query": "INSERT INTO reminders (\n uid,\n attachment,\n attachment_name,\n channel_id,\n content,\n embed_author,\n embed_author_url,\n embed_color,\n embed_description,\n embed_footer,\n embed_footer_url,\n embed_image_url,\n embed_thumbnail_url,\n embed_title,\n embed_fields,\n enabled,\n expires,\n interval_seconds,\n interval_days,\n interval_months,\n name,\n tts,\n `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 23 - }, - "nullable": [] - }, - "hash": "7f44451c441507ccd7f8d41633fb156f95a28389fcc9c489fdb228446f88342e" -} diff --git a/.sqlx/query-8051034e8334a31d5c8ffca9893f85b689955c1e3d144e1162d4c991a47d9852.json b/.sqlx/query-8051034e8334a31d5c8ffca9893f85b689955c1e3d144e1162d4c991a47d9852.json new file mode 100644 index 0000000..dc8f4fc --- /dev/null +++ b/.sqlx/query-8051034e8334a31d5c8ffca9893f85b689955c1e3d144e1162d4c991a47d9852.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT INTO reminders (\n uid, attachment, attachment_name, channel_id, message_json,\n enabled, expires, interval_seconds, interval_days, interval_months,\n name, restartable, `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 13 + }, + "nullable": [] + }, + "hash": "8051034e8334a31d5c8ffca9893f85b689955c1e3d144e1162d4c991a47d9852" +} diff --git a/.sqlx/query-fa58f079f00bec4c3f9cbb2ee0a607f63c5b586654c541bff6b99eff83ae0a44.json b/.sqlx/query-8e5e68f6cc7549dc3c1e777def9f6fd8a6b61c6790ee9594ca20c5504ca834fa.json similarity index 82% rename from .sqlx/query-fa58f079f00bec4c3f9cbb2ee0a607f63c5b586654c541bff6b99eff83ae0a44.json rename to .sqlx/query-8e5e68f6cc7549dc3c1e777def9f6fd8a6b61c6790ee9594ca20c5504ca834fa.json index 9b62686..42b62d0 100644 --- a/.sqlx/query-fa58f079f00bec4c3f9cbb2ee0a607f63c5b586654c541bff6b99eff83ae0a44.json +++ b/.sqlx/query-8e5e68f6cc7549dc3c1e777def9f6fd8a6b61c6790ee9594ca20c5504ca834fa.json @@ -1,10 +1,19 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.attachment_name,\n reminders.avatar,\n channels.channel,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.restartable,\n reminders.tts,\n reminders.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?", + "query": "\n SELECT reminders.attachment,\n reminders.attachment_name,\n reminders.message_json,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?\n ", "describe": { "columns": [ { "ordinal": 0, + "name": "attachment", + "type_info": { + "type": "Blob", + "flags": "BLOB | BINARY", + "max_size": 16777215 + } + }, + { + "ordinal": 1, "name": "attachment_name", "type_info": { "type": "VarString", @@ -12,22 +21,13 @@ "max_size": 1040 } }, - { - "ordinal": 1, - "name": "avatar", - "type_info": { - "type": "VarString", - "flags": "", - "max_size": 2048 - } - }, { "ordinal": 2, - "name": "channel", + "name": "message_json", "type_info": { - "type": "LongLong", - "flags": "UNIQUE_KEY | UNSIGNED | NO_DEFAULT_VALUE", - "max_size": 20 + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 } }, { @@ -185,15 +185,6 @@ }, { "ordinal": 20, - "name": "restartable", - "type_info": { - "type": "Tiny", - "flags": "NOT_NULL", - "max_size": 1 - } - }, - { - "ordinal": 21, "name": "tts", "type_info": { "type": "Tiny", @@ -202,7 +193,7 @@ } }, { - "ordinal": 22, + "ordinal": 21, "name": "uid", "type_info": { "type": "VarString", @@ -211,16 +202,7 @@ } }, { - "ordinal": 23, - "name": "username", - "type_info": { - "type": "VarString", - "flags": "", - "max_size": 128 - } - }, - { - "ordinal": 24, + "ordinal": 22, "name": "utc_time", "type_info": { "type": "Datetime", @@ -255,10 +237,8 @@ false, false, false, - false, - true, false ] }, - "hash": "fa58f079f00bec4c3f9cbb2ee0a607f63c5b586654c541bff6b99eff83ae0a44" + "hash": "8e5e68f6cc7549dc3c1e777def9f6fd8a6b61c6790ee9594ca20c5504ca834fa" } diff --git a/.sqlx/query-53db321bc70b45800f696115b65da8550de208104017f8e0d0a163781488a534.json b/.sqlx/query-8e8d6ebf8b5e69d246a73ef3b746ed3dc5dc6f8204f69aeb55dc5e2ae17319aa.json similarity index 80% rename from .sqlx/query-53db321bc70b45800f696115b65da8550de208104017f8e0d0a163781488a534.json rename to .sqlx/query-8e8d6ebf8b5e69d246a73ef3b746ed3dc5dc6f8204f69aeb55dc5e2ae17319aa.json index 52b524b..8f86158 100644 --- a/.sqlx/query-53db321bc70b45800f696115b65da8550de208104017f8e0d0a163781488a534.json +++ b/.sqlx/query-8e8d6ebf8b5e69d246a73ef3b746ed3dc5dc6f8204f69aeb55dc5e2ae17319aa.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.attachment_name,\n reminders.avatar,\n channels.channel,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.restartable,\n reminders.tts,\n reminders.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?", + "query": "\n SELECT\n reminders.attachment_name,\n reminders.avatar,\n channels.channel,\n reminders.message_json,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.restartable,\n reminders.tts,\n reminders.uid,\n reminders.username,\n reminders.utc_time\n FROM reminders\n LEFT JOIN channels ON channels.id = reminders.channel_id\n WHERE uid = ?", "describe": { "columns": [ { @@ -32,6 +32,15 @@ }, { "ordinal": 3, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 4, "name": "content", "type_info": { "type": "VarString", @@ -40,7 +49,7 @@ } }, { - "ordinal": 4, + "ordinal": 5, "name": "embed_author", "type_info": { "type": "VarString", @@ -49,7 +58,7 @@ } }, { - "ordinal": 5, + "ordinal": 6, "name": "embed_author_url", "type_info": { "type": "VarString", @@ -58,7 +67,7 @@ } }, { - "ordinal": 6, + "ordinal": 7, "name": "embed_color", "type_info": { "type": "Long", @@ -67,7 +76,7 @@ } }, { - "ordinal": 7, + "ordinal": 8, "name": "embed_description", "type_info": { "type": "VarString", @@ -76,7 +85,7 @@ } }, { - "ordinal": 8, + "ordinal": 9, "name": "embed_footer", "type_info": { "type": "VarString", @@ -85,7 +94,7 @@ } }, { - "ordinal": 9, + "ordinal": 10, "name": "embed_footer_url", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_image_url", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "embed_thumbnail_url", "type_info": { "type": "VarString", @@ -112,7 +121,7 @@ } }, { - "ordinal": 12, + "ordinal": 13, "name": "embed_title", "type_info": { "type": "VarString", @@ -121,7 +130,7 @@ } }, { - "ordinal": 13, + "ordinal": 14, "name": "embed_fields", "type_info": { "type": "Json", @@ -130,7 +139,7 @@ } }, { - "ordinal": 14, + "ordinal": 15, "name": "enabled", "type_info": { "type": "Tiny", @@ -139,7 +148,7 @@ } }, { - "ordinal": 15, + "ordinal": 16, "name": "expires", "type_info": { "type": "Datetime", @@ -148,7 +157,7 @@ } }, { - "ordinal": 16, + "ordinal": 17, "name": "interval_seconds", "type_info": { "type": "Long", @@ -157,7 +166,7 @@ } }, { - "ordinal": 17, + "ordinal": 18, "name": "interval_days", "type_info": { "type": "Long", @@ -166,7 +175,7 @@ } }, { - "ordinal": 18, + "ordinal": 19, "name": "interval_months", "type_info": { "type": "Long", @@ -175,7 +184,7 @@ } }, { - "ordinal": 19, + "ordinal": 20, "name": "name", "type_info": { "type": "VarString", @@ -184,7 +193,7 @@ } }, { - "ordinal": 20, + "ordinal": 21, "name": "restartable", "type_info": { "type": "Tiny", @@ -193,7 +202,7 @@ } }, { - "ordinal": 21, + "ordinal": 22, "name": "tts", "type_info": { "type": "Tiny", @@ -202,7 +211,7 @@ } }, { - "ordinal": 22, + "ordinal": 23, "name": "uid", "type_info": { "type": "VarString", @@ -211,7 +220,7 @@ } }, { - "ordinal": 23, + "ordinal": 24, "name": "username", "type_info": { "type": "VarString", @@ -220,7 +229,7 @@ } }, { - "ordinal": 24, + "ordinal": 25, "name": "utc_time", "type_info": { "type": "Datetime", @@ -236,6 +245,7 @@ true, true, true, + true, false, false, true, @@ -260,5 +270,5 @@ false ] }, - "hash": "53db321bc70b45800f696115b65da8550de208104017f8e0d0a163781488a534" + "hash": "8e8d6ebf8b5e69d246a73ef3b746ed3dc5dc6f8204f69aeb55dc5e2ae17319aa" } diff --git a/.sqlx/query-992666a8cec803ab31a5a86266a07daa4fdf26eeb0969abd4d7987f9672dfae8.json b/.sqlx/query-992666a8cec803ab31a5a86266a07daa4fdf26eeb0969abd4d7987f9672dfae8.json new file mode 100644 index 0000000..eca04e3 --- /dev/null +++ b/.sqlx/query-992666a8cec803ab31a5a86266a07daa4fdf26eeb0969abd4d7987f9672dfae8.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT INTO reminders (\n uid, attachment, attachment_name, channel_id, message_json,\n enabled, expires, interval_seconds, interval_days, interval_months,\n name, `utc_time`\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 12 + }, + "nullable": [] + }, + "hash": "992666a8cec803ab31a5a86266a07daa4fdf26eeb0969abd4d7987f9672dfae8" +} diff --git a/.sqlx/query-af08f3524c316f438feb62a24c67b96197e3655a87efe128914ed386be20d900.json b/.sqlx/query-af08f3524c316f438feb62a24c67b96197e3655a87efe128914ed386be20d900.json new file mode 100644 index 0000000..52b5e34 --- /dev/null +++ b/.sqlx/query-af08f3524c316f438feb62a24c67b96197e3655a87efe128914ed386be20d900.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "\n UPDATE channels\n SET\n nudge = ?,\n webhook_id = ?,\n webhook_token = ?,\n paused = ?,\n paused_until = ?\n WHERE id = ?\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 6 + }, + "nullable": [] + }, + "hash": "af08f3524c316f438feb62a24c67b96197e3655a87efe128914ed386be20d900" +} diff --git a/.sqlx/query-2613618c00941649726b31f7c50b5ef50d85323a73def5c87bab2a58c80bf6cd.json b/.sqlx/query-b708e057ab42c09c36fdc71bda77970fe04e8c066fd9c2701ac25afb059c32b6.json similarity index 80% rename from .sqlx/query-2613618c00941649726b31f7c50b5ef50d85323a73def5c87bab2a58c80bf6cd.json rename to .sqlx/query-b708e057ab42c09c36fdc71bda77970fe04e8c066fd9c2701ac25afb059c32b6.json index 7412dc1..0a2b39b 100644 --- a/.sqlx/query-2613618c00941649726b31f7c50b5ef50d85323a73def5c87bab2a58c80bf6cd.json +++ b/.sqlx/query-b708e057ab42c09c36fdc71bda77970fe04e8c066fd9c2701ac25afb059c32b6.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "SELECT\n reminders.attachment,\n reminders.attachment_name,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n WHERE uid = ?", + "query": "SELECT\n reminders.attachment,\n reminders.attachment_name,\n reminders.message_json,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n reminders.embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n WHERE uid = ?", "describe": { "columns": [ { @@ -23,6 +23,15 @@ }, { "ordinal": 2, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 3, "name": "content", "type_info": { "type": "VarString", @@ -31,7 +40,7 @@ } }, { - "ordinal": 3, + "ordinal": 4, "name": "embed_author", "type_info": { "type": "VarString", @@ -40,7 +49,7 @@ } }, { - "ordinal": 4, + "ordinal": 5, "name": "embed_author_url", "type_info": { "type": "VarString", @@ -49,7 +58,7 @@ } }, { - "ordinal": 5, + "ordinal": 6, "name": "embed_color", "type_info": { "type": "Long", @@ -58,7 +67,7 @@ } }, { - "ordinal": 6, + "ordinal": 7, "name": "embed_description", "type_info": { "type": "VarString", @@ -67,7 +76,7 @@ } }, { - "ordinal": 7, + "ordinal": 8, "name": "embed_footer", "type_info": { "type": "VarString", @@ -76,7 +85,7 @@ } }, { - "ordinal": 8, + "ordinal": 9, "name": "embed_footer_url", "type_info": { "type": "VarString", @@ -85,7 +94,7 @@ } }, { - "ordinal": 9, + "ordinal": 10, "name": "embed_image_url", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_thumbnail_url", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "embed_title", "type_info": { "type": "VarString", @@ -112,7 +121,7 @@ } }, { - "ordinal": 12, + "ordinal": 13, "name": "embed_fields", "type_info": { "type": "Json", @@ -121,7 +130,7 @@ } }, { - "ordinal": 13, + "ordinal": 14, "name": "enabled", "type_info": { "type": "Tiny", @@ -130,7 +139,7 @@ } }, { - "ordinal": 14, + "ordinal": 15, "name": "expires", "type_info": { "type": "Datetime", @@ -139,7 +148,7 @@ } }, { - "ordinal": 15, + "ordinal": 16, "name": "interval_seconds", "type_info": { "type": "Long", @@ -148,7 +157,7 @@ } }, { - "ordinal": 16, + "ordinal": 17, "name": "interval_days", "type_info": { "type": "Long", @@ -157,7 +166,7 @@ } }, { - "ordinal": 17, + "ordinal": 18, "name": "interval_months", "type_info": { "type": "Long", @@ -166,7 +175,7 @@ } }, { - "ordinal": 18, + "ordinal": 19, "name": "name", "type_info": { "type": "VarString", @@ -175,7 +184,7 @@ } }, { - "ordinal": 19, + "ordinal": 20, "name": "tts", "type_info": { "type": "Tiny", @@ -184,7 +193,7 @@ } }, { - "ordinal": 20, + "ordinal": 21, "name": "uid", "type_info": { "type": "VarString", @@ -193,7 +202,7 @@ } }, { - "ordinal": 21, + "ordinal": 22, "name": "utc_time", "type_info": { "type": "Datetime", @@ -206,6 +215,7 @@ "Right": 1 }, "nullable": [ + true, true, true, false, @@ -230,5 +240,5 @@ false ] }, - "hash": "2613618c00941649726b31f7c50b5ef50d85323a73def5c87bab2a58c80bf6cd" + "hash": "b708e057ab42c09c36fdc71bda77970fe04e8c066fd9c2701ac25afb059c32b6" } diff --git a/.sqlx/query-48a9268e9106a3a05e86ab36551944ba855a07b0241e912c6b1adee779c6f0ea.json b/.sqlx/query-d9e0f517df37a6c03868b6e1470169eaf06d2e6f1a9df62165656d3e97dbe929.json similarity index 78% rename from .sqlx/query-48a9268e9106a3a05e86ab36551944ba855a07b0241e912c6b1adee779c6f0ea.json rename to .sqlx/query-d9e0f517df37a6c03868b6e1470169eaf06d2e6f1a9df62165656d3e97dbe929.json index 3f27fe2..e630ad8 100644 --- a/.sqlx/query-48a9268e9106a3a05e86ab36551944ba855a07b0241e912c6b1adee779c6f0ea.json +++ b/.sqlx/query-d9e0f517df37a6c03868b6e1470169eaf06d2e6f1a9df62165656d3e97dbe929.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.attachment,\n reminders.attachment_name,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n IFNULL(reminders.embed_fields, '[]') AS embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n INNER JOIN channels ON channels.id = reminders.channel_id\n WHERE `status` = 'pending' AND channels.channel = ?\n ", + "query": "\n SELECT\n reminders.attachment,\n reminders.attachment_name,\n reminders.message_json,\n reminders.content,\n reminders.embed_author,\n reminders.embed_author_url,\n reminders.embed_color,\n reminders.embed_description,\n reminders.embed_footer,\n reminders.embed_footer_url,\n reminders.embed_image_url,\n reminders.embed_thumbnail_url,\n reminders.embed_title,\n IFNULL(reminders.embed_fields, '[]') AS embed_fields,\n reminders.enabled,\n reminders.expires,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.name,\n reminders.tts,\n reminders.uid,\n reminders.utc_time\n FROM reminders\n INNER JOIN channels ON channels.id = reminders.channel_id\n WHERE `status` = 'pending' AND channels.channel = ?\n ", "describe": { "columns": [ { @@ -23,6 +23,15 @@ }, { "ordinal": 2, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 3, "name": "content", "type_info": { "type": "VarString", @@ -31,7 +40,7 @@ } }, { - "ordinal": 3, + "ordinal": 4, "name": "embed_author", "type_info": { "type": "VarString", @@ -40,7 +49,7 @@ } }, { - "ordinal": 4, + "ordinal": 5, "name": "embed_author_url", "type_info": { "type": "VarString", @@ -49,7 +58,7 @@ } }, { - "ordinal": 5, + "ordinal": 6, "name": "embed_color", "type_info": { "type": "Long", @@ -58,7 +67,7 @@ } }, { - "ordinal": 6, + "ordinal": 7, "name": "embed_description", "type_info": { "type": "VarString", @@ -67,7 +76,7 @@ } }, { - "ordinal": 7, + "ordinal": 8, "name": "embed_footer", "type_info": { "type": "VarString", @@ -76,7 +85,7 @@ } }, { - "ordinal": 8, + "ordinal": 9, "name": "embed_footer_url", "type_info": { "type": "VarString", @@ -85,7 +94,7 @@ } }, { - "ordinal": 9, + "ordinal": 10, "name": "embed_image_url", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_thumbnail_url", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "embed_title", "type_info": { "type": "VarString", @@ -112,7 +121,7 @@ } }, { - "ordinal": 12, + "ordinal": 13, "name": "embed_fields", "type_info": { "type": "VarString", @@ -121,7 +130,7 @@ } }, { - "ordinal": 13, + "ordinal": 14, "name": "enabled", "type_info": { "type": "Tiny", @@ -130,7 +139,7 @@ } }, { - "ordinal": 14, + "ordinal": 15, "name": "expires", "type_info": { "type": "Datetime", @@ -139,7 +148,7 @@ } }, { - "ordinal": 15, + "ordinal": 16, "name": "interval_seconds", "type_info": { "type": "Long", @@ -148,7 +157,7 @@ } }, { - "ordinal": 16, + "ordinal": 17, "name": "interval_days", "type_info": { "type": "Long", @@ -157,7 +166,7 @@ } }, { - "ordinal": 17, + "ordinal": 18, "name": "interval_months", "type_info": { "type": "Long", @@ -166,7 +175,7 @@ } }, { - "ordinal": 18, + "ordinal": 19, "name": "name", "type_info": { "type": "VarString", @@ -175,7 +184,7 @@ } }, { - "ordinal": 19, + "ordinal": 20, "name": "tts", "type_info": { "type": "Tiny", @@ -184,7 +193,7 @@ } }, { - "ordinal": 20, + "ordinal": 21, "name": "uid", "type_info": { "type": "VarString", @@ -193,7 +202,7 @@ } }, { - "ordinal": 21, + "ordinal": 22, "name": "utc_time", "type_info": { "type": "Datetime", @@ -206,6 +215,7 @@ "Right": 1 }, "nullable": [ + true, true, true, false, @@ -230,5 +240,5 @@ false ] }, - "hash": "48a9268e9106a3a05e86ab36551944ba855a07b0241e912c6b1adee779c6f0ea" + "hash": "d9e0f517df37a6c03868b6e1470169eaf06d2e6f1a9df62165656d3e97dbe929" } diff --git a/.sqlx/query-d2921961627fef0e12892dbbcd4b891e58c0e52c20897aab3d95365774c01bda.json b/.sqlx/query-ec12254245aa433f8d31711c2942254d91c61f6b47a5b427c43a8cac468379a3.json similarity index 79% rename from .sqlx/query-d2921961627fef0e12892dbbcd4b891e58c0e52c20897aab3d95365774c01bda.json rename to .sqlx/query-ec12254245aa433f8d31711c2942254d91c61f6b47a5b427c43a8cac468379a3.json index e479b4d..4b996c3 100644 --- a/.sqlx/query-d2921961627fef0e12892dbbcd4b891e58c0e52c20897aab3d95365774c01bda.json +++ b/.sqlx/query-ec12254245aa433f8d31711c2942254d91c61f6b47a5b427c43a8cac468379a3.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n `status` = 'pending' AND\n channels.channel = ? AND\n reminders.enabled >= ?\n ORDER BY\n reminders.utc_time\n ", + "query": "\n SELECT\n reminders.id,\n reminders.uid,\n channels.channel,\n reminders.utc_time,\n reminders.interval_seconds,\n reminders.interval_days,\n reminders.interval_months,\n reminders.expires,\n reminders.enabled,\n reminders.message_json,\n reminders.content,\n reminders.embed_description,\n reminders.set_by\n FROM\n reminders\n INNER JOIN\n channels\n ON\n reminders.channel_id = channels.id\n WHERE\n `status` = 'pending' AND\n channels.channel = ? AND\n reminders.enabled >= ?\n ORDER BY\n reminders.utc_time\n ", "describe": { "columns": [ { @@ -86,6 +86,15 @@ }, { "ordinal": 9, + "name": "message_json", + "type_info": { + "type": "Json", + "flags": "BLOB | BINARY", + "max_size": 4294967295 + } + }, + { + "ordinal": 10, "name": "content", "type_info": { "type": "VarString", @@ -94,7 +103,7 @@ } }, { - "ordinal": 10, + "ordinal": 11, "name": "embed_description", "type_info": { "type": "VarString", @@ -103,7 +112,7 @@ } }, { - "ordinal": 11, + "ordinal": 12, "name": "set_by", "type_info": { "type": "LongLong", @@ -125,10 +134,11 @@ true, true, false, + true, false, false, true ] }, - "hash": "d2921961627fef0e12892dbbcd4b891e58c0e52c20897aab3d95365774c01bda" + "hash": "ec12254245aa433f8d31711c2942254d91c61f6b47a5b427c43a8cac468379a3" } diff --git a/migrations/20261011120000_reminder_message_json.sql b/migrations/20261011120000_reminder_message_json.sql new file mode 100644 index 0000000..6c83a6a --- /dev/null +++ b/migrations/20261011120000_reminder_message_json.sql @@ -0,0 +1 @@ +ALTER TABLE reminders ADD COLUMN message_json JSON DEFAULT NULL; diff --git a/src/main.rs b/src/main.rs index 48ae1c8..a55af9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod event_handlers; #[cfg(not(test))] mod hooks; mod interval_parser; +mod message; mod metrics; #[cfg(not(test))] mod models; diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 0000000..402d554 --- /dev/null +++ b/src/message.rs @@ -0,0 +1,387 @@ +//! Conversion between the dashboard's message fields and Discord webhook payloads. +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use sqlx::{types::Json, FromRow, MySqlConnection}; + +#[derive(Default, Deserialize, Serialize, FromRow)] +#[serde(default)] +struct MessageFields { + content: String, + tts: bool, + username: Option, + avatar: Option, + embed_title: String, + embed_description: String, + embed_author: String, + embed_author_url: Option, + embed_footer: String, + embed_footer_url: Option, + embed_image_url: Option, + embed_thumbnail_url: Option, + embed_color: u32, + embed_fields: Option>, + message_json: Option>, + attachment_name: Option, + has_attachment: bool, +} + +const EMBED_PATHS: &[(&str, &[&str])] = &[ + ("embed_title", &["title"]), + ("embed_description", &["description"]), + ("embed_author", &["author", "name"]), + ("embed_author_url", &["author", "icon_url"]), + ("embed_footer", &["footer", "text"]), + ("embed_footer_url", &["footer", "icon_url"]), + ("embed_image_url", &["image", "url"]), + ("embed_thumbnail_url", &["thumbnail", "url"]), + ("embed_color", &["color"]), + ("embed_fields", &["fields"]), +]; + +fn set_path(target: &mut Value, path: &[&str], value: Value) { + if !target.is_object() { + *target = json!({}); + } + let object = target.as_object_mut().unwrap(); + if path.len() == 1 { + if value.is_null() { + object.remove(path[0]); + } else { + object.insert(path[0].to_string(), value); + } + } else { + set_path(object.entry(path[0]).or_insert_with(|| json!({})), &path[1..], value); + } +} + +fn embed_has_content(embed: &Value) -> bool { + ["/title", "/description", "/author/name", "/footer/text", "/footer/icon_url", + "/image/url", "/thumbnail/url"] + .iter() + .any(|path| embed.pointer(path).and_then(Value::as_str).is_some_and(|s| !s.trim().is_empty())) + || embed["fields"].as_array().is_some_and(|fields| !fields.is_empty()) + // Preserve payload features the dashboard does not edit. + || embed.as_object().is_some_and(|object| object.keys().any(|key| + !["title", "description", "author", "footer", "image", "thumbnail", "color", "fields"].contains(&key.as_str()))) +} + +fn discord_fields(value: &Value) -> Value { + match value.as_array() { + Some(fields) => Value::Array( + fields + .iter() + .map(|field| { + json!({ + "name": field["title"], "value": field["value"], "inline": field["inline"] + }) + }) + .collect(), + ), + None => json!([]), + } +} + +/// Use an explicit payload verbatim, or build one from existing dashboard fields. +pub fn payload(reminder: &impl Serialize) -> Value { + let fields = serde_json::to_value(reminder).expect("Reminder fields are serializable"); + if !fields["message_json"].is_null() { + return fields["message_json"].clone(); + } + let mut payload = json!({ "content": fields["content"], "tts": fields["tts"] }); + if fields["username"].as_str().is_some_and(|s| !s.is_empty()) { + payload["username"] = fields["username"].clone(); + } + if !fields["avatar"].is_null() { + payload["avatar_url"] = fields["avatar"].clone(); + } + let mut embed = json!({}); + for (field, path) in EMBED_PATHS { + let value = if *field == "embed_fields" { + discord_fields(&fields[*field]) + } else { + fields[*field].clone() + }; + set_path(&mut embed, path, value); + } + if embed_has_content(&embed) { + payload["embeds"] = json!([embed]); + } + if (!fields["attachment"].is_null() || fields["has_attachment"].as_bool() == Some(true)) + && fields["attachment_name"].is_string() + { + payload["attachments"] = json!([{ "id": 0, "filename": fields["attachment_name"] }]); + } + payload +} + +/// Present the first embed through the existing dashboard controls. +pub fn response(reminder: impl Serialize) -> Value { + let mut reminder = serde_json::to_value(reminder).expect("Reminder fields are serializable"); + if let Some(reminders) = reminder.as_array_mut() { + for reminder in reminders { + *reminder = response(&*reminder); + } + return reminder; + } + let payload = reminder + .as_object_mut() + .and_then(|object| object.remove("message_json")) + .unwrap_or(Value::Null); + if payload.is_null() { + return reminder; + } + reminder["content"] = payload["content"].as_str().unwrap_or_default().into(); + reminder["tts"] = payload["tts"].as_bool().unwrap_or(false).into(); + reminder["username"] = payload["username"].clone(); + reminder["avatar"] = payload["avatar_url"].clone(); + for (field, path) in EMBED_PATHS { + let value = payload["embeds"][0] + .pointer(&format!("/{}", path.join("/"))) + .cloned() + .unwrap_or(Value::Null); + reminder[*field] = match *field { + "embed_fields" => Value::Array(value.as_array().map(|fields| fields.iter().map(|field| json!({ + "title": field["name"], "value": field["value"], "inline": field["inline"].as_bool().unwrap_or(false) + })).collect()).unwrap_or_default()), + "embed_color" => value.as_u64().unwrap_or(0).into(), + "embed_title" | "embed_description" | "embed_author" | "embed_footer" => value.as_str().unwrap_or_default().into(), + _ => value, + }; + } + reminder +} + +pub fn is_message_field(field: &str) -> bool { + ["content", "tts", "username", "avatar"].contains(&field) + || EMBED_PATHS.iter().any(|(name, _)| *name == field) +} + +fn patch(payload: &mut Value, field: &str, mut value: Value) { + if let Some((_, path)) = EMBED_PATHS.iter().find(|(name, _)| *name == field) { + if !payload["embeds"].is_array() { + payload["embeds"] = json!([]); + } + let embeds = payload["embeds"].as_array_mut().unwrap(); + if embeds.is_empty() { + embeds.push(json!({})); + } + if field == "embed_fields" { + value = discord_fields(&value); + } + set_path(&mut embeds[0], path, value); + if !embed_has_content(&embeds[0]) { + embeds.remove(0); + } + } else { + if field == "username" && value.as_str() == Some("") { + value = Value::Null; + } + set_path(payload, &[if field == "avatar" { "avatar_url" } else { field }], value); + } +} + +/// Convert legacy rows on first edit and preserve unedited payload keys. +pub async fn update_field( + connection: &mut MySqlConnection, + uid: &str, + field: &str, + value: &impl Serialize, +) -> Result<(), sqlx::Error> { + let fields = sqlx::query_as::<_, MessageFields>( + "SELECT content, tts, username, avatar, embed_title, embed_description, + embed_author, embed_author_url, embed_footer, embed_footer_url, embed_image_url, + embed_thumbnail_url, embed_color, embed_fields, message_json, attachment_name, + attachment IS NOT NULL AS has_attachment FROM reminders WHERE uid = ? FOR UPDATE", + ) + .bind(uid) + .fetch_one(&mut *connection) + .await?; + let mut message = payload(&fields); + let value = if field == "attachments" { + if fields.has_attachment { + fields + .attachment_name + .as_ref() + .map(|name| json!([{ "id": 0, "filename": name }])) + .unwrap_or_else(|| json!([])) + } else { + json!([]) + } + } else { + serde_json::to_value(value).map_err(|e| sqlx::Error::Encode(e.into()))? + }; + patch(&mut message, field, value); + sqlx::query("UPDATE reminders SET message_json = ? WHERE uid = ?") + .bind(Json(message)) + .bind(uid) + .execute(connection) + .await?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn builds_discord_payload_and_omits_empty_embed() { + assert_eq!( + payload(&json!({"content": "hello", "tts": true})), + json!({"content": "hello", "tts": true}) + ); + let message = payload(&json!({"content": "", "tts": false, "embed_description": "body", + "embed_fields": [{"title": "heading", "value": "value", "inline": true}], + "attachment": "bytes", "attachment_name": "file.txt"})); + assert_eq!(message["embeds"][0]["fields"][0]["name"], "heading"); + assert_eq!(message["attachments"], json!([{ "id": 0, "filename": "file.txt" }])); + assert_eq!(response(json!({"message_json": message}))["embed_description"], "body"); + } + + #[test] + fn explicit_payload_is_exact() { + let exact = json!({"content": "<>", "flags": 32768, "components": [], "custom": true}); + assert_eq!( + payload(&json!({"message_json": exact, "content": "ignored", "tts": true})), + exact + ); + } + + #[test] + fn null_payload_falls_back_to_legacy_fields() { + let legacy = json!({ + "message_json": null, "content": "legacy", "tts": false, + "username": "Reminder", "avatar": "https://example.com/avatar.png" + }); + + assert_eq!( + payload(&legacy), + json!({ + "content": "legacy", "tts": false, "username": "Reminder", + "avatar_url": "https://example.com/avatar.png" + }) + ); + let mut expected_response = legacy.clone(); + expected_response.as_object_mut().unwrap().remove("message_json"); + assert_eq!(response(legacy), expected_response); + } + + #[test] + fn dashboard_response_uses_payload_and_preserves_schedule() { + let reminder = response(json!({ + "uid": "reminder-id", "utc_time": "2026-10-12T12:00:00", "enabled": true, + "content": "stale", "embed_description": "stale", "tts": false, + "message_json": { + "content": "current", "tts": true, + "embeds": [{"description": "body", "fields": [{"name": "heading", "value": "value"}]}] + } + })); + + assert_eq!(reminder["content"], "current"); + assert_eq!(reminder["tts"], true); + assert_eq!(reminder["embed_description"], "body"); + assert_eq!( + reminder["embed_fields"], + json!([{"title": "heading", "value": "value", "inline": false}]) + ); + assert_eq!(reminder["uid"], "reminder-id"); + assert_eq!(reminder["utc_time"], "2026-10-12T12:00:00"); + assert_eq!(reminder["enabled"], true); + assert!(reminder.get("message_json").is_none()); + } + + #[test] + fn clearing_webhook_identity_preserves_other_payload_keys() { + let mut message = json!({ + "content": "hello", "username": "Reminder", + "avatar_url": "https://example.com/avatar.png", "flags": 4 + }); + + patch(&mut message, "username", json!("")); + patch(&mut message, "avatar", Value::Null); + + assert_eq!(message, json!({"content": "hello", "flags": 4})); + } + + #[test] + fn edits_preserve_extra_payload_features() { + let mut message = json!({"flags": 4, "embeds": [{"description": "before", "url": "https://example.com"}, {"title": "second"}]}); + patch(&mut message, "embed_description", json!("after")); + patch(&mut message, "embed_author_url", json!("https://example.com/icon.png")); + assert_eq!(message["flags"], 4); + assert_eq!(message["embeds"][0]["description"], "after"); + assert_eq!(message["embeds"][0]["url"], "https://example.com"); + assert_eq!(message["embeds"][1]["title"], "second"); + patch(&mut message, "embed_author_url", Value::Null); + assert!(message["embeds"][0]["author"]["icon_url"].is_null()); + } + + #[test] + fn clearing_last_embed_content_removes_embed() { + let mut message = json!({"embeds": [{"description": "body", "color": 123}]}); + patch(&mut message, "embed_description", json!("")); + assert_eq!(message["embeds"], json!([])); + } + + #[tokio::test] + #[ignore = "requires MESSAGE_JSON_TEST_DATABASE_URL pointing to an isolated MySQL database"] + async fn database_edits_convert_legacy_messages_and_preserve_payload() { + let pool = + sqlx::MySqlPool::connect(&std::env::var("MESSAGE_JSON_TEST_DATABASE_URL").unwrap()) + .await + .unwrap(); + let mut transaction = pool.begin().await.unwrap(); + let channel = sqlx::query("INSERT INTO channels (channel) VALUES (?)") + .bind(rand::random::()) + .execute(&mut *transaction) + .await + .unwrap() + .last_insert_id(); + let uid = format!("message-json-test-{}", rand::random::()); + sqlx::query("INSERT INTO reminders (uid, channel_id, `utc_time`, content, embed_description, attachment, attachment_name) VALUES (?, ?, NOW(), 'legacy', 'embed', 'bytes', 'file.txt')") + .bind(&uid).bind(channel).execute(&mut *transaction).await.unwrap(); + update_field(&mut transaction, &uid, "content", &"edited").await.unwrap(); + let stored: (Json, String) = + sqlx::query_as("SELECT message_json, content FROM reminders WHERE uid = ?") + .bind(&uid) + .fetch_one(&mut *transaction) + .await + .unwrap(); + assert_eq!(stored.0["content"], "edited"); + assert_eq!(stored.0["embeds"][0]["description"], "embed"); + assert_eq!(stored.0["attachments"][0]["filename"], "file.txt"); + assert_eq!(stored.1, "legacy"); + let exact = json!({"content": "exact", "custom": [1, 2], "embeds": [{"description": "first"}, {"description": "second"}]}); + sqlx::query("UPDATE reminders SET message_json = ? WHERE uid = ?") + .bind(Json(exact)) + .bind(&uid) + .execute(&mut *transaction) + .await + .unwrap(); + update_field(&mut transaction, &uid, "tts", &true).await.unwrap(); + update_field(&mut transaction, &uid, "embed_description", &"updated").await.unwrap(); + let stored: (Json,) = + sqlx::query_as("SELECT message_json FROM reminders WHERE uid = ?") + .bind(&uid) + .fetch_one(&mut *transaction) + .await + .unwrap(); + assert_eq!(stored.0["tts"], true); + assert_eq!(stored.0["custom"], json!([1, 2])); + assert_eq!(stored.0["embeds"][0]["description"], "updated"); + assert_eq!(stored.0["embeds"][1]["description"], "second"); + sqlx::query("UPDATE reminders SET attachment = NULL WHERE uid = ?") + .bind(&uid) + .execute(&mut *transaction) + .await + .unwrap(); + update_field(&mut transaction, &uid, "attachments", &()).await.unwrap(); + let stored: (Json,) = + sqlx::query_as("SELECT message_json FROM reminders WHERE uid = ?") + .bind(&uid) + .fetch_one(&mut *transaction) + .await + .unwrap(); + assert_eq!(stored.0["attachments"], json!([])); + transaction.rollback().await.unwrap(); + } +} diff --git a/src/models/reminder/builder.rs b/src/models/reminder/builder.rs index 1b78edd..3666ddd 100644 --- a/src/models/reminder/builder.rs +++ b/src/models/reminder/builder.rs @@ -6,7 +6,7 @@ use poise::serenity_prelude::{ model::id::{ChannelId, GuildId, UserId}, ChannelType, }; -use sqlx::MySqlPool; +use sqlx::{types::Json, MySqlPool}; use crate::{ consts::{DAY, MAX_TIME, MIN_INTERVAL}, @@ -76,6 +76,12 @@ impl ReminderBuilder { if utc_time < (Utc::now() - TimeDelta::try_minutes(1).unwrap()).naive_local() { Err(ReminderError::PastTime) } else { + let mut message_json = + serde_json::json!({"content": self.content, "tts": self.tts}); + if let (Some(_), Some(name)) = (&self.attachment, &self.attachment_name) { + message_json["attachments"] = + serde_json::json!([{ "id": 0, "filename": name }]); + } sqlx::query!( " INSERT INTO reminders ( @@ -88,13 +94,12 @@ impl ReminderBuilder { `interval_days`, `interval_months`, `expires`, - `content`, - `tts`, + `message_json`, `attachment_name`, `attachment`, `set_by` ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ", self.uid, @@ -106,8 +111,7 @@ impl ReminderBuilder { self.interval_days, self.interval_months, self.expires, - self.content, - self.tts, + Json(message_json), self.attachment_name, self.attachment, self.set_by diff --git a/src/models/reminder/mod.rs b/src/models/reminder/mod.rs index 6f60fdd..299c6f9 100644 --- a/src/models/reminder/mod.rs +++ b/src/models/reminder/mod.rs @@ -49,6 +49,7 @@ pub struct Reminder { pub interval_months: Option, pub expires: Option, pub enabled: bool, + pub message_json: Option>, pub content: String, pub embed_description: String, pub set_by: Option, @@ -83,6 +84,7 @@ impl Reminder { reminders.interval_months, reminders.expires, reminders.enabled, + reminders.message_json, reminders.content, reminders.embed_description, reminders.set_by @@ -116,6 +118,7 @@ impl Reminder { reminders.interval_months, reminders.expires, reminders.enabled, + reminders.message_json, reminders.content, reminders.embed_description, reminders.set_by @@ -156,6 +159,7 @@ impl Reminder { reminders.interval_months, reminders.expires, reminders.enabled, + reminders.message_json, reminders.content, reminders.embed_description, reminders.set_by @@ -215,6 +219,7 @@ impl Reminder { reminders.interval_months, reminders.expires, reminders.enabled, + reminders.message_json, reminders.content, reminders.embed_description, reminders.set_by @@ -251,6 +256,7 @@ impl Reminder { reminders.interval_months, reminders.expires, reminders.enabled, + reminders.message_json, reminders.content, reminders.embed_description, reminders.set_by @@ -284,6 +290,7 @@ impl Reminder { reminders.interval_months, reminders.expires, reminders.enabled, + reminders.message_json, reminders.content, reminders.embed_description, reminders.set_by @@ -321,6 +328,13 @@ impl Reminder { } pub fn display_content(&self) -> &str { + if let Some(message) = &self.message_json { + let content = message["content"].as_str().unwrap_or_default(); + if !content.is_empty() { + return content; + } + return message["embeds"][0]["description"].as_str().unwrap_or_default(); + } if self.content.is_empty() { &self.embed_description } else { diff --git a/src/postman/sender.rs b/src/postman/sender.rs index f9e2133..10ac1e8 100644 --- a/src/postman/sender.rs +++ b/src/postman/sender.rs @@ -17,6 +17,7 @@ use poise::serenity_prelude::{ Error, Result, }; use regex::{Captures, Regex}; +use secrecy::ExposeSecret; use serde::Deserialize; use sqlx::{ types::{ @@ -341,6 +342,7 @@ pub struct Reminder { tts: bool, pin: bool, content: String, + message_json: Option>, attachment: Option>, attachment_name: Option, @@ -376,6 +378,7 @@ impl Reminder { reminders.`tts` AS tts, reminders.`pin` AS pin, reminders.`content` AS content, + reminders.`message_json` AS message_json, reminders.`attachment` AS attachment, reminders.`attachment_name` AS attachment_name, @@ -585,6 +588,15 @@ impl Reminder { let _ = http.as_ref().pin_message(self.channel_id.into(), message_id.into(), None).await; } + fn files(&self) -> Vec { + match (&self.attachment, &self.attachment_name) { + (Some(attachment), Some(name)) => { + vec![CreateAttachment::bytes(attachment.clone(), name)] + } + _ => vec![], + } + } + pub async fn send( &self, pool: impl Executor<'_, Database = Database> + Copy, @@ -595,6 +607,22 @@ impl Reminder { reminder: &Reminder, embed: Option, ) -> Result<()> { + if let Some(payload) = &reminder.message_json { + let files = reminder.files(); + let message = cache_http + .http() + .send_message( + ChannelId::new(reminder.thread_id.unwrap_or(reminder.channel_id)), + files, + &payload.0, + ) + .await?; + if reminder.pin { + reminder.pin_message(message.id, cache_http.http()).await; + } + return Ok(()); + } + let channel = if let Some(thread_id) = reminder.thread_id { ChannelId::new(thread_id).to_channel(&cache_http).await } else { @@ -650,6 +678,27 @@ impl Reminder { webhook: Webhook, embed: Option, ) -> Result<()> { + if let Some(payload) = &reminder.message_json { + let token = webhook.token.as_ref().ok_or(Error::Other("Webhook token missing"))?; + let message = cache_http + .http() + .execute_webhook( + webhook.id, + reminder.thread_id.map(ChannelId::new), + token.expose_secret(), + reminder.pin || reminder.restartable, + reminder.files(), + &payload.0, + ) + .await?; + if reminder.pin { + if let Some(message) = message { + reminder.pin_message(message.id, cache_http.http()).await; + } + } + return Ok(()); + } + let mut builder = if let Some(thread_id) = reminder.thread_id { ExecuteWebhook::new() .content(&reminder.content) @@ -712,7 +761,11 @@ impl Reminder { .execute(pool) .await; - let embed = Embed::from_id(pool, self.id).await.map(|e| e.into()); + let embed = if self.message_json.is_none() { + Embed::from_id(pool, self.id).await.map(|e| e.into()) + } else { + None + }; let result = if let (Some(webhook_id), Some(webhook_token)) = (self.webhook_id, &self.webhook_token) diff --git a/src/web/guards/transaction.rs b/src/web/guards/transaction.rs index 63a9a72..b32231b 100644 --- a/src/web/guards/transaction.rs +++ b/src/web/guards/transaction.rs @@ -10,7 +10,7 @@ use crate::Database; pub struct Transaction<'a>(sqlx::Transaction<'a, Database>); impl Transaction<'_> { - pub fn executor(&mut self) -> impl sqlx::Executor<'_, Database = Database> { + pub fn executor(&mut self) -> &mut sqlx::MySqlConnection { &mut *(self.0) } diff --git a/src/web/macros.rs b/src/web/macros.rs index eff787f..a92ac12 100644 --- a/src/web/macros.rs +++ b/src/web/macros.rs @@ -57,16 +57,26 @@ macro_rules! check_url_opt { macro_rules! update_field { ($pool:expr, $error:ident, $reminder:ident.[$field:ident]) => { if let Some(value) = &$reminder.$field { - match sqlx::query(concat!( - "UPDATE reminders SET `", - stringify!($field), - "` = ? WHERE uid = ?" - )) - .bind(value) - .bind(&$reminder.uid) - .execute($pool) - .await - { + let result = if crate::message::is_message_field(stringify!($field)) { + crate::message::update_field($pool, &$reminder.uid, stringify!($field), value).await + } else { + let result = sqlx::query(concat!( + "UPDATE reminders SET `", + stringify!($field), + "` = ? WHERE uid = ?" + )) + .bind(value) + .bind(&$reminder.uid) + .execute($pool) + .await + .map(|_| ()); + if result.is_ok() && ["attachment", "attachment_name"].contains(&stringify!($field)) { + crate::message::update_field($pool, &$reminder.uid, "attachments", &()).await + } else { + result + } + }; + match result { Ok(_) => {} Err(e) => { warn!( diff --git a/src/web/routes/dashboard/api/guild/reminders.rs b/src/web/routes/dashboard/api/guild/reminders.rs index 9a44dd7..b307aaa 100644 --- a/src/web/routes/dashboard/api/guild/reminders.rs +++ b/src/web/routes/dashboard/api/guild/reminders.rs @@ -83,6 +83,7 @@ pub async fn get_reminders( reminders.attachment_name, reminders.avatar, channels.channel, + reminders.message_json, reminders.content, reminders.embed_author, reminders.embed_author_url, @@ -121,7 +122,7 @@ pub async fn get_reminders( query .fetch_all(pool.inner()) .await - .map(|reminders| Ok(json!(reminders))) + .map(|reminders| Ok(crate::message::response(reminders))) .unwrap_or_else(|e| { warn!("Failed to complete SQL query: {:?}", e); @@ -349,6 +350,7 @@ pub async fn edit_reminder( reminders.attachment_name, reminders.avatar, channels.channel, + reminders.message_json, reminders.content, reminders.embed_author, reminders.embed_author_url, @@ -379,7 +381,9 @@ pub async fn edit_reminder( .fetch_one(pool.inner()) .await { - Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})), + Ok(reminder) => { + Ok(json!({"reminder": crate::message::response(reminder), "errors": error})) + } Err(e) => { warn!("Error exiting `edit_reminder': {:?}", e); diff --git a/src/web/routes/dashboard/api/user/models.rs b/src/web/routes/dashboard/api/user/models.rs index f6f4fc0..ad68f59 100644 --- a/src/web/routes/dashboard/api/user/models.rs +++ b/src/web/routes/dashboard/api/user/models.rs @@ -24,17 +24,24 @@ use crate::web::{ #[derive(Serialize, Deserialize)] pub struct Reminder { + pub message_json: Option>, pub attachment: Option, pub attachment_name: Option, + #[serde(default)] pub content: String, + #[serde(default)] pub embed_author: String, pub embed_author_url: Option, + #[serde(default)] pub embed_color: u32, + #[serde(default)] pub embed_description: String, + #[serde(default)] pub embed_footer: String, pub embed_footer_url: Option, pub embed_image_url: Option, pub embed_thumbnail_url: Option, + #[serde(default)] pub embed_title: String, pub embed_fields: Option>>, pub enabled: bool, @@ -44,6 +51,7 @@ pub struct Reminder { pub interval_months: Option, #[serde(default = "name_default")] pub name: String, + #[serde(default)] pub tts: bool, #[serde(default)] pub uid: String, @@ -79,6 +87,10 @@ pub async fn create_reminder( let channel = channel.unwrap(); + if reminder.message_json.as_ref().is_some_and(|message| !message.is_object()) { + return Err(json!({"error": "message_json must be an object"})); + } + // validate lengths check_length!(MAX_NAME_LENGTH, reminder.name); check_length!(MAX_CONTENT_LENGTH, reminder.content); @@ -139,55 +151,26 @@ pub async fn create_reminder( let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() }; let new_uid = generate_uid(); + let message_json = Json(crate::message::payload(&reminder)); + // write to db match sqlx::query!( "INSERT INTO reminders ( - uid, - attachment, - attachment_name, - channel_id, - content, - embed_author, - embed_author_url, - embed_color, - embed_description, - embed_footer, - embed_footer_url, - embed_image_url, - embed_thumbnail_url, - embed_title, - embed_fields, - enabled, - expires, - interval_seconds, - interval_days, - interval_months, - name, - tts, - `utc_time` - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + uid, attachment, attachment_name, channel_id, message_json, + enabled, expires, interval_seconds, interval_days, interval_months, + name, `utc_time` + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new_uid, reminder.attachment, reminder.attachment_name, channel, - reminder.content, - reminder.embed_author, - reminder.embed_author_url, - reminder.embed_color, - reminder.embed_description, - reminder.embed_footer, - reminder.embed_footer_url, - reminder.embed_image_url, - reminder.embed_thumbnail_url, - reminder.embed_title, - reminder.embed_fields, + message_json, reminder.enabled, reminder.expires, reminder.interval_seconds, reminder.interval_days, reminder.interval_months, name, - reminder.tts, reminder.utc_time, ) .execute(transaction.executor()) @@ -198,6 +181,7 @@ pub async fn create_reminder( "SELECT reminders.attachment, reminders.attachment_name, + reminders.message_json, reminders.content, reminders.embed_author, reminders.embed_author_url, @@ -224,7 +208,7 @@ pub async fn create_reminder( ) .fetch_one(transaction.executor()) .await - .map(|r| Ok(json!(r))) + .map(|r| Ok(crate::message::response(r))) .unwrap_or_else(|e| { warn!("Failed to complete SQL query: {:?}", e); diff --git a/src/web/routes/dashboard/api/user/reminders.rs b/src/web/routes/dashboard/api/user/reminders.rs index ec663ff..f3d9859 100644 --- a/src/web/routes/dashboard/api/user/reminders.rs +++ b/src/web/routes/dashboard/api/user/reminders.rs @@ -69,6 +69,7 @@ pub async fn get_reminders( SELECT reminders.attachment, reminders.attachment_name, + reminders.message_json, reminders.content, reminders.embed_author, reminders.embed_author_url, @@ -97,7 +98,7 @@ pub async fn get_reminders( ) .fetch_all(pool.inner()) .await - .map(|r| Ok(json!(r))) + .map(|r| Ok(crate::message::response(r))) .unwrap_or_else(|e| { warn!("Failed to complete SQL query: {:?}", e); @@ -246,6 +247,7 @@ pub async fn edit_reminder( " SELECT reminders.attachment, reminders.attachment_name, + reminders.message_json, reminders.content, reminders.embed_author, reminders.embed_author_url, @@ -275,7 +277,9 @@ pub async fn edit_reminder( .fetch_one(pool.inner()) .await { - Ok(reminder) => Ok(json!({"reminder": reminder, "errors": error})), + Ok(reminder) => { + Ok(json!({"reminder": crate::message::response(reminder), "errors": error})) + } Err(e) => { warn!("Error exiting `edit_reminder': {:?}", e); diff --git a/src/web/routes/dashboard/export/reminders.rs b/src/web/routes/dashboard/export/reminders.rs index 680caf1..46380ff 100644 --- a/src/web/routes/dashboard/export/reminders.rs +++ b/src/web/routes/dashboard/export/reminders.rs @@ -48,6 +48,7 @@ pub async fn export( reminders.attachment_name, reminders.avatar, CONCAT('#', channels.channel) AS channel, + CAST(reminders.message_json AS CHAR) AS message_json, reminders.content, reminders.embed_author, reminders.embed_author_url, @@ -150,6 +151,11 @@ pub async fn import( match channel_id.parse::() { Ok(channel_id) => { let reminder = CreateReminder { + message_json: record + .message_json + .map(|s| serde_json::from_str(&s).map(sqlx::types::Json)) + .transpose() + .map_err(|_| json!({"error": "Invalid message_json"}))?, attachment: record.attachment, attachment_name: record.attachment_name, avatar: record.avatar, diff --git a/src/web/routes/dashboard/mod.rs b/src/web/routes/dashboard/mod.rs index 1ee5c74..44138ce 100644 --- a/src/web/routes/dashboard/mod.rs +++ b/src/web/routes/dashboard/mod.rs @@ -148,22 +148,29 @@ pub struct EmbedField { inline: bool, } -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] pub struct CreateReminder { + message_json: Option>, attachment: Option, attachment_name: Option, avatar: Option, #[serde(with = "string")] channel: u64, + #[serde(default)] content: String, + #[serde(default)] embed_author: String, embed_author_url: Option, + #[serde(default)] embed_color: u32, + #[serde(default)] embed_description: String, + #[serde(default)] embed_footer: String, embed_footer_url: Option, embed_image_url: Option, embed_thumbnail_url: Option, + #[serde(default)] embed_title: String, embed_fields: Option>>, enabled: bool, @@ -174,6 +181,7 @@ pub struct CreateReminder { #[serde(default = "name_default")] name: String, restartable: bool, + #[serde(default)] tts: bool, username: Option, utc_time: NaiveDateTime, @@ -181,6 +189,7 @@ pub struct CreateReminder { #[derive(Serialize, FromRow)] pub struct GetReminder { + message_json: Option>, attachment_name: Option, avatar: Option, #[serde(with = "string")] @@ -213,6 +222,8 @@ pub struct GetReminder { #[derive(Serialize, Deserialize, FromRow)] pub struct ReminderCsv { + #[serde(default)] + message_json: Option, attachment: Option, attachment_name: Option, avatar: Option, @@ -422,6 +433,10 @@ pub(crate) async fn create_reminder( let channel = channel.unwrap(); + if reminder.message_json.as_ref().is_some_and(|message| !message.is_object()) { + return Err(json!({"error": "message_json must be an object"})); + } + // validate lengths check_length!(MAX_NAME_LENGTH, reminder.name); check_length!(MAX_CONTENT_LENGTH, reminder.content); @@ -483,60 +498,22 @@ pub(crate) async fn create_reminder( } let name = if reminder.name.is_empty() { name_default() } else { reminder.name.clone() }; - let username = if reminder.username.as_ref().map(|s| s.is_empty()).unwrap_or(true) { - None - } else { - reminder.username - }; + let message_json = Json(crate::message::payload(&reminder)); let new_uid = generate_uid(); // write to db match sqlx::query!( "INSERT INTO reminders ( - uid, - attachment, - attachment_name, - channel_id, - avatar, - content, - embed_author, - embed_author_url, - embed_color, - embed_description, - embed_footer, - embed_footer_url, - embed_image_url, - embed_thumbnail_url, - embed_title, - embed_fields, - enabled, - expires, - interval_seconds, - interval_days, - interval_months, - name, - restartable, - tts, - username, - `utc_time` - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + uid, attachment, attachment_name, channel_id, message_json, + enabled, expires, interval_seconds, interval_days, interval_months, + name, restartable, `utc_time` + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new_uid, reminder.attachment, reminder.attachment_name, channel, - reminder.avatar, - reminder.content, - reminder.embed_author, - reminder.embed_author_url, - reminder.embed_color, - reminder.embed_description, - reminder.embed_footer, - reminder.embed_footer_url, - reminder.embed_image_url, - reminder.embed_thumbnail_url, - reminder.embed_title, - reminder.embed_fields, + message_json, reminder.enabled, reminder.expires, reminder.interval_seconds, @@ -544,8 +521,6 @@ pub(crate) async fn create_reminder( reminder.interval_months, name, reminder.restartable, - reminder.tts, - username, reminder.utc_time, ) .execute(transaction.executor()) @@ -558,6 +533,7 @@ pub(crate) async fn create_reminder( reminders.attachment_name, reminders.avatar, channels.channel, + reminders.message_json, reminders.content, reminders.embed_author, reminders.embed_author_url, @@ -587,7 +563,7 @@ pub(crate) async fn create_reminder( ) .fetch_one(transaction.executor()) .await - .map(|r| Ok(json!(r))) + .map(|r| Ok(crate::message::response(r))) .unwrap_or_else(|e| { warn!("Failed to complete SQL query: {:?}", e);