mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-11 12:17:44 +02:00
Fix threads and patches, recreate db schema (#50)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/communication-types": "workspace:*",
|
||||
"@hcengineering/core": "^0.7.28"
|
||||
"@hcengineering/core": "^0.7.88"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.6.3"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 ?? ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) ?? {}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
|
||||
export * from './retry'
|
||||
export * from './patch'
|
||||
export * from './utils'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user