Fix threads and patches, recreate db schema (#50)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina
2025-05-07 07:23:50 +04:00
committed by GitHub
parent a3fb7d0c58
commit b4dcc00ccf
38 changed files with 829 additions and 603 deletions
+1 -1
View File
@@ -1 +1 @@
0.1.175
0.1.176
+38 -37
View File
@@ -11,13 +11,13 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.4.0",
"prettier": "^3.5.3",
"turbo": "^2.5.2",
"turbo": "^2.5.3",
"typescript-eslint": "^8.32.0",
},
},
"packages/client-query": {
"name": "@hcengineering/communication-client-query",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-query": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
@@ -33,7 +33,7 @@
},
"packages/cockroach": {
"name": "@hcengineering/communication-cockroach",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-shared": "workspace:*",
@@ -50,7 +50,7 @@
},
"packages/query": {
"name": "@hcengineering/communication-query",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-shared": "workspace:*",
@@ -69,12 +69,12 @@
},
"packages/rest-client": {
"name": "@hcengineering/communication-rest-client",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/core": "^0.7.28",
"@hcengineering/core": "^0.7.88",
"snappyjs": "^0.7.0",
},
"devDependencies": {
@@ -87,10 +87,10 @@
},
"packages/sdk-types": {
"name": "@hcengineering/communication-sdk-types",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/core": "^0.7.28",
"@hcengineering/core": "^0.7.88",
},
"devDependencies": {
"@types/bun": "^1.1.14",
@@ -101,15 +101,16 @@
},
"packages/server": {
"name": "@hcengineering/communication-server",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/account-client": "^0.7.28",
"@hcengineering/account-client": "^0.7.88",
"@hcengineering/communication-cockroach": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/core": "^0.7.28",
"@hcengineering/platform": "^0.7.28",
"@hcengineering/server-token": "^0.7.28",
"@hcengineering/communication-yaml": "workspace:*",
"@hcengineering/core": "^0.7.88",
"@hcengineering/platform": "^0.7.88",
"@hcengineering/server-token": "^0.7.88",
"zod": "^3.24.2",
},
"devDependencies": {
@@ -124,7 +125,7 @@
},
"packages/shared": {
"name": "@hcengineering/communication-shared",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
@@ -138,11 +139,11 @@
},
"packages/types": {
"name": "@hcengineering/communication-types",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/card": "^0.7.28",
"@hcengineering/core": "^0.7.28",
"@hcengineering/tags": "^0.7.28",
"@hcengineering/card": "^0.7.88",
"@hcengineering/core": "^0.7.88",
"@hcengineering/tags": "^0.7.88",
},
"devDependencies": {
"@types/bun": "^1.1.14",
@@ -153,7 +154,7 @@
},
"packages/yaml": {
"name": "@hcengineering/communication-yaml",
"version": "0.1.174-beta.2",
"version": "0.1.0",
"dependencies": {
"@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
@@ -201,11 +202,11 @@
"@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.0", "", { "dependencies": { "tslib": "2" } }, "sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA=="],
"@hcengineering/account-client": ["@hcengineering/account-client@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/account-client/0.7.28/32c9bc0dd297f00f4d5461467f25e072d01b9c76", { "dependencies": { "@hcengineering/core": "^0.7.28", "@hcengineering/platform": "^0.7.28" } }, "sha512-k1dkioX+jYrLyAXLLLsBYVCdZKf3WjhnDHw/yeAQgLQQ/9W2ffLtDMZ8lYWV3XMQ3eWrUCodtTDOiAQgXd/LEQ=="],
"@hcengineering/account-client": ["@hcengineering/account-client@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/account-client/0.7.88/57caa95e62fecc4ca22a32c76bcdc900b0384f04", { "dependencies": { "@hcengineering/core": "^0.7.88", "@hcengineering/platform": "^0.7.88" } }, "sha512-CZWOJT/JZAUy+FuYuX0QcJQYDnVDNAB0QHU4RxKc7gSlYAYleibL7qJC0vRGL4rNkvDVZfJMKDPUQW4AauGqtA=="],
"@hcengineering/analytics": ["@hcengineering/analytics@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/analytics/0.7.28/a8faf3d575bb28b9682b659495e738f8e0ca596e", { "dependencies": { "@hcengineering/platform": "^0.7.28" } }, "sha512-+CI/wJ8zgop+3mEcvhlB1UJBU6tIKq2Y7qTichZjGli8BckEIEwbD4j2beu2qXRSoTT2yUGWM9zpS9+fmH/DAw=="],
"@hcengineering/analytics": ["@hcengineering/analytics@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/analytics/0.7.88/385c2ae512bbcae39189d63e53ea527aa21a662f", { "dependencies": { "@hcengineering/platform": "^0.7.88" } }, "sha512-3jT3h2Y85awrgG0JWZSrpWOXaX1J3dpq8zouo3QMw8OVO1RgHCwPEqrBPOALnB+gc9cZL02p6Aq4J+fnTZ2fww=="],
"@hcengineering/card": ["@hcengineering/card@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/card/0.7.28/5348996f5b7b4a34517780a5bf324122cbddfbad", { "dependencies": { "@hcengineering/core": "^0.7.28", "@hcengineering/platform": "^0.7.28", "@hcengineering/ui": "^0.7.28" } }, "sha512-1d3allyx9jTpE/gr9kk/g5qo5848i4FKsRiHgCpJqzNEdRW7TAednoHAr2hKRU5DZC/i3Br56axSkUQzV2Idhg=="],
"@hcengineering/card": ["@hcengineering/card@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/card/0.7.88/e39b9e182325945494f162f2dd6f16ef9bea0f3e", { "dependencies": { "@hcengineering/core": "^0.7.88", "@hcengineering/platform": "^0.7.88", "@hcengineering/ui": "^0.7.88" } }, "sha512-3JDgocHFSGmQuKsrtQ5CTTm4rpd/PHahIx6oNX8sshDGpS2hnvNtrHNkJf39seIKiq0NNKmJ//Etl4I21h4c6w=="],
"@hcengineering/communication-client-query": ["@hcengineering/communication-client-query@workspace:packages/client-query"],
@@ -225,21 +226,21 @@
"@hcengineering/communication-yaml": ["@hcengineering/communication-yaml@workspace:packages/yaml"],
"@hcengineering/core": ["@hcengineering/core@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/core/0.7.28/43b84c63262e2b8fe5554c1967812e2ce6bb56e1", { "dependencies": { "@hcengineering/analytics": "^0.7.28", "@hcengineering/platform": "^0.7.28", "fast-equals": "^5.2.2" } }, "sha512-qv4QE7mkect6JYlGMljW5CQ2uU80hFGq0E17lZ5rO79h1boWEKLJpINv+w3J2BdR/nNG7taIsdZg3yfl9fY4bg=="],
"@hcengineering/core": ["@hcengineering/core@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/core/0.7.88/ca01c3ccd43c85ea44b189d995b0693c5465956c", { "dependencies": { "@hcengineering/analytics": "^0.7.88", "@hcengineering/platform": "^0.7.88", "fast-equals": "^5.2.2" } }, "sha512-c3GRTGAZYARu1kMt4TqkugmnuprYesZ4XZUeakReRtZdYei9tAF+i9xPpDKb4J3hvMC3u82LlEiV8iK26iXpTA=="],
"@hcengineering/platform": ["@hcengineering/platform@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/platform/0.7.28/2126ff97284b679ceab3c86f24febd73a3a28712", { "dependencies": { "intl-messageformat": "^10.7.14" } }, "sha512-0zEifwsLdOs/M6TZ9xnPrQTTlhwW28sooiyL4ZfQsiC2AJUy05vLP5/W8V9eDLta+EiR5bjtlulGsg1GfPjUoQ=="],
"@hcengineering/platform": ["@hcengineering/platform@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/platform/0.7.88/b3fbe09d6b849f7eb3641792682bf133103037f0", { "dependencies": { "intl-messageformat": "^10.7.14" } }, "sha512-iFAwYQZjGox1NqEgUh8hiT1mRHdSJqYmYux0lkeIFyt8Nn/BAZOVXe0br0Dx7wXiueFPWhSZSeWVhxvBb78zoA=="],
"@hcengineering/preference": ["@hcengineering/preference@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/preference/0.7.28/f1739991cc012d194fa18f99565ae5bd64f0ad07", { "dependencies": { "@hcengineering/core": "^0.7.28", "@hcengineering/platform": "^0.7.28", "@hcengineering/ui": "^0.7.28" } }, "sha512-WwwOfjZ1TLdTfPhQGvSjo8dS63HyuE7BDVhfstxP8D+H4BI5UKYoev/6vlwQf/3d+jI1S4f2ivO//IDJAD7sGg=="],
"@hcengineering/preference": ["@hcengineering/preference@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/preference/0.7.88/e1181dae3cb2eaf53fed66ddd3d5c4854717ccf0", { "dependencies": { "@hcengineering/core": "^0.7.88", "@hcengineering/platform": "^0.7.88", "@hcengineering/ui": "^0.7.88" } }, "sha512-idzAlphM/HyNJfFMFwbpyKg6KvFTJ6rTaqgduNNJqkO4DsUCEF5/1WLNQMmiAeoFDY9y5+/mHJrx+1Ztq1XIuw=="],
"@hcengineering/server-token": ["@hcengineering/server-token@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/server-token/0.7.28/b1cffcb628aa0c6ac73171ef340c8ccf0f85ab07", { "dependencies": { "@hcengineering/core": "^0.7.28", "@hcengineering/platform": "^0.7.28", "jwt-simple": "^0.5.6" } }, "sha512-TaHwzxuoFKN6jnWHIZk0UcRmB+UQ1QE1HZzUh5doPSFUYOAtTrSb32iJDA8e8oEADcJpOp9RGIUP/EP/VED7BQ=="],
"@hcengineering/server-token": ["@hcengineering/server-token@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/server-token/0.7.88/11b3a8247869a50037fcbb13572e16545116ff93", { "dependencies": { "@hcengineering/core": "^0.7.88", "@hcengineering/platform": "^0.7.88", "jwt-simple": "^0.5.6" } }, "sha512-UQfW0E+v+PTwR7ICR+kSTQqAokpiV008ukqsxRSQJkCM4vmNYciUgd/7Y/ifDMFhKDvNyODb+wgAQs3QxkSbkA=="],
"@hcengineering/tags": ["@hcengineering/tags@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/tags/0.7.28/42545cc43e3c61e32ce96be87c245fa929f5cbb6", { "dependencies": { "@hcengineering/core": "^0.7.28", "@hcengineering/platform": "^0.7.28", "@hcengineering/ui": "^0.7.28", "@hcengineering/view": "^0.7.28" } }, "sha512-2KCVUu5N/GAkDsDRbdUi7gnBq4ze1Z3eCLoc8nz2G/zeAjAga8gmWkZEbXqAC8rNVqBQv72EKeJFGJyqnev6Xw=="],
"@hcengineering/tags": ["@hcengineering/tags@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/tags/0.7.88/f3f0de35b1be71d1f3f6711b8e4f161e0cf857eb", { "dependencies": { "@hcengineering/core": "^0.7.88", "@hcengineering/platform": "^0.7.88", "@hcengineering/ui": "^0.7.88", "@hcengineering/view": "^0.7.88" } }, "sha512-wpnVX/1Y8E4YPYnUcw+ZyVOpZvXYpboZC9D1btMqFs34CoMGc4tW1XT3YfsS8KxlkH6tsQjsAUvtWOPy2J46dg=="],
"@hcengineering/theme": ["@hcengineering/theme@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/theme/0.7.28/557640a7ba2ffaa4d6e5411a54577f60dc528300", { "dependencies": { "@hcengineering/analytics": "^0.7.28", "@hcengineering/platform": "^0.7.28", "svelte": "^4.2.19" } }, "sha512-Hv3wQKHr327TC7wMHCBiG4bMtg8vSlb7+UFwdKI8ztEs3359meqkXu+exqi7Nl5334voXIz5ZFxjwZwOMzNSFA=="],
"@hcengineering/theme": ["@hcengineering/theme@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/theme/0.7.88/d939d1e2a9047def2795dc5124653e49ca522cfc", { "dependencies": { "@hcengineering/analytics": "^0.7.88", "@hcengineering/platform": "^0.7.88", "svelte": "^4.2.19" } }, "sha512-PCt9bFuGPYOf1kQgjzxPSZgGXU4G14Z1XZJfgGu71u/zHxG6fu6nls35aSrEjy2Ks6oR+OCqqvpDh6RTf6utrg=="],
"@hcengineering/ui": ["@hcengineering/ui@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/ui/0.7.28/82025fc775702ea8c8569611356662bfbf28eb74", { "dependencies": { "@hcengineering/analytics": "^0.7.28", "@hcengineering/core": "^0.7.28", "@hcengineering/platform": "^0.7.28", "@hcengineering/theme": "^0.7.28", "autolinker": "4.0.0", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", "dompurify": "^3.1.6", "emojibase": "^16.0.0", "fast-equals": "^5.2.2", "hls.js": "^1.5.20", "plyr": "^3.7.8", "svelte": "^4.2.19" } }, "sha512-QsUpO0+Idu1n2dC6KtorPJ6SSp30It8FHSuG3Y+Awv4td/ftVocOSFqhskbk+us9Yw9KqOYgkqhGYEHbfRfw4Q=="],
"@hcengineering/ui": ["@hcengineering/ui@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/ui/0.7.88/fce3392a59f706c248ba4b17714163e5725d5f01", { "dependencies": { "@hcengineering/analytics": "^0.7.88", "@hcengineering/core": "^0.7.88", "@hcengineering/platform": "^0.7.88", "@hcengineering/theme": "^0.7.88", "autolinker": "4.0.0", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", "dompurify": "^3.1.6", "emojibase": "^16.0.0", "fast-equals": "^5.2.2", "hls.js": "^1.5.20", "plyr": "^3.7.8", "svelte": "^4.2.19" } }, "sha512-mzHXcX3aBC/OXUaV9F2hnB6+irwMfEYM5J1CnfqiHqgv3Hza20BZR6BrYe/nQCLb44DuHkXojbwL94AKK3QW6w=="],
"@hcengineering/view": ["@hcengineering/view@0.7.28", "https://npm.pkg.github.com/download/@hcengineering/view/0.7.28/8aad5b07d3750463a78aae1b9482361f2ff9f45e", { "dependencies": { "@hcengineering/core": "^0.7.28", "@hcengineering/platform": "^0.7.28", "@hcengineering/preference": "^0.7.28", "@hcengineering/ui": "^0.7.28" } }, "sha512-cG0isakqIxuiAG7T2KyRnztfOp3falr+oG77Qod915EeBKNEDqXfNrD29Dp04Wwf5TowifDT9smLzAyH8l6kgg=="],
"@hcengineering/view": ["@hcengineering/view@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/view/0.7.88/8a13ddff0178ac3b705ebed683d0c1cc328c8e1c", { "dependencies": { "@hcengineering/core": "^0.7.88", "@hcengineering/platform": "^0.7.88", "@hcengineering/preference": "^0.7.88", "@hcengineering/ui": "^0.7.88" } }, "sha512-W3/OgCu9W+OGZ8v8hoT0bjoCNflvIcWFwFafk40FtWFxvqzObm41e5ZWNj3yGWFdlVsyN/Nqdjkyz0txWUXQAA=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
@@ -691,19 +692,19 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"turbo": ["turbo@2.5.2", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.2", "turbo-darwin-arm64": "2.5.2", "turbo-linux-64": "2.5.2", "turbo-linux-arm64": "2.5.2", "turbo-windows-64": "2.5.2", "turbo-windows-arm64": "2.5.2" }, "bin": { "turbo": "bin/turbo" } }, "sha512-Qo5lfuStr6LQh3sPQl7kIi243bGU4aHGDQJUf6ylAdGwks30jJFloc9NYHP7Y373+gGU9OS0faA4Mb5Sy8X9Xw=="],
"turbo": ["turbo@2.5.3", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.3", "turbo-darwin-arm64": "2.5.3", "turbo-linux-64": "2.5.3", "turbo-linux-arm64": "2.5.3", "turbo-windows-64": "2.5.3", "turbo-windows-arm64": "2.5.3" }, "bin": { "turbo": "bin/turbo" } }, "sha512-iHuaNcq5GZZnr3XDZNuu2LSyCzAOPwDuo5Qt+q64DfsTP1i3T2bKfxJhni2ZQxsvAoxRbuUK5QetJki4qc5aYA=="],
"turbo-darwin-64": ["turbo-darwin-64@2.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-2aIl0Sx230nLk+Cg2qSVxvPOBWCZpwKNuAMKoROTvWKif6VMpkWWiR9XEPoz7sHeLmCOed4GYGMjL1bqAiIS/g=="],
"turbo-darwin-64": ["turbo-darwin-64@2.5.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-YSItEVBUIvAGPUDpAB9etEmSqZI3T6BHrkBkeSErvICXn3dfqXUfeLx35LfptLDEbrzFUdwYFNmt8QXOwe9yaw=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MrFYhK/jYu8N6QlqZtqSHi3e4QVxlzqU3ANHTKn3/tThuwTLbNHEvzBPWSj5W7nZcM58dCqi6gYrfRz6bJZyAA=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5PefrwHd42UiZX7YA9m1LPW6x9YJBDErXmsegCkVp+GjmWrADfEOxpFrGQNonH3ZMj77WZB2PVE5Aw3gA+IOhg=="],
"turbo-linux-64": ["turbo-linux-64@2.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-LxNqUE2HmAJQ/8deoLgMUDzKxd5bKxqH0UBogWa+DF+JcXhtze3UTMr6lEr0dEofdsEUYK1zg8FRjglmwlN5YA=="],
"turbo-linux-64": ["turbo-linux-64@2.5.3", "", { "os": "linux", "cpu": "x64" }, "sha512-M9xigFgawn5ofTmRzvjjLj3Lqc05O8VHKuOlWNUlnHPUltFquyEeSkpQNkE/vpPdOR14AzxqHbhhxtfS4qvb1w=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-0MI1Ao1q8zhd+UUbIEsrM+yLq1BsrcJQRGZkxIsHFlGp7WQQH1oR3laBgfnUCNdCotCMD6w4moc9pUbXdOR3bg=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.5.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-auJRbYZ8SGJVqvzTikpg1bsRAsiI9Tk0/SDkA5Xgg0GdiHDH/BOzv1ZjDE2mjmlrO/obr19Dw+39OlMhwLffrw=="],
"turbo-windows-64": ["turbo-windows-64@2.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-hOLcbgZzE5ttACHHyc1ajmWYq4zKT42IC3G6XqgiXxMbS+4eyVYTL+7UvCZBd3Kca1u4TLQdLQjeO76zyDJc2A=="],
"turbo-windows-64": ["turbo-windows-64@2.5.3", "", { "os": "win32", "cpu": "x64" }, "sha512-arLQYohuHtIEKkmQSCU9vtrKUg+/1TTstWB9VYRSsz+khvg81eX6LYHtXJfH/dK7Ho6ck+JaEh5G+QrE1jEmCQ=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.5.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-fMU41ABhSLa18H8V3Z7BMCGynQ8x+wj9WyBMvWm1jeyRKgkvUYJsO2vkIsy8m0vrwnIeVXKOIn6eSe1ddlBVqw=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.5.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3JPn66HAynJ0gtr6H+hjY4VHpu1RPKcEwGATvGUTmLmYSYBQieVlnGDRMMoYN066YfyPqnNGCfhYbXfH92Cm0g=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+1 -1
View File
@@ -20,7 +20,7 @@
"eslint-plugin-prettier": "^5.4.0",
"prettier": "^3.5.3",
"typescript-eslint": "^8.32.0",
"turbo": "^2.5.2"
"turbo": "^2.5.3"
},
"packageManager": "bun@1.2.9"
}
+11 -73
View File
@@ -41,10 +41,10 @@ import {
type FindLabelsParams,
type LabelID,
type CardType,
type MessageData
type MessageData,
type PatchData
} from '@hcengineering/communication-types'
import type { DbAdapter } from '@hcengineering/communication-sdk-types'
import { retry } from '@hcengineering/communication-shared'
import { MessagesDb } from './db/message'
import { NotificationsDb } from './db/notification'
@@ -77,9 +77,10 @@ export class CockroachAdapter implements DbAdapter {
creator: SocialID,
created: Date,
data?: MessageData,
externalId?: string
externalId?: string,
id?: MessageID
): Promise<MessageID> {
return await this.message.createMessage(card, type, content, creator, created, data, externalId)
return await this.message.createMessage(card, type, content, creator, created, data, externalId, id)
}
async removeMessages(card: CardID, messages: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]> {
@@ -91,11 +92,11 @@ export class CockroachAdapter implements DbAdapter {
message: MessageID,
messageCreated: Date,
type: PatchType,
content: RichText,
data: PatchData,
creator: SocialID,
created: Date
): Promise<void> {
await this.message.createPatch(card, message, messageCreated, type, content, creator, created)
await this.message.createPatch(card, message, messageCreated, type, data, creator, created)
}
async createMessagesGroup(card: CardID, blobId: BlobID, fromDate: Date, toDate: Date, count: number): Promise<void> {
@@ -150,9 +151,10 @@ export class CockroachAdapter implements DbAdapter {
message: MessageID,
messageCreated: Date,
thread: CardID,
threadType: CardType,
created: Date
): Promise<void> {
await this.message.createThread(card, message, messageCreated, thread, created)
await this.message.createThread(card, message, messageCreated, thread, threadType, created)
}
async updateThread(thread: CardID, op: 'increment' | 'decrement', lastReply?: Date): Promise<void> {
@@ -243,77 +245,13 @@ export async function createDbAdapter(
logger?: Logger,
options?: Options
): Promise<DbAdapter> {
const greenUrl = process.env.GREEN_URL ?? ''
const connection = connect(connectionString)
const sql = await connection.getClient()
await initSchema(sql)
if (greenUrl !== '') {
const client = new GreenClient(greenUrl, sql)
return new CockroachAdapter(client, workspace, logger, options)
} else {
const client = new CockroachClient(connection, sql)
const client = new CockroachClient(connection, sql)
return new CockroachAdapter(client, workspace, logger, options)
}
}
class GreenClient implements SqlClient {
private readonly url: string
private readonly token: string
constructor(
private readonly endpoint: string,
private readonly sql: postgres.Sql
) {
const url = new URL(this.endpoint)
this.token = url.searchParams.get('token') ?? 'secret'
const compression = url.searchParams.get('compression') ?? ''
const newHost = url.host
const newPathname = url.pathname
const newSearchParams = new URLSearchParams()
if (compression !== '') {
newSearchParams.set('compression', compression)
}
this.url = `${url.protocol}//${newHost}${newPathname}${newSearchParams.size > 0 ? '?' + newSearchParams.toString() : ''}`
}
async execute<T = SqlRow>(query: string, params?: SqlParams): Promise<T[]> {
return await retry(() => this.fetch<T[]>(query, params), { retries: 5 })
}
cursor<T = SqlRow>(query: string, params?: SqlParams, size?: number): AsyncIterable<NonNullable<T[][number]>[]> {
const sql = params !== undefined && params.length > 0 ? injectVars(query, params) : query
return this.sql.unsafe<T[]>(sql).cursor(size)
}
close(): void {
// do nothing
}
private async fetch<T = SqlRow>(query: string, params?: SqlParams): Promise<T> {
const url = this.url.endsWith('/') ? this.url + 'api/v1/sql' : this.url + '/api/v1/sql'
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + this.token,
Connection: 'keep-alive'
},
body: JSON.stringify({ query, params }, (_, value) => (typeof value === 'bigint' ? value.toString() : value))
})
if (!response.ok) {
throw new Error(`Failed to execute sql: ${response.status} ${response.statusText}`)
}
return await response.json()
}
return new CockroachAdapter(client, workspace, logger, options)
}
class CockroachClient implements SqlClient {
+43 -18
View File
@@ -33,8 +33,10 @@ import {
type SocialID,
type Thread,
type MessageData,
type Label
type Label,
type CardType
} from '@hcengineering/communication-types'
import { applyPatches } from '@hcengineering/communication-shared'
import {
type FileDb,
@@ -51,6 +53,7 @@ import {
interface RawMessage extends MessageDb {
thread_id?: CardID
thread_type?: CardType
replies_count?: number
last_reply?: Date
patches?: PatchDb[]
@@ -72,7 +75,7 @@ interface RawNotification extends NotificationDb {
message_group_count?: number
message_patches?: {
patch_type: PatchType
patch_content: RichText
patch_data: Record<string, any>
patch_creator: SocialID
patch_created: Date
}[]
@@ -83,32 +86,38 @@ type RawContext = ContextDb & { id: ContextID } & {
}
export function toMessage(raw: RawMessage): Message {
const lastPatch = raw.patches?.[0]
const patches = (raw.patches ?? []).map((it) => toPatch(it))
return {
const rawMessage: Message = {
id: String(raw.id) as MessageID,
type: raw.type,
card: raw.card_id,
content: lastPatch?.content ?? raw.content,
content: raw.content,
creator: raw.creator,
created: raw.created,
created: new Date(raw.created),
data: raw.data,
externalId: raw.external_id,
edited: lastPatch?.created ?? undefined,
thread:
raw.thread_id != null
raw.thread_id != null && raw.thread_type != null
? {
card: raw.card_id,
message: String(raw.id) as MessageID,
messageCreated: new Date(raw.created),
thread: raw.thread_id,
repliesCount: raw.replies_count ?? 0,
threadType: raw.thread_type,
repliesCount: raw.replies_count ? parseInt(raw.replies_count as any) : 0,
lastReply: raw.last_reply ?? new Date()
}
: undefined,
reactions: (raw.reactions ?? []).map(toReaction),
files: (raw.files ?? []).map(toFile)
}
if (patches.length === 0) {
return rawMessage
}
return applyPatches(rawMessage, patches, [PatchType.update])
}
export function toReaction(raw: ReactionDb): Reaction {
@@ -116,7 +125,7 @@ export function toReaction(raw: ReactionDb): Reaction {
message: String(raw.message_id) as MessageID,
reaction: raw.reaction,
creator: raw.creator,
created: raw.created
created: new Date(raw.created)
}
}
@@ -130,7 +139,7 @@ export function toFile(raw: FileDb): File {
filename: raw.filename,
size: parseInt(raw.size as any),
creator: raw.creator,
created: raw.created
created: new Date(raw.created)
}
}
@@ -145,12 +154,12 @@ export function toMessagesGroup(raw: MessagesGroupDb): MessagesGroup {
}
}
export function toPatch(raw: PatchDb): Patch {
export function toPatch(raw: Omit<PatchDb, 'workspace_id'>): Patch {
return {
type: raw.type,
messageCreated: new Date(raw.message_created),
message: String(raw.message_id) as MessageID,
content: raw.content,
data: raw.data as any,
creator: raw.creator,
created: new Date(raw.created)
}
@@ -162,8 +171,9 @@ export function toThread(raw: ThreadDb): Thread {
message: String(raw.message_id) as MessageID,
messageCreated: new Date(raw.message_created),
thread: raw.thread_id,
repliesCount: raw.replies_count,
lastReply: raw.last_reply
threadType: raw.thread_type,
repliesCount: parseInt(raw.replies_count as any),
lastReply: new Date(raw.last_reply)
}
}
@@ -198,20 +208,35 @@ function toNotificationRaw(
raw.message_created != null &&
raw.message_type != null
) {
const lastPatch = (raw.message_patches ?? []).find((it) => it.patch_type === PatchType.update)
const patches = (raw.message_patches ?? []).map((it) =>
toPatch({
card_id: card,
message_id: raw.message_id,
type: it.patch_type,
data: it.patch_data,
creator: it.patch_creator,
created: new Date(it.patch_created),
message_created: raw.message_created ? new Date(raw.message_created) : created
})
)
message = {
id: String(raw.message_id) as MessageID,
type: raw.message_type,
card,
content: lastPatch?.patch_content ?? raw.message_content,
content: raw.message_content,
data: raw.message_data,
externalId: raw.message_external_id,
creator: raw.message_creator,
created: new Date(raw.message_created),
edited: lastPatch?.patch_created != null ? new Date(lastPatch.patch_created) : undefined,
edited: undefined,
reactions: [],
files: []
}
if (patches.length > 0) {
message = applyPatches(message, patches, [PatchType.update])
}
}
if (message != null) {
+138 -102
View File
@@ -16,6 +16,7 @@
import {
type BlobID,
type CardID,
type CardType,
type FindMessagesGroupsParams,
type FindMessagesParams,
type Message,
@@ -23,6 +24,7 @@ import {
type MessageID,
type MessagesGroup,
type MessageType,
type PatchData,
PatchType,
type RichText,
type SocialID,
@@ -34,6 +36,7 @@ import { BaseDb } from './base'
import {
type FileDb,
type MessageDb,
messageSchema,
type MessagesGroupDb,
type PatchDb,
type ReactionDb,
@@ -52,9 +55,10 @@ export class MessagesDb extends BaseDb {
creator: SocialID,
created: Date,
data?: MessageData,
externalId?: string
externalId?: string,
id?: MessageID
): Promise<MessageID> {
const db: Omit<MessageDb, 'id'> = {
const db: Omit<MessageDb, 'id'> & { id?: MessageID } = {
type,
workspace_id: this.workspace,
card_id: card,
@@ -62,18 +66,26 @@ export class MessagesDb extends BaseDb {
creator,
created,
data,
external_id: externalId
external_id: externalId,
id
}
const sql = `INSERT INTO ${TableName.Message} (workspace_id, card_id, content, creator, created, type, data, external_id)
VALUES ($1::uuid, $2::varchar, $3::text, $4::varchar, $5::timestamptz, $6::varchar, $7::jsonb, $8::varchar)
RETURNING id::text`
const values: any[] = []
const keys: string[] = []
const result = await this.execute(
sql,
[db.workspace_id, db.card_id, db.content, db.creator, db.created, db.type, db.data ?? {}, externalId ?? null],
'insert message'
)
for (const key in db) {
const value: any = (db as any)[key]
if (value == null) continue
keys.push(key)
values.push(value)
}
const placeholders = keys.map((key, i) => `$${i + 1}::${(messageSchema as any)[key]}`)
const sql = `INSERT INTO ${TableName.Message} (${keys.join(', ')})
VALUES (${placeholders.join(', ')})
RETURNING id::text`
const result = await this.execute(sql, values, 'insert message')
return result.map((it: any) => it.id)[0]
}
@@ -104,7 +116,10 @@ export class MessagesDb extends BaseDb {
values.push(messages)
}
const sql = `DELETE FROM ${TableName.Message} WHERE ${where.join(' AND ')} RETURNING id::text`
const sql = `DELETE
FROM ${TableName.Message}
WHERE ${where.join(' AND ')}
RETURNING id::text`
const result = await this.execute(sql, values, 'remove messages')
@@ -116,7 +131,7 @@ export class MessagesDb extends BaseDb {
message: MessageID,
messageCreated: Date,
type: PatchType,
content: string,
data: PatchData,
creator: SocialID,
created: Date
): Promise<void> {
@@ -125,18 +140,20 @@ export class MessagesDb extends BaseDb {
card_id: card,
message_id: message,
type,
content,
data,
creator,
created,
message_created: messageCreated
}
const sql = `INSERT INTO ${TableName.Patch} (workspace_id, card_id, message_id, type, content, creator, created, message_created)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::text, $6::varchar, $7::timestamptz, $8::timestamptz)`
const sql = `INSERT INTO ${TableName.Patch} (workspace_id, card_id, message_id, type, data, creator, created,
message_created)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::jsonb, $6::varchar, $7::timestamptz,
$8::timestamptz)`
await this.execute(
sql,
[db.workspace_id, db.card_id, db.message_id, db.type, db.content, db.creator, db.created, db.message_created],
[db.workspace_id, db.card_id, db.message_id, db.type, db.data, db.creator, db.created, db.message_created],
'insert patch'
)
}
@@ -165,8 +182,10 @@ export class MessagesDb extends BaseDb {
created,
message_created: messageCreated
}
const sql = `INSERT INTO ${TableName.File} (workspace_id, card_id, message_id, blob_id, type, filename, creator, created, message_created, size)
VALUES ($1::uuid, $2::varchar, $3::int8, $4::uuid, $5::varchar, $6::varchar, $7::varchar, $8::timestamptz, $9::timestamptz, $10::int8)`
const sql = `INSERT INTO ${TableName.File} (workspace_id, card_id, message_id, blob_id, type, filename, creator,
created, message_created, size)
VALUES ($1::uuid, $2::varchar, $3::int8, $4::uuid, $5::varchar, $6::varchar, $7::varchar,
$8::timestamptz, $9::timestamptz, $10::int8)`
await this.execute(
sql,
@@ -206,10 +225,12 @@ export class MessagesDb extends BaseDb {
created: Date
): Promise<void> {
const select = `SELECT m.id
FROM ${TableName.Message} m
WHERE m.id = $1::bigint`
FROM ${TableName.Message} m
WHERE m.workspace_id = $1::uuid
AND m.card_id = $2::varchar
AND m.id = $3::bigint`
const messageDb = await this.execute(select, [message], 'select message')
const messageDb = await this.execute(select, [this.workspace, card, message], 'select message')
if (messageDb.length > 0) {
const db: ReactionDb = {
@@ -221,7 +242,7 @@ export class MessagesDb extends BaseDb {
created
}
const sql = `INSERT INTO ${TableName.Reaction} (workspace_id, card_id, message_id, reaction, creator, created)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::varchar, $6::timestamptz)`
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::varchar, $6::timestamptz)`
await this.execute(
sql,
@@ -229,7 +250,7 @@ export class MessagesDb extends BaseDb {
'insert reaction'
)
} else {
await this.createPatch(card, message, messageCreated, PatchType.addReaction, reaction, creator, created)
await this.createPatch(card, message, messageCreated, PatchType.addReaction, { reaction }, creator, created)
}
}
@@ -242,22 +263,24 @@ export class MessagesDb extends BaseDb {
created: Date
): Promise<void> {
const select = `SELECT m.id
FROM ${TableName.Message} m
WHERE m.id = $1::bigint`
FROM ${TableName.Message} m
WHERE m.workspace_id = $1::uuid
AND m.card_id = $2::varchar
AND m.id = $3::bigint`
const messageDb = await this.execute(select, [message], 'select message')
const messageDb = await this.execute(select, [this.workspace, card, message], 'select message')
if (messageDb.length > 0) {
const sql = `DELETE
FROM ${TableName.Reaction}
WHERE workspace_id = $1::uuid
AND card_id = $2::varchar
AND message_id = $3::bigint
AND reaction = $4::varchar
AND creator = $5::varchar`
FROM ${TableName.Reaction}
WHERE workspace_id = $1::uuid
AND card_id = $2::varchar
AND message_id = $3::bigint
AND reaction = $4::varchar
AND creator = $5::varchar`
await this.execute(sql, [this.workspace, card, message, reaction, creator], 'remove reaction')
} else {
await this.createPatch(card, message, messageCreated, PatchType.removeReaction, reaction, creator, created)
await this.createPatch(card, message, messageCreated, PatchType.removeReaction, { reaction }, creator, created)
}
}
@@ -267,6 +290,7 @@ export class MessagesDb extends BaseDb {
message: MessageID,
messageCreated: Date,
thread: CardID,
threadType: CardType,
created: Date
): Promise<void> {
const db: ThreadDb = {
@@ -275,15 +299,25 @@ export class MessagesDb extends BaseDb {
message_id: message,
message_created: messageCreated,
thread_id: thread,
thread_type: threadType,
replies_count: 0,
last_reply: created
}
const sql = `INSERT INTO ${TableName.Thread} (workspace_id, card_id, message_id, thread_id, replies_count,
last_reply, message_created)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::int, $6::timestamptz, $7::timestamptz)`
const sql = `INSERT INTO ${TableName.Thread} (workspace_id, card_id, message_id, thread_id, thread_type, replies_count,
last_reply, message_created)
VALUES ($1::uuid, $2::varchar, $3::bigint, $4::varchar, $5::varchar, $6::int, $7::timestamptz, $8::timestamptz)`
await this.execute(
sql,
[db.workspace_id, db.card_id, db.message_id, db.thread_id, db.replies_count, db.last_reply, db.message_created],
[
db.workspace_id,
db.card_id,
db.message_id,
db.thread_id,
db.thread_type,
db.replies_count,
db.last_reply,
db.message_created
],
'insert thread'
)
}
@@ -322,7 +356,7 @@ export class MessagesDb extends BaseDb {
}
const sql = `INSERT INTO ${TableName.MessagesGroup} (workspace_id, card_id, blob_id, from_date, to_date, count)
VALUES ($1::uuid, $2::varchar, $3::uuid, $4::timestamptz, $5::timestamptz, $6::int)`
VALUES ($1::uuid, $2::varchar, $3::uuid, $4::timestamptz, $5::timestamptz, $6::int)`
await this.execute(
sql,
[db.workspace_id, db.card_id, db.blob_id, db.from_date, db.to_date, db.count],
@@ -332,10 +366,10 @@ export class MessagesDb extends BaseDb {
async removeMessagesGroup(card: CardID, blobId: BlobID): Promise<void> {
const sql = `DELETE
FROM ${TableName.MessagesGroup}
WHERE workspace_id = $1::uuid
AND card_id = $2::varchar
AND blob_id = $3::uuid`
FROM ${TableName.MessagesGroup}
WHERE workspace_id = $1::uuid
AND card_id = $2::varchar
AND blob_id = $3::uuid`
await this.execute(sql, [this.workspace, card, blobId], 'remove messages group')
}
@@ -391,6 +425,7 @@ export class MessagesDb extends BaseDb {
'message_created', f.message_created,
'blob_id', f.blob_id,
'type', f.type,
'size', f.size,
'filename', f.filename,
'creator', f.creator,
'created', f.created
@@ -438,10 +473,11 @@ export class MessagesDb extends BaseDb {
p.message_id,
jsonb_agg(
jsonb_build_object(
'content', p.content,
'type', p.type,
'data', p.data,
'creator', p.creator,
'created', p.created
) ORDER BY p.created DESC
) ORDER BY p.created ASC
) AS patches
FROM ${TableName.Patch} p
INNER JOIN limited_messages m
@@ -457,7 +493,7 @@ export class MessagesDb extends BaseDb {
private buildMainSelect(params: FindMessagesParams): string {
const orderBy = this.buildOrderBy(params)
const selectReplies = params.replies
? `t.thread_id as thread_id, t.replies_count as replies_count, t.last_reply as last_reply,`
? `t.thread_id as thread_id, t.thread_type as thread_type, t.replies_count::int as replies_count, t.last_reply as last_reply,`
: ''
const selectFiles = params.files ? `COALESCE(f.files, '[]'::jsonb) AS files,` : `'[]'::jsonb AS files,`
@@ -483,32 +519,31 @@ export class MessagesDb extends BaseDb {
: ''
return `
SELECT
m.id::text,
m.card_id,
m.type,
m.content,
m.creator,
m.created,
m.data,
m.external_id,
${selectReplies}
${selectFiles}
${selectReactions}
COALESCE(p.patches, '[]'::jsonb) AS patches
FROM limited_messages m
LEFT JOIN ${TableName.Thread} t
ON t.workspace_id = m.workspace_id
AND t.card_id = m.card_id
AND t.message_id = m.id
${joinFiles}
${joinReactions}
LEFT JOIN agg_patches p
ON p.workspace_id = m.workspace_id
AND p.card_id = m.card_id
AND p.message_id = m.id
${orderBy}
`
SELECT m.id::text,
m.card_id,
m.type,
m.content,
m.creator,
m.created,
m.data,
m.external_id,
${selectReplies}
${selectFiles}
${selectReactions}
COALESCE(p.patches, '[]'::jsonb) AS patches
FROM limited_messages m
LEFT JOIN ${TableName.Thread} t
ON t.workspace_id = m.workspace_id
AND t.card_id = m.card_id
AND t.message_id = m.id
${joinFiles}
${joinReactions}
LEFT JOIN agg_patches p
ON p.workspace_id = m.workspace_id
AND p.card_id = m.card_id
AND p.message_id = m.id
${orderBy}
`
}
buildMessageWhere(params: FindMessagesParams): { where: string; values: any[] } {
@@ -546,15 +581,16 @@ export class MessagesDb extends BaseDb {
// Find thread
async findThread(thread: CardID): Promise<Thread | undefined> {
const sql = `SELECT t.card_id,
t.message_id::text,
t.message_created,
t.thread_id,
t.replies_count,
t.last_reply
FROM ${TableName.Thread} t
WHERE t.workspace_id = $1::uuid
AND t.thread_id = $2::varchar
LIMIT 1;`
t.message_id::text,
t.message_created,
t.thread_id,
t.thread_type,
t.replies_count::int,
t.last_reply
FROM ${TableName.Thread} t
WHERE t.workspace_id = $1::uuid
AND t.thread_id = $2::varchar
LIMIT 1;`
const result = await this.execute(sql, [this.workspace, thread], 'find thread')
return result.map((it: any) => toThread(it))[0]
@@ -563,27 +599,27 @@ export class MessagesDb extends BaseDb {
// Find messages groups
async findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]> {
const select = `
SELECT mg.card_id,
mg.blob_id,
mg.from_date,
mg.to_date,
mg.count,
patches
FROM ${TableName.MessagesGroup} mg
CROSS JOIN LATERAL (
SELECT jsonb_agg(jsonb_build_object(
'message_id', p.message_id::text,
'message_created', p.message_created,
'type', p.type,
'content', p.content,
'creator', p.creator,
'created', p.created
) ORDER BY p.created) AS patches
FROM ${TableName.Patch} p
WHERE p.workspace_id = mg.workspace_id
AND p.card_id = mg.card_id
AND p.message_created BETWEEN mg.from_date AND mg.to_date
) sub`
SELECT mg.card_id,
mg.blob_id,
mg.from_date,
mg.to_date,
mg.count,
patches
FROM ${TableName.MessagesGroup} mg
CROSS JOIN LATERAL (
SELECT jsonb_agg(jsonb_build_object(
'message_id', p.message_id::text,
'message_created', p.message_created,
'type', p.type,
'data', p.data,
'creator', p.creator,
'created', p.created
) ORDER BY p.created) AS patches
FROM ${TableName.Patch} p
WHERE p.workspace_id = mg.workspace_id
AND p.card_id = mg.card_id
AND p.message_created BETWEEN mg.from_date AND mg.to_date
) sub`
const { where, values } = this.buildMessagesGroupWhere(params)
const orderBy =
+1 -1
View File
@@ -219,7 +219,7 @@ export class NotificationsDb extends BaseDb {
JSON_AGG(
JSONB_BUILD_OBJECT(
'patch_type', p.type,
'patch_content', p.content,
'patch_data', p.data,
'patch_creator', p.creator,
'patch_created', p.created
) ORDER BY p.created DESC
+14 -1
View File
@@ -54,6 +54,18 @@ export interface MessageDb {
external_id?: string
}
export const messageSchema: Record<keyof MessageDb, string> = {
id: 'int8',
workspace_id: 'uuid',
card_id: 'varchar',
content: 'text',
creator: 'varchar',
created: 'timestamptz',
type: 'varchar',
data: 'jsonb',
external_id: 'varchar'
}
export interface MessagesGroupDb {
workspace_id: WorkspaceID
card_id: CardID
@@ -69,7 +81,7 @@ export interface PatchDb {
card_id: CardID
message_id: MessageID
type: PatchType
content: RichText
data: Record<string, any>
creator: SocialID
created: Date
message_created: Date
@@ -103,6 +115,7 @@ export interface ThreadDb {
message_id: MessageID
message_created: Date
thread_id: CardID
thread_type: CardType
replies_count: number
last_reply: Date
}
+115 -206
View File
@@ -17,17 +17,60 @@ import type postgres from 'postgres'
const migrationsTableName = 'communication._migrations'
let isInitialized = false
let isSchemaInitialized = false
let initPromise: Promise<void> | null = null
export async function initSchema(sql: postgres.Sql) {
if (isInitialized) return
export function isInitialized(): boolean {
return isSchemaInitialized
}
export async function initSchema(sql: postgres.Sql): Promise<void> {
if (isInitialized()) return
if (!initPromise) {
initPromise = (async () => {
const maxAttempts = 3
const retryDelay = 3000
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await init(sql)
isSchemaInitialized = true
return
} catch (err) {
if (attempt === maxAttempts) {
throw err
}
console.warn(`InitSchema attempt ${attempt} failed, retrying in ${retryDelay}ms…`, err)
await delay(retryDelay)
}
}
})()
.catch((err) => {
throw err
})
.finally(() => {
initPromise = null
})
}
await initPromise
}
function delay(ms: number): Promise<void> {
return new Promise((res) => setTimeout(res, ms))
}
async function init(sql: postgres.Sql) {
if (isSchemaInitialized) return
const start = performance.now()
console.log('🗃️ Initializing schema...')
await sql.unsafe('CREATE SCHEMA IF NOT EXISTS communication;')
await sql.unsafe(`CREATE TABLE IF NOT EXISTS ${migrationsTableName}
(
name VARCHAR(255) NOT NULL,
created_on TIMESTAMPTZ NOT NULL DEFAULT now()
created_on TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (name)
)`)
const appliedMigrations = await sql.unsafe(`SELECT name
@@ -50,103 +93,100 @@ export async function initSchema(sql: postgres.Sql) {
throw err
}
}
isInitialized = true
isSchemaInitialized = true
const end = performance.now()
const resTime = (end - start) / 1000
console.log(`🎉 All migrations complete in ${resTime.toFixed(2)} sec`)
}
function getMigrations(): [string, string][] {
return [
migrationV1_1(),
migrationV2_1(),
migrationV3_1(),
migrationV4_1(),
migrationV4_2(),
migrationV5_1(),
migrationV5_2(),
migrationV5_3(),
migrationV5_4(),
migrationV5_5(),
migrationV5_6(),
migrationV6_1(),
migrationV6_2()
]
return [migrationV1_1(), migrationV1_2()]
}
function migrationV1_1(): [string, string] {
const sql = `
DROP SCHEMA IF EXISTS communication CASCADE;
CREATE SCHEMA IF NOT EXISTS communication;
CREATE TABLE IF NOT EXISTS ${migrationsTableName}
(
name VARCHAR(255) NOT NULL,
created_on TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (name)
)
`
return ['recreate_schema-v1_1', sql]
}
function migrationV1_2(): [string, string] {
const sql = `
CREATE TABLE IF NOT EXISTS communication.messages
(
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
id INT8 NOT NULL,
id INT8 NOT NULL DEFAULT unique_rowid(),
content TEXT NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL,
type VARCHAR(255) NOT NULL,
data JSONB NOT NULL DEFAULT '{}',
external_id VARCHAR(255),
PRIMARY KEY (workspace_id, card_id, id)
);
CREATE INDEX IF NOT EXISTS idx_messages_workspace_card ON communication.messages (workspace_id, card_id);
CREATE INDEX IF NOT EXISTS idx_messages_workspace_card_id ON communication.messages (workspace_id, card_id, id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_unique_workspace_card_external_id
ON communication.messages (workspace_id, card_id, external_id)
WHERE external_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS communication.messages_groups
(
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
blob_id UUID NOT NULL,
from_sec TIMESTAMPTZ(0) NOT NULL,
to_sec TIMESTAMPTZ(0) NOT NULL,
count INT NOT NULL,
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
blob_id UUID NOT NULL,
from_date TIMESTAMPTZ NOT NULL,
to_date TIMESTAMPTZ NOT NULL,
count INT NOT NULL,
PRIMARY KEY (workspace_id, card_id, blob_id)
);
CREATE INDEX IF NOT EXISTS idx_messages_groups_workspace_card ON communication.messages_groups (workspace_id, card_id);
CREATE TABLE IF NOT EXISTS communication.patch
(
id INT8 NOT NULL DEFAULT unique_rowid(),
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
type VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL,
message_created_sec TIMESTAMPTZ(0) NOT NULL,
id INT8 NOT NULL DEFAULT unique_rowid(),
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
type VARCHAR(255) NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL,
message_created TIMESTAMPTZ NOT NULL,
data JSONB NOT NULL DEFAULT '{}',
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_patch_workspace_card_message ON communication.patch (workspace_id, card_id, message_id);
CREATE TABLE IF NOT EXISTS communication.files
(
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
blob_id UUID NOT NULL,
filename VARCHAR(255) NOT NULL,
type VARCHAR(255) NOT NULL,
size INT8 NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now(),
message_created_sec TIMESTAMPTZ(0) NOT NULL,
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
blob_id UUID NOT NULL,
filename VARCHAR(255) NOT NULL,
type VARCHAR(255) NOT NULL,
size INT8 NOT NULL,
meta JSONB NOT NULL DEFAULT '{}',
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now(),
message_created TIMESTAMPTZ NOT NULL,
PRIMARY KEY (workspace_id, card_id, message_id, blob_id)
);
CREATE INDEX IF NOT EXISTS files_workspace_card_message_idx ON communication.files (workspace_id, card_id, message_id);
CREATE TABLE IF NOT EXISTS communication.reactions
(
workspace_id UUID NOT NULL,
@@ -155,38 +195,37 @@ function migrationV1_1(): [string, string] {
reaction VARCHAR(100) NOT NULL,
creator VARCHAR(255) NOT NULL,
created TIMESTAMPTZ NOT NULL DEFAULT now(),
FOREIGN KEY (workspace_id, card_id, message_id) REFERENCES communication.messages (workspace_id, card_id, id) ON DELETE CASCADE,
PRIMARY KEY (workspace_id, card_id, message_id, creator, reaction)
PRIMARY KEY (workspace_id, card_id, message_id, creator, reaction),
FOREIGN KEY (workspace_id, card_id, message_id) REFERENCES communication.messages (workspace_id, card_id, id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_reactions_workspace_card_message ON communication.reactions (workspace_id, card_id, message_id);
CREATE TABLE IF NOT EXISTS communication.thread
(
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
thread_id VARCHAR(255) NOT NULL,
replies_count INT NOT NULL,
last_reply TIMESTAMPTZ NOT NULL,
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
message_id INT8 NOT NULL,
thread_id VARCHAR(255) NOT NULL,
thread_type VARCHAR(255) NOT NULL,
replies_count INT NOT NULL,
last_reply TIMESTAMPTZ NOT NULL,
message_created TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (workspace_id, thread_id),
UNIQUE (workspace_id, card_id, message_id)
CONSTRAINT thread_unique_constraint UNIQUE (workspace_id, card_id, message_id)
);
CREATE INDEX IF NOT EXISTS idx_thread_workspace_card_message ON communication.thread (workspace_id, thread_id);
CREATE INDEX IF NOT EXISTS idx_thread_workspace_thread_message ON communication.thread (workspace_id, thread_id);
CREATE INDEX IF NOT EXISTS idx_thread_workspace_card_message ON communication.thread (workspace_id, card_id, message_id);
CREATE TABLE IF NOT EXISTS communication.notification_context
(
id INT8 NOT NULL DEFAULT unique_rowid(),
workspace_id UUID NOT NULL,
card_id VARCHAR(255) NOT NULL,
account UUID NOT NULL,
last_view TIMESTAMPTZ NOT NULL DEFAULT now(),
last_update TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (id),
UNIQUE (workspace_id, card_id, account)
);
@@ -195,10 +234,9 @@ function migrationV1_1(): [string, string] {
(
id INT8 NOT NULL DEFAULT unique_rowid(),
context_id INT8 NOT NULL,
message_id INT8,
message_id INT8 NOT NULL,
created TIMESTAMPTZ NOT NULL,
content JSONB NOT NULL DEFAULT '{}',
PRIMARY KEY (id),
FOREIGN KEY (context_id) REFERENCES communication.notification_context (id) ON DELETE CASCADE
);
@@ -211,15 +249,10 @@ function migrationV1_1(): [string, string] {
card_id VARCHAR(255) NOT NULL,
account UUID NOT NULL,
date TIMESTAMPTZ NOT NULL DEFAULT now(),
card_type VARCHAR(255) NOT NULL,
PRIMARY KEY (workspace_id, card_id, account)
);
`
return ['init_tables_01', sql]
}
function migrationV2_1(): [string, string] {
const sql = `
CREATE TABLE IF NOT EXISTS communication.label
(
workspace_id UUID NOT NULL,
@@ -231,129 +264,5 @@ function migrationV2_1(): [string, string] {
PRIMARY KEY (workspace_id, card_id, label_id, account)
);
`
return ['init_labels_02', sql]
}
function migrationV3_1(): [string, string] {
const sql = `
ALTER TABLE communication.collaborators
ADD COLUMN IF NOT EXISTS card_type VARCHAR(255) NOT NULL DEFAULT 'card:class:Card';
`
return ['add_card_type_to_collaborators_03', sql]
}
function migrationV4_1(): [string, string] {
const sql = `
ALTER TABLE communication.messages
ADD COLUMN IF NOT EXISTS external_id VARCHAR(255);
`
return ['message_add_external_id_column', sql]
}
function migrationV4_2(): [string, string] {
const sql = `
CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_unique_external_id
ON communication.messages (external_id)
WHERE external_id IS NOT NULL;
`
return ['message_add_external_id_column_unique_index', sql]
}
function migrationV5_1(): [string, string] {
const sql = `
ALTER TABLE communication.reactions
DROP CONSTRAINT IF EXISTS reactions_workspace_id_card_id_message_id_fkey;
`
return ['remove-reactions-fk_v5_1', sql]
}
function migrationV5_2(): [string, string] {
const sql = `
ALTER TABLE communication.messages
ALTER COLUMN id SET DEFAULT unique_rowid();
ALTER TABLE communication.messages
DROP CONSTRAINT IF EXISTS messages_pkey,
ADD CONSTRAINT messages_pkey PRIMARY KEY (id);`
return ['migrate-message-id_v5_2', sql]
}
function migrationV5_3(): [string, string] {
const sql = `
ALTER TABLE communication.messages_groups ADD COLUMN from_date TIMESTAMPTZ;
ALTER TABLE communication.messages_groups ADD COLUMN to_date TIMESTAMPTZ;
ALTER TABLE communication.patch ADD COLUMN message_created TIMESTAMPTZ;
ALTER TABLE communication.files ADD COLUMN message_created TIMESTAMPTZ;
ALTER TABLE communication.thread ADD COLUMN IF NOT EXISTS message_created TIMESTAMPTZ NOT NULL DEFAULT now();
DROP INDEX IF EXISTS communication.thread_workspace_id_card_id_message_id_key CASCADE;
ALTER TABLE communication.thread ADD CONSTRAINT thread_unique_constraint UNIQUE (message_id);
`
return ['add-date-columns_v5_3', sql]
}
function migrationV5_4(): [string, string] {
const sql = `
UPDATE communication.messages_groups
SET from_date = from_sec::TIMESTAMPTZ,
to_date = to_sec::TIMESTAMPTZ;
ALTER TABLE communication.messages_groups ALTER COLUMN from_date SET NOT NULL;
ALTER TABLE communication.messages_groups ALTER COLUMN to_date SET NOT NULL;
ALTER TABLE communication.messages_groups DROP COLUMN from_sec;
ALTER TABLE communication.messages_groups DROP COLUMN to_sec;
UPDATE communication.patch
SET message_created = message_created_sec::TIMESTAMPTZ;
ALTER TABLE communication.patch ALTER COLUMN message_created SET NOT NULL;
ALTER TABLE communication.patch DROP COLUMN message_created_sec;
UPDATE communication.files
SET message_created = message_created_sec::TIMESTAMPTZ;
ALTER TABLE communication.files ALTER COLUMN message_created SET NOT NULL;
ALTER TABLE communication.files DROP COLUMN message_created_sec;
`
return ['migrate-date-values_v5_4', sql]
}
function migrationV5_5(): [string, string] {
const sql = `
ALTER TABLE communication.reactions
DROP CONSTRAINT IF EXISTS reactions_pkey;
ALTER TABLE communication.reactions
ADD CONSTRAINT reactions_pkey PRIMARY KEY (message_id, creator, reaction);
`
return ['migrate-reactions-pk_v5_5', sql]
}
function migrationV5_6(): [string, string] {
const sql = `
ALTER TABLE communication.files
DROP CONSTRAINT IF EXISTS files_pkey;
ALTER TABLE communication.files
ADD CONSTRAINT files_pkey PRIMARY KEY (message_id, blob_id);
ALTER TABLE communication.reactions
ADD CONSTRAINT reactions_message_fkey FOREIGN KEY (message_id)
REFERENCES communication.messages (id) ON DELETE CASCADE;
`
return ['migrate-constraints_v5_6', sql]
}
function migrationV6_1(): [string, string] {
const sql = `
DROP INDEX IF EXISTS communication.idx_messages_unique_external_id CASCADE;
`
return ['message_drop_external_id_unique_index', sql]
}
function migrationV6_2(): [string, string] {
const sql = `
CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_unique_workspace_card_external_id
ON communication.messages (workspace_id, card_id, external_id)
WHERE external_id IS NOT NULL;
`
return ['idx_messages_unique_workspace_card_external_id', sql]
return ['reinit_tables-v1_2', sql]
}
+1 -1
View File
@@ -951,7 +951,7 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
result.push(PatchType.addFile, PatchType.removeFile)
}
if (this.params.replies === true) {
result.push(PatchType.addReply, PatchType.removeReply)
result.push(PatchType.updateThread)
}
return result
}
@@ -17,7 +17,6 @@ import {
type FindNotificationContextParams,
type Notification,
type NotificationContext,
PatchType,
SortingOrder,
type WorkspaceID
} from '@hcengineering/communication-types'
@@ -43,8 +42,6 @@ import { QueryResult } from '../result'
import { WindowImpl } from '../window'
import { loadMessageFromGroup } from '../utils'
const allowedPatchTypes = [PatchType.update, PatchType.addReaction, PatchType.removeReaction]
export class NotificationContextsQuery implements PagedQuery<NotificationContext, FindNotificationContextParams> {
private result: QueryResult<NotificationContext> | Promise<QueryResult<NotificationContext>>
private forward: Promise<NotificationContext[]> | NotificationContext[] = []
@@ -311,9 +308,7 @@ export class NotificationContextsQuery implements PagedQuery<NotificationContext
notifications: context.notifications?.map((it) => ({
...it,
message:
it.messageId === event.patch.message && it.message != null
? applyPatch(it.message, event.patch, allowedPatchTypes)
: it.message
it.messageId === event.patch.message && it.message != null ? applyPatch(it.message, event.patch) : it.message
}))
})
+1 -1
View File
@@ -29,7 +29,7 @@
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/communication-shared": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/core": "^0.7.28",
"@hcengineering/core": "^0.7.88",
"snappyjs": "^0.7.0"
},
"repository": {
+8 -4
View File
@@ -102,7 +102,9 @@ class RestClientImpl implements RestClient {
creator: SocialID,
type: MessageType,
data?: MessageData,
created?: Date
created?: Date,
externalId?: string,
id?: MessageID
): Promise<CreateMessageResult> {
const result = await this.event({
type: MessageRequestEventType.CreateMessage,
@@ -112,7 +114,9 @@ class RestClientImpl implements RestClient {
content,
creator,
data,
created
created,
externalId,
id
})
return result as CreateMessageResult
}
@@ -130,8 +134,8 @@ class RestClientImpl implements RestClient {
messageCreated,
card,
message,
content,
creator
creator,
data: { content }
})
}
+1 -1
View File
@@ -23,7 +23,7 @@
},
"dependencies": {
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/core": "^0.7.28"
"@hcengineering/core": "^0.7.88"
},
"peerDependencies": {
"typescript": "^5.6.3"
+13 -4
View File
@@ -39,7 +39,8 @@ import type {
Label,
FindLabelsParams,
LabelID,
CardType
CardType,
PatchData
} from '@hcengineering/communication-types'
export interface DbAdapter {
@@ -50,7 +51,8 @@ export interface DbAdapter {
creator: SocialID,
created: Date,
data?: MessageData,
externalId?: string
externalId?: string,
id?: MessageID
): Promise<MessageID>
removeMessages(card: CardID, ids: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]>
@@ -59,7 +61,7 @@ export interface DbAdapter {
message: MessageID,
messageCreated: Date,
type: PatchType,
content: RichText,
data: PatchData,
creator: SocialID,
created: Date
): Promise<void>
@@ -96,7 +98,14 @@ export interface DbAdapter {
): Promise<void>
removeFile(card: CardID, message: MessageID, blobId: BlobID): Promise<void>
createThread(card: CardID, message: MessageID, messageCreated: Date, thread: CardID, created: Date): Promise<void>
createThread(
card: CardID,
message: MessageID,
messageCreated: Date,
thread: CardID,
threadType: CardType,
created: Date
): Promise<void>
updateThread(thread: CardID, op: 'increment' | 'decrement', lastReply?: Date): Promise<void>
findMessages(params: FindMessagesParams): Promise<Message[]>
@@ -23,7 +23,8 @@ import type {
PatchType,
MessageType,
MessageData,
CardType
CardType,
PatchData
} from '@hcengineering/communication-types'
import type { BaseRequestEvent } from './common'
@@ -70,6 +71,7 @@ export interface CreateMessageEvent extends BaseRequestEvent {
data?: MessageData
externalId?: string
created?: Date
id?: MessageID
}
export interface RemoveMessagesEvent extends BaseRequestEvent {
@@ -84,7 +86,7 @@ export interface CreatePatchEvent extends BaseRequestEvent {
card: CardID
message: MessageID
messageCreated: Date
content: RichText
data: PatchData
creator: SocialID
}
@@ -133,6 +135,7 @@ export interface CreateThreadEvent extends BaseRequestEvent {
message: MessageID
messageCreated: Date
thread: CardID
threadType: CardType
}
export interface UpdateThreadEvent extends BaseRequestEvent {
+4 -1
View File
@@ -23,7 +23,9 @@ import type {
NotificationContext,
Notification,
FindLabelsParams,
Label
Label,
FindCollaboratorsParams,
Collaborator
} from '@hcengineering/communication-types'
import type { Account } from '@hcengineering/core'
@@ -49,6 +51,7 @@ export interface ServerApi {
): Promise<Notification[]>
findLabels(session: SessionData, params: FindLabelsParams): Promise<Label[]>
findCollaborators(session: SessionData, params: FindCollaboratorsParams): Promise<Collaborator[]>
event(session: SessionData, event: RequestEvent): Promise<EventResult>
+5 -4
View File
@@ -24,13 +24,14 @@
"@types/ws": "^8.5.13"
},
"dependencies": {
"@hcengineering/account-client": "^0.7.28",
"@hcengineering/account-client": "^0.7.88",
"@hcengineering/communication-cockroach": "workspace:*",
"@hcengineering/communication-sdk-types": "workspace:*",
"@hcengineering/communication-types": "workspace:*",
"@hcengineering/core": "^0.7.28",
"@hcengineering/server-token": "^0.7.28",
"@hcengineering/platform": "^0.7.28",
"@hcengineering/communication-yaml": "workspace:*",
"@hcengineering/core": "^0.7.88",
"@hcengineering/server-token": "^0.7.88",
"@hcengineering/platform": "^0.7.88",
"zod": "^3.24.2"
},
"peerDependencies": {
+7 -1
View File
@@ -25,7 +25,9 @@ import type {
WorkspaceID,
Notification,
FindLabelsParams,
Label
Label,
FindCollaboratorsParams,
Collaborator
} from '@hcengineering/communication-types'
import { createDbAdapter } from '@hcengineering/communication-cockroach'
import type { EventResult, RequestEvent, ServerApi, SessionData } from '@hcengineering/communication-sdk-types'
@@ -89,6 +91,10 @@ export class Api implements ServerApi {
return await this.middlewares.findLabels(session, params)
}
async findCollaborators(session: SessionData, params: FindCollaboratorsParams): Promise<Collaborator[]> {
return await this.middlewares.findCollaborators(session, params)
}
async unsubscribeQuery(session: SessionData, id: number): Promise<void> {
await this.middlewares.unsubscribeQuery(session, id)
}
+2 -1
View File
@@ -19,6 +19,7 @@ export function getMetadata(): Metadata {
return {
msg2fileUrl: process.env.MSG2FILE_URL ?? '',
accountsUrl: process.env.ACCOUNTS_URL ?? '',
secret: process.env.SERVER_SECRET
secret: process.env.SERVER_SECRET,
filesUrl: process.env.FILES_URL ?? ''
}
}
+21 -5
View File
@@ -29,7 +29,9 @@ import type {
NotificationContext,
Notification,
FindLabelsParams,
Label
Label,
FindCollaboratorsParams,
Collaborator
} from '@hcengineering/communication-types'
import type { Middleware, MiddlewareContext, QueryId } from '../types'
@@ -72,12 +74,16 @@ export class BaseMiddleware implements Middleware {
return await this.provideFindLabels(session, params, queryId)
}
async findCollaborators(session: SessionData, params: FindCollaboratorsParams): Promise<Collaborator[]> {
return await this.provideFindCollaborators(session, params)
}
async event(session: SessionData, event: RequestEvent, derived: boolean): Promise<EventResult> {
return await this.provideEvent(session, event, derived)
}
async response(session: SessionData, event: ResponseEvent): Promise<void> {
return await this.provideResponse(session, event)
async response(session: SessionData, event: ResponseEvent, derived: boolean): Promise<void> {
return await this.provideResponse(session, event, derived)
}
unsubscribeQuery(session: SessionData, queryId: number): void {
@@ -151,9 +157,19 @@ export class BaseMiddleware implements Middleware {
return []
}
protected async provideResponse(session: SessionData, event: ResponseEvent): Promise<void> {
protected async provideFindCollaborators(
session: SessionData,
params: FindCollaboratorsParams
): Promise<Collaborator[]> {
if (this.next !== undefined) {
return this.next.response(session, event)
return this.next.findCollaborators(session, params)
}
return []
}
protected async provideResponse(session: SessionData, event: ResponseEvent, derived: boolean): Promise<void> {
if (this.next !== undefined) {
return this.next.response(session, event, derived)
}
}
}
+2 -2
View File
@@ -118,7 +118,7 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware {
data.contextQueries.delete(queryId)
}
async response(session: SessionData, event: ResponseEvent): Promise<void> {
async response(session: SessionData, event: ResponseEvent, derived: boolean): Promise<void> {
const sessionIds: string[] = []
for (const [sessionId, session] of this.dataBySessionId.entries()) {
if (this.match(event, session)) {
@@ -133,7 +133,7 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware {
this.context.ctx.error('Failed to broadcast event', { error: e })
}
}
await this.provideResponse(session, event)
await this.provideResponse(session, event, derived)
}
closeSession(sessionId: string): void {
+29 -21
View File
@@ -13,19 +13,21 @@
// limitations under the License.
//
import type {
FindLabelsParams,
FindMessagesGroupsParams,
FindMessagesParams,
FindNotificationContextParams,
FindNotificationsParams,
Label,
Message,
MessagesGroup,
Notification,
NotificationContext,
Patch,
Reaction
import {
type Collaborator,
type FindCollaboratorsParams,
type FindLabelsParams,
type FindMessagesGroupsParams,
type FindMessagesParams,
type FindNotificationContextParams,
type FindNotificationsParams,
type Label,
type Message,
type MessagesGroup,
type Notification,
type NotificationContext,
type Patch,
type Reaction
} from '@hcengineering/communication-types'
import {
type AddCollaboratorsEvent,
@@ -117,10 +119,14 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
return await this.db.findLabels(params)
}
async event(session: SessionData, event: RequestEvent): Promise<EventResult> {
async findCollaborators(_: SessionData, params: FindCollaboratorsParams): Promise<Collaborator[]> {
return await this.db.findCollaborators(params)
}
async event(session: SessionData, event: RequestEvent, derived: boolean): Promise<EventResult> {
const result = await this.processEvent(session, event)
if (result.responseEvent) {
void this.context.head?.response(session, result.responseEvent)
void this.context.head?.response(session, result.responseEvent, derived)
}
return result.result ?? {}
@@ -220,7 +226,8 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
event.creator,
created,
event.data,
event.externalId
event.externalId,
event.id
)
const message: Message = {
id,
@@ -257,19 +264,19 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
event.message,
event.messageCreated,
event.patchType,
event.content,
event.data,
event.creator,
created
)
const patch: Patch = {
const patch = {
type: event.patchType,
messageCreated: event.messageCreated,
message: event.message,
content: event.content,
data: event.data,
creator: event.creator,
created
}
} as Patch
const responseEvent: PatchCreatedEvent = {
_id: event._id,
type: MessageResponseEventType.PatchCreated,
@@ -505,13 +512,14 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
private async createThread(event: CreateThreadEvent): Promise<Result> {
const date = new Date()
await this.db.createThread(event.card, event.message, event.messageCreated, event.thread, date)
await this.db.createThread(event.card, event.message, event.messageCreated, event.thread, event.threadType, date)
const responseEvent: ThreadCreatedEvent = {
_id: event._id,
type: MessageResponseEventType.ThreadCreated,
thread: {
card: event.card,
thread: event.thread,
threadType: event.threadType,
message: event.message,
messageCreated: event.messageCreated,
repliesCount: 0,
+2 -1
View File
@@ -33,13 +33,14 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
this.ctx = context.ctx.newChild('triggers', {})
}
async response(session: SessionData, event: ResponseEvent): Promise<void> {
async response(session: SessionData, event: ResponseEvent, derived: boolean): Promise<void> {
const ctx: Omit<TriggerCtx, 'ctx'> = {
metadata: this.context.metadata,
db: this.db,
workspace: this.context.workspace,
account: session.account,
registeredCards: this.context.registeredCards,
derived,
execute: async (event: RequestEvent) => {
return (await this.context.head?.event(session, event, true)) ?? {}
}
+17 -3
View File
@@ -22,6 +22,8 @@ import {
NotificationRequestEventType
} from '@hcengineering/communication-sdk-types'
import type {
Collaborator,
FindCollaboratorsParams,
FindLabelsParams,
FindMessagesGroupsParams,
FindMessagesParams,
@@ -90,6 +92,11 @@ export class ValidateMiddleware extends BaseMiddleware implements Middleware {
return await this.provideFindLabels(session, params, queryId)
}
async findCollaborators(session: SessionData, params: FindCollaboratorsParams): Promise<Collaborator[]> {
this.validate(params, FindCollaboratorsParamsSchema)
return await this.provideFindCollaborators(session, params)
}
async event(session: SessionData, event: RequestEvent, derived: boolean): Promise<EventResult> {
if (derived) return await this.provideEvent(session, event, derived)
switch (event.type) {
@@ -165,6 +172,7 @@ const CardType = z.string()
const ContextID = z.string()
const LabelID = z.string()
const MessageData = z.any()
const PatchData = z.any()
const MessageID = z.string()
const MessageType = z.string()
const MessagesGroup = z.any()
@@ -233,6 +241,10 @@ const FindLabelsParamsSchema = FindParamsSchema.extend({
account: AccountID.optional()
}).strict()
const FindCollaboratorsParamsSchema = FindParamsSchema.extend({
card: CardID.optional(),
account: z.union([AccountID, z.array(AccountID)]).optional()
}).strict()
//Events
const BaseRequestEventSchema = z
@@ -267,7 +279,8 @@ const CreateMessageEventSchema = BaseRequestEventSchema.extend({
creator: SocialID,
data: MessageData.optional(),
externalId: z.string().optional(),
created: Date.optional()
created: Date.optional(),
id: MessageID.optional()
}).strict()
const RemoveMessagesEventSchema = BaseRequestEventSchema.extend({
@@ -282,7 +295,7 @@ const CreatePatchEventSchema = BaseRequestEventSchema.extend({
card: CardID,
message: MessageID,
messageCreated: Date,
content: RichText,
data: PatchData,
creator: SocialID
}).strict()
@@ -329,7 +342,8 @@ const CreateThreadEventSchema = BaseRequestEventSchema.extend({
card: CardID,
message: MessageID,
messageCreated: Date,
thread: CardID
thread: CardID,
threadType: CardType
}).strict()
const UpdateThreadEventSchema = BaseRequestEventSchema.extend({
+7
View File
@@ -16,6 +16,8 @@
import type { MeasureContext } from '@hcengineering/core'
import type { DbAdapter, EventResult, RequestEvent, SessionData } from '@hcengineering/communication-sdk-types'
import type {
Collaborator,
FindCollaboratorsParams,
FindLabelsParams,
FindMessagesGroupsParams,
FindMessagesParams,
@@ -149,6 +151,11 @@ export class Middlewares {
return await this.head.findLabels(session, params)
}
async findCollaborators(session: SessionData, params: FindCollaboratorsParams): Promise<Collaborator[]> {
if (this.head === undefined) return []
return await this.head.findCollaborators(session, params)
}
async unsubscribeQuery(session: SessionData, id: number): Promise<void> {
if (this.head === undefined) return
this.head?.unsubscribeQuery(session, id)
+96 -10
View File
@@ -25,14 +25,23 @@ import {
NotificationRequestEventType,
type PatchCreatedEvent,
type RequestEvent,
type ThreadCreatedEvent,
type UpdateThreadEvent
} from '@hcengineering/communication-sdk-types'
import { type CardID, PatchType, type File } from '@hcengineering/communication-types'
import {
type AddFilePatchData,
type CardID,
type Message,
MessageType,
PatchType
} from '@hcengineering/communication-types'
import { generateToken } from '@hcengineering/server-token'
import { concatLink, systemAccountUuid } from '@hcengineering/core'
import { generateMessageId } from '@hcengineering/communication-shared'
import type { TriggerCtx, TriggerFn, Triggers } from '../types'
import { findAccount } from '../utils'
import { findMessageInFiles } from './utils'
async function onMessagesGroupCreated(ctx: TriggerCtx, event: MessagesGroupCreatedEvent): Promise<RequestEvent[]> {
ctx.registeredCards.delete(event.group.card)
@@ -49,11 +58,11 @@ async function onMessagesRemoved(ctx: TriggerCtx, event: MessagesRemovedEvent):
return event.messages.flatMap(() => {
const patchEvent: CreatePatchEvent = {
type: MessageRequestEventType.CreatePatch,
patchType: PatchType.removeReply,
patchType: PatchType.updateThread,
card: thread.card,
message: thread.message,
messageCreated: thread.messageCreated,
content: thread.thread,
data: { thread: thread.thread, threadType: thread.threadType, replies: 'decrement' },
creator: socialId
}
const threadEvent: UpdateThreadEvent = {
@@ -62,7 +71,7 @@ async function onMessagesRemoved(ctx: TriggerCtx, event: MessagesRemovedEvent):
replies: 'decrement'
}
return [patchEvent, threadEvent]
return [threadEvent]
})
}
@@ -71,7 +80,7 @@ async function onFileCreated(ctx: TriggerCtx, event: FileCreatedEvent): Promise<
if (message !== undefined) return []
const { file } = event
const patchContent: Omit<File, 'card' | 'message' | 'created' | 'creator' | 'messageCreated'> = {
const patchData: AddFilePatchData = {
blobId: file.blobId,
type: file.type,
filename: file.filename,
@@ -85,7 +94,7 @@ async function onFileCreated(ctx: TriggerCtx, event: FileCreatedEvent): Promise<
card: event.card,
message: file.message,
messageCreated: file.messageCreated,
content: JSON.stringify(patchContent),
data: patchData,
creator: file.creator
}
]
@@ -103,7 +112,7 @@ async function onFileRemoved(ctx: TriggerCtx, event: FileRemovedEvent): Promise<
card: event.card,
message: event.message,
messageCreated: event.messageCreated,
content: JSON.stringify({ blobId }),
data: { blobId },
creator: event.creator
}
]
@@ -157,6 +166,7 @@ async function addCollaborators(ctx: TriggerCtx, event: MessageCreatedEvent): Pr
}
async function addThreadReply(ctx: TriggerCtx, event: MessageCreatedEvent): Promise<RequestEvent[]> {
if (event.message.type !== MessageType.Message || ctx.derived) return []
const { message } = event
const thread = await ctx.db.findThread(message.card)
if (thread === undefined) return []
@@ -164,11 +174,11 @@ async function addThreadReply(ctx: TriggerCtx, event: MessageCreatedEvent): Prom
return [
{
type: MessageRequestEventType.CreatePatch,
patchType: PatchType.addReply,
patchType: PatchType.updateThread,
card: thread.card,
message: thread.message,
messageCreated: thread.messageCreated,
content: thread.thread,
data: { thread: thread.thread, threadType: thread.threadType, replies: 'increment' },
creator: message.creator
},
{
@@ -180,6 +190,81 @@ async function addThreadReply(ctx: TriggerCtx, event: MessageCreatedEvent): Prom
]
}
async function onThreadCreated(ctx: TriggerCtx, event: ThreadCreatedEvent): Promise<RequestEvent[]> {
let message: Message | undefined = (
await ctx.db.findMessages({
card: event.thread.card,
id: event.thread.message,
limit: 1,
files: true,
reactions: true
})
)[0]
const result: RequestEvent[] = []
if (message === undefined) {
message = await findMessageInFiles(ctx, event.thread.card, event.thread.message, event.thread.messageCreated)
if (message !== undefined) {
result.push({
type: MessageRequestEventType.CreatePatch,
patchType: PatchType.updateThread,
card: event.thread.card,
message: event.thread.message,
messageCreated: event.thread.messageCreated,
data: { thread: event.thread.thread, threadType: event.thread.threadType },
creator: message.creator
})
}
}
if (message === undefined) {
return []
}
const messageId = generateMessageId()
result.push({
type: MessageRequestEventType.CreateMessage,
messageType: message.type,
card: event.thread.thread,
cardType: event.thread.threadType,
content: message.content,
creator: message.creator,
data: message.data,
externalId: message.externalId,
created: message.created,
id: messageId
})
for (const file of message.files) {
result.push({
type: MessageRequestEventType.CreateFile,
card: event.thread.thread,
message: messageId,
messageCreated: message.created,
blobId: file.blobId,
fileType: file.type,
filename: file.filename,
size: file.size,
creator: file.creator
})
}
for (const reaction of message.reactions) {
result.push({
type: MessageRequestEventType.CreateReaction,
card: event.thread.thread,
message: messageId,
messageCreated: message.created,
reaction: reaction.reaction,
creator: reaction.creator
})
}
return result
}
const triggers: Triggers = [
['add_collaborators_on_message_created', MessageResponseEventType.MessageCreated, addCollaborators as TriggerFn],
['add_thread_reply_on_message_created', MessageResponseEventType.MessageCreated, addThreadReply as TriggerFn],
@@ -188,7 +273,8 @@ const triggers: Triggers = [
['on_messages_group_created', MessageResponseEventType.MessagesGroupCreated, onMessagesGroupCreated as TriggerFn],
['remove_reply_on_messages_removed', MessageResponseEventType.MessagesRemoved, onMessagesRemoved as TriggerFn],
['on_file_created', MessageResponseEventType.FileCreated, onFileCreated as TriggerFn],
['on_file_removed', MessageResponseEventType.FileRemoved, onFileRemoved as TriggerFn]
['on_file_removed', MessageResponseEventType.FileRemoved, onFileRemoved as TriggerFn],
['on_thread_created', MessageResponseEventType.ThreadCreated, onThreadCreated as TriggerFn]
]
export default triggers
+76
View File
@@ -0,0 +1,76 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type CardID, type Message, type MessageID, SortingOrder } from '@hcengineering/communication-types'
import { loadGroupFile } from '@hcengineering/communication-yaml'
import { applyPatches } from '@hcengineering/communication-shared'
import type { TriggerCtx } from '../types'
export async function findMessage(
ctx: TriggerCtx,
card: CardID,
id: MessageID,
created: Date
): Promise<Message | undefined> {
const message = (await ctx.db.findMessages({ card, id, limit: 1, files: true }))[0]
if (message !== undefined) {
return message
}
return await findMessageInFiles(ctx, card, id, created)
}
export async function findMessageInFiles(
ctx: TriggerCtx,
card: CardID,
id: MessageID,
created: Date
): Promise<Message | undefined> {
const filesUrl = ctx.metadata.filesUrl
if (filesUrl === '') {
ctx.ctx.error('FILES_URL is missing', { filesUrl })
return undefined
}
const group = (
await ctx.db.findMessagesGroups({
card,
fromDate: { lessOrEqual: created },
toDate: { greaterOrEqual: created },
limit: 1,
order: SortingOrder.Ascending,
orderBy: 'fromDate'
})
)[0]
if (group === undefined) {
return undefined
}
try {
const parsedFile = await loadGroupFile(ctx.workspace, filesUrl, group, { retries: 3 })
const messageFromFile = parsedFile.messages.find((it) => it.id === id)
if (messageFromFile === undefined) {
return undefined
}
const patches = (group.patches ?? []).filter((it) => it.message === id)
return patches.length > 0 ? applyPatches(messageFromFile, patches) : messageFromFile
} catch (e) {
ctx.ctx.error('Failed to find message in files', { card, id, created })
ctx.ctx.error('Error:', { error: e })
}
}
+6 -1
View File
@@ -24,6 +24,8 @@ import type {
} from '@hcengineering/communication-sdk-types'
import type {
CardID,
Collaborator,
FindCollaboratorsParams,
FindLabelsParams,
FindMessagesGroupsParams,
FindMessagesParams,
@@ -40,6 +42,7 @@ import type {
export interface Metadata {
msg2fileUrl: string
accountsUrl: string
filesUrl: string
secret?: string
}
@@ -65,12 +68,13 @@ export interface Middleware {
) => Promise<Notification[]>
findLabels: (session: SessionData, params: FindLabelsParams, queryId?: QueryId) => Promise<Label[]>
findCollaborators: (session: SessionData, params: FindCollaboratorsParams) => Promise<Collaborator[]>
event: (session: SessionData, event: RequestEvent, derived: boolean) => Promise<EventResult>
unsubscribeQuery: (session: SessionData, queryId: number) => void
response: (session: SessionData, event: ResponseEvent) => Promise<void>
response: (session: SessionData, event: ResponseEvent, derived: boolean) => Promise<void>
closeSession: (sessionId: string) => void
close: () => void
@@ -109,6 +113,7 @@ export interface TriggerCtx {
workspace: WorkspaceID
account: Account
registeredCards: Set<CardID>
derived: boolean
execute: (event: RequestEvent) => Promise<EventResult>
}
+3 -38
View File
@@ -16,6 +16,7 @@
import type { AccountID, SocialID } from '@hcengineering/communication-types'
import { generateToken } from '@hcengineering/server-token'
import { systemAccountUuid } from '@hcengineering/core'
import { getClient as getAccountClient } from '@hcengineering/account-client'
import type { TriggerCtx } from './types'
@@ -28,47 +29,11 @@ export async function findAccount(ctx: TriggerCtx, socialString: SocialID): Prom
if (url === '') return undefined
const token = generateToken(systemAccountUuid)
// const account = getAccountClient(ctx.metadata.accountsUrl, token)
const account = getAccountClient(ctx.metadata.accountsUrl, token)
try {
//TODO: FIXME
return await fetchAccount(socialString, url, token)
return (await account.findPersonBySocialId(socialString, true)) as AccountID | undefined
} catch (err: any) {
ctx.ctx.warn('Cannot find account', { socialString, err })
}
}
//TODO: replace with AccountClient
async function fetchAccount(socialId: SocialID, url: string, token: string): Promise<AccountID | undefined> {
const body = {
method: 'findPersonBySocialId' as const,
params: { socialId, requireAccount: true }
}
const request: RequestInit = {
keepalive: true,
headers: {
...(token === undefined
? {}
: {
Authorization: 'Bearer ' + token
})
}
}
const response = await fetch(url, {
...request,
headers: {
...request.headers,
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(body)
})
const result = await response.json()
if (result.error != null) {
throw Error(result.error)
}
return result.result as AccountID | undefined
}
+1
View File
@@ -15,3 +15,4 @@
export * from './retry'
export * from './patch'
export * from './utils'
+33 -46
View File
@@ -16,16 +16,14 @@
import {
PatchType,
type BlobID,
type CardID,
type Message,
type Patch,
type Reaction,
type SocialID,
type File
type AddFilePatchData,
type UpdateThreadPatchData
} from '@hcengineering/communication-types'
type PatchFile = Pick<File, 'blobId' | 'type' | 'filename' | 'size'>
export function applyPatches(message: Message, patches: Patch[], allowedPatchTypes: PatchType[] = []): Message {
if (patches.length === 0) return message
@@ -42,25 +40,24 @@ export function applyPatch(message: Message, patch: Patch, allowedPatchTypes: Pa
return {
...message,
edited: patch.created,
content: patch.content
content: patch.data.content ?? message.content,
data: patch.data.data ?? message.data
}
case PatchType.addReaction:
return addReaction(message, {
message: message.id,
reaction: patch.content,
reaction: patch.data.reaction,
creator: patch.creator,
created: patch.created
})
case PatchType.removeReaction:
return removeReaction(message, patch.content, patch.creator)
case PatchType.addReply:
return addReply(message, patch.content as CardID, patch.created)
case PatchType.removeReply:
return removeReply(message, patch.content as CardID)
return removeReaction(message, patch.data.reaction, patch.creator)
case PatchType.addFile:
return addFile(message, JSON.parse(patch.content) as PatchFile, patch.created, patch.creator)
return addFile(message, patch.data, patch.created, patch.creator)
case PatchType.removeFile:
return removeFile(message, patch.content as BlobID)
return removeFile(message, patch.data.blobId)
case PatchType.updateThread:
return updateThread(message, patch.data, patch.created)
}
return message
@@ -81,36 +78,38 @@ function removeReaction(message: Message, emoji: string, creator: SocialID): Mes
}
}
function addReply(message: Message, thread: CardID, created: Date): Message {
if (message.thread === undefined) {
return {
...message,
thread: {
card: message.card,
message: message.id,
messageCreated: message.created,
thread,
repliesCount: 1,
lastReply: created
}
}
function updateThread(message: Message, data: UpdateThreadPatchData, created: Date): Message {
const thread = message.thread ?? {
card: message.card,
message: message.id,
messageCreated: message.created,
thread: data.thread,
threadType: data.threadType,
repliesCount: 0,
lastReply: created
}
if (message.thread.thread !== thread) return message
thread.thread = data.thread
thread.threadType = data.threadType
if (data.replies === 'increment') {
thread.repliesCount = thread.repliesCount + 1
thread.lastReply = created
}
if (data.replies === 'decrement') {
thread.repliesCount = Math.max(thread.repliesCount - 1, 0)
}
return {
...message,
thread: {
...message.thread,
repliesCount: message.thread.repliesCount + 1,
lastReply: created
}
thread
}
}
function addFile(message: Message, file: PatchFile, created: Date, creator: SocialID): Message {
function addFile(message: Message, data: AddFilePatchData, created: Date, creator: SocialID): Message {
message.files.push({
...file,
...data,
card: message.card,
message: message.id,
created,
@@ -129,15 +128,3 @@ function removeFile(message: Message, blobId: BlobID): Message {
files
}
}
function removeReply(message: Message, thread: CardID): Message {
if (message.thread === undefined || message.thread.thread !== thread) return message
return {
...message,
thread: {
...message.thread,
repliesCount: message.thread.repliesCount - 1
}
}
}
+37
View File
@@ -0,0 +1,37 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { MessageID } from '@hcengineering/communication-types'
const COUNTER_BITS = 12n
const RANDOM_BITS = 10n
const MAX_SEQUENCE = (1n << COUNTER_BITS) - 1n
const MAX_RANDOM = (1n << RANDOM_BITS) - 1n
let counter = 0n
/**
* Generate 64-bit MessageID and return it as string.
*/
export function generateMessageId(): MessageID {
const ts = BigInt(Date.now())
counter = counter < MAX_SEQUENCE ? counter + 1n : 0n
const random = BigInt(Math.floor(Math.random() * Number(MAX_RANDOM + 1n)))
const id = (ts << (COUNTER_BITS + RANDOM_BITS)) | (counter << RANDOM_BITS) | random
return id.toString() as MessageID
}
+3 -3
View File
@@ -25,9 +25,9 @@
"typescript": "^5.6.3"
},
"dependencies": {
"@hcengineering/core": "^0.7.28",
"@hcengineering/card": "^0.7.28",
"@hcengineering/tags": "^0.7.28"
"@hcengineering/core": "^0.7.88",
"@hcengineering/card": "^0.7.88",
"@hcengineering/tags": "^0.7.88"
},
"repository": {
"type": "git",
+2 -1
View File
@@ -13,7 +13,7 @@
// limitations under the License.
//
import type { BlobID, CardID, RichText, SocialID } from './core'
import type { BlobID, CardID, CardType, RichText, SocialID } from './core'
import type { Message, MessageID, MessageType, MessageData } from './message'
export interface FileMetadata {
@@ -54,6 +54,7 @@ export interface FileReaction {
export interface FileThread {
thread: CardID
threadType: CardType
repliesCount: number
lastReply: Date
}
+82 -6
View File
@@ -14,7 +14,7 @@
//
import type { Attribute, Class, Mixin, Ref } from '@hcengineering/core'
import type { BlobID, CardID, ID, RichText, SocialID } from './core'
import type { BlobID, CardID, CardType, ID, RichText, SocialID } from './core'
import type { Card, Tag } from '@hcengineering/card'
export type MessageID = ID & { message: true }
@@ -85,23 +85,98 @@ export interface MessagesGroup {
patches?: Patch[]
}
export interface Patch {
interface BasePatch {
message: MessageID
messageCreated: Date
type: PatchType
content: string
creator: SocialID
created: Date
data: Record<string, any>
}
export interface UpdatePatch extends BasePatch {
type: PatchType.update
data: UpdatePatchData
}
export interface AddReactionPatch extends BasePatch {
type: PatchType.addReaction
data: AddReactionPatchData
}
export interface RemoveReactionPatch extends BasePatch {
type: PatchType.removeReaction
data: RemoveReactionPatchData
}
export interface UpdateThreadPatch extends BasePatch {
type: PatchType.updateThread
data: UpdateThreadPatchData
}
export interface AddFilePatch extends BasePatch {
type: PatchType.addFile
data: AddFilePatchData
}
export interface RemoveFilePatch extends BasePatch {
type: PatchType.removeFile
data: RemoveFilePatchData
}
export type Patch =
| UpdatePatch
| AddReactionPatch
| RemoveReactionPatch
| AddFilePatch
| RemoveFilePatch
| UpdateThreadPatch
export type PatchData =
| UpdatePatchData
| AddReactionPatchData
| RemoveReactionPatchData
| AddFilePatchData
| RemoveFilePatchData
| UpdateThreadPatchData
export interface UpdateThreadPatchData {
thread: CardID
threadType: CardType
replies?: 'increment' | 'decrement'
}
export interface UpdatePatchData {
content?: RichText
data?: MessageData
}
export interface AddReactionPatchData {
reaction: string
}
export interface RemoveReactionPatchData {
reaction: string
}
export interface AddFilePatchData {
blobId: BlobID
type: string
filename: string
size: number
}
export interface RemoveFilePatchData {
blobId: BlobID
}
export enum PatchType {
update = 'update',
addReaction = 'addReaction',
removeReaction = 'removeReaction',
addReply = 'addReply',
removeReply = 'removeReply',
addFile = 'addFile',
removeFile = 'removeFile'
removeFile = 'removeFile',
updateThread = 'updateThread'
}
export interface Reaction {
@@ -128,6 +203,7 @@ export interface Thread {
message: MessageID
messageCreated: Date
thread: CardID
threadType: CardType
repliesCount: number
lastReply: Date
}
+1
View File
@@ -29,6 +29,7 @@ export function deserializeMessage(message: Message): FileMessage {
message.thread != null
? {
thread: message.thread.thread,
threadType: message.thread.threadType,
repliesCount: message.thread.repliesCount,
lastReply: message.thread.lastReply
}
+1
View File
@@ -77,6 +77,7 @@ export function parseYaml(data: string): ParsedFile {
message: message.id,
messageCreated: message.created,
thread: message.thread.thread,
threadType: message.thread.threadType,
repliesCount: message.thread.repliesCount,
lastReply: message.thread.lastReply
}