Add card events and files meta (#53)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina
2025-05-14 10:21:09 +04:00
committed by GitHub
parent f7de73eca7
commit cb3ad660fb
33 changed files with 887 additions and 191 deletions
+1 -1
View File
@@ -1 +1 @@
0.1.180
0.1.181
+42 -10
View File
@@ -12,7 +12,7 @@
"eslint-plugin-prettier": "^5.4.0",
"prettier": "^3.5.3",
"turbo": "^2.5.3",
"typescript-eslint": "^8.32.0",
"typescript-eslint": "^8.32.1",
},
},
"packages/client-query": {
@@ -111,6 +111,8 @@
"@hcengineering/core": "^0.7.88",
"@hcengineering/platform": "^0.7.88",
"@hcengineering/server-token": "^0.7.88",
"@hcengineering/text-core": "^0.7.88",
"@hcengineering/text-markdown": "^0.7.88",
"zod": "^3.24.2",
},
"devDependencies": {
@@ -236,6 +238,12 @@
"@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/text-core": ["@hcengineering/text-core@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/text-core/0.7.88/7e53b0da9622709b2d1071049ead3843e0c45764", { "dependencies": { "@hcengineering/core": "^0.7.88", "fast-equals": "^5.2.2" } }, "sha512-mFri9SMzD99uR3/H0gZ4KEekYf3lEmJ2bqEQrjIVmi9z7+CzDTxmKnXeHKzcFpvQaLt5kmbPIoXM4MyxpK7ZXA=="],
"@hcengineering/text-html": ["@hcengineering/text-html@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/text-html/0.7.88/e48beced20921d9e1e9c84548c3caf5c9770bac1", { "dependencies": { "@hcengineering/text-core": "^0.7.88", "htmlparser2": "^9.0.0" } }, "sha512-Wj0gYv0JEUIKpuaDFmRn7/60/SXGg0CZVmzbux0XlUzayly+C4Pkfj5U3G/ZEUyDBtAtyeBdWtnkzgQgN6qOuQ=="],
"@hcengineering/text-markdown": ["@hcengineering/text-markdown@0.7.88", "https://npm.pkg.github.com/download/@hcengineering/text-markdown/0.7.88/86938b13b0bdd81941a22378e3b2e92998f71d80", { "dependencies": { "@hcengineering/text-core": "^0.7.88", "@hcengineering/text-html": "^0.7.88", "fast-equals": "^5.2.2", "markdown-it": "^14.0.0" } }, "sha512-066WeD/6EI5VmFQZ/Cv6eEOFXBd+fA+YPxX5fb0ytlZr5bLpQs6/AifpyqJoZ5gqQzfAf/HmtDVxDl5QU95FGw=="],
"@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.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=="],
@@ -310,21 +318,21 @@
"@types/ws": ["@types/ws@8.18.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.0", "@typescript-eslint/type-utils": "8.32.0", "@typescript-eslint/utils": "8.32.0", "@typescript-eslint/visitor-keys": "8.32.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/type-utils": "8.32.1", "@typescript-eslint/utils": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.0", "@typescript-eslint/types": "8.32.0", "@typescript-eslint/typescript-estree": "8.32.0", "@typescript-eslint/visitor-keys": "8.32.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.32.0", "", { "dependencies": { "@typescript-eslint/types": "8.32.0", "@typescript-eslint/visitor-keys": "8.32.0" } }, "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1" } }, "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.32.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.32.0", "@typescript-eslint/utils": "8.32.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.32.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.32.0", "", {}, "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.32.1", "", {}, "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.0", "", { "dependencies": { "@typescript-eslint/types": "8.32.0", "@typescript-eslint/visitor-keys": "8.32.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.32.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.0", "@typescript-eslint/types": "8.32.0", "@typescript-eslint/typescript-estree": "8.32.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.32.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.0", "", { "dependencies": { "@typescript-eslint/types": "8.32.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
@@ -402,8 +410,16 @@
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"dompurify": ["dompurify@3.2.4", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
@@ -412,6 +428,8 @@
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
@@ -506,6 +524,8 @@
"hls.js": ["hls.js@1.5.20", "", {}, "sha512-uu0VXUK52JhihhnN/MVVo1lvqNNuhoxkonqgO3IpjvQiGpJBdIXMGkofjQb/j9zvV7a1SW8U9g1FslWx/1HOiQ=="],
"htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
@@ -548,6 +568,8 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
"loadjs": ["loadjs@4.3.0", "", {}, "sha512-vNX4ZZLJBeDEOBvdr2v/F+0aN5oMuPu7JTqrMwp+DtgK+AryOlpy6Xtm2/HpNr+azEa828oQjOtWsB6iDtSfSQ=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
@@ -558,10 +580,14 @@
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="],
"mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
@@ -626,6 +652,8 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -712,7 +740,9 @@
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"typescript-eslint": ["typescript-eslint@8.32.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.32.0", "@typescript-eslint/parser": "8.32.0", "@typescript-eslint/utils": "8.32.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-UMq2kxdXCzinFFPsXc9o2ozIpYCCOiEC46MG3yEh5Vipq6BO27otTtEBZA1fQ66DulEUgE97ucQ/3YY66CPg0A=="],
"typescript-eslint": ["typescript-eslint@8.32.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.32.1", "@typescript-eslint/parser": "8.32.1", "@typescript-eslint/utils": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg=="],
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
@@ -742,6 +772,8 @@
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
+1 -1
View File
@@ -19,7 +19,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.4.0",
"prettier": "^3.5.3",
"typescript-eslint": "^8.32.0",
"typescript-eslint": "^8.32.1",
"turbo": "^2.5.3"
},
"packageManager": "bun@1.2.9"
+60 -12
View File
@@ -42,7 +42,9 @@ import {
type LabelID,
type CardType,
type MessageData,
type PatchData
type PatchData,
type File,
type BlobMetadata
} from '@hcengineering/communication-types'
import type { DbAdapter } from '@hcengineering/communication-sdk-types'
@@ -83,7 +85,7 @@ export class CockroachAdapter implements DbAdapter {
return await this.message.createMessage(card, type, content, creator, created, data, externalId, id)
}
async removeMessages(card: CardID, messages: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]> {
async removeMessages(card: CardID, messages?: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]> {
return await this.message.removeMessages(card, messages, socialIds)
}
@@ -99,6 +101,10 @@ export class CockroachAdapter implements DbAdapter {
await this.message.createPatch(card, message, messageCreated, type, data, creator, created)
}
async removePatches(card: CardID): Promise<void> {
await this.message.removePatches(card)
}
async createMessagesGroup(card: CardID, blobId: BlobID, fromDate: Date, toDate: Date, count: number): Promise<void> {
await this.message.createMessagesGroup(card, blobId, fromDate, toDate, count)
}
@@ -136,14 +142,26 @@ export class CockroachAdapter implements DbAdapter {
fileType: string,
filename: string,
size: number,
meta: BlobMetadata | undefined,
creator: SocialID,
created: Date
): Promise<void> {
await this.message.createFile(card, message, messageCreated, blobId, fileType, filename, size, creator, created)
await this.message.createFile(
card,
message,
messageCreated,
blobId,
fileType,
filename,
size,
meta,
creator,
created
)
}
async removeFile(card: CardID, message: MessageID, blobId: BlobID): Promise<void> {
await this.message.removeFile(card, message, blobId)
async removeFiles(query: Partial<File>): Promise<void> {
await this.message.removeFiles(query)
}
async createThread(
@@ -157,8 +175,19 @@ export class CockroachAdapter implements DbAdapter {
await this.message.createThread(card, message, messageCreated, thread, threadType, created)
}
async updateThread(thread: CardID, op: 'increment' | 'decrement', lastReply?: Date): Promise<void> {
await this.message.updateThread(thread, op, lastReply)
async removeThreads(query: Partial<Thread>): Promise<void> {
await this.message.removeThreads(query)
}
async updateThread(
thread: CardID,
update: {
threadType?: CardType
op?: 'increment' | 'decrement'
lastReply?: Date
}
): Promise<void> {
await this.message.updateThread(thread, update)
}
async findMessages(params: FindMessagesParams): Promise<Message[]> {
@@ -182,10 +211,14 @@ export class CockroachAdapter implements DbAdapter {
return await this.notification.addCollaborators(card, cardType, collaborators, date)
}
async removeCollaborators(card: CardID, collaborators: AccountID[]): Promise<void> {
async removeCollaborators(card: CardID, collaborators?: AccountID[]): Promise<void> {
await this.notification.removeCollaborators(card, collaborators)
}
async updateCollaborators(params: FindCollaboratorsParams, data: Partial<Collaborator>): Promise<void> {
await this.notification.updateCollaborators(params, data)
}
async createNotification(context: ContextID, message: MessageID, messageCreated: Date): Promise<NotificationID> {
return await this.notification.createNotification(context, message, messageCreated)
}
@@ -202,8 +235,8 @@ export class CockroachAdapter implements DbAdapter {
await this.notification.updateContext(context, account, lastUpdate, lastView)
}
async removeContext(context: ContextID, account: AccountID): Promise<void> {
await this.notification.removeContext(context, account)
async removeContexts(query: Partial<NotificationContext>): Promise<void> {
await this.notification.removeContexts(query)
}
async findNotificationContexts(params: FindNotificationContextParams): Promise<NotificationContext[]> {
@@ -230,13 +263,28 @@ export class CockroachAdapter implements DbAdapter {
return this.label.createLabel(label, card, cardType, account, created)
}
removeLabel(label: LabelID, card: CardID, account: AccountID): Promise<void> {
return this.label.removeLabel(label, card, account)
removeLabels(query: Partial<Label>): Promise<void> {
return this.label.removeLabels(query)
}
findLabels(params: FindLabelsParams): Promise<Label[]> {
return this.label.findLabels(params)
}
updateLabels(params: FindLabelsParams, data: Partial<Label>): Promise<void> {
return this.label.updateLabels(params, data)
}
//TODO: remove it!
async getAccountByPersonId(_id: string): Promise<AccountID | undefined> {
const sql = `SELECT data ->> 'personUuid' AS "personUuid"
FROM public.contact
WHERE "workspaceId" = $1::uuid
AND _id = $2::varchar
LIMIT 1`
const result = await this.sql.execute(sql, [this.workspace, _id])
return result?.[0]?.personUuid as AccountID
}
}
export async function createDbAdapter(
+76 -31
View File
@@ -53,66 +53,111 @@ export class LabelsDb extends BaseDb {
)
}
async removeLabel(label: LabelID, card: CardID, account: AccountID): Promise<void> {
async removeLabels(query: Partial<Label>): Promise<void> {
const db: Partial<LabelDb> = {
label_id: query.label,
card_id: query.card,
card_type: query.cardType,
account: query.account
}
const entries = Object.entries(db).filter(([_, value]) => value != undefined)
if (entries.length === 0) return
entries.push(['workspace_id', this.workspace])
const whereClauses = entries.map(([key], index) => `${key} = $${index + 1}`)
const whereValues = entries.map(([_, value]) => value)
const sql = `DELETE
FROM ${TableName.Label}
WHERE workspace_id = $1::uuid
AND label_id = $2::varchar
AND card_id = $3::varchar
AND account = $4::uuid`
await this.execute(sql, [this.workspace, label, card, account], 'remove label')
WHERE ${whereClauses.join(' AND ')}`
await this.execute(sql, whereValues, 'remove labels')
}
async updateLabels(params: FindLabelsParams, data: Partial<Label>): Promise<void> {
const dbData: Partial<LabelDb> = {
label_id: data.label,
card_id: data.card,
card_type: data.cardType,
account: data.account,
created: data.created
}
const entries = Object.entries(dbData).filter(([_, value]) => value != undefined)
if (entries.length === 0) return
const setClauses = entries.map(([key], index) => `${key} = $${index + 1}`)
const setValues = entries.map(([_, value]) => value)
const { where, values: whereValues } = this.buildWhere(params, setValues.length, '')
const sql = `UPDATE ${TableName.Label}
SET ${setClauses.join(', ')}
${where}`
await this.execute(sql, [...setValues, ...whereValues], 'update labels')
}
async findLabels(params: FindLabelsParams): Promise<Label[]> {
const select = `SELECT *
FROM ${TableName.Label} l`
const where: string[] = ['l.workspace_id = $1::uuid']
const whereValues: any[] = [this.workspace]
let index = whereValues.length + 1
const { where, values } = this.buildWhere(params)
const limit = params.limit != null ? `LIMIT ${params.limit}` : ''
const orderBy =
params.order != null ? `ORDER BY l.created ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` : ''
const sql = [select, where, orderBy, limit].join(' ')
const result = await this.execute(sql, values, 'find labels')
return result.map((it: any) => toLabel(it))
}
buildWhere(params: FindLabelsParams, startIndex: number = 0, prefix = 'l.'): { where: string; values: any[] } {
const where: string[] = []
const values: any[] = []
let index = startIndex + 1
where.push(`${prefix}workspace_id = $${index++}::uuid`)
values.push(this.workspace)
if (params.label != null) {
const labels = Array.isArray(params.label) ? params.label : [params.label]
if (labels.length === 1) {
where.push(`l.label_id = $${index++}::varchar`)
whereValues.push(labels[0])
where.push(`${prefix}label_id = $${index++}::varchar`)
values.push(labels[0])
} else {
where.push(`l.label_id = ANY($${index++}::varchar[])`)
whereValues.push(labels)
where.push(`${prefix}label_id = ANY($${index++}::varchar[])`)
values.push(labels)
}
}
if (params.card != null) {
where.push(`l.card_id = $${index++}::varchar`)
whereValues.push(params.card)
where.push(`${prefix}card_id = $${index++}::varchar`)
values.push(params.card)
}
if (params.cardType != null) {
const types = Array.isArray(params.cardType) ? params.cardType : [params.cardType]
if (types.length === 1) {
where.push(`l.card_type = $${index++}::varchar`)
whereValues.push(types[0])
where.push(`${prefix}card_type = $${index++}::varchar`)
values.push(types[0])
} else {
where.push(`l.card_type = ANY($${index++}::varchar[])`)
whereValues.push(types)
where.push(`${prefix}card_type = ANY($${index++}::varchar[])`)
values.push(types)
}
}
if (params.account != null) {
where.push(`l.account = $${index++}::uuid`)
whereValues.push(params.account)
where.push(`${prefix}account = $${index++}::uuid`)
values.push(params.account)
}
const limit = params.limit != null ? `LIMIT ${params.limit}` : ''
const orderBy =
params.order != null ? `ORDER BY l.created ${params.order === SortingOrder.Ascending ? 'ASC' : 'DESC'}` : ''
const whereString = `WHERE ${where.join(' AND ')}`
const sql = [select, whereString, orderBy, limit].join(' ')
const result = await this.execute(sql, whereValues, 'find labels')
return result.map((it: any) => toLabel(it))
return { where: `WHERE ${where.join(' AND ')}`, values }
}
}
+2 -1
View File
@@ -129,7 +129,7 @@ export function toReaction(raw: ReactionDb): Reaction {
}
}
export function toFile(raw: FileDb): File {
export function toFile(raw: Omit<FileDb, 'workspace_id'>): File {
return {
card: raw.card_id,
message: String(raw.message_id) as MessageID,
@@ -138,6 +138,7 @@ export function toFile(raw: FileDb): File {
type: raw.type,
filename: raw.filename,
size: parseInt(raw.size as any),
meta: raw.meta,
creator: raw.creator,
created: new Date(raw.created)
}
+90 -26
View File
@@ -29,7 +29,9 @@ import {
type RichText,
type SocialID,
SortingOrder,
type Thread
type Thread,
type File,
type BlobMetadata
} from '@hcengineering/communication-types'
import { BaseDb } from './base'
@@ -90,9 +92,7 @@ export class MessagesDb extends BaseDb {
return result.map((it: any) => it.id)[0]
}
async removeMessages(card: CardID, messages: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]> {
if (messages.length === 0) return []
async removeMessages(card: CardID, messages?: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]> {
const where: string[] = ['workspace_id = $1::uuid', 'card_id = $2::varchar']
const values: any[] = [this.workspace, card]
@@ -108,10 +108,10 @@ export class MessagesDb extends BaseDb {
values.push(socialIds)
}
if (messages.length === 1) {
if (messages && messages.length === 1) {
where.push(`id = $${index++}::bigint`)
values.push(messages[0])
} else {
} else if (messages && messages.length > 1) {
where.push(`id = ANY($${index++}::bigint[])`)
values.push(messages)
}
@@ -158,6 +158,14 @@ export class MessagesDb extends BaseDb {
)
}
async removePatches(card: CardID): Promise<void> {
const sql = `DELETE
FROM ${TableName.Patch}
WHERE workspace_id = $1::uuid
AND card_id = $2::varchar`
await this.execute(sql, [this.workspace, card], 'remove patches')
}
// File
async createFile(
card: CardID,
@@ -167,6 +175,7 @@ export class MessagesDb extends BaseDb {
fileType: string,
filename: string,
size: number,
meta: BlobMetadata | undefined,
creator: SocialID,
created: Date
): Promise<void> {
@@ -180,12 +189,13 @@ export class MessagesDb extends BaseDb {
size,
creator,
created,
message_created: messageCreated
message_created: messageCreated,
meta
}
const sql = `INSERT INTO ${TableName.File} (workspace_id, card_id, message_id, blob_id, type, filename, creator,
created, message_created, size)
created, message_created, size, meta)
VALUES ($1::uuid, $2::varchar, $3::int8, $4::uuid, $5::varchar, $6::varchar, $7::varchar,
$8::timestamptz, $9::timestamptz, $10::int8)`
$8::timestamptz, $9::timestamptz, $10::int8, $11::jsonb)`
await this.execute(
sql,
@@ -199,20 +209,34 @@ export class MessagesDb extends BaseDb {
db.creator,
db.created,
db.message_created,
db.size
db.size,
db.meta ?? {}
],
'insert file'
)
}
async removeFile(card: CardID, message: MessageID, blobId: BlobID): Promise<void> {
async removeFiles(query: Partial<File>): Promise<void> {
const db: Partial<FileDb> = {
card_id: query.card,
message_id: query.message,
blob_id: query.blobId
}
const entries = Object.entries(db).filter(([_, value]) => value != undefined)
if (entries.length === 0) return
entries.push(['workspace_id', this.workspace])
const whereClauses = entries.map(([key], index) => `${key} = $${index + 1}`)
const whereValues = entries.map(([_, value]) => value)
const sql = `DELETE
FROM ${TableName.File}
WHERE workspace_id = $1::uuid
AND card_id = $2::varchar
AND message_id = $3::bigint
AND blob_id = $4::uuid`
await this.execute(sql, [this.workspace, card, message, blobId], 'remove file')
WHERE ${whereClauses.join(' AND ')}`
await this.execute(sql, whereValues, 'remove files')
}
// Reaction
@@ -322,26 +346,65 @@ export class MessagesDb extends BaseDb {
)
}
async updateThread(thread: CardID, op: 'increment' | 'decrement', lastReply?: Date): Promise<void> {
async removeThreads(query: Partial<Thread>): Promise<void> {
const db: Partial<ThreadDb> = {
card_id: query.card,
message_id: query.message,
thread_id: query.thread,
thread_type: query.threadType
}
const entries = Object.entries(db).filter(([_, value]) => value != undefined)
if (entries.length === 0) return
entries.push(['workspace_id', this.workspace])
const whereClauses = entries.map(([key], index) => `${key} = $${index + 1}`)
const whereValues = entries.map(([_, value]) => value)
const sql = `DELETE
FROM ${TableName.Thread}
WHERE ${whereClauses.join(' AND ')}`
await this.execute(sql, whereValues, 'remove threads')
}
async updateThread(
thread: CardID,
update: {
threadType?: CardType
op?: 'increment' | 'decrement'
lastReply?: Date
}
): Promise<void> {
const set: string[] = []
const values: any[] = []
if (lastReply != null) {
set.push('last_reply = $3::timestamptz')
values.push(lastReply)
let index = 1
if (update.lastReply != null) {
set.push(`last_reply = $${index++}::timestamptz`)
values.push(update.lastReply)
}
if (op === 'increment') {
if (update.op === 'increment') {
set.push('replies_count = replies_count + 1')
} else if (op === 'decrement') {
} else if (update.op === 'decrement') {
set.push('replies_count = GREATEST(replies_count - 1, 0)')
}
const update = `UPDATE ${TableName.Thread}`
if (update.threadType != null) {
set.push(`thread_type = $${index++}::varchar`)
values.push(update.threadType)
}
if (set.length === 0) return
const updateSql = `UPDATE ${TableName.Thread}`
const setSql = 'SET ' + set.join(', ')
const where = 'WHERE workspace_id = $1::uuid AND thread_id = $2::varchar'
const sql = [update, setSql, where].join(' ')
await this.execute(sql, [this.workspace, thread, ...values], 'update thread')
const where = `WHERE workspace_id = $${index++}::uuid AND thread_id = $${index++}::varchar`
const sql = [updateSql, setSql, where].join(' ')
await this.execute(sql, [...values, this.workspace, thread], 'update thread')
}
// MessagesGroup
@@ -427,6 +490,7 @@ export class MessagesDb extends BaseDb {
'type', f.type,
'size', f.size,
'filename', f.filename,
'meta', f.meta,
'creator', f.creator,
'created', f.created
)) AS files
+61 -15
View File
@@ -30,7 +30,7 @@ import {
} from '@hcengineering/communication-types'
import { BaseDb } from './base'
import { type ContextDb, type NotificationDb, TableName } from './schema'
import { type CollaboratorDb, type ContextDb, type NotificationDb, TableName } from './schema'
import { getCondition } from './utils'
import { toCollaborator, toNotification, toNotificationContext } from './mapping'
@@ -58,10 +58,11 @@ export class NotificationsDb extends BaseDb {
return result.map((it: any) => it.account)
}
async removeCollaborators(card: CardID, collaborators: AccountID[]): Promise<void> {
if (collaborators.length === 0) return
if (collaborators.length === 1) {
async removeCollaborators(card: CardID, collaborators?: AccountID[]): Promise<void> {
if (collaborators === undefined) {
const sql = `DELETE FROM ${TableName.Collaborators} WHERE workspace_id = $1::uuid AND card_id = $2::varchar`
await this.execute(sql, [this.workspace, card], 'remove collaborators')
} else if (collaborators.length === 1) {
const sql = `DELETE
FROM ${TableName.Collaborators}
WHERE workspace_id = $1::uuid
@@ -140,11 +141,26 @@ export class NotificationsDb extends BaseDb {
return result[0].id as ContextID
}
async removeContext(context: ContextID, account: AccountID): Promise<void> {
async removeContexts(query: Partial<NotificationContext>): Promise<void> {
const db: Partial<ContextDb> & { id?: ContextID } = {
id: query.id,
card_id: query.card,
account: query.account
}
const entries = Object.entries(db).filter(([_, value]) => value != undefined)
if (entries.length === 0) return
entries.push(['workspace_id', this.workspace])
const whereClauses = entries.map(([key], index) => `${key} = $${index + 1}`)
const whereValues = entries.map(([_, value]) => value)
const sql = `DELETE
FROM ${TableName.NotificationContext}
WHERE id = $1::int8 AND account = $2::uuid`
await this.execute(sql, [context, account], 'remove notification context')
FROM ${TableName.NotificationContext}
WHERE ${whereClauses.join(' AND ')}`
await this.execute(sql, whereValues, 'remove notification context')
}
async updateContext(context: ContextID, account: AccountID, lastUpdate?: Date, lastView?: Date): Promise<void> {
@@ -349,6 +365,27 @@ export class NotificationsDb extends BaseDb {
return result.map((it: any) => toNotification(it))
}
async updateCollaborators(params: FindCollaboratorsParams, data: Partial<Collaborator>): Promise<void> {
const dbData: Partial<CollaboratorDb> = {
account: data.account,
card_id: data.card,
card_type: data.cardType
}
const entries = Object.entries(dbData).filter(([_, value]) => value != undefined)
if (entries.length === 0) return
const setClauses = entries.map(([key], index) => `${key} = $${index + 1}`)
const setValues = entries.map(([_, value]) => value)
const { where, values: whereValues } = this.buildCollaboratorsWhere(params, setValues.length, '')
const sql = `UPDATE ${TableName.Collaborators}
SET ${setClauses.join(', ')}
${where}`
await this.execute(sql, [...setValues, ...whereValues], 'update collaborators')
}
async findCollaborators(params: FindCollaboratorsParams): Promise<Collaborator[]> {
const { where, values } = this.buildCollaboratorsWhere(params)
const select = `
@@ -366,18 +403,27 @@ export class NotificationsDb extends BaseDb {
return result.map((it: any) => toCollaborator(it))
}
private buildCollaboratorsWhere(params: FindCollaboratorsParams): { where: string; values: any[] } {
const where: string[] = ['c.workspace_id = $1::uuid', 'c.card_id = $2::varchar']
const values: any[] = [this.workspace, params.card]
let index = values.length + 1
private buildCollaboratorsWhere(
params: FindCollaboratorsParams,
startIndex: number = 0,
prefix: string = 'c.'
): { where: string; values: any[] } {
const where: string[] = []
const values: any[] = []
let index = startIndex + 1
where.push(`${prefix}workspace_id = $${index++}::uuid`)
values.push(this.workspace)
where.push(`${prefix}card_id = $${index++}::varchar`)
values.push(params.card)
if (params.account != null) {
const accounts = Array.isArray(params.account) ? params.account : [params.account]
if (accounts.length === 1) {
where.push(`c.account = $${index++}::uuid`)
where.push(`${prefix}account = $${index++}::uuid`)
values.push(accounts[0])
} else if (accounts.length > 1) {
where.push(`c.account = ANY($${index++}::uuid[])`)
where.push(`${prefix}account = ANY($${index++}::uuid[])`)
values.push(accounts)
}
}
+3 -1
View File
@@ -26,7 +26,8 @@ import {
type WorkspaceID,
type NotificationID,
type LabelID,
type CardType
type CardType,
type BlobMetadata
} from '@hcengineering/communication-types'
export enum TableName {
@@ -104,6 +105,7 @@ export interface FileDb {
filename: string
size: number
type: string
meta?: BlobMetadata
creator: SocialID
created: Date
message_created: Date
+57
View File
@@ -15,6 +15,9 @@
import type { FindLabelsParams, Label, WorkspaceID } from '@hcengineering/communication-types'
import {
type CardRemovedEvent,
CardResponseEventType,
type CardTypeUpdatedEvent,
type EventResult,
type FindClient,
type LabelCreatedEvent,
@@ -34,6 +37,7 @@ function getId(label: Label): string {
export class LabelsQuery implements Query<Label, FindLabelsParams> {
private result: Promise<QueryResult<Label>> | QueryResult<Label>
private isCardRemoved = false
constructor(
private readonly client: FindClient,
@@ -53,6 +57,7 @@ export class LabelsQuery implements Query<Label, FindLabelsParams> {
}
async onEvent(event: ResponseEvent): Promise<void> {
if (this.isCardRemoved) return
switch (event.type) {
case LabelResponseEventType.LabelCreated:
await this.onLabelCreated(event)
@@ -60,6 +65,13 @@ export class LabelsQuery implements Query<Label, FindLabelsParams> {
case LabelResponseEventType.LabelRemoved:
await this.onLabelRemoved(event)
break
case CardResponseEventType.CardTypeUpdated:
await this.onCardTypeUpdated(event)
break
case CardResponseEventType.CardRemoved:
await this.onCardRemoved(event)
break
}
}
@@ -93,6 +105,50 @@ export class LabelsQuery implements Query<Label, FindLabelsParams> {
void this.notify()
}
async onCardTypeUpdated(event: CardTypeUpdatedEvent): Promise<void> {
if (this.result instanceof Promise) this.result = await this.result
const result = this.result.getResult()
let updated = false
for (const label of result) {
if (label.card == event.card) {
this.result.update({ ...label, cardType: event.cardType })
updated = true
}
}
if (updated) {
void this.notify()
}
}
async onCardRemoved(event: CardRemovedEvent): Promise<void> {
if (this.result instanceof Promise) this.result = await this.result
if (this.params.card === event.card) {
this.isCardRemoved = true
this.result.deleteAll()
void this.notify()
return
}
const result = this.result.getResult()
const prevLength = this.result.length
let deleted = false
for (const label of result) {
if (label.card == event.card) {
this.result.delete(label.label)
deleted = true
}
}
if (deleted) {
if (this.params.limit && this.result.length < this.params.limit && prevLength >= this.params.limit) {
const labels = await this.find(this.params)
this.result = new QueryResult(labels, getId)
}
void this.notify()
}
}
async onRequest(event: RequestEvent, promise: Promise<EventResult>): Promise<void> {}
private async initResult(): Promise<QueryResult<Label>> {
@@ -130,6 +186,7 @@ export class LabelsQuery implements Query<Label, FindLabelsParams> {
}
private async find(params: FindLabelsParams): Promise<Label[]> {
if (this.isCardRemoved) return []
return await this.client.findLabels(params, this.id)
}
+33 -7
View File
@@ -31,6 +31,8 @@ import {
type WorkspaceID
} from '@hcengineering/communication-types'
import {
type CardRemovedEvent,
CardResponseEventType,
type CreateMessageEvent,
type CreateMessageResult,
type EventResult,
@@ -94,6 +96,7 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
}
private readonly tmpMessages = new Map<string, MessageID>()
private isCardRemoved = false
constructor(
private readonly client: FindClient,
@@ -144,6 +147,7 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
}
async onEvent(event: ResponseEvent): Promise<void> {
if (this.isCardRemoved) return
switch (event.type) {
case MessageResponseEventType.MessageCreated: {
await this.onMessageCreatedEvent(event)
@@ -173,13 +177,29 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
await this.onFileRemovedEvent(event)
break
}
case MessageResponseEventType.ThreadCreated: {
case MessageResponseEventType.ThreadCreated:
await this.onThreadCreatedEvent(event)
}
break
case CardResponseEventType.CardRemoved:
await this.onCardRemoved(event)
break
}
}
async onCardRemoved(event: CardRemovedEvent): Promise<void> {
if (this.result instanceof Promise) this.result = await this.result
if (this.params.card === event.card) {
this.isCardRemoved = true
this.result.deleteAll()
this.result.setHead(true)
this.result.setTail(true)
void this.notify()
return
}
}
async onRequest(event: RequestEvent, promise: Promise<EventResult>): Promise<void> {
if (this.isCardRemoved) return
switch (event.type) {
case MessageRequestEventType.CreateMessage: {
await this.onCreateMessageRequest(event, promise as Promise<CreateMessageResult>)
@@ -260,6 +280,7 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
}
async requestLoadNextPage(): Promise<void> {
if (this.isCardRemoved) return
if (this.result instanceof Promise) this.result = await this.result
if (!this.result.isTail()) {
@@ -274,6 +295,7 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
}
async requestLoadPrevPage(): Promise<void> {
if (this.isCardRemoved) return
if (this.result instanceof Promise) this.result = await this.result
if (!this.result.isHead()) {
this.result = this.loadPage(Direction.Backward, this.result)
@@ -898,15 +920,17 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
}
private async onFileCreatedEvent(event: FileCreatedEvent): Promise<void> {
if (this.params.files !== true) return
if (this.params.files !== true || event.file.card !== this.params.card) return
if (this.result instanceof Promise) this.result = await this.result
const { file } = event
const message = this.result.get(file.message)
if (message !== undefined) {
message.files.push(file)
this.result.update(message)
await this.notify()
if (!message.files.some((it) => it.blobId === file.blobId)) {
message.files.push(file)
this.result.update(message)
await this.notify()
}
}
const fromNextBuffer = this.next.buffer.find((it) => it.id === file.message)
@@ -962,7 +986,9 @@ export class MessagesQuery implements PagedQuery<Message, MessageQueryParams> {
}
function addFile(message: Message, file: File): Message {
message.files.push(file)
if (!message.files.some((it) => it.blobId === file.blobId)) {
message.files.push(file)
}
return message
}
@@ -33,7 +33,9 @@ import {
type PagedQueryCallback,
type RequestEvent,
type ResponseEvent,
type FindClient
type FindClient,
CardResponseEventType,
type CardRemovedEvent
} from '@hcengineering/communication-sdk-types'
import { applyPatch } from '@hcengineering/communication-shared'
@@ -124,7 +126,11 @@ export class NotificationContextsQuery implements PagedQuery<NotificationContext
}
case NotificationResponseEventType.NotificationContextRemoved: {
await this.onRemoveNotificationContextEvent(event)
break
}
case CardResponseEventType.CardRemoved:
await this.onCardRemoved(event)
break
}
}
@@ -471,6 +477,26 @@ export class NotificationContextsQuery implements PagedQuery<NotificationContext
}
}
async onCardRemoved(event: CardRemovedEvent): Promise<void> {
if (this.result instanceof Promise) this.result = await this.result
let deleted = false
const result = this.result.getResult()
for (const context of result) {
if (context.card == event.card) {
this.result.delete(context.id)
deleted = true
}
}
if (deleted) {
if (this.params.limit && this.result.length < this.params.limit && result.length >= this.params.limit) {
const contexts = await this.find(this.params)
this.result = new QueryResult(contexts, (x) => x.id)
}
void this.notify()
}
}
private filterNotifications(notifications: Notification[]): Notification[] {
if (this.params.notifications == null) return notifications
const read = this.params.notifications.read
+4 -2
View File
@@ -13,7 +13,7 @@
// limitations under the License.
//
import { concatLink } from '@hcengineering/core'
import { type BlobMetadata, concatLink } from '@hcengineering/core'
import { PlatformError, unknownError } from '@hcengineering/platform'
import {
MessageRequestEventType,
@@ -161,7 +161,8 @@ class RestClientImpl implements RestClient {
fileType: string,
filename: string,
size: number,
creator: SocialID
creator: SocialID,
meta?: BlobMetadata
): Promise<void> {
await this.event({
type: MessageRequestEventType.CreateFile,
@@ -172,6 +173,7 @@ class RestClientImpl implements RestClient {
fileType,
filename,
size,
meta,
creator
})
}
+24 -7
View File
@@ -40,7 +40,9 @@ import type {
FindLabelsParams,
LabelID,
CardType,
PatchData
PatchData,
File,
BlobMetadata
} from '@hcengineering/communication-types'
export interface DbAdapter {
@@ -54,7 +56,7 @@ export interface DbAdapter {
externalId?: string,
id?: MessageID
): Promise<MessageID>
removeMessages(card: CardID, ids: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]>
removeMessages(card: CardID, ids?: MessageID[], socialIds?: SocialID[]): Promise<MessageID[]>
createPatch(
card: CardID,
@@ -66,6 +68,8 @@ export interface DbAdapter {
created: Date
): Promise<void>
removePatches(card: CardID): Promise<void>
createMessagesGroup(card: CardID, blobId: BlobID, fromDate: Date, toDate: Date, count: number): Promise<void>
removeMessagesGroup(card: CardID, blobId: BlobID): Promise<void>
@@ -93,10 +97,11 @@ export interface DbAdapter {
fileType: string,
filename: string,
size: number,
meta: BlobMetadata | undefined,
creator: SocialID,
created: Date
): Promise<void>
removeFile(card: CardID, message: MessageID, blobId: BlobID): Promise<void>
removeFiles(query: Partial<File>): Promise<void>
createThread(
card: CardID,
@@ -106,31 +111,43 @@ export interface DbAdapter {
threadType: CardType,
created: Date
): Promise<void>
updateThread(thread: CardID, op: 'increment' | 'decrement', lastReply?: Date): Promise<void>
removeThreads(query: Partial<Thread>): Promise<void>
updateThread(
thread: CardID,
update: {
threadType?: CardType
op?: 'increment' | 'decrement'
lastReply?: Date
}
): Promise<void>
findMessages(params: FindMessagesParams): Promise<Message[]>
findMessagesGroups(params: FindMessagesGroupsParams): Promise<MessagesGroup[]>
findThread(thread: CardID): Promise<Thread | undefined>
addCollaborators(card: CardID, cardType: CardType, collaborators: AccountID[], date?: Date): Promise<AccountID[]>
removeCollaborators(card: CardID, collaborators: AccountID[]): Promise<void>
removeCollaborators(card: CardID, collaborators?: AccountID[]): Promise<void>
getCollaboratorsCursor(card: CardID, date: Date, size?: number): AsyncIterable<Collaborator[]>
findCollaborators(params: FindCollaboratorsParams): Promise<Collaborator[]>
updateCollaborators(params: FindCollaboratorsParams, data: Partial<Collaborator>): Promise<void>
createNotification(context: ContextID, message: MessageID, created: Date): Promise<NotificationID>
removeNotification(context: ContextID, account: AccountID, untilDate: Date): Promise<void>
createContext(account: AccountID, card: CardID, lastUpdate: Date, lastView: Date): Promise<ContextID>
updateContext(context: ContextID, account: AccountID, lastUpdate?: Date, lastView?: Date): Promise<void>
removeContext(context: ContextID, account: AccountID): Promise<void>
removeContexts(query: Partial<NotificationContext>): Promise<void>
findNotificationContexts(params: FindNotificationContextParams): Promise<NotificationContext[]>
findNotifications(params: FindNotificationsParams): Promise<Notification[]>
createLabel(label: LabelID, card: CardID, cardType: CardType, account: AccountID, created: Date): Promise<void>
removeLabel(label: LabelID, card: CardID, account: AccountID): Promise<void>
removeLabels(query: Partial<Label>): Promise<void>
findLabels(params: FindLabelsParams): Promise<Label[]>
updateLabels(params: FindLabelsParams, data: Partial<Label>): Promise<void>
getAccountByPersonId(_id: string): Promise<AccountID | undefined>
close(): void
}
+18 -6
View File
@@ -23,17 +23,29 @@ import type {
import { type LabelResponseEvent, LabelResponseEventType } from './responseEvents/label'
import type { MessageResponseEvent, MessageResponseEventType } from './responseEvents/message'
import type { NotificationResponseEvent, NotificationResponseEventType } from './responseEvents/notification'
import type { CardRequestEvent, CardRequestEventType } from './requestEvents/card'
import type { CardResponseEvent, CardResponseEventType } from './responseEvents/card'
export * from './requestEvents/message'
export * from './requestEvents/notification'
export * from './requestEvents/label'
export * from './responseEvents/message'
export * from './requestEvents/notification'
export * from './responseEvents/notification'
export * from './requestEvents/label'
export * from './responseEvents/label'
export * from './requestEvents/card'
export * from './responseEvents/card'
export type RequestEventType = MessageRequestEventType | NotificationRequestEventType | LabelRequestEventType
export type RequestEvent = MessageRequestEvent | NotificationRequestEvent | LabelRequestEvent
export type RequestEventType =
| MessageRequestEventType
| NotificationRequestEventType
| LabelRequestEventType
| CardRequestEventType
export type RequestEvent = MessageRequestEvent | NotificationRequestEvent | LabelRequestEvent | CardRequestEvent
export type EventResult = MessageEventResult | NotificationEventResult | {}
export type ResponseEventType = MessageResponseEventType | NotificationResponseEventType | LabelResponseEventType
export type ResponseEvent = MessageResponseEvent | NotificationResponseEvent | LabelResponseEvent
export type ResponseEventType =
| MessageResponseEventType
| NotificationResponseEventType
| LabelResponseEventType
| CardResponseEventType
export type ResponseEvent = MessageResponseEvent | NotificationResponseEvent | LabelResponseEvent | CardResponseEvent
@@ -0,0 +1,36 @@
//
// 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, CardType } from '@hcengineering/communication-types'
import type { BaseRequestEvent } from './common'
export enum CardRequestEventType {
UpdateCardType = 'updateCardType',
RemoveCard = 'removeCard'
}
export type CardRequestEvent = UpdateCardTypeEvent | RemoveCardEvent
export interface UpdateCardTypeEvent extends BaseRequestEvent {
type: CardRequestEventType.UpdateCardType
card: CardID
cardType: CardType
}
export interface RemoveCardEvent extends BaseRequestEvent {
type: CardRequestEventType.RemoveCard
card: CardID
}
@@ -24,7 +24,8 @@ import type {
MessageType,
MessageData,
CardType,
PatchData
PatchData,
BlobMetadata
} from '@hcengineering/communication-types'
import type { BaseRequestEvent } from './common'
@@ -118,6 +119,7 @@ export interface CreateFileEvent extends BaseRequestEvent {
fileType: string
filename: string
creator: SocialID
meta?: BlobMetadata
}
export interface RemoveFileEvent extends BaseRequestEvent {
@@ -0,0 +1,36 @@
//
// 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, CardType } from '@hcengineering/communication-types'
import type { BaseResponseEvent } from './common'
export enum CardResponseEventType {
CardTypeUpdated = 'cardTypeUpdated',
CardRemoved = 'cardRemoved'
}
export type CardResponseEvent = CardTypeUpdatedEvent | CardRemovedEvent
export interface CardTypeUpdatedEvent extends BaseResponseEvent {
type: CardResponseEventType.CardTypeUpdated
card: CardID
cardType: CardType
}
export interface CardRemovedEvent extends BaseResponseEvent {
type: CardResponseEventType.CardRemoved
card: CardID
}
+2
View File
@@ -32,6 +32,8 @@
"@hcengineering/core": "^0.7.88",
"@hcengineering/server-token": "^0.7.88",
"@hcengineering/platform": "^0.7.88",
"@hcengineering/text-core": "^0.7.88",
"@hcengineering/text-markdown": "^0.7.88",
"zod": "^3.24.2"
},
"peerDependencies": {
@@ -14,6 +14,7 @@
//
import {
CardResponseEventType,
type EventResult,
LabelResponseEventType,
MessageResponseEventType,
@@ -247,6 +248,10 @@ export class BroadcastMiddleware extends BaseMiddleware implements Middleware {
return info.account === event.label.account
case LabelResponseEventType.LabelRemoved:
return info.account === event.account
case CardResponseEventType.CardTypeUpdated:
return true
case CardResponseEventType.CardRemoved:
return true
}
}
+52 -4
View File
@@ -31,6 +31,8 @@ import {
} from '@hcengineering/communication-types'
import {
type AddCollaboratorsEvent,
CardRequestEventType,
CardResponseEventType,
type CreateFileEvent,
type CreateLabelEvent,
type CreateMessageEvent,
@@ -61,6 +63,7 @@ import {
type PatchCreatedEvent,
type ReactionCreatedEvent,
type ReactionRemovedEvent,
type RemoveCardEvent,
type RemoveCollaboratorsEvent,
type RemoveFileEvent,
type RemoveLabelEvent,
@@ -74,6 +77,7 @@ import {
type ResponseEvent,
type SessionData,
type ThreadCreatedEvent,
type UpdateCardTypeEvent,
type UpdateNotificationContextEvent,
type UpdateThreadEvent
} from '@hcengineering/communication-sdk-types'
@@ -174,11 +178,18 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
return await this.createLabel(event)
case LabelRequestEventType.RemoveLabel:
return await this.removeLabel(event)
case CardRequestEventType.UpdateCardType:
return await this.updateCardType(event)
case CardRequestEventType.RemoveCard:
return await this.removeCard(event)
}
}
private async updateThread(event: UpdateThreadEvent): Promise<Result> {
await this.db.updateThread(event.thread, event.replies, event.lastReply)
await this.db.updateThread(event.thread, {
op: event.replies,
lastReply: event.lastReply
})
return {
responseEvent: {
_id: event._id,
@@ -207,6 +218,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
private async removeCollaborators(event: RemoveCollaboratorsEvent): Promise<Result> {
if (event.collaborators.length === 0) return {}
await this.db.removeCollaborators(event.card, event.collaborators)
return {
@@ -291,6 +303,12 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
private async removeMessages(event: RemoveMessagesEvent, session: SessionData): Promise<Result> {
if (event.messages.length === 0)
return {
result: {
messages: []
}
}
const account = session.account
const socialIds = systemAccountUuid === account.uuid ? undefined : account.socialIds
const deleted = await this.db.removeMessages(event.card, event.messages, socialIds)
@@ -364,6 +382,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
event.fileType,
event.filename,
event.size,
event.meta,
event.creator,
created
)
@@ -380,6 +399,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
filename: event.filename,
size: event.size,
creator: event.creator,
meta: event.meta,
created
}
}
@@ -389,7 +409,7 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
private async removeFile(event: RemoveFileEvent): Promise<Result> {
await this.db.removeFile(event.card, event.message, event.blobId)
await this.db.removeFiles({ card: event.card, message: event.message, blobId: event.blobId })
const responseEvent: FileRemovedEvent = {
_id: event._id,
type: MessageResponseEventType.FileRemoved,
@@ -458,7 +478,10 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
private async removeNotificationContext(event: RemoveNotificationContextEvent): Promise<Result> {
await this.db.removeContext(event.context, event.account)
await this.db.removeContexts({
id: event.context,
account: event.account
})
const responseEvent: NotificationContextRemovedEvent = {
_id: event._id,
type: NotificationResponseEventType.NotificationContextRemoved,
@@ -552,7 +575,11 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
private async removeLabel(event: RemoveLabelEvent): Promise<Result> {
await this.db.removeLabel(event.label, event.card, event.account)
await this.db.removeLabels({
label: event.label,
card: event.card,
account: event.account
})
return {
responseEvent: {
_id: event._id,
@@ -564,6 +591,27 @@ export class DatabaseMiddleware extends BaseMiddleware implements Middleware {
}
}
private async updateCardType(event: UpdateCardTypeEvent): Promise<Result> {
return {
responseEvent: {
_id: event._id,
type: CardResponseEventType.CardTypeUpdated,
card: event.card,
cardType: event.cardType
}
}
}
private async removeCard(event: RemoveCardEvent): Promise<Result> {
return {
responseEvent: {
_id: event._id,
type: CardResponseEventType.CardRemoved,
card: event.card
}
}
}
close(): void {
this.db.close()
}
+2 -15
View File
@@ -17,7 +17,7 @@ import type { DbAdapter, RequestEvent, ResponseEvent, SessionData } from '@hceng
import type { MeasureContext } from '@hcengineering/core'
import triggers from '../triggers/all'
import type { Middleware, MiddlewareContext, TriggerCtx, TriggersDb } from '../types'
import type { Middleware, MiddlewareContext, TriggerCtx } from '../types'
import { BaseMiddleware } from './base'
import { notify } from '../notification/notification'
@@ -25,7 +25,7 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
private readonly ctx: MeasureContext
constructor(
private readonly db: TriggersDb,
private readonly db: DbAdapter,
context: MiddlewareContext,
next?: Middleware
) {
@@ -82,16 +82,3 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
await Promise.all(derived.map((d) => this.context.head?.event(session, d, true)))
}
}
export function createTriggersDb(db: DbAdapter): TriggersDb {
return {
findMessagesGroups: (params) => db.findMessagesGroups(params),
findMessages: (params) => db.findMessages(params),
findCollaborators: (params) => db.findCollaborators(params),
findNotifications: (params) => db.findNotifications(params),
findNotificationContexts: (params) => db.findNotificationContexts(params),
findLabels: (params) => db.findLabels(params),
findThread: (params) => db.findThread(params),
getCollaboratorsCursor: (card, date, size) => db.getCollaboratorsCursor(card, date, size)
}
}
+91 -20
View File
@@ -161,7 +161,7 @@ export class ValidateMiddleware extends BaseMiddleware implements Middleware {
this.validate(event, UpdateNotificationContextEventSchema)
break
}
return await this.provideEvent(session, event, derived)
return await this.provideEvent(session, deserializeEvent(event), derived)
}
}
@@ -180,10 +180,10 @@ const PatchType = z.string()
const RichText = z.string()
const SocialID = z.string()
const SortingOrder = z.number()
const Date = z.union([z.date(), z.string()])
const DateSchema = z.union([z.date(), z.string()])
// Find params
const dateOrRecordSchema = z.union([Date, z.record(Date)])
const dateOrRecordSchema = z.union([DateSchema, z.record(DateSchema)])
const FindParamsSchema = z
.object({
@@ -279,7 +279,7 @@ const CreateMessageEventSchema = BaseRequestEventSchema.extend({
creator: SocialID,
data: MessageData.optional(),
externalId: z.string().optional(),
created: Date.optional(),
created: DateSchema.optional(),
id: MessageID.optional()
}).strict()
@@ -294,7 +294,7 @@ const CreatePatchEventSchema = BaseRequestEventSchema.extend({
patchType: PatchType,
card: CardID,
message: MessageID,
messageCreated: Date,
messageCreated: DateSchema,
data: PatchData,
creator: SocialID
}).strict()
@@ -303,7 +303,7 @@ const CreateReactionEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.CreateReaction),
card: CardID,
message: MessageID,
messageCreated: Date,
messageCreated: DateSchema,
reaction: z.string(),
creator: SocialID
}).strict()
@@ -312,7 +312,7 @@ const RemoveReactionEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.RemoveReaction),
card: CardID,
message: MessageID,
messageCreated: Date,
messageCreated: DateSchema,
reaction: z.string(),
creator: SocialID
}).strict()
@@ -321,27 +321,29 @@ const CreateFileEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.CreateFile),
card: CardID,
message: MessageID,
messageCreated: Date,
messageCreated: DateSchema,
blobId: BlobID,
size: z.number(),
fileType: z.string(),
filename: z.string(),
creator: SocialID
creator: SocialID,
meta: z.record(z.string(), z.any()).optional()
}).strict()
const RemoveFileEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.RemoveFile),
card: CardID,
message: MessageID,
messageCreated: Date,
creator: SocialID
messageCreated: DateSchema,
creator: SocialID,
blobId: BlobID
}).strict()
const CreateThreadEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.CreateThread),
card: CardID,
message: MessageID,
messageCreated: Date,
messageCreated: DateSchema,
thread: CardID,
threadType: CardType
}).strict()
@@ -350,7 +352,7 @@ const UpdateThreadEventSchema = BaseRequestEventSchema.extend({
type: z.literal(MessageRequestEventType.UpdateThread),
thread: CardID,
replies: z.enum(['increment', 'decrement']),
lastReply: Date.optional()
lastReply: DateSchema.optional()
}).strict()
const CreateMessagesGroupEventSchema = BaseRequestEventSchema.extend({
@@ -370,7 +372,7 @@ const CreateNotificationEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.CreateNotification),
context: ContextID,
message: MessageID,
created: Date,
created: DateSchema,
account: AccountID
}).strict()
@@ -378,15 +380,15 @@ const RemoveNotificationsEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.RemoveNotifications),
context: ContextID,
account: AccountID,
untilDate: Date
untilDate: DateSchema
}).strict()
const CreateNotificationContextEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.CreateNotificationContext),
card: CardID,
account: AccountID,
lastView: Date,
lastUpdate: Date
lastView: DateSchema,
lastUpdate: DateSchema
}).strict()
const RemoveNotificationContextEventSchema = BaseRequestEventSchema.extend({
@@ -399,8 +401,8 @@ const UpdateNotificationContextEventSchema = BaseRequestEventSchema.extend({
type: z.literal(NotificationRequestEventType.UpdateNotificationContext),
context: ContextID,
account: AccountID,
lastView: Date.optional(),
lastUpdate: Date.optional()
lastView: DateSchema.optional(),
lastUpdate: DateSchema.optional()
}).strict()
const AddCollaboratorsEventSchema = BaseRequestEventSchema.extend({
@@ -408,7 +410,7 @@ const AddCollaboratorsEventSchema = BaseRequestEventSchema.extend({
card: CardID,
cardType: CardType,
collaborators: z.array(AccountID).nonempty(),
date: Date.optional()
date: DateSchema.optional()
}).strict()
const RemoveCollaboratorsEventSchema = BaseRequestEventSchema.extend({
@@ -416,3 +418,72 @@ const RemoveCollaboratorsEventSchema = BaseRequestEventSchema.extend({
card: CardID,
collaborators: z.array(AccountID)
}).strict()
function deserializeEvent(event: RequestEvent): RequestEvent {
switch (event.type) {
case MessageRequestEventType.CreateMessage:
return {
...event,
created: deserializeDate(event.created)
}
case MessageRequestEventType.CreateThread:
case MessageRequestEventType.RemoveFile:
case MessageRequestEventType.CreateFile:
case MessageRequestEventType.RemoveReaction:
case MessageRequestEventType.CreateReaction:
case MessageRequestEventType.CreatePatch:
return {
...event,
messageCreated: deserializeDate(event.messageCreated)!
}
case MessageRequestEventType.UpdateThread:
return {
...event,
lastReply: deserializeDate(event.lastReply)
}
case MessageRequestEventType.CreateMessagesGroup:
return {
...event,
group: {
...event.group,
fromDate: deserializeDate(event.group.fromDate)!,
toDate: deserializeDate(event.group.toDate)!
}
}
case NotificationRequestEventType.AddCollaborators:
return {
...event,
date: deserializeDate(event.date)
}
case NotificationRequestEventType.CreateNotification:
return {
...event,
created: deserializeDate(event.created)!
}
case NotificationRequestEventType.RemoveNotifications:
return {
...event,
untilDate: deserializeDate(event.untilDate)!
}
case NotificationRequestEventType.CreateNotificationContext:
return {
...event,
lastView: deserializeDate(event.lastView)!,
lastUpdate: deserializeDate(event.lastUpdate)!
}
case NotificationRequestEventType.UpdateNotificationContext:
return {
...event,
lastView: deserializeDate(event.lastView)!,
lastUpdate: deserializeDate(event.lastUpdate)!
}
}
return event
}
function deserializeDate(date?: Date | string | undefined | null): Date | undefined {
if (date == null) return undefined
if (date instanceof Date) return date
return new Date(date)
}
+2 -2
View File
@@ -42,7 +42,7 @@ import type {
import { PermissionsMiddleware } from './middleware/permissions'
import { DatabaseMiddleware } from './middleware/db'
import { BroadcastMiddleware } from './middleware/broadcast'
import { createTriggersDb, TriggersMiddleware } from './middleware/triggers'
import { TriggersMiddleware } from './middleware/triggers'
import { ValidateMiddleware } from './middleware/validate'
import { DateMiddleware } from './middleware/date'
@@ -59,7 +59,7 @@ export async function buildMiddlewares(
async (context, next) => new DateMiddleware(context, next),
async (context, next) => new BroadcastMiddleware(broadcast, context, next),
async (context, next) => new DatabaseMiddleware(db, context, next),
async (context, next) => new TriggersMiddleware(createTriggersDb(db), context, next)
async (context, next) => new TriggersMiddleware(db, context, next)
]
const context: MiddlewareContext = {
@@ -102,7 +102,6 @@ async function processCollaborator(
result.push(...events)
if (contextId == null || isOwn) return result
if (message.type !== MessageType.Message) return result
result.push({
type: NotificationRequestEventType.CreateNotification,
+2 -1
View File
@@ -16,7 +16,8 @@
import type { Triggers } from '../types'
import message from './message'
import notification from './notification'
import card from './card'
const allTriggers: Triggers = [...message, ...notification]
const allTriggers: Triggers = [...message, ...notification, ...card]
export default allTriggers
+96
View File
@@ -0,0 +1,96 @@
//
// 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 CardRemovedEvent,
CardResponseEventType,
type CardTypeUpdatedEvent,
NotificationRequestEventType,
type RequestEvent
} from '@hcengineering/communication-sdk-types'
import type { CardID } from '@hcengineering/communication-types'
import type { TriggerCtx, TriggerFn, Triggers } from '../types'
async function onCardTypeUpdates(ctx: TriggerCtx, event: CardTypeUpdatedEvent): Promise<RequestEvent[]> {
await ctx.db.updateCollaborators({ card: event.card }, { cardType: event.cardType })
await ctx.db.updateLabels({ card: event.card }, { cardType: event.cardType })
await ctx.db.updateThread(event.card, { threadType: event.cardType })
return []
}
async function removeCollaborators(ctx: TriggerCtx, event: CardRemovedEvent): Promise<RequestEvent[]> {
await ctx.db.removeCollaborators(event.card)
return []
}
async function removeLabels(ctx: TriggerCtx, event: CardRemovedEvent): Promise<RequestEvent[]> {
await ctx.db.removeLabels({ card: event.card })
return []
}
async function removeThreads(ctx: TriggerCtx, event: CardRemovedEvent): Promise<RequestEvent[]> {
await ctx.db.removeThreads({ card: event.card })
return []
}
async function removeMessages(ctx: TriggerCtx, event: CardRemovedEvent): Promise<RequestEvent[]> {
await ctx.db.removeMessages(event.card)
await ctx.db.removePatches(event.card)
await ctx.db.removeFiles({ card: event.card })
await removeMessageGroups(ctx, event.card)
return []
}
async function removeNotificationContexts(ctx: TriggerCtx, event: CardRemovedEvent): Promise<RequestEvent[]> {
const result: RequestEvent[] = []
const contexts = await ctx.db.findNotificationContexts({ card: event.card })
for (const context of contexts) {
result.push({
type: NotificationRequestEventType.RemoveNotificationContext,
context: context.id,
account: context.account
})
}
return result
}
async function removeMessageGroups(ctx: TriggerCtx, card: CardID): Promise<void> {
while (true) {
const groups = await ctx.db.findMessagesGroups({ card })
if (groups.length === 0) return
for (const group of groups) {
///TODO: delete blob
await ctx.db.removeMessagesGroup(group.card, group.blobId)
}
}
}
const triggers: Triggers = [
['on_card_type_updates', CardResponseEventType.CardTypeUpdated, onCardTypeUpdates as TriggerFn],
['remove_collaborators_on_card_removed', CardResponseEventType.CardRemoved, removeCollaborators as TriggerFn],
['remove_labels_on_card_removed', CardResponseEventType.CardRemoved, removeLabels as TriggerFn],
['remove_threads_on_card_removed', CardResponseEventType.CardRemoved, removeThreads as TriggerFn],
['remove_messages_on_card_removed', CardResponseEventType.CardRemoved, removeMessages as TriggerFn],
[
'remove_notification_contexts_on_card_removed',
CardResponseEventType.CardRemoved,
removeNotificationContexts as TriggerFn
]
]
export default triggers
+25 -3
View File
@@ -36,8 +36,10 @@ import {
PatchType
} from '@hcengineering/communication-types'
import { generateToken } from '@hcengineering/server-token'
import { concatLink, systemAccountUuid } from '@hcengineering/core'
import { type AccountUuid, concatLink, systemAccountUuid } from '@hcengineering/core'
import { generateMessageId } from '@hcengineering/communication-shared'
import { extractReferences } from '@hcengineering/text-core'
import { markdownToMarkup } from '@hcengineering/text-markdown'
import type { TriggerCtx, TriggerFn, Triggers } from '../types'
import { findAccount } from '../utils'
@@ -151,15 +153,34 @@ async function registerCard(ctx: TriggerCtx, event: MessageCreatedEvent | PatchC
async function addCollaborators(ctx: TriggerCtx, event: MessageCreatedEvent): Promise<RequestEvent[]> {
const { creator } = event.message
const account = await findAccount(ctx, creator)
const collaborators: AccountUuid[] = []
if (account === undefined) return []
if (account !== undefined) {
collaborators.push(account)
}
const markup = markdownToMarkup(event.message.content)
const references = extractReferences(markup)
for (const reference of references) {
const { objectId, objectClass } = reference
if (['contact:class:Person', 'contact:mixin:Employee'].includes(objectClass)) {
const account = await ctx.db.getAccountByPersonId(objectId)
if (account !== undefined) {
collaborators.push(account)
}
}
}
if (collaborators.length === 0) {
return []
}
return [
{
type: NotificationRequestEventType.AddCollaborators,
card: event.message.card,
cardType: event.cardType,
collaborators: [account],
collaborators,
date: event.message.created
}
]
@@ -247,6 +268,7 @@ async function onThreadCreated(ctx: TriggerCtx, event: ThreadCreatedEvent): Prom
fileType: file.type,
filename: file.filename,
size: file.size,
meta: file.meta,
creator: file.creator
})
}
+30 -7
View File
@@ -29,7 +29,9 @@ import type { TriggerCtx, TriggerFn, Triggers } from '../types'
async function onAddedCollaborators(ctx: TriggerCtx, event: AddedCollaboratorsEvent): Promise<RequestEvent[]> {
const { card, cardType, collaborators } = event
const result: RequestEvent[] = []
const contexts = await ctx.db.findNotificationContexts({ card, account: event.collaborators })
for (const collaborator of collaborators) {
const context = contexts.find((it) => it.account === collaborator)
result.push({
type: LabelRequestEventType.CreateLabel,
card,
@@ -38,13 +40,23 @@ async function onAddedCollaborators(ctx: TriggerCtx, event: AddedCollaboratorsEv
label: SubscriptionLabelID
})
result.push({
type: NotificationRequestEventType.CreateNotificationContext,
account: collaborator,
card,
lastUpdate: event.date,
lastView: event.date
})
if (context === undefined) {
result.push({
type: NotificationRequestEventType.CreateNotificationContext,
account: collaborator,
card,
lastUpdate: event.date,
lastView: event.date
})
} else {
result.push({
type: NotificationRequestEventType.UpdateNotificationContext,
context: context.id,
account: collaborator,
lastUpdate: event.date,
lastView: event.date
})
}
}
return result
}
@@ -52,13 +64,24 @@ async function onAddedCollaborators(ctx: TriggerCtx, event: AddedCollaboratorsEv
async function onRemovedCollaborators(ctx: TriggerCtx, event: RemovedCollaboratorsEvent): Promise<RequestEvent[]> {
const { card, collaborators } = event
const result: RequestEvent[] = []
const contexts = await ctx.db.findNotificationContexts({ card, account: event.collaborators })
for (const collaborator of collaborators) {
const context = contexts.find((it) => it.account === collaborator)
result.push({
type: LabelRequestEventType.RemoveLabel,
card,
account: collaborator,
label: SubscriptionLabelID
})
if (context !== undefined && context.lastUpdate.getTime() > context.lastView.getTime()) {
result.push({
type: NotificationRequestEventType.UpdateNotificationContext,
context: context.id,
account: collaborator,
lastView: context.lastUpdate
})
}
}
return result
}
-1
View File
@@ -40,7 +40,6 @@ export async function findMessageInFiles(
): Promise<Message | undefined> {
const filesUrl = ctx.metadata.filesUrl
if (filesUrl === '') {
ctx.ctx.error('FILES_URL is missing', { filesUrl })
return undefined
}
+1 -13
View File
@@ -94,22 +94,10 @@ export type MiddlewareCreateFn = (context: MiddlewareContext, next?: Middleware)
export type BroadcastSessionsFunc = (ctx: MeasureContext, sessionIds: string[], result: any) => void
export type TriggersDb = Pick<
DbAdapter,
| 'findMessagesGroups'
| 'findMessages'
| 'findCollaborators'
| 'findNotifications'
| 'findNotificationContexts'
| 'findLabels'
| 'getCollaboratorsCursor'
| 'findThread'
>
export interface TriggerCtx {
ctx: MeasureContext
metadata: Metadata
db: TriggersDb
db: DbAdapter
workspace: WorkspaceID
account: Account
registeredCards: Set<CardID>
+3 -1
View File
@@ -13,9 +13,11 @@
// limitations under the License.
//
import type { Ref, Blob, AccountUuid, WorkspaceUuid, PersonId } from '@hcengineering/core'
import type { Ref, Blob, AccountUuid, WorkspaceUuid, PersonId, BlobMetadata } from '@hcengineering/core'
import type { Card, MasterTag } from '@hcengineering/card'
export type { BlobMetadata }
export type BlobID = Ref<Blob>
export type CardID = Ref<Card>
export type CardType = Ref<MasterTag>
+2 -1
View File
@@ -13,7 +13,7 @@
// limitations under the License.
//
import type { Attribute, Class, Mixin, Ref } from '@hcengineering/core'
import type { Attribute, BlobMetadata, Class, Mixin, Ref } from '@hcengineering/core'
import type { BlobID, CardID, CardType, ID, RichText, SocialID } from './core'
import type { Card, Tag } from '@hcengineering/card'
@@ -195,6 +195,7 @@ export interface File {
filename: string
size: number
creator: SocialID
meta?: BlobMetadata
created: Date
}