diff --git a/api/messaging/spec.json b/api/messaging/spec.json index 0e27c011b..49ee7dd5e 100644 --- a/api/messaging/spec.json +++ b/api/messaging/spec.json @@ -472,7 +472,7 @@ "title": "Manages read/unread messages in a channel or a thread", "parameters": { "path": [], - "post": [ + "get": [ { "type": "uint64", "name": "threadID", diff --git a/api/messaging/spec/message.json b/api/messaging/spec/message.json index 7a91bd5d1..077017bf6 100644 --- a/api/messaging/spec/message.json +++ b/api/messaging/spec/message.json @@ -72,8 +72,7 @@ "Title": "Manages read/unread messages in a channel or a thread", "Path": "/mark-as-read", "Parameters": { - "path": [], - "post": [ + "get": [ { "name": "threadID", "required": false, @@ -86,7 +85,8 @@ "title": "ID of the last read message", "type": "uint64" } - ] + ], + "path": [] } }, { diff --git a/docs/messaging/README.md b/docs/messaging/README.md index 4f5703091..de3575f5c 100644 --- a/docs/messaging/README.md +++ b/docs/messaging/README.md @@ -363,9 +363,9 @@ A channel is a representation of a sequence of messages. It has meta data like c | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | +| threadID | uint64 | GET | ID of thread (messageID) | N/A | NO | +| lastReadMessageID | uint64 | GET | ID of the last read message | N/A | NO | | channelID | uint64 | PATH | Channel ID | N/A | YES | -| threadID | uint64 | POST | ID of thread (messageID) | N/A | NO | -| lastReadMessageID | uint64 | POST | ID of the last read message | N/A | NO | ## Edit existing message diff --git a/internal/payload/outgoing.go b/internal/payload/outgoing.go index b15f17fe7..2218d8fdc 100644 --- a/internal/payload/outgoing.go +++ b/internal/payload/outgoing.go @@ -40,6 +40,7 @@ func Message(ctx context.Context, msg *messagingTypes.Message) *outgoing.Message ReplyTo: msg.ReplyTo, Replies: msg.Replies, RepliesFrom: Uint64stoa(msg.RepliesFrom), + Unread: Unread(msg.Unread), Attachment: Attachment(msg.Attachment, currentUserID), Mentions: messageMentionSet(msg.Mentions), @@ -198,6 +199,7 @@ func Unread(v *messagingTypes.Unread) *outgoing.Unread { return &outgoing.Unread{ LastMessageID: v.LastMessageID, Count: v.Count, + InThreadCount: v.InThreadCount, } } diff --git a/internal/payload/outgoing/message.go b/internal/payload/outgoing/message.go index 5867d97e1..8b213b442 100644 --- a/internal/payload/outgoing/message.go +++ b/internal/payload/outgoing/message.go @@ -16,6 +16,7 @@ type ( ReplyTo uint64 `json:"replyTo,omitempty,string"` Replies uint `json:"replies,omitempty"` RepliesFrom []string `json:"repliesFrom,omitempty"` + Unread *Unread `json:"unread,omitempty"` Attachment *Attachment `json:"att,omitempty"` Mentions MessageMentionSet `json:"mentions,omitempty"` diff --git a/internal/payload/outgoing/unread.go b/internal/payload/outgoing/unread.go index 4da9f99df..a9d904041 100644 --- a/internal/payload/outgoing/unread.go +++ b/internal/payload/outgoing/unread.go @@ -5,5 +5,6 @@ type ( // Channel to part (nil) for ALL channels LastMessageID uint64 `json:"lastMessageID,string,omitempty"` Count uint32 `json:"count"` + InThreadCount uint32 `json:"tcount,omitempty"` } ) diff --git a/messaging/db/mysql/static.go b/messaging/db/mysql/static.go index 04ea26ba5..a15b7b8d2 100644 --- a/messaging/db/mysql/static.go +++ b/messaging/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known channels\nCREATE TABLE channels (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the channel\n topic TEXT NOT NULL,\n meta JSON NOT NULL,\n\n type ENUM ('private', 'public', 'group') NOT NULL DEFAULT 'public',\n\n rel_organisation BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_creator BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- channel soft delete\n\n rel_last_message BIGINT UNSIGNED NOT NULL DEFAULT 0,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- handles channel membership\nCREATE TABLE channel_members (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n type ENUM ('owner', 'member', 'invitee') NOT NULL DEFAULT 'member',\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n\n PRIMARY KEY (rel_channel, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE channel_views (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n -- timestamp of last view, should be enough to find out which messaghr\n viewed_at DATETIME NOT NULL DEFAULT NOW(),\n\n -- new messages count since last view\n new_since INT UNSIGNED NOT NULL DEFAULT 0,\n\n PRIMARY KEY (rel_user, rel_channel)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE channel_pins (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (rel_channel, rel_message)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE messages (\n id BIGINT UNSIGNED NOT NULL,\n type TEXT,\n message TEXT NOT NULL,\n meta JSON,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n reply_to BIGINT UNSIGNED NULL REFERENCES messages(id),\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE reactions (\n id BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n reaction TEXT NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE attachments (\n id BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE message_attachment (\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_attachment BIGINT UNSIGNED NOT NULL REFERENCES attachment(id),\n\n PRIMARY KEY (rel_message)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE event_queue (\n id BIGINT UNSIGNED NOT NULL,\n origin BIGINT UNSIGNED NOT NULL,\n subscriber TEXT,\n payload JSON,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE event_queue_synced (\n origin BIGINT UNSIGNED NOT NULL,\n rel_last BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (origin)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xd5\x9c\xef\x89V\x10\x00\x00V\x10\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181009080000.altering_types.up.sqlUT\x05\x00\x01\x80Cm8update channels set type = 'group' where type = 'direct';\nalter table channels CHANGE type type enum('private', 'public', 'group');\nalter table channel_members CHANGE type type enum('owner', 'member', 'invitee');\nPK\x07\x08E1\xf5\xa4\xd7\x00\x00\x00\xd7\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181013080000.channel_views.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE channel_views DROP viewed_at;\nALTER TABLE channel_views ADD rel_last_message_id BIGINT UNSIGNED;\nALTER TABLE channel_views CHANGE new_since new_messages_count INT UNSIGNED;\n\n-- Table structure after these changes:\n-- +---------------------+---------------------+------+-----+---------+-------+\n-- | Field | Type | Null | Key | Default | Extra |\n-- +---------------------+---------------------+------+-----+---------+-------+\n-- | rel_channel | bigint(20) unsigned | NO | PRI | NULL | |\n-- | rel_user | bigint(20) unsigned | NO | PRI | NULL | |\n-- | rel_last_message_id | bigint(20) unsigned | YES | | NULL | |\n-- | new_messages_count | int(10) unsigned | NO | | 0 | |\n-- +---------------------+---------------------+------+-----+---------+-------+\n\n-- Prefill with data\nINSERT INTO channel_views (rel_channel, rel_user, rel_last_message_id)\n SELECT cm.rel_channel, cm.rel_user, max(m.ID)\n FROM channel_members AS cm INNER JOIN messages AS m ON (m.rel_channel = cm.rel_channel)\n GROUP BY cm.rel_channel, cm.rel_user;\n\nPK\x07\x08`\xcbP\xf9t\x04\x00\x00t\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x00 \x0020181013080000.replies.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE messages CHANGE reply_to reply_to BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE messages ADD replies INT UNSIGNED NOT NULL DEFAULT 0;\nPK\x07\x08m\xedWA\x94\x00\x00\x00\x94\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\x00 \x0020181101080000.pins_and_reactions.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE channel_pins;\nDROP TABLE reactions;\n\nCREATE TABLE message_flags (\n id BIGINT UNSIGNED NOT NULL,\n rel_channel BIGINT UNSIGNED NOT NULL,\n rel_message BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n flag TEXT,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08eA\x1eo\x90\x01\x00\x00\x90\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00 \x0020181107080000.mentions.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE mentions (\n id BIGINT UNSIGNED NOT NULL,\n rel_channel BIGINT UNSIGNED NOT NULL,\n rel_message BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_mentioned_by BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX lookup_mentions ON mentions (rel_mentioned_by)\nPK\x07\x08\xfb\xe8\x9b\x98\xac\x01\x00\x00\xac\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x00 \x0020181115080000.unreads.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE channel_views RENAME TO unreads;\n\nALTER TABLE unreads ADD rel_reply_to BIGINT UNSIGNED NOT NULL AFTER rel_channel;\nALTER TABLE unreads CHANGE rel_channel rel_channel BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE unreads CHANGE rel_user rel_user BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE unreads CHANGE rel_last_message_id rel_last_message BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE unreads CHANGE new_messages_count count INT UNSIGNED NOT NULL DEFAULT 0;\n\nPK\x07\x08jf1Q+\x02\x00\x00+\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*\x00 \x0020181124173028.remove_events_tables.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE event_queue;\nDROP TABLE event_queue_synced;PK\x07\x08\xdd.y06\x00\x00\x006\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020181205153145.messages-to-utf8mb4.up.sqlUT\x05\x00\x01\x80Cm8alter table messages convert to character set utf8mb4 collate utf8mb4_unicode_ci;PK\x07\x08Ig\xbfOQ\x00\x00\x00Q\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190122191150.membership-flags.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE channel_members ADD flag ENUM ('pinned', 'hidden', 'ignored', '') NOT NULL DEFAULT '' AFTER `type`;\nPK\x07\x084\xfb\xe3\xf4p\x00\x00\x00p\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190206112022.prefix-tables.up.sqlUT\x05\x00\x01\x80Cm8-- misc tables\n\nALTER TABLE attachments RENAME TO messaging_attachment;\nALTER TABLE mentions RENAME TO messaging_mention;\nALTER TABLE unreads RENAME TO messaging_unread;\n\n-- channel tables\n\nALTER TABLE channels RENAME TO messaging_channel;\nALTER TABLE channel_members RENAME TO messaging_channel_member;\n\n-- message tables\n\nALTER TABLE messages RENAME TO messaging_message;\nALTER TABLE message_attachment RENAME TO messaging_message_attachment;\nALTER TABLE message_flags RENAME TO messaging_message_flag;\nPK\x07\x08\x145\xde}Q\x02\x00\x00Q\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190326181923.webhook-table.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `messaging_webhook` (\n `id` bigint(20) unsigned NOT NULL,\n `kind` varchar(8) NOT NULL COMMENT 'Kind: incoming, outgoing',\n `token` varchar(255) NOT NULL COMMENT 'Authentication token',\n `rel_owner` bigint(20) unsigned NOT NULL COMMENT 'Webhook owner User ID',\n `rel_user` bigint(20) unsigned NOT NULL COMMENT 'Webhook message User ID',\n `rel_channel` bigint(20) unsigned NOT NULL COMMENT 'Channel ID',\n `outgoing_trigger` varchar(32) NOT NULL COMMENT 'Outgoing command trigger',\n `outgoing_url` varchar(255) NOT NULL COMMENT 'URL for POST request',\n `created_at` datetime NOT NULL,\n `updated_at` datetime NULL,\n `deleted_at` datetime NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- get webhook by command trigger\nALTER TABLE `messaging_webhook` ADD UNIQUE(`outgoing_trigger`);\n\n-- list webhooks by owner (list your own webhooks)\nALTER TABLE `messaging_webhook` ADD INDEX(`rel_owner`);\n\n-- list webhooks on a channel\nALTER TABLE `messaging_webhook` ADD INDEX(`rel_channel`);\nPK\x07\x08\x16\x95.\xf3\xf7\x03\x00\x00\xf7\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS messaging_permission_rules (\n rel_role BIGINT UNSIGNED NOT NULL,\n resource VARCHAR(128) NOT NULL,\n operation VARCHAR(128) NOT NULL,\n access TINYINT(1) NOT NULL,\n\n PRIMARY KEY (rel_role, resource, operation)\n) ENGINE=InnoDB;\nPK\x07\x08\xf0d&V\x14\x01\x00\x00\x14\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd5\x9c\xef\x89V\x10\x00\x00V\x10\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(E1\xf5\xa4\xd7\x00\x00\x00\xd7\x00\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xa7\x10\x00\x0020181009080000.altering_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(`\xcbP\xf9t\x04\x00\x00t\x04\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd9\x11\x00\x0020181013080000.channel_views.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xedWA\x94\x00\x00\x00\x94\x00\x00\x00\x1d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xa7\x16\x00\x0020181013080000.replies.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(eA\x1eo\x90\x01\x00\x00\x90\x01\x00\x00(\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8f\x17\x00\x0020181101080000.pins_and_reactions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xfb\xe8\x9b\x98\xac\x01\x00\x00\xac\x01\x00\x00\x1e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81~\x19\x00\x0020181107080000.mentions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(jf1Q+\x02\x00\x00+\x02\x00\x00\x1d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x7f\x1b\x00\x0020181115080000.unreads.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xdd.y06\x00\x00\x006\x00\x00\x00*\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xfe\x1d\x00\x0020181124173028.remove_events_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(Ig\xbfOQ\x00\x00\x00Q\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x95\x1e\x00\x0020181205153145.messages-to-utf8mb4.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(4\xfb\xe3\xf4p\x00\x00\x00p\x00\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81F\x1f\x00\x0020190122191150.membership-flags.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x145\xde}Q\x02\x00\x00Q\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x13 \x00\x0020190206112022.prefix-tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x16\x95.\xf3\xf7\x03\x00\x00\xf7\x03\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe\"\x00\x0020190326181923.webhook-table.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf0d&V\x14\x01\x00\x00\x14\x01\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x0f'\x00\x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81{(\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x818*\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x0f\x00\x0f\x00\x0e\x05\x00\x00\xa3*\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known channels\nCREATE TABLE channels (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the channel\n topic TEXT NOT NULL,\n meta JSON NOT NULL,\n\n type ENUM ('private', 'public', 'group') NOT NULL DEFAULT 'public',\n\n rel_organisation BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_creator BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- channel soft delete\n\n rel_last_message BIGINT UNSIGNED NOT NULL DEFAULT 0,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- handles channel membership\nCREATE TABLE channel_members (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n type ENUM ('owner', 'member', 'invitee') NOT NULL DEFAULT 'member',\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n\n PRIMARY KEY (rel_channel, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE channel_views (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n -- timestamp of last view, should be enough to find out which messaghr\n viewed_at DATETIME NOT NULL DEFAULT NOW(),\n\n -- new messages count since last view\n new_since INT UNSIGNED NOT NULL DEFAULT 0,\n\n PRIMARY KEY (rel_user, rel_channel)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE channel_pins (\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (rel_channel, rel_message)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE messages (\n id BIGINT UNSIGNED NOT NULL,\n type TEXT,\n message TEXT NOT NULL,\n meta JSON,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n reply_to BIGINT UNSIGNED NULL REFERENCES messages(id),\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE reactions (\n id BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_channel BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n reaction TEXT NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE attachments (\n id BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE message_attachment (\n rel_message BIGINT UNSIGNED NOT NULL REFERENCES messages(id),\n rel_attachment BIGINT UNSIGNED NOT NULL REFERENCES attachment(id),\n\n PRIMARY KEY (rel_message)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE event_queue (\n id BIGINT UNSIGNED NOT NULL,\n origin BIGINT UNSIGNED NOT NULL,\n subscriber TEXT,\n payload JSON,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE event_queue_synced (\n origin BIGINT UNSIGNED NOT NULL,\n rel_last BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (origin)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xd5\x9c\xef\x89V\x10\x00\x00V\x10\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181009080000.altering_types.up.sqlUT\x05\x00\x01\x80Cm8update channels set type = 'group' where type = 'direct';\nalter table channels CHANGE type type enum('private', 'public', 'group');\nalter table channel_members CHANGE type type enum('owner', 'member', 'invitee');\nPK\x07\x08E1\xf5\xa4\xd7\x00\x00\x00\xd7\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181013080000.channel_views.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE channel_views DROP viewed_at;\nALTER TABLE channel_views ADD rel_last_message_id BIGINT UNSIGNED;\nALTER TABLE channel_views CHANGE new_since new_messages_count INT UNSIGNED;\n\n-- Table structure after these changes:\n-- +---------------------+---------------------+------+-----+---------+-------+\n-- | Field | Type | Null | Key | Default | Extra |\n-- +---------------------+---------------------+------+-----+---------+-------+\n-- | rel_channel | bigint(20) unsigned | NO | PRI | NULL | |\n-- | rel_user | bigint(20) unsigned | NO | PRI | NULL | |\n-- | rel_last_message_id | bigint(20) unsigned | YES | | NULL | |\n-- | new_messages_count | int(10) unsigned | NO | | 0 | |\n-- +---------------------+---------------------+------+-----+---------+-------+\n\n-- Prefill with data\nINSERT INTO channel_views (rel_channel, rel_user, rel_last_message_id)\n SELECT cm.rel_channel, cm.rel_user, max(m.ID)\n FROM channel_members AS cm INNER JOIN messages AS m ON (m.rel_channel = cm.rel_channel)\n GROUP BY cm.rel_channel, cm.rel_user;\n\nPK\x07\x08`\xcbP\xf9t\x04\x00\x00t\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x00 \x0020181013080000.replies.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE messages CHANGE reply_to reply_to BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE messages ADD replies INT UNSIGNED NOT NULL DEFAULT 0;\nPK\x07\x08m\xedWA\x94\x00\x00\x00\x94\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\x00 \x0020181101080000.pins_and_reactions.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE channel_pins;\nDROP TABLE reactions;\n\nCREATE TABLE message_flags (\n id BIGINT UNSIGNED NOT NULL,\n rel_channel BIGINT UNSIGNED NOT NULL,\n rel_message BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n flag TEXT,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08eA\x1eo\x90\x01\x00\x00\x90\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00 \x0020181107080000.mentions.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE mentions (\n id BIGINT UNSIGNED NOT NULL,\n rel_channel BIGINT UNSIGNED NOT NULL,\n rel_message BIGINT UNSIGNED NOT NULL,\n rel_user BIGINT UNSIGNED NOT NULL,\n rel_mentioned_by BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX lookup_mentions ON mentions (rel_mentioned_by)\nPK\x07\x08\xfb\xe8\x9b\x98\xac\x01\x00\x00\xac\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x00 \x0020181115080000.unreads.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE channel_views RENAME TO unreads;\n\nALTER TABLE unreads ADD rel_reply_to BIGINT UNSIGNED NOT NULL AFTER rel_channel;\nALTER TABLE unreads CHANGE rel_channel rel_channel BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE unreads CHANGE rel_user rel_user BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE unreads CHANGE rel_last_message_id rel_last_message BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE unreads CHANGE new_messages_count count INT UNSIGNED NOT NULL DEFAULT 0;\n\nPK\x07\x08jf1Q+\x02\x00\x00+\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*\x00 \x0020181124173028.remove_events_tables.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE event_queue;\nDROP TABLE event_queue_synced;PK\x07\x08\xdd.y06\x00\x00\x006\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020181205153145.messages-to-utf8mb4.up.sqlUT\x05\x00\x01\x80Cm8alter table messages convert to character set utf8mb4 collate utf8mb4_unicode_ci;PK\x07\x08Ig\xbfOQ\x00\x00\x00Q\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190122191150.membership-flags.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE channel_members ADD flag ENUM ('pinned', 'hidden', 'ignored', '') NOT NULL DEFAULT '' AFTER `type`;\nPK\x07\x084\xfb\xe3\xf4p\x00\x00\x00p\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190206112022.prefix-tables.up.sqlUT\x05\x00\x01\x80Cm8-- misc tables\n\nALTER TABLE attachments RENAME TO messaging_attachment;\nALTER TABLE mentions RENAME TO messaging_mention;\nALTER TABLE unreads RENAME TO messaging_unread;\n\n-- channel tables\n\nALTER TABLE channels RENAME TO messaging_channel;\nALTER TABLE channel_members RENAME TO messaging_channel_member;\n\n-- message tables\n\nALTER TABLE messages RENAME TO messaging_message;\nALTER TABLE message_attachment RENAME TO messaging_message_attachment;\nALTER TABLE message_flags RENAME TO messaging_message_flag;\nPK\x07\x08\x145\xde}Q\x02\x00\x00Q\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190326181923.webhook-table.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `messaging_webhook` (\n `id` bigint(20) unsigned NOT NULL,\n `kind` varchar(8) NOT NULL COMMENT 'Kind: incoming, outgoing',\n `token` varchar(255) NOT NULL COMMENT 'Authentication token',\n `rel_owner` bigint(20) unsigned NOT NULL COMMENT 'Webhook owner User ID',\n `rel_user` bigint(20) unsigned NOT NULL COMMENT 'Webhook message User ID',\n `rel_channel` bigint(20) unsigned NOT NULL COMMENT 'Channel ID',\n `outgoing_trigger` varchar(32) NOT NULL COMMENT 'Outgoing command trigger',\n `outgoing_url` varchar(255) NOT NULL COMMENT 'URL for POST request',\n `created_at` datetime NOT NULL,\n `updated_at` datetime NULL,\n `deleted_at` datetime NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- get webhook by command trigger\nALTER TABLE `messaging_webhook` ADD UNIQUE(`outgoing_trigger`);\n\n-- list webhooks by owner (list your own webhooks)\nALTER TABLE `messaging_webhook` ADD INDEX(`rel_owner`);\n\n-- list webhooks on a channel\nALTER TABLE `messaging_webhook` ADD INDEX(`rel_channel`);\nPK\x07\x08\x16\x95.\xf3\xf7\x03\x00\x00\xf7\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS messaging_permission_rules (\n rel_role BIGINT UNSIGNED NOT NULL,\n resource VARCHAR(128) NOT NULL,\n operation VARCHAR(128) NOT NULL,\n access TINYINT(1) NOT NULL,\n\n PRIMARY KEY (rel_role, resource, operation)\n) ENGINE=InnoDB;\nPK\x07\x08\xf0d&V\x14\x01\x00\x00\x14\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x00 \x0020190623080000.unreads.up.sqlUT\x05\x00\x01\x80Cm8UPDATE `messaging_unread` SET rel_reply_to = 0 WHERE rel_reply_to IS NULL;\nALTER TABLE `messaging_unread` CHANGE COLUMN `rel_reply_to` `rel_reply_to` BIGINT UNSIGNED NOT NULL;\nALTER TABLE `messaging_unread` DROP PRIMARY KEY, ADD PRIMARY KEY(`rel_channel`, `rel_reply_to`, `rel_user`);\n\n-- Add entries for all (unexisting) unreads (channels & threads)\nINSERT IGNORE INTO messaging_unread\n (rel_channel, rel_reply_to, rel_user)\nSELECT DISTINCT cm.rel_channel, msg.id, cm.rel_user\n FROM messaging_channel_member AS cm\n INNER JOIN messaging_message AS msg ON (cm.rel_channel = msg.rel_channel AND replies > 0)\n WHERE NOT EXISTS (SELECT 1 FROM messaging_unread AS u WHERE u.rel_reply_to = msg.id AND u.rel_user = cm.rel_user)\n AND msg.rel_user > 0\n\nUNION\n\nSELECT DISTINCT cm.rel_channel, 0, cm.rel_user\n FROM messaging_channel_member AS cm\n WHERE NOT EXISTS (SELECT 1 FROM messaging_unread AS u WHERE u.rel_channel = cm.rel_channel AND u.rel_user = cm.rel_user)\n AND cm.rel_user > 0\n;\n\n\n-- Update counters for channel messages\nINSERT IGNORE INTO messaging_unread\n (rel_channel, rel_reply_to, rel_user, count, rel_last_message)\nSELECT u.rel_channel, 0, u.rel_user, COUNT(m.id), u.rel_last_message\n FROM messaging_unread AS u\n INNER JOIN messaging_message AS m ON (u.rel_channel = m.rel_channel AND m.id > u.rel_last_message)\n WHERE u.rel_reply_to = 0\n AND m.reply_to = 0\n GROUP BY u.rel_channel, u.rel_user;\n\n-- Update counters for thread messages\n\nINSERT IGNORE INTO messaging_unread\n (rel_channel, rel_reply_to, rel_user, count, rel_last_message)\nSELECT u.rel_channel, rpl.reply_to, u.rel_user, COUNT(rpl.id), u.rel_last_message\n FROM messaging_unread AS u\n INNER JOIN messaging_message AS rpl ON (u.rel_channel = rpl.rel_channel AND rpl.reply_to = u.rel_reply_to AND rpl.id > u.rel_last_message)\n WHERE rpl.replies > 0 AND u.rel_reply_to > 0\n GROUP BY u.rel_channel, rpl.reply_to, u.rel_user;\nPK\x07\x08\xa3(M\xda\xa1\x07\x00\x00\xa1\x07\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd5\x9c\xef\x89V\x10\x00\x00V\x10\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(E1\xf5\xa4\xd7\x00\x00\x00\xd7\x00\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xa7\x10\x00\x0020181009080000.altering_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(`\xcbP\xf9t\x04\x00\x00t\x04\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd9\x11\x00\x0020181013080000.channel_views.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xedWA\x94\x00\x00\x00\x94\x00\x00\x00\x1d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xa7\x16\x00\x0020181013080000.replies.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(eA\x1eo\x90\x01\x00\x00\x90\x01\x00\x00(\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8f\x17\x00\x0020181101080000.pins_and_reactions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xfb\xe8\x9b\x98\xac\x01\x00\x00\xac\x01\x00\x00\x1e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81~\x19\x00\x0020181107080000.mentions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(jf1Q+\x02\x00\x00+\x02\x00\x00\x1d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x7f\x1b\x00\x0020181115080000.unreads.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xdd.y06\x00\x00\x006\x00\x00\x00*\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xfe\x1d\x00\x0020181124173028.remove_events_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(Ig\xbfOQ\x00\x00\x00Q\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x95\x1e\x00\x0020181205153145.messages-to-utf8mb4.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(4\xfb\xe3\xf4p\x00\x00\x00p\x00\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81F\x1f\x00\x0020190122191150.membership-flags.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x145\xde}Q\x02\x00\x00Q\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x13 \x00\x0020190206112022.prefix-tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x16\x95.\xf3\xf7\x03\x00\x00\xf7\x03\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe\"\x00\x0020190326181923.webhook-table.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf0d&V\x14\x01\x00\x00\x14\x01\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x0f'\x00\x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa3(M\xda\xa1\x07\x00\x00\xa1\x07\x00\x00\x1d\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81{(\x00\x0020190623080000.unreads.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81p0\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81-2\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x10\x00\x10\x00b\x05\x00\x00\x982\x00\x00\x00\x00" diff --git a/messaging/db/schema/mysql/20190623080000.unreads.up.sql b/messaging/db/schema/mysql/20190623080000.unreads.up.sql new file mode 100644 index 000000000..17a6e08ff --- /dev/null +++ b/messaging/db/schema/mysql/20190623080000.unreads.up.sql @@ -0,0 +1,41 @@ +UPDATE `messaging_unread` SET rel_reply_to = 0 WHERE rel_reply_to IS NULL; +ALTER TABLE `messaging_unread` CHANGE COLUMN `rel_reply_to` `rel_reply_to` BIGINT UNSIGNED NOT NULL; +ALTER TABLE `messaging_unread` DROP PRIMARY KEY, ADD PRIMARY KEY(`rel_channel`, `rel_reply_to`, `rel_user`); + +-- Add entries for all (unexisting) unreads (channels & threads) +INSERT IGNORE INTO messaging_unread + (rel_channel, rel_reply_to, rel_user) +SELECT DISTINCT cm.rel_channel, msg.id, cm.rel_user + FROM messaging_channel_member AS cm + INNER JOIN messaging_message AS msg ON (cm.rel_channel = msg.rel_channel AND replies > 0) + WHERE NOT EXISTS (SELECT 1 FROM messaging_unread AS u WHERE u.rel_reply_to = msg.id AND u.rel_user = cm.rel_user) + AND msg.rel_user > 0 + +UNION + +SELECT DISTINCT cm.rel_channel, 0, cm.rel_user + FROM messaging_channel_member AS cm + WHERE NOT EXISTS (SELECT 1 FROM messaging_unread AS u WHERE u.rel_channel = cm.rel_channel AND u.rel_user = cm.rel_user) + AND cm.rel_user > 0 +; + + +-- Update counters for channel messages +INSERT IGNORE INTO messaging_unread + (rel_channel, rel_reply_to, rel_user, count, rel_last_message) +SELECT u.rel_channel, 0, u.rel_user, COUNT(m.id), u.rel_last_message + FROM messaging_unread AS u + INNER JOIN messaging_message AS m ON (u.rel_channel = m.rel_channel AND m.id > u.rel_last_message) + WHERE u.rel_reply_to = 0 + AND m.reply_to = 0 + GROUP BY u.rel_channel, u.rel_user; + +-- Update counters for thread messages + +INSERT IGNORE INTO messaging_unread + (rel_channel, rel_reply_to, rel_user, count, rel_last_message) +SELECT u.rel_channel, rpl.reply_to, u.rel_user, COUNT(rpl.id), u.rel_last_message + FROM messaging_unread AS u + INNER JOIN messaging_message AS rpl ON (u.rel_channel = rpl.rel_channel AND rpl.reply_to = u.rel_reply_to AND rpl.id > u.rel_last_message) + WHERE rpl.replies > 0 AND u.rel_reply_to > 0 + GROUP BY u.rel_channel, rpl.reply_to, u.rel_user; diff --git a/messaging/internal/repository/message.go b/messaging/internal/repository/message.go index 5555c09b1..4a06def1c 100644 --- a/messaging/internal/repository/message.go +++ b/messaging/internal/repository/message.go @@ -21,6 +21,7 @@ type ( Find(filter *types.MessageFilter) (types.MessageSet, error) FindThreads(filter *types.MessageFilter) (types.MessageSet, error) CountFromMessageID(channelID, threadID, messageID uint64) (uint32, error) + LastMessageID(channelID, threadID uint64) (uint64, error) PrefillThreadParticipants(mm types.MessageSet) error Create(mod *types.Message) (*types.Message, error) @@ -88,6 +89,13 @@ const ( "AND COALESCE(type, '') NOT IN (?) " + "AND id > ? AND deleted_at IS NULL" + sqlLastMessageID = "SELECT COALESCE(MAX(id), 0) AS last " + + "FROM messaging_message " + + "WHERE rel_channel = ? " + + "AND reply_to = ? " + + "AND COALESCE(type, '') NOT IN (?) " + + "AND deleted_at IS NULL" + sqlMessageRepliesIncCount = `UPDATE messaging_message SET replies = replies + 1 WHERE id = ? AND reply_to = 0` sqlMessageRepliesDecCount = `UPDATE messaging_message SET replies = replies - 1 WHERE id = ? AND reply_to = 0` @@ -250,12 +258,27 @@ func (r *message) CountFromMessageID(channelID, threadID, lastReadMessageID uint ) } +func (r *message) LastMessageID(channelID, threadID uint64) (uint64, error) { + rval := struct{ Last uint64 }{} + return rval.Last, r.db().Get(&rval, + sqlLastMessageID, + channelID, + threadID, + types.MessageTypeChannelEvent, + ) +} + func (r *message) PrefillThreadParticipants(mm types.MessageSet) error { var rval = []struct { ReplyTo uint64 `db:"reply_to"` UserID uint64 `db:"rel_user"` }{} + // Filter out only relevant messages -- ones with replies + mm, _ = mm.Filter(func(m *types.Message) (b bool, e error) { + return m.Replies > 0, nil + }) + if len(mm) == 0 { return nil } diff --git a/messaging/internal/repository/unread.go b/messaging/internal/repository/unread.go index 0155a3411..15206f4be 100644 --- a/messaging/internal/repository/unread.go +++ b/messaging/internal/repository/unread.go @@ -3,6 +3,7 @@ package repository import ( "context" + "github.com/jmoiron/sqlx" "github.com/titpetric/factory" "github.com/cortezaproject/corteza-server/messaging/types" @@ -14,6 +15,7 @@ type ( With(ctx context.Context, db *factory.DB) UnreadRepository Find(filter *types.UnreadFilter) (types.UnreadSet, error) + Preset(channelID, threadID uint64, userIDs ...uint64) (err error) Record(userID, channelID, threadID, lastReadMessageID uint64, count uint32) error Inc(channelID, replyTo, userID uint64) error Dec(channelID, replyTo, userID uint64) error @@ -30,8 +32,14 @@ type ( const ( // Fetching channel members of all channels a specific user has access to sqlUnreadSelect = `SELECT rel_channel, rel_reply_to, rel_user, count, rel_last_message + FROM messaging_unread + WHERE true ` + + // Fetching channel members of all channels a specific user has access to + sqlThreadUnreadSelect = `SELECT rel_channel, sum(count) as count FROM messaging_unread - WHERE true ` + WHERE rel_user = ? AND rel_reply_to > 0 + GROUP BY rel_channel` sqlUnreadIncCount = `UPDATE messaging_unread SET count = count + 1 @@ -39,7 +47,14 @@ const ( sqlUnreadDecCount = `UPDATE messaging_unread SET count = count - 1 - WHERE rel_channel = ? AND rel_reply_to = ? AND rel_user <> ? AND count > 0` + WHERE rel_channel = ? AND rel_reply_to = ? AND count > 0` + + sqlUnreadPresetChannel = `INSERT IGNORE INTO messaging_unread (rel_channel, rel_reply_to, rel_user) VALUES (?, ?, ?)` + sqlUnreadPresetThreads = `INSERT IGNORE INTO messaging_unread (rel_channel, rel_reply_to, rel_user) + SELECT rel_channel, id, ? + FROM messaging_message + WHERE rel_channel = ? + AND replies > 0` ) // Unread creates new instance of channel member repository @@ -54,13 +69,9 @@ func (r *unread) With(ctx context.Context, db *factory.DB) UnreadRepository { } } -// FindMembers fetches membership info -// -// If channelID > 0 it returns members of a specific channel -// If userID > 0 it returns members of all channels this user is member of -func (r *unread) Find(filter *types.UnreadFilter) (types.UnreadSet, error) { +// Find unread info +func (r *unread) Find(filter *types.UnreadFilter) (uu types.UnreadSet, err error) { params := make([]interface{}, 0) - vv := types.UnreadSet{} sql := sqlUnreadSelect if filter != nil { @@ -69,12 +80,76 @@ func (r *unread) Find(filter *types.UnreadFilter) (types.UnreadSet, error) { sql += ` AND rel_user = ?` params = append(params, filter.UserID) } + + if len(filter.ThreadIDs) > 0 { + sql += ` AND rel_reply_to IN (?)` + params = append(params, filter.ThreadIDs) + } } - return vv, r.db().Select(&vv, sql, params...) + if sql, params, err = sqlx.In(sql, params...); err != nil { + return nil, err + } else if err = r.db().Select(&uu, sql, params...); err != nil { + return nil, err + } else if len(filter.ThreadIDs) == 0 && filter.UserID > 0 { + // Check for unread thread messages + + // We'll abuse Unread/UnreadSet + tt := types.UnreadSet{} + + err = r.db().Select(&tt, sqlThreadUnreadSelect, filter.UserID) + + _ = tt.Walk(func(t *types.Unread) error { + c := uu.FindByChannelId(t.ChannelID) + if c != nil { + c.InThreadCount = t.Count + } else { + // No un-reads in channel but we have them in threads (of that channel) + // swap values and append + t.InThreadCount, t.Count = t.Count, 0 + uu = append(uu, t) + } + return nil + }) + } + + return uu, nil } -// Records channel view +// Preset channel unread records for all users (and threads in that channel) +// +// Whenever channel member is added or a new thread is created +// we generate records +func (r unread) Preset(channelID, threadID uint64, userIDs ...uint64) (err error) { + if channelID == 0 { + return + } + + for _, userID := range userIDs { + if userID == 0 { + continue + } + + _, err = r.db().Exec(sqlUnreadPresetChannel, channelID, threadID, userID) + + if err != nil { + return + } + + if threadID == 0 { + // Preset for all threads in the channel + _, err = r.db().Exec(sqlUnreadPresetThreads, userID, channelID) + + if err != nil { + return + } + } + } + + return +} + +// Record channel/thread view func (r *unread) Record(userID, channelID, threadID, lastReadMessageID uint64, count uint32) error { mod := &types.Unread{ ChannelID: channelID, @@ -87,15 +162,15 @@ func (r *unread) Record(userID, channelID, threadID, lastReadMessageID uint64, c return r.db().Replace("messaging_unread", mod) } -// Increments unread message count on a channel/thread for all but one user +// Inc increments unread message count on a channel/thread for all but one user func (r *unread) Inc(channelID, threadID, userID uint64) error { _, err := r.db().Exec(sqlUnreadIncCount, channelID, threadID, userID) return err } -// Decrements unread message count on a channel/thread for all but one user +// Dec decrements unread message count on a channel/thread for all but one user func (r *unread) Dec(channelID, threadID, userID uint64) error { - _, err := r.db().Exec(sqlUnreadDecCount, channelID, threadID, userID) + _, err := r.db().Exec(sqlUnreadDecCount, channelID, threadID) return err } diff --git a/messaging/internal/service/channel.go b/messaging/internal/service/channel.go index de78fd099..38c8c73b6 100644 --- a/messaging/internal/service/channel.go +++ b/messaging/internal/service/channel.go @@ -70,7 +70,6 @@ type ( Unarchive(ID uint64) (*types.Channel, error) Delete(ID uint64) (*types.Channel, error) Undelete(ID uint64) (*types.Channel, error) - RecordView(userID, channelID, lastMessageID uint64) error } ) @@ -145,7 +144,7 @@ func (svc *channel) preloadExtras(cc types.ChannelSet) (err error) { return err } - if err = svc.preloadViews(cc); err != nil { + if err = svc.preloadUnreads(cc); err != nil { return } @@ -171,19 +170,17 @@ func (svc *channel) preloadMembers(cc types.ChannelSet) (err error) { return } -func (svc *channel) preloadViews(cc types.ChannelSet) error { +func (svc *channel) preloadUnreads(cc types.ChannelSet) error { var userID = auth.GetIdentityFromContext(svc.ctx).Identity() if vv, err := svc.unread.Find(&types.UnreadFilter{UserID: userID}); err != nil { return err } else { - cc.Walk(func(ch *types.Channel) error { + return cc.Walk(func(ch *types.Channel) error { ch.Unread = vv.FindByChannelId(ch.ID) return nil }) } - - return nil } // FindMembers loads all members (and full users) for a specific channel @@ -266,7 +263,7 @@ func (svc *channel) Create(in *types.Channel) (out *types.Channel, err error) { m.ChannelID = out.ID // Create member - if m, err = svc.cmember.Create(m); err != nil { + if m, err = svc.createMember(m); err != nil { return err } @@ -646,7 +643,7 @@ func (svc *channel) InviteUser(channelID uint64, memberIDs ...uint64) (out types Type: types.ChannelMembershipTypeInvitee, } - if member, err = svc.cmember.Create(member); err != nil { + if member, err = svc.createMember(member); err != nil { return err } @@ -724,7 +721,7 @@ func (svc *channel) AddMember(channelID uint64, memberIDs ...uint64) (out types. if exists { member, err = svc.cmember.Update(member) } else { - member, err = svc.cmember.Create(member) + member, err = svc.createMember(member) } svc.event.Join(memberID, channelID) @@ -745,6 +742,20 @@ func (svc *channel) AddMember(channelID uint64, memberIDs ...uint64) (out types. }) } +// createMember orchestrates member creation +func (svc channel) createMember(member *types.ChannelMember) (m *types.ChannelMember, err error) { + if m, err = svc.cmember.Create(member); err != nil { + return + } + + // Create zero-count unread record + if err = svc.unread.Preset(m.ChannelID, 0, m.UserID); err != nil { + return + } + + return +} + func (svc *channel) DeleteMember(channelID uint64, memberIDs ...uint64) (err error) { var ( userID = repository.Identity(svc.ctx) @@ -795,14 +806,6 @@ func (svc *channel) DeleteMember(channelID uint64, memberIDs ...uint64) (err err }) } -// RecordView -// @deprecated -func (svc *channel) RecordView(userID, channelID, lastMessageID uint64) error { - return svc.db.Transaction(func() (err error) { - return svc.unread.Record(userID, channelID, 0, lastMessageID, 0) - }) -} - func (svc *channel) scheduleSystemMessage(ch *types.Channel, format string, a ...interface{}) { svc.sysmsgs = append(svc.sysmsgs, &types.Message{ ChannelID: ch.ID, diff --git a/messaging/internal/service/message.go b/messaging/internal/service/message.go index d58c12e00..bc868c359 100644 --- a/messaging/internal/service/message.go +++ b/messaging/internal/service/message.go @@ -25,7 +25,7 @@ type ( attachment repository.AttachmentRepository channel repository.ChannelRepository cmember repository.ChannelMemberRepository - unreads repository.UnreadRepository + unread repository.UnreadRepository message repository.MessageRepository mflag repository.MessageFlagRepository mentions repository.MentionRepository @@ -56,7 +56,7 @@ type ( React(messageID uint64, reaction string) error RemoveReaction(messageID uint64, reaction string) error - MarkAsRead(channelID, threadID, lastReadMessageID uint64) (uint32, error) + MarkAsRead(channelID, threadID, lastReadMessageID uint64) (uint64, uint32, error) Pin(messageID uint64) error RemovePin(messageID uint64) error @@ -96,7 +96,7 @@ func (svc message) With(ctx context.Context) MessageService { attachment: repository.Attachment(ctx, db), channel: repository.Channel(ctx, db), cmember: repository.ChannelMember(ctx, db), - unreads: repository.Unread(ctx, db), + unread: repository.Unread(ctx, db), message: repository.Message(ctx, db), mflag: repository.MessageFlag(ctx, db), mentions: repository.Mention(ctx, db), @@ -240,6 +240,22 @@ func (svc message) Create(in *types.Message) (message *types.Message, err error) in.ChannelID = original.ChannelID + if original.Replies == 0 { + // First reply, + // + // reset unreads for all members + var mm types.ChannelMemberSet + mm, err = svc.cmember.Find(&types.ChannelMemberFilter{ChannelID: original.ChannelID}) + if err != nil { + return err + } + + err = svc.unread.Preset(original.ChannelID, original.ID, mm.AllMemberIDs()...) + if err != nil { + return err + } + } + // Increment counter, on struct and in repository. original.Replies++ if err = svc.message.IncReplyCount(original.ID); err != nil { @@ -271,7 +287,7 @@ func (svc message) Create(in *types.Message) (message *types.Message, err error) return } - if err = svc.unreads.Inc(message.ChannelID, message.ReplyTo, message.UserID); err != nil { + if err = svc.unread.Inc(message.ChannelID, message.ReplyTo, message.UserID); err != nil { return } @@ -397,7 +413,7 @@ func (svc message) Delete(messageID uint64) error { return } - if err = svc.unreads.Dec(deletedMsg.ChannelID, deletedMsg.ReplyTo, deletedMsg.UserID); err != nil { + if err = svc.unread.Dec(deletedMsg.ChannelID, deletedMsg.ReplyTo, deletedMsg.UserID); err != nil { return err } else { // Set deletedAt timestamp so that our clients can react properly... @@ -412,9 +428,15 @@ func (svc message) Delete(messageID uint64) error { }) } -// M -func (svc message) MarkAsRead(channelID, threadID, lastReadMessageID uint64) (count uint32, err error) { - var currentUserID uint64 = repository.Identity(svc.ctx) +// MarkAsRead marks channel/thread as read +// +// If lastReadMessageID is set, it uses that message as last read message +func (svc message) MarkAsRead(channelID, threadID, lastReadMessageID uint64) (uint64, uint32, error) { + var ( + currentUserID uint64 = repository.Identity(svc.ctx) + count uint32 + err error + ) err = svc.db.Transaction(func() (err error) { var ch *types.Channel @@ -439,24 +461,37 @@ func (svc message) MarkAsRead(channelID, threadID, lastReadMessageID uint64) (co } if lastReadMessageID > 0 { - // Validate thread + // Validate messageID/threadID/channelID combo if lastMessage, err = svc.message.FindByID(lastReadMessageID); err != nil { return errors.Wrap(err, "unable to verify last message") } else if !lastMessage.IsValid() { return errors.New("invalid message") + } else if lastMessage.ChannelID != channelID { + return errors.New("last read message not in the same channel") + } else if threadID > 0 && lastMessage.ReplyTo != threadID { + return errors.New("last read message not in the same thread") } + + count, err = svc.message.CountFromMessageID(channelID, threadID, lastReadMessageID) + if err != nil { + return errors.Wrap(err, "unable to count unread messages") + } + + } else { + // use last message ID + if lastReadMessageID, err = svc.message.LastMessageID(channelID, threadID); err != nil { + return errors.Wrap(err, "unable to find last message") + } + + // no need to count + count = 0 } - count, err = svc.message.CountFromMessageID(channelID, threadID, lastReadMessageID) - if err != nil { - return errors.Wrap(err, "unable to count unread messages") - } - - err = svc.unreads.Record(currentUserID, channelID, threadID, lastReadMessageID, count) + err = svc.unread.Record(currentUserID, channelID, threadID, lastReadMessageID, count) return errors.Wrap(err, "unable to record unread messages") }) - return count, errors.Wrap(err, "unable to mark as read") + return lastReadMessageID, count, errors.Wrap(err, "unable to mark as read") } // React on a message with an emoji @@ -577,6 +612,10 @@ func (svc message) preload(mm types.MessageSet) (err error) { return } + if err = svc.preloadUnreads(mm); err != nil { + return + } + if err = svc.message.PrefillThreadParticipants(mm); err != nil { return } @@ -646,6 +685,28 @@ func (svc message) preloadAttachments(mm types.MessageSet) (err error) { } } +func (svc message) preloadUnreads(mm types.MessageSet) error { + var userID = auth.GetIdentityFromContext(svc.ctx).Identity() + + // Filter out only relevant messages -- ones with replies + mm, _ = mm.Filter(func(m *types.Message) (b bool, e error) { + return m.Replies > 0, nil + }) + + if len(mm) == 0 { + return nil + } + + if vv, err := svc.unread.Find(&types.UnreadFilter{UserID: userID, ThreadIDs: mm.IDs()}); err != nil { + return err + } else { + return mm.Walk(func(m *types.Message) error { + m.Unread = vv.FindByThreadId(m.ID) + return nil + }) + } +} + // Sends message to event loop func (svc message) sendEvent(mm ...*types.Message) (err error) { if err = svc.preload(mm); err != nil { diff --git a/messaging/rest/message.go b/messaging/rest/message.go index aa1f663d7..38749d143 100644 --- a/messaging/rest/message.go +++ b/messaging/rest/message.go @@ -63,7 +63,12 @@ func (ctrl *Message) Delete(ctx context.Context, r *request.MessageDelete) (inte } func (ctrl *Message) MarkAsRead(ctx context.Context, r *request.MessageMarkAsRead) (interface{}, error) { - return ctrl.svc.msg.With(ctx).MarkAsRead(r.ChannelID, r.ThreadID, r.LastReadMessageID) + var messageID, count, err = ctrl.svc.msg.With(ctx).MarkAsRead(r.ChannelID, r.ThreadID, r.LastReadMessageID) + + return outgoing.Unread{ + LastMessageID: messageID, + Count: count, + }, err } func (ctrl *Message) PinCreate(ctx context.Context, r *request.MessagePinCreate) (interface{}, error) { diff --git a/messaging/rest/request/message.go b/messaging/rest/request/message.go index 5a40dd144..ae7271dd0 100644 --- a/messaging/rest/request/message.go +++ b/messaging/rest/request/message.go @@ -154,9 +154,9 @@ var _ RequestFiller = NewMessageExecuteCommand() // Message markAsRead request parameters type MessageMarkAsRead struct { - ChannelID uint64 `json:",string"` ThreadID uint64 `json:",string"` LastReadMessageID uint64 `json:",string"` + ChannelID uint64 `json:",string"` } func NewMessageMarkAsRead() *MessageMarkAsRead { @@ -166,9 +166,9 @@ func NewMessageMarkAsRead() *MessageMarkAsRead { func (r MessageMarkAsRead) Auditable() map[string]interface{} { var out = map[string]interface{}{} - out["channelID"] = r.ChannelID out["threadID"] = r.ThreadID out["lastReadMessageID"] = r.LastReadMessageID + out["channelID"] = r.ChannelID return out } @@ -200,13 +200,13 @@ func (r *MessageMarkAsRead) Fill(req *http.Request) (err error) { post[name] = string(param[0]) } - r.ChannelID = parseUInt64(chi.URLParam(req, "channelID")) - if val, ok := post["threadID"]; ok { + if val, ok := get["threadID"]; ok { r.ThreadID = parseUInt64(val) } - if val, ok := post["lastReadMessageID"]; ok { + if val, ok := get["lastReadMessageID"]; ok { r.LastReadMessageID = parseUInt64(val) } + r.ChannelID = parseUInt64(chi.URLParam(req, "channelID")) return err } diff --git a/messaging/types/message.go b/messaging/types/message.go index b75fbd1a1..7b8111ef7 100644 --- a/messaging/types/message.go +++ b/messaging/types/message.go @@ -28,6 +28,8 @@ type ( Attachment *Attachment `json:"attachment,omitempty"` Flags MessageFlagSet `json:"flags,omitempty"` + Unread *Unread `json:"-" db:"-"` + Mentions MentionSet RepliesFrom []uint64 } diff --git a/messaging/types/sets.go b/messaging/types/sets.go index 77e46a551..350833d80 100644 --- a/messaging/types/sets.go +++ b/messaging/types/sets.go @@ -164,3 +164,13 @@ func (set UnreadSet) FindByChannelId(channelID uint64) *Unread { return nil } + +func (set UnreadSet) FindByThreadId(threadID uint64) *Unread { + for i := range set { + if set[i].ReplyTo == threadID { + return set[i] + } + } + + return nil +} diff --git a/messaging/types/unread.go b/messaging/types/unread.go index f6a4ea465..5f612423a 100644 --- a/messaging/types/unread.go +++ b/messaging/types/unread.go @@ -7,10 +7,12 @@ type ( UserID uint64 `db:"rel_user"` LastMessageID uint64 `db:"rel_last_message"` - Count uint32 `db:"count"` + Count uint32 `db:"count"` + InThreadCount uint32 `db:"-"` } UnreadFilter struct { - UserID uint64 + UserID uint64 + ThreadIDs []uint64 } ) diff --git a/messaging/websocket/session_incoming.go b/messaging/websocket/session_incoming.go index f58021e15..588d7fc58 100644 --- a/messaging/websocket/session_incoming.go +++ b/messaging/websocket/session_incoming.go @@ -34,9 +34,6 @@ func (s *Session) dispatch(raw []byte) error { case p.ChannelUpdate != nil: return s.channelUpdate(ctx, p.ChannelUpdate) - // @deprecated - case p.ChannelViewRecord != nil: - return s.channelViewRecord(ctx, p.ChannelViewRecord) } return nil diff --git a/messaging/websocket/session_incoming_channel.go b/messaging/websocket/session_incoming_channel.go index a8aa75e44..4312b7e53 100644 --- a/messaging/websocket/session_incoming_channel.go +++ b/messaging/websocket/session_incoming_channel.go @@ -101,10 +101,3 @@ func (s *Session) channelUpdate(ctx context.Context, p *incoming.ChannelUpdate) _, err = s.svc.ch.With(ctx).Update(ch) return err } - -// @deprecated -func (s *Session) channelViewRecord(ctx context.Context, p *incoming.ChannelViewRecord) error { - var userID = auth.GetIdentityFromContext(ctx).Identity() - - return s.svc.ch.With(ctx).RecordView(userID, p.ChannelID, p.LastMessageID) -}