mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-09 11:17:43 +02:00
Add card events and files meta (#53)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
@@ -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
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user