mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
Fix separation by productId (#2382)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
55e3cbea59
commit
a8af08de88
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@ -35,12 +35,13 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"src/index.ts"
|
||||
"src/__start.ts"
|
||||
],
|
||||
"env": {
|
||||
"MONGO_URL": "mongodb://localhost:27017",
|
||||
"MONGO_URL": "mongodb://localhost:27018",
|
||||
"SERVER_SECRET": "secret",
|
||||
"TRANSACTOR_URL": "ws:/localhost:3333"
|
||||
"TRANSACTOR_URL": "ws:/localhost:3333",
|
||||
"ACCOUNT_PORT": "3000"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--nolazy",
|
||||
|
@ -64,6 +64,7 @@ specifiers:
|
||||
'@rush-temp/login-assets': file:./projects/login-assets.tgz
|
||||
'@rush-temp/login-resources': file:./projects/login-resources.tgz
|
||||
'@rush-temp/middleware': file:./projects/middleware.tgz
|
||||
'@rush-temp/minio': file:./projects/minio.tgz
|
||||
'@rush-temp/model': file:./projects/model.tgz
|
||||
'@rush-temp/model-activity': file:./projects/model-activity.tgz
|
||||
'@rush-temp/model-all': file:./projects/model-all.tgz
|
||||
@ -399,6 +400,7 @@ dependencies:
|
||||
'@rush-temp/login-assets': file:projects/login-assets.tgz_typescript@4.8.4
|
||||
'@rush-temp/login-resources': file:projects/login-resources.tgz_49b4785992daa3b61a639b2b31601e76
|
||||
'@rush-temp/middleware': file:projects/middleware.tgz
|
||||
'@rush-temp/minio': file:projects/minio.tgz
|
||||
'@rush-temp/model': file:projects/model.tgz
|
||||
'@rush-temp/model-activity': file:projects/model-activity.tgz_typescript@4.8.4
|
||||
'@rush-temp/model-all': file:projects/model-all.tgz_typescript@4.8.4
|
||||
@ -11135,7 +11137,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/account.tgz:
|
||||
resolution: {integrity: sha512-EZcey8t5zWFBsNNvGqJiaPLFgSs2JwaFVOd5qKw2sNo7KbMYdYtVWUNS/SVnDmF2TlzbIysdzZc39dYoyegHhA==, tarball: file:projects/account.tgz}
|
||||
resolution: {integrity: sha512-8yjt4NrdmP1f+lfABJvi/UMWdJyh7o5ozzLsBxAOoNPPJdv2hfWGkQRlaix24WJY69UMXug587gbLi8kvl1JDQ==, tarball: file:projects/account.tgz}
|
||||
name: '@rush-temp/account'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11184,7 +11186,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/activity-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-hem1W1iTJU3wP58o4nHQ1BtG/K7sYtRGZPRG60N17LEHYEeKU3K0hnMHo17uCeT9w5EyEp5/3oZjvP5jyXU6Jw==, tarball: file:projects/activity-resources.tgz}
|
||||
resolution: {integrity: sha512-XHOxvpcYfiLdYp0O1q6ji0g5cqK1WrcYb8e9G0HVFu7WzKhSJ7mvSZcfb5fngTL8XjTLaika8BVvh4Dfz+Al+A==, tarball: file:projects/activity-resources.tgz}
|
||||
id: file:projects/activity-resources.tgz
|
||||
name: '@rush-temp/activity-resources'
|
||||
version: 0.0.0
|
||||
@ -11285,7 +11287,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/attachment-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-RC9+heJmAgXIBPY0P4mrE7BWaOS2pP1ddegWy5/TLHIi23XGAt02FRu0rYmNbxM9p9Z3hwW0dSGuHBpSzpailg==, tarball: file:projects/attachment-resources.tgz}
|
||||
resolution: {integrity: sha512-9RxkG18ZVsrmz3scEahdgy0a8r8QCjWuh4zZ0ZxAun8QIy1SIw3pBR2HB2qzMergiZzK8YtZltxFEycjFggKeQ==, tarball: file:projects/attachment-resources.tgz}
|
||||
id: file:projects/attachment-resources.tgz
|
||||
name: '@rush-temp/attachment-resources'
|
||||
version: 0.0.0
|
||||
@ -11441,7 +11443,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/board-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-wZB0AWu/DawUcePr1rCZRa61LwWg7pdT/i0gR017Z9O6HCrQP2H23wJtjn4CvyTE2HsAneKYDAB55PX2/4C1/g==, tarball: file:projects/board-resources.tgz}
|
||||
resolution: {integrity: sha512-5YVKhvdOcaXKoVvMniN9nzBOzwUL0SR5klgOCm6iIdV1dKLui/07T1e86lFgVAdsPgtzxWJlvSEQ3G1M53B+lg==, tarball: file:projects/board-resources.tgz}
|
||||
id: file:projects/board-resources.tgz
|
||||
name: '@rush-temp/board-resources'
|
||||
version: 0.0.0
|
||||
@ -11476,7 +11478,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/board.tgz:
|
||||
resolution: {integrity: sha512-6kULPATq0+6/1mevbXuB430pPu072K21Pv1IczE0tXXaKuXwuaPV4nR4QbeD/BE+esD8XgOVE497rhYGnpD4nQ==, tarball: file:projects/board.tgz}
|
||||
resolution: {integrity: sha512-md//1C1/iEupRv4ycc6d2a4MWORj/AMcIp3blZ2S+8ZEkIr/yUXS6oAEcW2S6IJQ8UPlhDhq35wBng5FrtVdeg==, tarball: file:projects/board.tgz}
|
||||
name: '@rush-temp/board'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11518,7 +11520,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/calendar-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-Wz8JrvayplUY+oNPxqiYa8asg6NtssCRTkRB85SLFvxuRKAwQASSXjuDphCvBOylMF/9dNsHaNWTQZ08OdRJ+g==, tarball: file:projects/calendar-resources.tgz}
|
||||
resolution: {integrity: sha512-iW7MK0QNp0R5DZ1I/UlE+5bHccepUVGyOHzqzNh6z14qnIokE5BgrDtjoucYqPbTlDFv2UW1S5xiXNsydRAztA==, tarball: file:projects/calendar-resources.tgz}
|
||||
id: file:projects/calendar-resources.tgz
|
||||
name: '@rush-temp/calendar-resources'
|
||||
version: 0.0.0
|
||||
@ -11553,7 +11555,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/calendar.tgz:
|
||||
resolution: {integrity: sha512-X7whcXZsRvqicnxJPBHHINOI1YxmUGbERdxYdhNILkyDdQNyuduOpijPZiFUT094U8wyxd7pPDIGUh23wkXX1A==, tarball: file:projects/calendar.tgz}
|
||||
resolution: {integrity: sha512-Z7WVKethTuqQJnE+RhrI7nNvNPrnLENofC/nA71QWyFqJPSw81QUMIPCcizhTksxSI5t2zDQiVjUaK1GEk5qRw==, tarball: file:projects/calendar.tgz}
|
||||
name: '@rush-temp/calendar'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11595,7 +11597,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/chunter-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-PCYdxv+j37WKeFIcQ/FrKcXs0j37H0+1ot0d6F2VkXIrTX0OvFGIZJG+xRAzk6wPqsEkjXt0loyR4P0FuY6Pqw==, tarball: file:projects/chunter-resources.tgz}
|
||||
resolution: {integrity: sha512-tdUNzswIFdvN6b6bhqRPTn1KUjFNMvNvVYfRfr38NRRpoHGIOrL/fNVcDU8c+4y9p880JFvngFBopIFEp5GYVw==, tarball: file:projects/chunter-resources.tgz}
|
||||
id: file:projects/chunter-resources.tgz
|
||||
name: '@rush-temp/chunter-resources'
|
||||
version: 0.0.0
|
||||
@ -11631,7 +11633,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/chunter.tgz:
|
||||
resolution: {integrity: sha512-gYPNBzmHCCc/PmxpKzgRqRnUkcp0SLfEyP8Hr8sneriMFNjD0PfMjVzQLPPbRF51zpwoJ1Zr10+mUWJ8ScZP/A==, tarball: file:projects/chunter.tgz}
|
||||
resolution: {integrity: sha512-dKNFBXk+iMygMyZfOYVOuoSLIEhNp1Jft1F1lXO3jonxEUzInGto6cU004XW2xX9blUM/l7MyF6LX7SnV3dZ3Q==, tarball: file:projects/chunter.tgz}
|
||||
name: '@rush-temp/chunter'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11691,7 +11693,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/contact-assets.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-1hp8KJRpc5+xgDMndnrHtzyGFI6tV3LDPAcVPDy7eMBOf/Rt67/QCzGG7txsA6pdNsf8rcnGZ8glfyYnU2pVwA==, tarball: file:projects/contact-assets.tgz}
|
||||
resolution: {integrity: sha512-nTzb48LU1qn3ktHCeM/aIXxE5K+QhJyp21cOogImGLi9wp7CGHcmwaRHBasZ97qogD0uzNE0rhV2IbcNvC+57w==, tarball: file:projects/contact-assets.tgz}
|
||||
id: file:projects/contact-assets.tgz
|
||||
name: '@rush-temp/contact-assets'
|
||||
version: 0.0.0
|
||||
@ -11713,7 +11715,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/contact-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-tiemPkkX5m+f8IpwQV8NoPJhYtfHNsUYemK3xgmT5/QbFKqekdky7GELmpq6fY9zkdXp1pKRU0jaoEC8s0w1Ow==, tarball: file:projects/contact-resources.tgz}
|
||||
resolution: {integrity: sha512-MUTOkFTO+OF484QeG3bQXv4b6wd8GdjBoEcyikAybS5cnENj8ZYCXCXbXHcF7tRu8GHXrirez958V2TdIhwaUQ==, tarball: file:projects/contact-resources.tgz}
|
||||
id: file:projects/contact-resources.tgz
|
||||
name: '@rush-temp/contact-resources'
|
||||
version: 0.0.0
|
||||
@ -11903,7 +11905,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/devmodel-resources.tgz_5536a2d3219f8677582bfc0330dae14a:
|
||||
resolution: {integrity: sha512-oF7bkVohXrka8/IEBrjbK+JxQqsZ7OV101DjYb7cDePyj5ofnGEIv2r8DDmXO36H7zoQWpSSJjX3A+EUlBFo6w==, tarball: file:projects/devmodel-resources.tgz}
|
||||
resolution: {integrity: sha512-EE1jWJBSvCdX6juiSh/zzAfrqkyBDWPV89fZVZH9Twfyae05shz+Guifvn0GXrHCc+z9zxcCUNOJa+vABJc8Aw==, tarball: file:projects/devmodel-resources.tgz}
|
||||
id: file:projects/devmodel-resources.tgz
|
||||
name: '@rush-temp/devmodel-resources'
|
||||
version: 0.0.0
|
||||
@ -11981,7 +11983,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/document-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-K75XTawcHH1MSsC/OIFILTVWFdikC9rZACQD+TZgDGsk6cnzJRz7WaoYqj+2oPK/qwsRryi+/7amdKIg/1kytw==, tarball: file:projects/document-resources.tgz}
|
||||
resolution: {integrity: sha512-Q4PGk2NShFmlCK3mStJC4ZgOCBEYs2cnWDM+oi6W9UioOE4joj/3KOP9Px/3294UWbq5eS+BzQHNtZ40h2ZukQ==, tarball: file:projects/document-resources.tgz}
|
||||
id: file:projects/document-resources.tgz
|
||||
name: '@rush-temp/document-resources'
|
||||
version: 0.0.0
|
||||
@ -12016,7 +12018,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/document.tgz:
|
||||
resolution: {integrity: sha512-jahDbKxsWybK48eLZUVfFGWtq0pJ8daCxTFABIhLnPZIDPQu8Ik7PM3mRxQY8u7sYurRJgl40E+a+ud+eg2TJA==, tarball: file:projects/document.tgz}
|
||||
resolution: {integrity: sha512-ASeqqVDRZgKNc/fwCU3iyaiuaP8Wk+seON655YeQwhvbClARe9yy7+KKX7XzE2pIbPdCTxwUBIiqR+/u9rt5Vw==, tarball: file:projects/document.tgz}
|
||||
name: '@rush-temp/document'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -12103,7 +12105,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/generator.tgz:
|
||||
resolution: {integrity: sha512-4bFipFYTQCqnCbezi7dUjsyHwywpffCmekAwVkZ+S1+WHS9sNz8mI7vTpWdc1D4iikETukkO/aXkqoRXi3n2RQ==, tarball: file:projects/generator.tgz}
|
||||
resolution: {integrity: sha512-knz6FRYXSNtm1MU/tTKpG1PwQvePi1uaNqNwKmWRURDg/59PESdFMQadueRbIdgJJ15tlJU4ki8yCHmO6l+n0Q==, tarball: file:projects/generator.tgz}
|
||||
name: '@rush-temp/generator'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -12161,7 +12163,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/gmail-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-xaaL8WfttW0hFwVoD1CLyuaXGrRcQ/Dwkv9WIo9OGnrsdrjeSLcVvt+nR6u2TcoIVqWhYJEScrdEHL4DHmIz0A==, tarball: file:projects/gmail-resources.tgz}
|
||||
resolution: {integrity: sha512-emQZ2qN1r74yxViTo8Uef5YWdTB9eK+bGvUdbMmtrNUSBL60+KdOgv0661cqmtZxIlPupCkA3PJpmiv+Nd8azQ==, tarball: file:projects/gmail-resources.tgz}
|
||||
id: file:projects/gmail-resources.tgz
|
||||
name: '@rush-temp/gmail-resources'
|
||||
version: 0.0.0
|
||||
@ -12196,7 +12198,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/gmail.tgz:
|
||||
resolution: {integrity: sha512-nr4baabaooIoYZIIgJe/SyM1mZwwBOCJu6IIlvnH8wAqEHMgu6akue7krMewBcHM+qFiMzuwVy1OMD6TwjfZfA==, tarball: file:projects/gmail.tgz}
|
||||
resolution: {integrity: sha512-LOZ+NuO1daLQsEoaq1h9Y9ysB80lyHbVm8cFJaArhIb9J0L+Jm4Orx0fEmbKBNwPlS3fTwVkezAf5Gv7oeig8g==, tarball: file:projects/gmail.tgz}
|
||||
name: '@rush-temp/gmail'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -12238,7 +12240,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/hr-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-9Y+8tgNwZRF5xyscGddizuYZ7g7ca5/Ga2gGuVYE5C7A5j8ODp4yhz9llCuLDL/kYEl4uuwHa2L0jpUIzNsXQg==, tarball: file:projects/hr-resources.tgz}
|
||||
resolution: {integrity: sha512-WGtSBxKPXMjLGaqlH58ho5KqGGXAOat/pxd+xDDa/SRY23KYYc0N7t0a4td1uVbWE2lAw/DMIARrVYRlKbQTtg==, tarball: file:projects/hr-resources.tgz}
|
||||
id: file:projects/hr-resources.tgz
|
||||
name: '@rush-temp/hr-resources'
|
||||
version: 0.0.0
|
||||
@ -12273,7 +12275,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/hr.tgz:
|
||||
resolution: {integrity: sha512-JS/5Vkfw0MZaqWP8/cn8/Iqe7htNp0kWKZxVbg+oa4ztD6c9ECUY7GfFCFU2oKKrl+TLa8az2ycQpm8PgmlmqQ==, tarball: file:projects/hr.tgz}
|
||||
resolution: {integrity: sha512-YVZ57Gu5PlmB1oi0bZCF2WUE+LV271qyMTDSJxO38tFUKtCcwylU28J1fcial2TlR2zdmzuu7JfzkQh8+k/Ktg==, tarball: file:projects/hr.tgz}
|
||||
name: '@rush-temp/hr'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -12371,7 +12373,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/inventory-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-k7OWnh4GrGbAeVbHV2y/VGNX+c4d8N3okU1dUMOYc/rYPk+DmjWoHoC2POZ2QUSkUiv9gTqpRvLvc3mNumAcEQ==, tarball: file:projects/inventory-resources.tgz}
|
||||
resolution: {integrity: sha512-icjBvRl/8K10Hv4JULUQzhomrNocRy5YRGDtOzUYSiN31mHygrgJMBbsY6Lo/JAYMYBIa2X8IKhqtn/Zlxt/Nw==, tarball: file:projects/inventory-resources.tgz}
|
||||
id: file:projects/inventory-resources.tgz
|
||||
name: '@rush-temp/inventory-resources'
|
||||
version: 0.0.0
|
||||
@ -12484,7 +12486,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/lead-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-hnoDwg89HfQwSntwS6MqLO87j5533mEr7yqzB8UBTo5XkkUP0tHoop8jBTP5EkaPm4VC6itT4c5MISYGke2OOQ==, tarball: file:projects/lead-resources.tgz}
|
||||
resolution: {integrity: sha512-QTtzdm5tOqm9IJlSjJAvjGezpTk81CQgDUnpz9KjRQhqpnkIZZQOwmsZ9afFTveU40u+yg7ul01lKn2vxF9naw==, tarball: file:projects/lead-resources.tgz}
|
||||
id: file:projects/lead-resources.tgz
|
||||
name: '@rush-temp/lead-resources'
|
||||
version: 0.0.0
|
||||
@ -12519,7 +12521,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/lead.tgz:
|
||||
resolution: {integrity: sha512-ZcvXbxP8xjUpIVllrH37R/JzhnlJPJ9g7cOqGa3zU03lRkPwvypWhrImz4RWpTXvW1hL5hyp7VZBYx8s5KlXOg==, tarball: file:projects/lead.tgz}
|
||||
resolution: {integrity: sha512-xR3SR5JiLaiXNTXk4h2t4h4/A8hqwbX0EG41lxHdC3AoBsGHuQ2XCW4KozmhgRZhqmycCCpFSpWrUzIu0OG8dw==, tarball: file:projects/lead.tgz}
|
||||
name: '@rush-temp/lead'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -12561,7 +12563,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/login-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-bx9MRAon/n8GKBvSzSHBRBeLHLrMiREcKHA5PzkPb84aCSFRRN4GxbuX3QLWWM2xyem2OW0iOb2/VdV2kamJnA==, tarball: file:projects/login-resources.tgz}
|
||||
resolution: {integrity: sha512-BQLnglPIn6mMQ3GpBjQylOOb8VRY9znhgpeuCqwP5YlMoGLV/conYKpZwjrSzF55GnTlLaYxJdaVtnQrljThdA==, tarball: file:projects/login-resources.tgz}
|
||||
id: file:projects/login-resources.tgz
|
||||
name: '@rush-temp/login-resources'
|
||||
version: 0.0.0
|
||||
@ -12635,8 +12637,30 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/minio.tgz:
|
||||
resolution: {integrity: sha512-VoxVx9EB3UAH22AGrPedW7GCOMvELT35uTIjYoJM91wAg2AeN2yJBTpgoStYCZVIWT9kmCbgGE6+L2Vi0PT7fA==, tarball: file:projects/minio.tgz}
|
||||
name: '@rush-temp/minio'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@rushstack/heft': 0.47.11
|
||||
'@types/heft-jest': 1.0.3
|
||||
'@types/minio': 7.0.14
|
||||
'@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
|
||||
'@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
|
||||
eslint: 8.27.0
|
||||
eslint-config-standard-with-typescript: 23.0.0_c9fe9619f50f4e82337a86c3af25e566
|
||||
eslint-plugin-import: 2.26.0_eslint@8.27.0
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
minio: 7.0.32
|
||||
prettier: 2.7.1
|
||||
typescript: 4.8.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/model-activity.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-FnAjRYTmHMV4mkSb0kq6Oj3F/dc355Hapwx7ztOTwpnTgSkqnguQIiMxLvRmuZVM7/VgFhPj3n0A9NWtPFa4Gg==, tarball: file:projects/model-activity.tgz}
|
||||
resolution: {integrity: sha512-ltIKPc9CmnmlRJ6HQY9zKwYNYMem68EtF1OKKoUVtZ4qckpvVbz4AmGmXgq/CHF6iOIy+2pqI7z1BIQvRWXXEA==, tarball: file:projects/model-activity.tgz}
|
||||
id: file:projects/model-activity.tgz
|
||||
name: '@rush-temp/model-activity'
|
||||
version: 0.0.0
|
||||
@ -12724,7 +12748,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-board.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-Y2ojGzljGwQ9Txndgmepy4xICqv+hJxn0hkFWcmRZvqQWLoCCas8c33sElYC/oT7fRC3zQwNWNfkV6VppoT4Bw==, tarball: file:projects/model-board.tgz}
|
||||
resolution: {integrity: sha512-PBcE6FlUziG9LLtIh2DNEERb67xKf0CyV0TjYAXc/bK5+MGpj5+6Nqut95PG9a7lmOUF5slPej/2u7UxKSPcEw==, tarball: file:projects/model-board.tgz}
|
||||
id: file:projects/model-board.tgz
|
||||
name: '@rush-temp/model-board'
|
||||
version: 0.0.0
|
||||
@ -12745,7 +12769,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-calendar.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-BmnLqC+uaySe8LfLrPn5I6/akmNe3Clg0OTUhRwNjlP8OZBUmdqD76PdMvb+h7xi2Hnz+PXE8d8XgmEayV+cgw==, tarball: file:projects/model-calendar.tgz}
|
||||
resolution: {integrity: sha512-yVeJUcUz8gS5CkEENilac9nkgOwlCJK7FY/A9f2Pm5EwTKfdvvnoAmM2xoQp3DOEQRI97qog92M3jxBBzOi1qw==, tarball: file:projects/model-calendar.tgz}
|
||||
id: file:projects/model-calendar.tgz
|
||||
name: '@rush-temp/model-calendar'
|
||||
version: 0.0.0
|
||||
@ -12766,7 +12790,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-chunter.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-B7obo6hYDAkso5X5DX/2LikbY/9n55pZobzHFk8+NH6xaIFjvj6KfHkww5cpmitidaB04sw1TQypIgt6clTiqQ==, tarball: file:projects/model-chunter.tgz}
|
||||
resolution: {integrity: sha512-WBKyQoFvo5wdEG4YKXq+OZGggI7dptfEPUte8LXYhgng+Bx/oYE3wDTMqTOW5Lo8/Tp2ZzQ4Wf0qEq/wd7cfcQ==, tarball: file:projects/model-chunter.tgz}
|
||||
id: file:projects/model-chunter.tgz
|
||||
name: '@rush-temp/model-chunter'
|
||||
version: 0.0.0
|
||||
@ -12787,7 +12811,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-contact.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-1LXDgoRC+p48CfA8Dhk/JuUttYtwkJXaXADwFcvDJGLmWN062SEHy5mOCB56RcYxbBZvMOuRbKKkdZ7u678jzw==, tarball: file:projects/model-contact.tgz}
|
||||
resolution: {integrity: sha512-skK2ivL1u7OjOqFKrwaDAUsFKYisT34pJohI8uUrRB7d/O4GtLev+C5zRhAy/d6f/yvO46r96gEOnOLCQVqY8A==, tarball: file:projects/model-contact.tgz}
|
||||
id: file:projects/model-contact.tgz
|
||||
name: '@rush-temp/model-contact'
|
||||
version: 0.0.0
|
||||
@ -12831,7 +12855,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-demo.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-bGYWsKGMQt8TBsZUstuFnm2pFw60vkiUD7qZyucUyFgqpvPxkTPRXYSluL8nY+7Frv923EEbnogOGgn++uaQ1Q==, tarball: file:projects/model-demo.tgz}
|
||||
resolution: {integrity: sha512-wcDEJcDjcxjJ5wxa/OlG/OIBxNagBH5UmiC2MIj1CE6Cr+IufMYM3e+dOuAXBZe1IB8SZSmV5MF006dRwKo9Gg==, tarball: file:projects/model-demo.tgz}
|
||||
id: file:projects/model-demo.tgz
|
||||
name: '@rush-temp/model-demo'
|
||||
version: 0.0.0
|
||||
@ -12852,7 +12876,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-document.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-erlRfCr8MOH6uLf3EdKyPSHQc0SOkQ3UKf38lNfz4lxWV77z5NDMzeV5ReoApYL29nQuFd17nmgYa8uqI6wlEg==, tarball: file:projects/model-document.tgz}
|
||||
resolution: {integrity: sha512-8gybSaEK3nD4LoxikWLsHdY9kcUF2DiPmn5eWG7wRhKKBcuoyuDfuItqB9WpEoNViiAVgNTIftAiLZnIOcTFpw==, tarball: file:projects/model-document.tgz}
|
||||
id: file:projects/model-document.tgz
|
||||
name: '@rush-temp/model-document'
|
||||
version: 0.0.0
|
||||
@ -12873,7 +12897,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-gmail.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-iIiCPdHDCd183h52Gp/f4E32zWzgWhmhl1DYE6nYi1P/jZTFDlJcImdwJnDJ/S69EdBAoPsn4QAWoX+1GLZTsw==, tarball: file:projects/model-gmail.tgz}
|
||||
resolution: {integrity: sha512-1N6oNODRhzjbZ92q1bTNu44VD4AzQLodNva0IzcswlhqPnGSPfpK+eeaz2dJqcdj6Ob5RpjEc2LKNdt5xysM9Q==, tarball: file:projects/model-gmail.tgz}
|
||||
id: file:projects/model-gmail.tgz
|
||||
name: '@rush-temp/model-gmail'
|
||||
version: 0.0.0
|
||||
@ -12894,7 +12918,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-hr.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-/pKwQmDLkegCRfAcbSVrRHode4trh58qMkNU6NLyy2v+RGOC4lj+/l93+2nD6vS6XT4dPdOvEgjTAIgGPec+/Q==, tarball: file:projects/model-hr.tgz}
|
||||
resolution: {integrity: sha512-EmKSttuNIARD0H5JMl/GL2j38po8Be7sf4VDhY5yTp4/aaUAk5Yr3ANc+MDmm2ZLnemQWZXM3VJS8rfAoiX4sQ==, tarball: file:projects/model-hr.tgz}
|
||||
id: file:projects/model-hr.tgz
|
||||
name: '@rush-temp/model-hr'
|
||||
version: 0.0.0
|
||||
@ -12915,7 +12939,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-inventory.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-aSv3XnWQ/x6y+KgH5/uPhB+OTsMBt40CvDoBGuVUV7Iza8a/5LLoVch3uPq03UyNimvmu17s9ByV3JSMLrLfGw==, tarball: file:projects/model-inventory.tgz}
|
||||
resolution: {integrity: sha512-X81p18418UhgRKOgvd3Eo7mAwnfmwqhItWq/gz8H4aBWd35Ov7E2Xkl1cReo0OrahqXCXXPLGWyWYdEzce9FTA==, tarball: file:projects/model-inventory.tgz}
|
||||
id: file:projects/model-inventory.tgz
|
||||
name: '@rush-temp/model-inventory'
|
||||
version: 0.0.0
|
||||
@ -12936,7 +12960,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-lead.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-5BtncJVFf4Mdb1bPRUXA3a9C5dJSwF7Zn+tjsW3OGDTroRGt9nnqMwqX2+e/CKYMTOiYDalIHlp6MuobZut3hw==, tarball: file:projects/model-lead.tgz}
|
||||
resolution: {integrity: sha512-6zs+yoagGEV9C5h/i2YKtsrH78m+ACIWsuQ97/rnS4p7ieuNUo0NMZkD8SPKBBOwt2H3jK1QcLoPXQ1fLKjmQA==, tarball: file:projects/model-lead.tgz}
|
||||
id: file:projects/model-lead.tgz
|
||||
name: '@rush-temp/model-lead'
|
||||
version: 0.0.0
|
||||
@ -13020,7 +13044,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-recruit.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-8D3C5KRAGvCXhF7PbWRuAuGDxgYxM50wTrs4oFrBBitNgDoMEVJtlUs0NmxTZcBejT6TuvL1GIZ5AdzxosaNsg==, tarball: file:projects/model-recruit.tgz}
|
||||
resolution: {integrity: sha512-VE7ozm46NXmnFFFJ/8bT32JqDkgB6iIfK87GHU/2faBJECPmbLXGbsiiT3o+IbQWPqNQzkNTTPUQzCz9RGnHpA==, tarball: file:projects/model-recruit.tgz}
|
||||
id: file:projects/model-recruit.tgz
|
||||
name: '@rush-temp/model-recruit'
|
||||
version: 0.0.0
|
||||
@ -13124,7 +13148,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-server-contact.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-V/QenOWUhRg5f8yNSccvIsZP3y6VFPNtCmMDvO/KCjIHWdddMmlOgGbV0SzWp+0rfYXpt5XfDsUqaaFTqXelCw==, tarball: file:projects/model-server-contact.tgz}
|
||||
resolution: {integrity: sha512-/0i0O2KtsAbmaqfqgC7nroFMRAZxkfhalGrCrrj2E+aOnzObvmtluv/SArW1MlfpVERblUuT/AYmekUqcFYBNQ==, tarball: file:projects/model-server-contact.tgz}
|
||||
id: file:projects/model-server-contact.tgz
|
||||
name: '@rush-temp/model-server-contact'
|
||||
version: 0.0.0
|
||||
@ -13166,7 +13190,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-server-gmail.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-SX6g9Go7KIAcTinSGdpC4QbxqemB6WpWLrGC/dIWtg8R1tGmZdZLuNdhjybsRy8fO/COaeSInKxEYO1fhptSRw==, tarball: file:projects/model-server-gmail.tgz}
|
||||
resolution: {integrity: sha512-yur2GVjsax/AYLft85HImrjWqywPJO163qbRwaTYk3wn5Spr1kW0bVcnsZcXg0f9iRHkMDJOIpRY2P1FO/GVuw==, tarball: file:projects/model-server-gmail.tgz}
|
||||
id: file:projects/model-server-gmail.tgz
|
||||
name: '@rush-temp/model-server-gmail'
|
||||
version: 0.0.0
|
||||
@ -13355,7 +13379,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-server-telegram.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-9ll09PUnHtFPEFmo4SO57sGc8+6fBVAV4gcFoL+5swtX3v159CzXaUGLIiUgG6GclEpd9BIMrHq2tHH1f7AAIA==, tarball: file:projects/model-server-telegram.tgz}
|
||||
resolution: {integrity: sha512-4z3rQykurAKm3Zo/lP1q8FB1geAj4iOj51DGbo1occQ6AQhS03qqrHoqsGm858btLu2FI0Wbps8H37yJo8UlcQ==, tarball: file:projects/model-server-telegram.tgz}
|
||||
id: file:projects/model-server-telegram.tgz
|
||||
name: '@rush-temp/model-server-telegram'
|
||||
version: 0.0.0
|
||||
@ -13439,7 +13463,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-task.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-U9kGIuNhrWKxoQAioRp2WqYj7v+4aIfoGI6uKVFGQFfuUoVOq7dL/NZDAnw6M3g/GpJxQOB4yalevRrgkaGNVA==, tarball: file:projects/model-task.tgz}
|
||||
resolution: {integrity: sha512-ijA6O177qrq9Mz5oJf02ESaHZgChn7zSWsbj9vL6EssvGMjhNASfo+ilsJ10ZwLxOC8QFh0h+IiSbWkiyNRnbw==, tarball: file:projects/model-task.tgz}
|
||||
id: file:projects/model-task.tgz
|
||||
name: '@rush-temp/model-task'
|
||||
version: 0.0.0
|
||||
@ -13460,7 +13484,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-telegram.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-MCDcZMHGd1Mu7F8Rxb7qMfWM/9NnLxJo5aAHByEZGvFvZVc42nj+P5RrQps52bcUcwGA92wELWi6T+EljoL44g==, tarball: file:projects/model-telegram.tgz}
|
||||
resolution: {integrity: sha512-JBPt3wBuXvvwjXC4XzHpO/0ILZWc2pcvQfjCjtSMtKNhXJslC7OQlThc2pRTYN8ZCP/CSbnzw2xoX6u3khSkNg==, tarball: file:projects/model-telegram.tgz}
|
||||
id: file:projects/model-telegram.tgz
|
||||
name: '@rush-temp/model-telegram'
|
||||
version: 0.0.0
|
||||
@ -13523,7 +13547,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-tracker.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-XuNhqGceTe6+ymCYOc/tOnBsbPEJy1ufeAVT9T1NFWLwIbxBsP1txvbiyu1R4r1AVZ4QjJu/Be1kQBKQGyJGbQ==, tarball: file:projects/model-tracker.tgz}
|
||||
resolution: {integrity: sha512-ZLbvHWpvbUBJLMlUDmhAJkoYE5pJx1V73BmM2EmPhS7pBpFh1enPXqQTW4M+0UotnbKBCpkssq3+1NYMoK+frQ==, tarball: file:projects/model-tracker.tgz}
|
||||
id: file:projects/model-tracker.tgz
|
||||
name: '@rush-temp/model-tracker'
|
||||
version: 0.0.0
|
||||
@ -13565,7 +13589,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-workbench.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-IJiF3ij2t4uD8Y5s7v+GC05XTCP0QwifaThVQWHWLroHXkXXXPSXmt08YpLYkBs1LPV9bCSkfAKXpZbqBPPa/Q==, tarball: file:projects/model-workbench.tgz}
|
||||
resolution: {integrity: sha512-Dssj/dje85sBveIduaksgpY4UHNg08a8foinnYSmW1JaTqdbvOrdfKvadE/knc3C6YaCzcM4zwrl2YIBoUjy6w==, tarball: file:projects/model-workbench.tgz}
|
||||
id: file:projects/model-workbench.tgz
|
||||
name: '@rush-temp/model-workbench'
|
||||
version: 0.0.0
|
||||
@ -13653,7 +13677,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/notification-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-9lZ01h6+oSEL1t+QgLXy97qgJERc1oOR9EXNfhv1xWoCu6sjXe10GPmlMe4sGq/YkOS9LqnvuGLKTgXXcGzOMA==, tarball: file:projects/notification-resources.tgz}
|
||||
resolution: {integrity: sha512-/5WLnwCSppaUhTK5CBTbIdbYEsHQxgvaLoQd/DrI7m0Axuve9OMcmOO7rseG3FyazklV3xZJboWYFxNBdYNPVA==, tarball: file:projects/notification-resources.tgz}
|
||||
id: file:projects/notification-resources.tgz
|
||||
name: '@rush-temp/notification-resources'
|
||||
version: 0.0.0
|
||||
@ -13807,7 +13831,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/pod-account.tgz:
|
||||
resolution: {integrity: sha512-XdRcITa4VLyzbj6jon8nbgIgAP+l+06Pvc65Sf54wADzBWFO27AX/BhZtIlhL5eCAMMrE2Tidh52xPDoXkbXpg==, tarball: file:projects/pod-account.tgz}
|
||||
resolution: {integrity: sha512-n507oMGA9w0oyJHDG4Ct65c2q/reFMbeOYkqd8GLrV15ggrNObeMt21vKuGmNzcoa275qIUeUdOY5xbDuR4FQg==, tarball: file:projects/pod-account.tgz}
|
||||
name: '@rush-temp/pod-account'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14024,7 +14048,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/presentation.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-BRrMCOYZQWq7l4bHtorlsbF3Vd1xC3d1+HFwxAapz+2dWVdzTZa9LWbUnxdgOdVKnoPNEBshBb7yweuAz+QYvQ==, tarball: file:projects/presentation.tgz}
|
||||
resolution: {integrity: sha512-VellyX1RtijHjE5/zTtkkxnnf6XEuOs+Miwa2GRBuPtNGzLho+fx78CgA3TNj12zv+v40N5KRgRJQZyGE+LyEg==, tarball: file:projects/presentation.tgz}
|
||||
id: file:projects/presentation.tgz
|
||||
name: '@rush-temp/presentation'
|
||||
version: 0.0.0
|
||||
@ -14060,7 +14084,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/prod.tgz_b3a81ceaadec606c0eb174aef12a6049:
|
||||
resolution: {integrity: sha512-L/+vs3539DkLEsndUMgU7L7LtdncWMbyKJTqNA4TWOIM5kOK4cLMqKqufG+sM/9Rjr5TMUpy2LmgwgNwS5tnPA==, tarball: file:projects/prod.tgz}
|
||||
resolution: {integrity: sha512-j3kGErL/B4HOsYiCJ3qCdbVeyhLMmu4Ajz0a4a6Zu4LoNaiwKlLvYbD5qiJiXu5LARkmGFCFABw7yioFLLBnWw==, tarball: file:projects/prod.tgz}
|
||||
id: file:projects/prod.tgz
|
||||
name: '@rush-temp/prod'
|
||||
version: 0.0.0
|
||||
@ -14150,7 +14174,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/recruit-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-BOswQ3tPI0sMMChbQBrca0qGo1nfFTS/UOMyqZYLU1vEQTPXIBwu7Eq9OmvyLRUYrHPriIrodEQ+um7246gYgQ==, tarball: file:projects/recruit-resources.tgz}
|
||||
resolution: {integrity: sha512-xVQquoInxUPu3415Qcuwl5DctHN6soWJtUyHmMy+jZffMvxovcsHsk44bxebSzRNv8wtrShmjKcGGhJ643MZYg==, tarball: file:projects/recruit-resources.tgz}
|
||||
id: file:projects/recruit-resources.tgz
|
||||
name: '@rush-temp/recruit-resources'
|
||||
version: 0.0.0
|
||||
@ -14187,7 +14211,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/recruit.tgz:
|
||||
resolution: {integrity: sha512-V2+cf6AZI3Aw40vMEYBi0aQCR5gQTvuoR1dyi3q61B/zs9GZx6EV/plInET8Z7q+OtXm7PfSwR4dH8+UxGF1Ow==, tarball: file:projects/recruit.tgz}
|
||||
resolution: {integrity: sha512-0kgdxh5Bjz8Z9oZjf1+9fYdgepnMouOtRqTlstB9iuABgoeLVK1fEUevxwCnu9DC7ViEsm600JQ0DtWoKiprrg==, tarball: file:projects/recruit.tgz}
|
||||
name: '@rush-temp/recruit'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14271,7 +14295,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-backup.tgz:
|
||||
resolution: {integrity: sha512-8SeFK5Vj1yuAZ25nnV4dlt15yHFzF743DF9vF/TLxc4D7PaTEnCo6K2RcHRFX9s9ua8bh0fWwOFnYxBoAQYFkg==, tarball: file:projects/server-backup.tgz}
|
||||
resolution: {integrity: sha512-snkRz4+41T/IJEpLmAnc9lUqo+K1xa0jkpcB5wdg1YSG+XQupGOuJ4Lu6V63+KrHEbWpircaTNL9gFEHQsWl5w==, tarball: file:projects/server-backup.tgz}
|
||||
name: '@rush-temp/server-backup'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14337,7 +14361,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-chunter-resources.tgz:
|
||||
resolution: {integrity: sha512-eK6az//EWwcQnoe9BJUmTW5lVwx1m3zlqEYfSw400E1U+mCs1tmoxQrYRduY1x9rcOObwEjLqGfVRU9RHe/aKw==, tarball: file:projects/server-chunter-resources.tgz}
|
||||
resolution: {integrity: sha512-G3CbPm0EUXqt9O3K6j4/LZ/7NoXP1cPn60A7Y+YG4U/sWeAIfycKosrL6FLDlucMz3+vZSeX6wK8W5VezBnk6g==, tarball: file:projects/server-chunter-resources.tgz}
|
||||
name: '@rush-temp/server-chunter-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14378,7 +14402,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-contact-resources.tgz:
|
||||
resolution: {integrity: sha512-9l0juRGQ7OMxnhOBHsvby5gzX3xC3fkKJJ9IQ9HKr+PmoZRF+fkeorBF5ttPnMva6rtY/uV0ukSH0pnOWxgdOA==, tarball: file:projects/server-contact-resources.tgz}
|
||||
resolution: {integrity: sha512-RmqLUSQSZYSc1UFyU0RmZ7b7IBMT0rrbF3B1KpGcnFbu+sduTPW5byef5DnRLIDX2YlNvejYsfXssBCNk0LU/w==, tarball: file:projects/server-contact-resources.tgz}
|
||||
name: '@rush-temp/server-contact-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14442,7 +14466,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-gmail-resources.tgz:
|
||||
resolution: {integrity: sha512-rRnrXbwpcT6AqzWz+yDmfy0XuVgUWmz84Br2bZExPLpJwIdyvt7Wbn1kOqJfDRhsws6LRQb18vXn6xe/aKrx5w==, tarball: file:projects/server-gmail-resources.tgz}
|
||||
resolution: {integrity: sha512-Kg6JAqy3b1OhPjbe6VCHgb6XZOm2Odj4lyFnxE/L8mZ+9UPRXYCzsrtGvLmjcHPYf4MmwSl5IAMok5D8vhBh0Q==, tarball: file:projects/server-gmail-resources.tgz}
|
||||
name: '@rush-temp/server-gmail-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14483,7 +14507,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-hr-resources.tgz:
|
||||
resolution: {integrity: sha512-MdFafFBbo3+AeGGi2hgKYOFDawLzjNUTcShGD3/SPKBqN7bJMrBQGaLHCw9O+7DatauGM+pIq/FwJi1a850CAA==, tarball: file:projects/server-hr-resources.tgz}
|
||||
resolution: {integrity: sha512-ISPKgIxsz2z+AWmDLhfh4LSeORVewQv7pWa9QSJd2ZcMgg6ovZ3GOiX48PvzMGbuQ2jMqnQ4et3AUvDqn8jXDA==, tarball: file:projects/server-hr-resources.tgz}
|
||||
name: '@rush-temp/server-hr-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14524,7 +14548,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-inventory-resources.tgz:
|
||||
resolution: {integrity: sha512-uGp1LUr1aE7sIc4TIFa1+gx4gqps4PGF+LeGBuKVd06Nr0lqXRHwARKwcE8eiINVNc8kKo6ZC4PzV2ZnrNw21Q==, tarball: file:projects/server-inventory-resources.tgz}
|
||||
resolution: {integrity: sha512-PHYWPX4xwfs2lob/d0j+uvPW/fPnjwP3Gs4SbAPIQudT+RYO5/8+sjvVh1pHFuJm6iOHoDAgRfukaD17rBIr5w==, tarball: file:projects/server-inventory-resources.tgz}
|
||||
name: '@rush-temp/server-inventory-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14565,7 +14589,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-lead-resources.tgz:
|
||||
resolution: {integrity: sha512-GH2zRy02F0/XEaK26uBrZFhd1YDfY791rszTd9rrjtkLwMDnXHF+drPPv3YZOZ7cah3NwSNnqnTAwOpJltnPzA==, tarball: file:projects/server-lead-resources.tgz}
|
||||
resolution: {integrity: sha512-gsUYeV01/K2yXOU010jq8lW3GT49n0CjBU3RTc0xDb3WAukk9kLt9ukz397XAEfWGK40rltKYP5l+44CkJxcAA==, tarball: file:projects/server-lead-resources.tgz}
|
||||
name: '@rush-temp/server-lead-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14606,7 +14630,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-notification-resources.tgz:
|
||||
resolution: {integrity: sha512-FC05id1ogET5SgZypREim3huQwOt44WokPAg9vr5FlL7/AhFArJ9ChQJpNy1woSxf1Hf0v0ZywMx6FQDIqEe3w==, tarball: file:projects/server-notification-resources.tgz}
|
||||
resolution: {integrity: sha512-UnY/SBcWBaxORRl0oN+jMG6FHsEfs9oiErCZSqvqSeZezKWjxE1pqB80zZC9vMa2pGSPzljqWyzKLbzP+7qQhw==, tarball: file:projects/server-notification-resources.tgz}
|
||||
name: '@rush-temp/server-notification-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14626,7 +14650,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-notification.tgz:
|
||||
resolution: {integrity: sha512-O4cSZKuzK9PnQpIVzGnOQd2nGwWnAORjNyqRbAr8OvCfcK1P7rp9Q6tMr8MyjftEot/age3PPmfBPvqQ28ac7g==, tarball: file:projects/server-notification.tgz}
|
||||
resolution: {integrity: sha512-YZlf0UP5kL0NAzT42JJmlSFApgDCiNiW/YHXiNey5ZMoWSRL3XP7hepEBQVcIoJkR0Gks+3ppwQPdxty43MTVw==, tarball: file:projects/server-notification.tgz}
|
||||
name: '@rush-temp/server-notification'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14668,7 +14692,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-recruit-resources.tgz:
|
||||
resolution: {integrity: sha512-HGH2H8uKiAD2hkOXf/SybyVDwMXd6faqaj89ZbJ9TJ2nGGXObFvW7gVIIJB+3trzTa8cOUgQHHQzU0R12ZQAfQ==, tarball: file:projects/server-recruit-resources.tgz}
|
||||
resolution: {integrity: sha512-iPb7WAwVpg3XlbnztUzulyySQ0Uh5M20faBc4nkyHYXkee2jKtAjFT+X20Ht+2lVzk1u3PJ2AenOSJXgftipwA==, tarball: file:projects/server-recruit-resources.tgz}
|
||||
name: '@rush-temp/server-recruit-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14709,7 +14733,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-setting-resources.tgz:
|
||||
resolution: {integrity: sha512-PjjNR2MskZHvWLJl9vONhQ+QgM9+uM/WOBZOMymDsyPd8rCHhBDaOFXe9cHbDGpAz81qoT4d4EeRwfYpZBczKg==, tarball: file:projects/server-setting-resources.tgz}
|
||||
resolution: {integrity: sha512-ybG+7dgX8uT16IPRZsvS8WctWNcrEziDlw59lf3ah3c4B5UaVPWyCy/pNKIC/HZNsa+GXQ8izUteURBVHuloig==, tarball: file:projects/server-setting-resources.tgz}
|
||||
name: '@rush-temp/server-setting-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14791,7 +14815,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-task-resources.tgz:
|
||||
resolution: {integrity: sha512-KtkbvfRH++JZX32+o9ZHqnRfYx+w2Y93URNbmwlC2hTmvpN1/+2IFjrVb/P4465D0LkLQpUZH3Y8FyG72Vz68Q==, tarball: file:projects/server-task-resources.tgz}
|
||||
resolution: {integrity: sha512-VK0GdhbZ/hjVcV35GGjAsSfFvRblhWy/oOinB58S/m8dPMqcYKIJlUIvH4KDBM+wT79iKs91Y0T9unePeeLCPA==, tarball: file:projects/server-task-resources.tgz}
|
||||
name: '@rush-temp/server-task-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14832,7 +14856,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-telegram-resources.tgz:
|
||||
resolution: {integrity: sha512-b13BJhX2STuToJhP6lg0RwE9uVmLNp1t9QxDAgck7h0Fy8768RESGcpAHpew8bDZhlI/MqBKo6+4rmFfbSALQQ==, tarball: file:projects/server-telegram-resources.tgz}
|
||||
resolution: {integrity: sha512-w+aKvtg2SEfBGHUgBfKbUpsxBZufLNqBYFJUVCCLr9VyT+OFcLLrGnXsOAAnaOwQ4Qgf5v1jAivSSuF3sM/jiQ==, tarball: file:projects/server-telegram-resources.tgz}
|
||||
name: '@rush-temp/server-telegram-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14897,7 +14921,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-tool.tgz:
|
||||
resolution: {integrity: sha512-4LbvIbvlIpS89Azl2cVRzaJheqQxwnBISelhQv16paTJ6P14YRbr9Af76BDqFUXURo2Ze/dyyn4wH9n47OMNJw==, tarball: file:projects/server-tool.tgz}
|
||||
resolution: {integrity: sha512-VxPpII/+/u5gj40wFyU/wiV9EevgwadwNF3FFZGsnG4SJmWf34aSvvH4Cz8WilEAzeA4LXlUS20stY+9hFFycg==, tarball: file:projects/server-tool.tgz}
|
||||
name: '@rush-temp/server-tool'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14925,7 +14949,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-tracker-resources.tgz:
|
||||
resolution: {integrity: sha512-12vsorCEm5CJLA5wLdI+6cZxtVT4rbCrJLPTVGPM9UIO6SxZRgYUde+6WWMXVUrReAlnuq8VV/G6dIOXkjiNIg==, tarball: file:projects/server-tracker-resources.tgz}
|
||||
resolution: {integrity: sha512-vjwoWiXZ7Zp6yt6qzcJWA9SWl9wSWbL7Rrvha9BGksJltWnRElUKF46bJ2w6UbI/x8xiuqhyrs1ao/JqkE12nA==, tarball: file:projects/server-tracker-resources.tgz}
|
||||
name: '@rush-temp/server-tracker-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15042,7 +15066,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/setting-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-eAAOQAq+VNDEMWirKNp9bEfNSG6QioFNvOSJv5Cb5LAPOFmOvwXfohJpu2SpQxqSblKA6vIpJ3Md+7VXuKKCEw==, tarball: file:projects/setting-resources.tgz}
|
||||
resolution: {integrity: sha512-P+IXGteZUB8of/aZfG5kPBk2X6/mnjBXJ2wHPBqEoeCQ4E6zG0vGuLepchPsIglC0c31otUJ8hdKvUnAtR1LHw==, tarball: file:projects/setting-resources.tgz}
|
||||
id: file:projects/setting-resources.tgz
|
||||
name: '@rush-temp/setting-resources'
|
||||
version: 0.0.0
|
||||
@ -15196,7 +15220,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/task-resources.tgz_5536a2d3219f8677582bfc0330dae14a:
|
||||
resolution: {integrity: sha512-mv08VBqVcmiX3RPPWrnXOFNRVy0cig+oZ+veREZ7TQ9nd1famDje4fHsKoWdU6+IO/OICPEOZ4whxo9KjEBCeQ==, tarball: file:projects/task-resources.tgz}
|
||||
resolution: {integrity: sha512-5D40zL2OmfT/AUhLntguTpyNU69aI7rPa1LY3gc/ECo36wS0sy3KMpCWKSHo1bPmhOjTYnFLLN/Q8osYPZC8qw==, tarball: file:projects/task-resources.tgz}
|
||||
id: file:projects/task-resources.tgz
|
||||
name: '@rush-temp/task-resources'
|
||||
version: 0.0.0
|
||||
@ -15231,7 +15255,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/task.tgz:
|
||||
resolution: {integrity: sha512-vuIDzbmNkJCJOIgGk5kKyYMdX+b9SWj6waIdaauTu56LU3+eqzPCmVCF+DB+0ScKH4m8WpagsXbSAesyy/GrzQ==, tarball: file:projects/task.tgz}
|
||||
resolution: {integrity: sha512-gELHSUNsbaoFb7WSNRwJvbGwL1cZdxrEV8CKNnxgYQY57q4aUuA0GhDcB708C28w9ZO+oMEWgHyv6rDEybT76g==, tarball: file:projects/task.tgz}
|
||||
name: '@rush-temp/task'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15273,7 +15297,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/telegram-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-JgI/RYlXUX9pmPE485FJB9YH9UCoPcM2DLOvpPM9G2WXYgt4uinesyC+q5DNyisqARB6es5DgZaeNuVVF3t1Ng==, tarball: file:projects/telegram-resources.tgz}
|
||||
resolution: {integrity: sha512-vkLKhAlKuBp79AlHPC6V9PZ6w0vg8Yt4hXU85BwU7ClVX3eSDEl+98DuWRw3b57oBNnPx3g9ou22XHSO+wOb3Q==, tarball: file:projects/telegram-resources.tgz}
|
||||
id: file:projects/telegram-resources.tgz
|
||||
name: '@rush-temp/telegram-resources'
|
||||
version: 0.0.0
|
||||
@ -15308,7 +15332,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/telegram.tgz:
|
||||
resolution: {integrity: sha512-+rjDzAtro+awqj9URYLk9VfjjEOuheHNMPpvfK1Lzf76bn2VpkZEPSdFXn0MAoYI66RQbhfvVYambWV+FGuxBg==, tarball: file:projects/telegram.tgz}
|
||||
resolution: {integrity: sha512-+8aAsUVL2poPv9BqbOyQnO0iDD57+hzldkVloiZ7WBg+0+772elvhzcK/8umDr6ge8gYUhkdm2RDJSuZuwWpNQ==, tarball: file:projects/telegram.tgz}
|
||||
name: '@rush-temp/telegram'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15427,7 +15451,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/text-editor.tgz_89204ec304a9fe9c91bbfc5394a172bd:
|
||||
resolution: {integrity: sha512-CUuJVSwdoBUNvSy7GIaxemhr6fLCaU1BrbzzEpecP4HuRfQviyQQ2ME6gj4PWhcYVweFmUa1g3gRrCECtWIU8g==, tarball: file:projects/text-editor.tgz}
|
||||
resolution: {integrity: sha512-jGIekdEoT82BTFc4UVhbLOuQZJkVp4NSb7P3DOw5JJwJ0KvuU0mQZcA7AGKTI7eAuPBG6xjtFR2f4ykt+QcjmA==, tarball: file:projects/text-editor.tgz}
|
||||
id: file:projects/text-editor.tgz
|
||||
name: '@rush-temp/text-editor'
|
||||
version: 0.0.0
|
||||
@ -15529,7 +15553,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/tool.tgz:
|
||||
resolution: {integrity: sha512-Ioj5hc4nIdKPXhwXnABbqeWZHk9AppnFv8SZWBY4MQPH2TQTUm1DeY3p20qs8btaCzYeTNYDqaGYaWISGxmLqQ==, tarball: file:projects/tool.tgz}
|
||||
resolution: {integrity: sha512-lPQdPlcd6ajld4cEO1nodKIc4avevkjqB1qAYMfrLfN9X+F7WWv+bf3r0OLkDpXurUiufMgCo/KAMhypIeLdJw==, tarball: file:projects/tool.tgz}
|
||||
name: '@rush-temp/tool'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15597,7 +15621,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/tracker-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-lo7E5NfHbF6MhcCtZQxIIyL7Y0Ekx6Uw5x66IBv6xJy51KuLrIhgXDvkz+1o/rB+iTVzkVlJvKr/7VNOFz7agQ==, tarball: file:projects/tracker-resources.tgz}
|
||||
resolution: {integrity: sha512-mAMoLXcGbvCBFiy0w3I2qDd//xQHlzzNsjkDY9h/xREiAzkdzgusZNu6J/tlYEK0gJGKKXki46a1sH9dBNgIzQ==, tarball: file:projects/tracker-resources.tgz}
|
||||
id: file:projects/tracker-resources.tgz
|
||||
name: '@rush-temp/tracker-resources'
|
||||
version: 0.0.0
|
||||
@ -15633,7 +15657,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/tracker.tgz:
|
||||
resolution: {integrity: sha512-EI6jw1l4ymzG7wWjy1i2h6YGREq+MZmJ4lccKzARPncmuniPu705pcyZJNyt2JaI8buyMBs5MGMWZfqhaiEkHg==, tarball: file:projects/tracker.tgz}
|
||||
resolution: {integrity: sha512-nvbByvB71GEtdBV8AKozLay8li3ur+dd4BGncUFHzBhQ2eBt1ys6NTMYMbYrnqgxd2428MewMloSuBzo5aph9g==, tarball: file:projects/tracker.tgz}
|
||||
name: '@rush-temp/tracker'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15713,7 +15737,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/view-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-/ZeEjUOaNbJrTki3A99XO4dPaGS2ppXY5RClZJg8gbF4HVwcQG8PtglqJkc0ZWXzhAYUwYyM8sXEKQA9KJgtWw==, tarball: file:projects/view-resources.tgz}
|
||||
resolution: {integrity: sha512-QAyHumkqjZdhKiWroVQgmOXtsG7G5TKLEVro5hGna94kol1x/9jgl73djPm3Qjnc7zsQJ6A2Rp1WYjrhq0nzSw==, tarball: file:projects/view-resources.tgz}
|
||||
id: file:projects/view-resources.tgz
|
||||
name: '@rush-temp/view-resources'
|
||||
version: 0.0.0
|
||||
@ -15769,7 +15793,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/workbench-assets.tgz:
|
||||
resolution: {integrity: sha512-qtMH4kaxQh95CWI0kc2UYhHUV4xLjpi+x0I8YvAmieQqPL/S8Mw6B7bNdRX3uDogVgzu4SNjqq96ugSbauGIeQ==, tarball: file:projects/workbench-assets.tgz}
|
||||
resolution: {integrity: sha512-xWctLqgcvqMWeALmiUfT3/EZMt4G/PhEGy/cDkbUImIHRHP1REVK6wHhiFscUDTKDKZ8RFqp0ADgDhBcZaocfQ==, tarball: file:projects/workbench-assets.tgz}
|
||||
name: '@rush-temp/workbench-assets'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15790,7 +15814,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/workbench-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-ff56NyjSj5PgQmkJbToL25SlAvhBjTKIJ1Zu6cTQuAwoji1/c+K5VC5xNj5lgE+ZD0hVkGqKUHZwmZNK5ti8RA==, tarball: file:projects/workbench-resources.tgz}
|
||||
resolution: {integrity: sha512-yR3vzKvvDRTboCppCkQH/gvl2AP9hX79KeB9U1Zgh85Iaf23xCz4ayUGuQVN4N/2WBGSP/37WK0vXt+Uu36qqg==, tarball: file:projects/workbench-resources.tgz}
|
||||
id: file:projects/workbench-resources.tgz
|
||||
name: '@rush-temp/workbench-resources'
|
||||
version: 0.0.0
|
||||
|
@ -28,6 +28,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"@hcengineering/server-token": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
//
|
||||
|
||||
import type { Request, Response } from '@hcengineering/platform'
|
||||
import platform, { Status, Severity } from '@hcengineering/platform'
|
||||
import platform, { Severity, Status } from '@hcengineering/platform'
|
||||
|
||||
import { getWorkspaceId } from '@hcengineering/core'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
|
||||
interface LoginInfo {
|
||||
@ -37,7 +38,7 @@ function login (endpoint: string, email: string, password: string, workspace: st
|
||||
return { error: new Status(Severity.ERROR, platform.status.Unauthorized, {}) }
|
||||
}
|
||||
|
||||
const token = generateToken(email, workspace)
|
||||
const token = generateToken(email, getWorkspaceId(workspace))
|
||||
return { result: { token, endpoint } }
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
DOMAIN_TX,
|
||||
FindOptions,
|
||||
FindResult,
|
||||
getWorkspaceId,
|
||||
MeasureMetricsContext,
|
||||
Ref,
|
||||
ServerStorage,
|
||||
@ -122,7 +123,7 @@ export async function connect (handler: (tx: Tx) => void): Promise<ClientConnect
|
||||
factory: createNullFullTextAdapter,
|
||||
url: ''
|
||||
},
|
||||
workspace: ''
|
||||
workspace: getWorkspaceId('')
|
||||
}
|
||||
const serverStorage = await createServerStorage(conf)
|
||||
setMetadata(devmodel.metadata.DevModel, serverStorage)
|
||||
|
@ -31,8 +31,7 @@
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@types/faker": "~5.5.9",
|
||||
"@types/minio": "~7.0.11"
|
||||
"@types/faker": "~5.5.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^8.1.0",
|
||||
@ -53,10 +52,10 @@
|
||||
"@hcengineering/chunter": "~0.6.1",
|
||||
"pdfkit": "~0.13.0",
|
||||
"@hcengineering/attachment": "~0.6.1",
|
||||
"minio": "^7.0.26",
|
||||
"@types/pdfkit": "~0.12.3",
|
||||
"@hcengineering/task": "~0.6.0",
|
||||
"jpeg-js": "~0.4.3",
|
||||
"@hcengineering/server-token": "~0.6.0"
|
||||
"@hcengineering/server-token": "~0.6.0",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { Class, Doc, generateId, Ref, Space, TxOperations } from '@hcengineering/core'
|
||||
import { Class, Doc, generateId, Ref, Space, TxOperations, WorkspaceId } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import faker from 'faker'
|
||||
import { Client } from 'minio'
|
||||
import PDFDocument from 'pdfkit'
|
||||
|
||||
export interface AttachmentOptions {
|
||||
@ -13,8 +13,8 @@ export interface AttachmentOptions {
|
||||
export async function addAttachments<T extends Doc> (
|
||||
options: AttachmentOptions,
|
||||
client: TxOperations,
|
||||
minio: Client,
|
||||
dbName: string,
|
||||
minio: MinioService,
|
||||
workspaceId: WorkspaceId,
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>,
|
||||
_class: Ref<Class<T>>,
|
||||
@ -35,7 +35,7 @@ export async function addAttachments<T extends Doc> (
|
||||
|
||||
const buf = doc.read()
|
||||
bufLen = buf.length
|
||||
await minio.putObject(dbName, attachmentId, buf, bufLen, { 'Content-Type': 'application/pdf' })
|
||||
await minio.put(workspaceId, attachmentId, buf, bufLen, { 'Content-Type': 'application/pdf' })
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
|
@ -1,13 +1,13 @@
|
||||
import client from '@hcengineering/client'
|
||||
import clientResources from '@hcengineering/client-resources'
|
||||
import { Client } from '@hcengineering/core'
|
||||
import { Client, WorkspaceId } from '@hcengineering/core'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
|
||||
// eslint-disable-next-line
|
||||
const WebSocket = require('ws')
|
||||
|
||||
export async function connect (transactorUrl: string, workspace: string): Promise<Client> {
|
||||
export async function connect (transactorUrl: string, workspace: WorkspaceId): Promise<Client> {
|
||||
console.log('connecting to transactor...')
|
||||
const token = generateToken('anticrm@hc.engineering', workspace)
|
||||
|
||||
|
@ -15,9 +15,10 @@
|
||||
//
|
||||
|
||||
import { program } from 'commander'
|
||||
import { Client } from 'minio'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { generateIssues } from './issues'
|
||||
import { generateContacts } from './recruit'
|
||||
import { getWorkspaceId } from '@hcengineering/core'
|
||||
|
||||
const transactorUrl = process.env.TRANSACTOR_URL
|
||||
if (transactorUrl === undefined) {
|
||||
@ -43,7 +44,7 @@ if (minioSecretKey === undefined) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const minio = new Client({
|
||||
const minio = new MinioService({
|
||||
endPoint: minioEndpoint,
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
@ -55,16 +56,16 @@ program.version('0.0.1')
|
||||
|
||||
// available types: recruit, issue
|
||||
program
|
||||
.command('gen <genType> <workspace> <count>')
|
||||
.command('gen <genType> <workspace> <productId> <count>')
|
||||
.description('generate a bunch of random candidates with attachemnts and comments or issues')
|
||||
.option('-r, --random', 'generate random ids. So every call will add count <count> more candidates.', false)
|
||||
.option('-l, --lite', 'use same pdf and same account for applicant and candidates', false)
|
||||
.action(async (genType: string, workspace: string, count: number, cmd) => {
|
||||
.action(async (genType: string, workspace: string, productId: string, count: number, cmd) => {
|
||||
switch (genType) {
|
||||
case 'recruit':
|
||||
return await generateContacts(
|
||||
transactorUrl,
|
||||
workspace,
|
||||
getWorkspaceId(workspace, productId),
|
||||
{
|
||||
contacts: count,
|
||||
random: cmd.random as boolean,
|
||||
@ -81,7 +82,7 @@ program
|
||||
minio
|
||||
)
|
||||
case 'issue':
|
||||
return await generateIssues(transactorUrl, workspace, {
|
||||
return await generateIssues(transactorUrl, getWorkspaceId(workspace, productId), {
|
||||
count
|
||||
})
|
||||
default:
|
||||
|
@ -8,7 +8,8 @@ import core, {
|
||||
AttachedData,
|
||||
generateId,
|
||||
Ref,
|
||||
SortingOrder
|
||||
SortingOrder,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import tracker, { calcRank, Issue, IssuePriority, IssueStatus } from '../../../plugins/tracker/lib'
|
||||
|
||||
@ -41,8 +42,12 @@ export interface IssueOptions {
|
||||
count: number // how many issues to add
|
||||
}
|
||||
|
||||
export async function generateIssues (transactorUrl: string, dbName: string, options: IssueOptions): Promise<void> {
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
export async function generateIssues (
|
||||
transactorUrl: string,
|
||||
workspaceId: WorkspaceId,
|
||||
options: IssueOptions
|
||||
): Promise<void> {
|
||||
const connection = await connect(transactorUrl, workspaceId)
|
||||
const accounts = await connection.findAll(contact.class.EmployeeAccount, {})
|
||||
const account = faker.random.arrayElement(accounts)
|
||||
const client = new TxOperations(connection, account._id)
|
||||
|
@ -8,14 +8,15 @@ import core, {
|
||||
metricsToString,
|
||||
MixinUpdate,
|
||||
Ref,
|
||||
TxOperations
|
||||
TxOperations,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import recruit from '@hcengineering/model-recruit'
|
||||
import { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
||||
import { genRanks, State } from '@hcengineering/task'
|
||||
import faker from 'faker'
|
||||
import jpeg, { BufferRet } from 'jpeg-js'
|
||||
import { Client } from 'minio'
|
||||
import { addAttachments, AttachmentOptions } from './attachments'
|
||||
import { addComments, CommentOptions } from './comments'
|
||||
import { connect } from './connect'
|
||||
@ -41,11 +42,11 @@ export interface RecruitOptions {
|
||||
|
||||
export async function generateContacts (
|
||||
transactorUrl: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
options: RecruitOptions,
|
||||
minio: Client
|
||||
minio: MinioService
|
||||
): Promise<void> {
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
const connection = await connect(transactorUrl, workspaceId)
|
||||
|
||||
const accounts = await connection.findAll(contact.class.EmployeeAccount, {})
|
||||
const accountIds = accounts.map((a) => a._id)
|
||||
@ -60,12 +61,12 @@ export async function generateContacts (
|
||||
const ctx = new MeasureMetricsContext('recruit', { contacts: options.contacts })
|
||||
|
||||
for (let i = 0; i < options.contacts; i++) {
|
||||
await ctx.with('candidate', {}, (ctx) => genCandidate(ctx, i, minio, dbName, options, candidates, client))
|
||||
await ctx.with('candidate', {}, (ctx) => genCandidate(ctx, i, minio, workspaceId, options, candidates, client))
|
||||
}
|
||||
// Work on Vacancy/Applications.
|
||||
for (let i = 0; i < options.vacancy; i++) {
|
||||
await ctx.with('vacancy', {}, (ctx) =>
|
||||
genVacansyApplicants(ctx, accountIds, options, i, client, minio, dbName, candidates, emoloyeeIds)
|
||||
genVacansyApplicants(ctx, accountIds, options, i, client, minio, workspaceId, candidates, emoloyeeIds)
|
||||
)
|
||||
}
|
||||
|
||||
@ -81,8 +82,8 @@ async function genVacansyApplicants (
|
||||
options: RecruitOptions,
|
||||
i: number,
|
||||
client: TxOperations,
|
||||
minio: Client,
|
||||
dbName: string,
|
||||
minio: MinioService,
|
||||
workspaceId: WorkspaceId,
|
||||
candidates: Ref<Candidate>[],
|
||||
emoloyeeIds: Ref<Employee>[]
|
||||
): Promise<void> {
|
||||
@ -112,7 +113,7 @@ async function genVacansyApplicants (
|
||||
options.attachments,
|
||||
client,
|
||||
minio,
|
||||
dbName,
|
||||
workspaceId,
|
||||
vacancyId,
|
||||
vacancyId,
|
||||
recruit.class.Vacancy,
|
||||
@ -133,7 +134,7 @@ async function genVacansyApplicants (
|
||||
const rankGen = genRanks(candidates.length)
|
||||
for (const candidateId of applicantsFor) {
|
||||
await ctx.with('applicant', {}, (ctx) =>
|
||||
genApplicant(ctx, vacancyId, candidateId, emoloyeeIds, states, client, options, minio, dbName, rankGen)
|
||||
genApplicant(ctx, vacancyId, candidateId, emoloyeeIds, states, client, options, minio, workspaceId, rankGen)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -146,8 +147,8 @@ async function genApplicant (
|
||||
states: Ref<State>[],
|
||||
client: TxOperations,
|
||||
options: RecruitOptions,
|
||||
minio: Client,
|
||||
dbName: string,
|
||||
minio: MinioService,
|
||||
workspaceId: WorkspaceId,
|
||||
rankGen: Generator<string, void, unknown>
|
||||
): Promise<void> {
|
||||
const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref<Applicant>
|
||||
@ -180,7 +181,7 @@ async function genApplicant (
|
||||
options.attachments,
|
||||
client,
|
||||
minio,
|
||||
dbName,
|
||||
workspaceId,
|
||||
vacancyId,
|
||||
applicantId,
|
||||
recruit.class.Applicant,
|
||||
@ -212,8 +213,8 @@ const liteAvatar = generateAvatar(0)
|
||||
async function genCandidate (
|
||||
ctx: MeasureContext,
|
||||
i: number,
|
||||
minio: Client,
|
||||
dbName: string,
|
||||
minio: MinioService,
|
||||
workspaceId: WorkspaceId,
|
||||
options: RecruitOptions,
|
||||
candidates: Ref<Candidate>[],
|
||||
client: TxOperations
|
||||
@ -225,7 +226,7 @@ async function genCandidate (
|
||||
|
||||
if (!options.lite) {
|
||||
await ctx.with('avatar', {}, () =>
|
||||
minio.putObject(dbName, imgId, jpegImageData.data, jpegImageData.data.length, { 'Content-Type': 'image/jpeg' })
|
||||
minio.put(workspaceId, imgId, jpegImageData.data, jpegImageData.data.length, { 'Content-Type': 'image/jpeg' })
|
||||
)
|
||||
}
|
||||
const candidate: Data<Person> = {
|
||||
@ -283,7 +284,7 @@ async function genCandidate (
|
||||
options.attachments,
|
||||
client,
|
||||
minio,
|
||||
dbName,
|
||||
workspaceId,
|
||||
recruit.space.CandidatesPublic,
|
||||
candidateId,
|
||||
contact.class.Person,
|
||||
|
@ -14,8 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Doc, Ref, TxResult } from '@hcengineering/core'
|
||||
import { DOMAIN_TX, MeasureMetricsContext } from '@hcengineering/core'
|
||||
import { Doc, DOMAIN_TX, getWorkspaceId, MeasureMetricsContext, Ref, TxResult } from '@hcengineering/core'
|
||||
import { createInMemoryTxAdapter } from '@hcengineering/dev-storage'
|
||||
import {
|
||||
createInMemoryAdapter,
|
||||
@ -74,12 +73,13 @@ export async function start (port: number, host?: string): Promise<void> {
|
||||
factory: createNullFullTextAdapter,
|
||||
url: ''
|
||||
},
|
||||
workspace: ''
|
||||
workspace: getWorkspaceId('')
|
||||
}
|
||||
return createPipeline(conf, [])
|
||||
},
|
||||
(token, pipeline, broadcast) => new ClientSession(broadcast, token, pipeline),
|
||||
port,
|
||||
'',
|
||||
host
|
||||
)
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ import type {
|
||||
Ref,
|
||||
StorageIterator,
|
||||
Tx,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { Hierarchy, TxDb } from '@hcengineering/core'
|
||||
import builder from '@hcengineering/model-all'
|
||||
@ -82,7 +83,7 @@ class InMemoryTxAdapter implements TxAdapter {
|
||||
export async function createInMemoryTxAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
workspace: string
|
||||
workspace: WorkspaceId
|
||||
): Promise<TxAdapter> {
|
||||
return new InMemoryTxAdapter(hierarchy)
|
||||
}
|
||||
|
@ -43,12 +43,11 @@
|
||||
"@types/request": "~2.48.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"mongodb": "^4.9.0",
|
||||
"mongodb": "^4.11.0",
|
||||
"commander": "^8.1.0",
|
||||
"@hcengineering/account": "~0.6.0",
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"@hcengineering/contact": "~0.6.8",
|
||||
"minio": "^7.0.26",
|
||||
"@hcengineering/model-all": "~0.6.0",
|
||||
"@hcengineering/model-telegram": "~0.6.0",
|
||||
"@hcengineering/telegram": "~0.6.2",
|
||||
@ -112,6 +111,7 @@
|
||||
"@hcengineering/lead": "~0.6.0",
|
||||
"email-addresses": "^5.0.0",
|
||||
"libphonenumber-js": "^1.9.46",
|
||||
"@hcengineering/setting": "~0.6.1"
|
||||
"@hcengineering/setting": "~0.6.1",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -18,18 +18,50 @@ import { prepareTools as prepareToolsRaw } from '@hcengineering/server-tool'
|
||||
import { Data, Tx, Version } from '@hcengineering/core'
|
||||
import { MigrateOperation } from '@hcengineering/model'
|
||||
import builder, { migrateOperations, version } from '@hcengineering/model-all'
|
||||
import { Client } from 'minio'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { devTool } from '.'
|
||||
|
||||
import { serverAttachmentId } from '@hcengineering/server-attachment'
|
||||
import { serverCalendarId } from '@hcengineering/server-calendar'
|
||||
import { serverChunterId } from '@hcengineering/server-chunter'
|
||||
import { serverContactId } from '@hcengineering/server-contact'
|
||||
import { serverGmailId } from '@hcengineering/server-gmail'
|
||||
import { serverInventoryId } from '@hcengineering/server-inventory'
|
||||
import { serverLeadId } from '@hcengineering/server-lead'
|
||||
import { serverNotificationId } from '@hcengineering/server-notification'
|
||||
import { serverRecruitId } from '@hcengineering/server-recruit'
|
||||
import { serverSettingId } from '@hcengineering/server-setting'
|
||||
import { serverTagsId } from '@hcengineering/server-tags'
|
||||
import { serverTaskId } from '@hcengineering/server-task'
|
||||
import { serverTrackerId } from '@hcengineering/server-tracker'
|
||||
import { serverTelegramId } from '@hcengineering/server-telegram'
|
||||
import { serverHrId } from '@hcengineering/server-hr'
|
||||
import { addLocation } from '@hcengineering/platform'
|
||||
|
||||
addLocation(serverAttachmentId, () => import('@hcengineering/server-attachment-resources'))
|
||||
addLocation(serverContactId, () => import('@hcengineering/server-contact-resources'))
|
||||
addLocation(serverNotificationId, () => import('@hcengineering/server-notification-resources'))
|
||||
addLocation(serverChunterId, () => import('@hcengineering/server-chunter-resources'))
|
||||
addLocation(serverInventoryId, () => import('@hcengineering/server-inventory-resources'))
|
||||
addLocation(serverLeadId, () => import('@hcengineering/server-lead-resources'))
|
||||
addLocation(serverRecruitId, () => import('@hcengineering/server-recruit-resources'))
|
||||
addLocation(serverSettingId, () => import('@hcengineering/server-setting-resources'))
|
||||
addLocation(serverTaskId, () => import('@hcengineering/server-task-resources'))
|
||||
addLocation(serverTrackerId, () => import('@hcengineering/server-tracker-resources'))
|
||||
addLocation(serverTagsId, () => import('@hcengineering/server-tags-resources'))
|
||||
addLocation(serverCalendarId, () => import('@hcengineering/server-calendar-resources'))
|
||||
addLocation(serverGmailId, () => import('@hcengineering/server-gmail-resources'))
|
||||
addLocation(serverTelegramId, () => import('@hcengineering/server-telegram-resources'))
|
||||
addLocation(serverHrId, () => import('@hcengineering/server-hr-resources'))
|
||||
|
||||
function prepareTools (): {
|
||||
mongodbUri: string
|
||||
minio: Client
|
||||
minio: MinioService
|
||||
txes: Tx[]
|
||||
version: Data<Version>
|
||||
migrateOperations: MigrateOperation[]
|
||||
productId: string
|
||||
} {
|
||||
return { ...prepareToolsRaw(builder.getTxes()), version, migrateOperations, productId: '' }
|
||||
return { ...prepareToolsRaw(builder.getTxes()), version, migrateOperations }
|
||||
}
|
||||
|
||||
devTool(prepareTools)
|
||||
devTool(prepareTools, '')
|
||||
|
@ -14,12 +14,12 @@
|
||||
//
|
||||
|
||||
import contact, { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { Ref, TxOperations } from '@hcengineering/core'
|
||||
import { Ref, TxOperations, WorkspaceId } from '@hcengineering/core'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
export async function removeDuplicates (transactorUrl: string, dbName: string): Promise<void> {
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
export async function removeDuplicates (transactorUrl: string, workspaceId: WorkspaceId): Promise<void> {
|
||||
const connection = await connect(transactorUrl, workspaceId)
|
||||
|
||||
try {
|
||||
console.log('loading cvs document...')
|
||||
|
@ -14,7 +14,15 @@
|
||||
//
|
||||
|
||||
import contact, { Contact, EmployeeAccount, Organization } from '@hcengineering/contact'
|
||||
import core, { Class, DocumentUpdate, MixinUpdate, Ref, SortingOrder, TxOperations } from '@hcengineering/core'
|
||||
import core, {
|
||||
Class,
|
||||
DocumentUpdate,
|
||||
MixinUpdate,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
TxOperations,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import lead, { Customer, Funnel, Lead } from '@hcengineering/lead'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import task, { calcRank, DoneState, genRanks, State } from '@hcengineering/task'
|
||||
@ -125,8 +133,8 @@ export async function updateStates<T extends State | DoneState> (
|
||||
}
|
||||
}
|
||||
|
||||
export async function importLead (transactorUrl: string, dbName: string, csvFile: string): Promise<void> {
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
export async function importLead (transactorUrl: string, workspaceId: WorkspaceId, csvFile: string): Promise<void> {
|
||||
const connection = await connect(transactorUrl, workspaceId)
|
||||
|
||||
try {
|
||||
console.log('loading cvs document...')
|
||||
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import contact, { Contact, EmployeeAccount, Organization } from '@hcengineering/contact'
|
||||
import core, { generateId, MixinUpdate, Ref, TxOperations } from '@hcengineering/core'
|
||||
import core, { generateId, MixinUpdate, Ref, TxOperations, WorkspaceId } from '@hcengineering/core'
|
||||
import lead from '@hcengineering/lead'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import { readFile } from 'fs/promises'
|
||||
@ -98,8 +98,8 @@ export async function parseCSV (csvData: string): Promise<any[]> {
|
||||
})
|
||||
}
|
||||
|
||||
export async function importLead2 (transactorUrl: string, dbName: string, csvFile: string): Promise<void> {
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
export async function importLead2 (transactorUrl: string, workspaceId: WorkspaceId, csvFile: string): Promise<void> {
|
||||
const connection = await connect(transactorUrl, workspaceId)
|
||||
|
||||
try {
|
||||
console.log('loading cvs document...')
|
||||
|
@ -24,7 +24,8 @@ import core, {
|
||||
Ref,
|
||||
SortingOrder,
|
||||
TxOperations,
|
||||
WithLookup
|
||||
WithLookup,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import lead, { Customer, Funnel, Lead } from '@hcengineering/lead'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
@ -229,8 +230,8 @@ export async function parseCSV (csvData: string): Promise<any[]> {
|
||||
})
|
||||
}
|
||||
|
||||
export async function importOrgs (transactorUrl: string, dbName: string, csvFile: string): Promise<void> {
|
||||
const connection = (await connect(transactorUrl, dbName, undefined, {
|
||||
export async function importOrgs (transactorUrl: string, workspaceId: WorkspaceId, csvFile: string): Promise<void> {
|
||||
const connection = (await connect(transactorUrl, workspaceId, undefined, {
|
||||
mode: 'backup'
|
||||
})) as unknown as Client & BackupClient
|
||||
try {
|
||||
|
@ -31,7 +31,8 @@ import core, {
|
||||
Ref,
|
||||
Timestamp,
|
||||
TxOperations,
|
||||
WithLookup
|
||||
WithLookup,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { Asset, getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
import recruit, { Candidate } from '@hcengineering/recruit'
|
||||
@ -385,11 +386,11 @@ export async function updateTalantClasses (
|
||||
|
||||
export async function importTalants (
|
||||
transactorUrl: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
csvFile: string,
|
||||
rekoniUrl: string
|
||||
): Promise<void> {
|
||||
const connection = (await connect(transactorUrl, dbName, undefined, {
|
||||
const connection = (await connect(transactorUrl, workspaceId, undefined, {
|
||||
mode: 'backup'
|
||||
})) as unknown as Client & BackupClient
|
||||
|
||||
@ -410,7 +411,13 @@ export async function importTalants (
|
||||
await createMissingClass(client, locationDetails, getEmbeddedLabel('Language & Relocations'))
|
||||
|
||||
await updateTalantClasses(client, records, fieldMapping)
|
||||
await createTalants(client, filledFields, connection, rekoniUrl, generateToken('anticrm@hc.engineering', dbName))
|
||||
await createTalants(
|
||||
client,
|
||||
filledFields,
|
||||
connection,
|
||||
rekoniUrl,
|
||||
generateToken('anticrm@hc.engineering', workspaceId)
|
||||
)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Client as ElasticClient } from '@elastic/elasticsearch'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import core, {
|
||||
AttachedDoc,
|
||||
@ -32,6 +33,7 @@ import core, {
|
||||
Ref,
|
||||
ServerStorage,
|
||||
StorageIterator,
|
||||
toWorkspaceString,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
@ -39,16 +41,13 @@ import core, {
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxResult,
|
||||
TxUpdateDoc
|
||||
TxUpdateDoc,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { createElasticAdapter } from '@hcengineering/elastic'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
|
||||
import { createMongoAdapter, createMongoTxAdapter } from '@hcengineering/mongo'
|
||||
import { addLocation } from '@hcengineering/platform'
|
||||
import { serverAttachmentId } from '@hcengineering/server-attachment'
|
||||
import { serverCalendarId } from '@hcengineering/server-calendar'
|
||||
import { serverChunterId } from '@hcengineering/server-chunter'
|
||||
import { serverContactId } from '@hcengineering/server-contact'
|
||||
import { createMongoAdapter, createMongoTxAdapter, getWorkspaceDB } from '@hcengineering/mongo'
|
||||
import {
|
||||
createServerStorage,
|
||||
DbAdapter,
|
||||
@ -58,48 +57,35 @@ import {
|
||||
IndexedDoc,
|
||||
TxAdapter
|
||||
} from '@hcengineering/server-core'
|
||||
import { serverGmailId } from '@hcengineering/server-gmail'
|
||||
import { serverInventoryId } from '@hcengineering/server-inventory'
|
||||
import { serverLeadId } from '@hcengineering/server-lead'
|
||||
import { serverNotificationId } from '@hcengineering/server-notification'
|
||||
import { serverRecruitId } from '@hcengineering/server-recruit'
|
||||
import { serverSettingId } from '@hcengineering/server-setting'
|
||||
import { serverTagsId } from '@hcengineering/server-tags'
|
||||
import { serverTaskId } from '@hcengineering/server-task'
|
||||
import { serverTrackerId } from '@hcengineering/server-tracker'
|
||||
import { serverTelegramId } from '@hcengineering/server-telegram'
|
||||
import { serverHrId } from '@hcengineering/server-hr'
|
||||
import { Client as ElasticClient } from '@elastic/elasticsearch'
|
||||
import { Client } from 'minio'
|
||||
import { Db, MongoClient } from 'mongodb'
|
||||
import { listMinioObjects } from './minio'
|
||||
|
||||
export async function rebuildElastic (
|
||||
mongoUrl: string,
|
||||
dbName: string,
|
||||
minio: Client,
|
||||
workspaceId: WorkspaceId,
|
||||
minio: MinioService,
|
||||
elasticUrl: string
|
||||
): Promise<void> {
|
||||
await dropElastic(elasticUrl, dbName)
|
||||
return await restoreElastic(mongoUrl, dbName, minio, elasticUrl)
|
||||
await dropElastic(elasticUrl, workspaceId)
|
||||
return await restoreElastic(mongoUrl, workspaceId, minio, elasticUrl)
|
||||
}
|
||||
|
||||
async function dropElastic (elasticUrl: string, dbName: string): Promise<void> {
|
||||
async function dropElastic (elasticUrl: string, workspaceId: WorkspaceId): Promise<void> {
|
||||
console.log('drop existing elastic docment')
|
||||
const client = new ElasticClient({
|
||||
node: elasticUrl
|
||||
})
|
||||
const productWs = toWorkspaceString(workspaceId)
|
||||
await new Promise((resolve, reject) => {
|
||||
client.indices.exists(
|
||||
{
|
||||
index: dbName
|
||||
index: productWs
|
||||
},
|
||||
(err: any, result: any) => {
|
||||
if (err != null) reject(err)
|
||||
if (result.body === true) {
|
||||
client.indices.delete(
|
||||
{
|
||||
index: dbName
|
||||
index: productWs
|
||||
},
|
||||
(err: any, result: any) => {
|
||||
if (err != null) reject(err)
|
||||
@ -122,31 +108,21 @@ export class ElasticTool {
|
||||
db!: Db
|
||||
fulltext!: FullTextIndex
|
||||
|
||||
constructor (readonly mongoUrl: string, readonly dbName: string, readonly minio: Client, readonly elasticUrl: string) {
|
||||
addLocation(serverAttachmentId, () => import('@hcengineering/server-attachment-resources'))
|
||||
addLocation(serverContactId, () => import('@hcengineering/server-contact-resources'))
|
||||
addLocation(serverNotificationId, () => import('@hcengineering/server-notification-resources'))
|
||||
addLocation(serverChunterId, () => import('@hcengineering/server-chunter-resources'))
|
||||
addLocation(serverInventoryId, () => import('@hcengineering/server-inventory-resources'))
|
||||
addLocation(serverLeadId, () => import('@hcengineering/server-lead-resources'))
|
||||
addLocation(serverRecruitId, () => import('@hcengineering/server-recruit-resources'))
|
||||
addLocation(serverSettingId, () => import('@hcengineering/server-setting-resources'))
|
||||
addLocation(serverTaskId, () => import('@hcengineering/server-task-resources'))
|
||||
addLocation(serverTrackerId, () => import('@hcengineering/server-tracker-resources'))
|
||||
addLocation(serverTagsId, () => import('@hcengineering/server-tags-resources'))
|
||||
addLocation(serverCalendarId, () => import('@hcengineering/server-calendar-resources'))
|
||||
addLocation(serverGmailId, () => import('@hcengineering/server-gmail-resources'))
|
||||
addLocation(serverTelegramId, () => import('@hcengineering/server-telegram-resources'))
|
||||
addLocation(serverHrId, () => import('@hcengineering/server-hr-resources'))
|
||||
constructor (
|
||||
readonly mongoUrl: string,
|
||||
readonly workspaceId: WorkspaceId,
|
||||
readonly minio: MinioService,
|
||||
readonly elasticUrl: string
|
||||
) {
|
||||
this.mongoClient = new MongoClient(mongoUrl)
|
||||
}
|
||||
|
||||
async connect (): Promise<() => Promise<void>> {
|
||||
await this.mongoClient.connect()
|
||||
|
||||
this.db = this.mongoClient.db(this.dbName)
|
||||
this.elastic = await createElasticAdapter(this.elasticUrl, this.dbName)
|
||||
this.storage = await createStorage(this.mongoUrl, this.elasticUrl, this.dbName)
|
||||
this.db = getWorkspaceDB(this.mongoClient, this.workspaceId)
|
||||
this.elastic = await createElasticAdapter(this.elasticUrl, this.workspaceId)
|
||||
this.storage = await createStorage(this.mongoUrl, this.elasticUrl, this.workspaceId)
|
||||
this.fulltext = new FullTextIndex(this.storage.hierarchy, this.elastic, this.storage, true)
|
||||
|
||||
return async () => {
|
||||
@ -160,8 +136,8 @@ export class ElasticTool {
|
||||
const doc: Attachment | null = await this.db.collection<Attachment>(DOMAIN_ATTACHMENT).findOne({ file: name })
|
||||
if (doc == null) return
|
||||
|
||||
const buffer = await this.readMinioObject(name)
|
||||
await this.indexAttachmentDoc(doc, buffer)
|
||||
const buffer = await this.minio.read(this.workspaceId, name)
|
||||
await this.indexAttachmentDoc(doc, Buffer.concat(buffer))
|
||||
}
|
||||
|
||||
async indexAttachmentDoc (doc: Attachment, buffer: Buffer): Promise<void> {
|
||||
@ -179,29 +155,15 @@ export class ElasticTool {
|
||||
|
||||
await this.elastic.index(indexedDoc)
|
||||
}
|
||||
|
||||
private async readMinioObject (name: string): Promise<Buffer> {
|
||||
const data = await this.minio.getObject(this.dbName, name)
|
||||
const chunks: Buffer[] = []
|
||||
await new Promise<void>((resolve) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
const b = chunk as Buffer
|
||||
chunks.push(b)
|
||||
}
|
||||
})
|
||||
|
||||
data.on('end', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
return Buffer.concat(chunks)
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreElastic (mongoUrl: string, dbName: string, minio: Client, elasticUrl: string): Promise<void> {
|
||||
const tool = new ElasticTool(mongoUrl, dbName, minio, elasticUrl)
|
||||
async function restoreElastic (
|
||||
mongoUrl: string,
|
||||
workspaceId: WorkspaceId,
|
||||
minio: MinioService,
|
||||
elasticUrl: string
|
||||
): Promise<void> {
|
||||
const tool = new ElasticTool(mongoUrl, workspaceId, minio, elasticUrl)
|
||||
const done = await tool.connect()
|
||||
try {
|
||||
const data = await tool.db.collection<Tx>(DOMAIN_TX).find().toArray()
|
||||
@ -319,8 +281,8 @@ async function restoreElastic (mongoUrl: string, dbName: string, minio: Client,
|
||||
console.log('replay elastic mixin transactions done', Date.now() - startMixin)
|
||||
|
||||
let apos = 0
|
||||
if (await minio.bucketExists(dbName)) {
|
||||
const minioObjects = await listMinioObjects(minio, dbName)
|
||||
if (await minio.exists(workspaceId)) {
|
||||
const minioObjects = await minio.list(workspaceId)
|
||||
console.log('reply elastic documents', minioObjects.length)
|
||||
for (const d of minioObjects) {
|
||||
apos++
|
||||
@ -342,7 +304,7 @@ async function restoreElastic (mongoUrl: string, dbName: string, minio: Client,
|
||||
}
|
||||
}
|
||||
|
||||
async function createStorage (mongoUrl: string, elasticUrl: string, workspace: string): Promise<ServerStorage> {
|
||||
async function createStorage (mongoUrl: string, elasticUrl: string, workspace: WorkspaceId): Promise<ServerStorage> {
|
||||
const conf: DbConfiguration = {
|
||||
domains: {
|
||||
[DOMAIN_TX]: 'MongoTx'
|
||||
@ -370,20 +332,20 @@ async function createStorage (mongoUrl: string, elasticUrl: string, workspace: s
|
||||
async function createMongoReadOnlyAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
modelDb: ModelDb
|
||||
): Promise<DbAdapter> {
|
||||
const adapter = await createMongoAdapter(hierarchy, url, dbName, modelDb)
|
||||
const adapter = await createMongoAdapter(hierarchy, url, workspaceId, modelDb)
|
||||
return new MongoReadOnlyAdapter(adapter)
|
||||
}
|
||||
|
||||
async function createMongoReadOnlyTxAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
modelDb: ModelDb
|
||||
): Promise<TxAdapter> {
|
||||
const adapter = await createMongoTxAdapter(hierarchy, url, dbName, modelDb)
|
||||
const adapter = await createMongoTxAdapter(hierarchy, url, workspaceId, modelDb)
|
||||
return new MongoReadOnlyTxAdapter(adapter)
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,10 @@ import core, {
|
||||
SortingOrder,
|
||||
Space,
|
||||
TxOperations,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import recruit from '@hcengineering/model-recruit'
|
||||
import { Applicant, Candidate, Vacancy } from '@hcengineering/recruit'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
@ -37,7 +39,6 @@ import { deepEqual } from 'fast-equals'
|
||||
import { existsSync } from 'fs'
|
||||
import { readdir, readFile, stat } from 'fs/promises'
|
||||
import mime from 'mime-types'
|
||||
import { Client } from 'minio'
|
||||
import { dirname, join } from 'path'
|
||||
import { parseStringPromise } from 'xml2js'
|
||||
import { ElasticTool } from './elastic'
|
||||
@ -90,15 +91,15 @@ function get (data: any, key: string): string | undefined {
|
||||
|
||||
export async function importXml (
|
||||
transactorUrl: string,
|
||||
dbName: string,
|
||||
minio: Client,
|
||||
workspaceId: WorkspaceId,
|
||||
minio: MinioService,
|
||||
xmlFile: string,
|
||||
mongoUrl: string,
|
||||
elasticUrl: string
|
||||
): Promise<void> {
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
const connection = await connect(transactorUrl, workspaceId)
|
||||
|
||||
const tool = new ElasticTool(mongoUrl, dbName, minio, elasticUrl)
|
||||
const tool = new ElasticTool(mongoUrl, workspaceId, minio, elasticUrl)
|
||||
const done = await tool.connect()
|
||||
try {
|
||||
console.log('loading xml document...')
|
||||
@ -152,10 +153,10 @@ export async function importXml (
|
||||
const fileName = join(candidateRoot, f)
|
||||
const data = await readFile(fileName)
|
||||
try {
|
||||
await minio.statObject(dbName, attachId)
|
||||
await minio.stat(workspaceId, attachId)
|
||||
} catch (err: any) {
|
||||
// No object, put new one.
|
||||
await minio.putObject(dbName, attachId, data, data.length, {
|
||||
await minio.put(workspaceId, attachId, data, data.length, {
|
||||
'Content-Type': type
|
||||
})
|
||||
}
|
||||
|
@ -54,9 +54,9 @@ import { updateCandidates } from './recruit'
|
||||
import { clearTelegramHistory } from './telegram'
|
||||
import { diffWorkspace, dumpWorkspace, restoreWorkspace } from './workspace'
|
||||
|
||||
import { Data, Tx, Version } from '@hcengineering/core'
|
||||
import { Data, getWorkspaceId, Tx, Version } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { MigrateOperation } from '@hcengineering/model'
|
||||
import { Client } from 'minio'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -64,12 +64,12 @@ import { Client } from 'minio'
|
||||
export function devTool (
|
||||
prepareTools: () => {
|
||||
mongodbUri: string
|
||||
minio: Client
|
||||
minio: MinioService
|
||||
txes: Tx[]
|
||||
version: Data<Version>
|
||||
migrateOperations: MigrateOperation[]
|
||||
productId: string
|
||||
}
|
||||
},
|
||||
productId: string
|
||||
): void {
|
||||
const serverSecret = process.env.SERVER_SECRET
|
||||
if (serverSecret === undefined) {
|
||||
@ -117,7 +117,7 @@ export function devTool (
|
||||
const { mongodbUri } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
console.log(`creating account ${cmd.first as string} ${cmd.last as string} (${email})...`)
|
||||
await createAccount(db, email, cmd.password, cmd.first, cmd.last)
|
||||
await createAccount(db, productId, email, cmd.password, cmd.first, cmd.last)
|
||||
})
|
||||
})
|
||||
|
||||
@ -129,7 +129,7 @@ export function devTool (
|
||||
const { mongodbUri } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
console.log(`update account ${email} ${cmd.first as string} ${cmd.last as string}...`)
|
||||
await replacePassword(db, email, cmd.password)
|
||||
await replacePassword(db, productId, email, cmd.password)
|
||||
})
|
||||
})
|
||||
|
||||
@ -140,7 +140,7 @@ export function devTool (
|
||||
const { mongodbUri } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db, client) => {
|
||||
console.log(`assigning user ${email} to ${workspace}...`)
|
||||
await assignWorkspace(db, email, workspace)
|
||||
await assignWorkspace(db, productId, email, workspace)
|
||||
})
|
||||
})
|
||||
|
||||
@ -160,9 +160,9 @@ export function devTool (
|
||||
.description('create workspace')
|
||||
.requiredOption('-o, --organization <organization>', 'organization name')
|
||||
.action(async (workspace, cmd) => {
|
||||
const { mongodbUri, txes, version, migrateOperations, productId } = prepareTools()
|
||||
const { mongodbUri, txes, version, migrateOperations } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
await createWorkspace(version, txes, migrateOperations, productId, db, workspace, cmd.organization)
|
||||
await createWorkspace(version, txes, migrateOperations, db, productId, workspace, cmd.organization)
|
||||
})
|
||||
})
|
||||
|
||||
@ -171,14 +171,14 @@ export function devTool (
|
||||
.description('set user role')
|
||||
.action(async (email: string, workspace: string, role: number, cmd) => {
|
||||
console.log(`set user ${email} role for ${workspace}...`)
|
||||
await setRole(email, workspace, role)
|
||||
await setRole(email, workspace, productId, role)
|
||||
})
|
||||
|
||||
program
|
||||
.command('upgrade-workspace <name>')
|
||||
.description('upgrade workspace')
|
||||
.action(async (workspace, cmd) => {
|
||||
const { mongodbUri, version, txes, migrateOperations, productId } = prepareTools()
|
||||
const { mongodbUri, version, txes, migrateOperations } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
await upgradeWorkspace(version, txes, migrateOperations, productId, db, workspace)
|
||||
})
|
||||
@ -188,7 +188,7 @@ export function devTool (
|
||||
.command('upgrade')
|
||||
.description('upgrade')
|
||||
.action(async (cmd) => {
|
||||
const { mongodbUri, version, txes, migrateOperations, productId } = prepareTools()
|
||||
const { mongodbUri, version, txes, migrateOperations } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
const workspaces = await listWorkspaces(db, productId)
|
||||
for (const ws of workspaces) {
|
||||
@ -204,12 +204,12 @@ export function devTool (
|
||||
.action(async (workspace, cmd) => {
|
||||
const { mongodbUri } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
const ws = await getWorkspace(db, workspace)
|
||||
const ws = await getWorkspace(db, productId, workspace)
|
||||
if (ws === null) {
|
||||
console.log('no workspace exists')
|
||||
return
|
||||
}
|
||||
await dropWorkspace(db, workspace)
|
||||
await dropWorkspace(db, productId, workspace)
|
||||
})
|
||||
})
|
||||
|
||||
@ -217,7 +217,7 @@ export function devTool (
|
||||
.command('list-workspaces')
|
||||
.description('List workspaces')
|
||||
.action(async () => {
|
||||
const { mongodbUri, version, productId } = prepareTools()
|
||||
const { mongodbUri, version } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
const workspacesJSON = JSON.stringify(await listWorkspaces(db, productId), null, 2)
|
||||
console.info(workspacesJSON)
|
||||
@ -240,27 +240,27 @@ export function devTool (
|
||||
program
|
||||
.command('drop-account <name>')
|
||||
.description('drop account')
|
||||
.action(async (email, cmd) => {
|
||||
.action(async (email: string, cmd) => {
|
||||
const { mongodbUri } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
await dropAccount(db, email)
|
||||
await dropAccount(db, productId, email)
|
||||
})
|
||||
})
|
||||
|
||||
program
|
||||
.command('dump-workspace <workspace> <dirName>')
|
||||
.description('dump workspace transactions and minio resources')
|
||||
.action(async (workspace, dirName, cmd) => {
|
||||
.action(async (workspace: string, dirName: string, cmd) => {
|
||||
const { mongodbUri, minio } = prepareTools()
|
||||
return await dumpWorkspace(mongodbUri, workspace, dirName, minio)
|
||||
return await dumpWorkspace(mongodbUri, getWorkspaceId(workspace, productId), dirName, minio)
|
||||
})
|
||||
|
||||
program
|
||||
.command('backup <dirName> <workspace>')
|
||||
.description('dump workspace transactions and minio resources')
|
||||
.action(async (dirName, workspace, cmd) => {
|
||||
.action(async (dirName: string, workspace: string, cmd) => {
|
||||
const storage = await createFileBackupStorage(dirName)
|
||||
return await backup(transactorUrl, workspace, storage)
|
||||
return await backup(transactorUrl, getWorkspaceId(workspace, productId), storage)
|
||||
})
|
||||
|
||||
program
|
||||
@ -274,7 +274,7 @@ export function devTool (
|
||||
program
|
||||
.command('backup-list <dirName>')
|
||||
.description('list snaphost ids for backup')
|
||||
.action(async (dirName, cmd) => {
|
||||
.action(async (dirName: string, cmd) => {
|
||||
const storage = await createFileBackupStorage(dirName)
|
||||
return await backupList(storage)
|
||||
})
|
||||
@ -282,36 +282,39 @@ export function devTool (
|
||||
program
|
||||
.command('backup-s3 <bucketName> <dirName> <workspace>')
|
||||
.description('dump workspace transactions and minio resources')
|
||||
.action(async (bucketName, dirName, workspace, cmd) => {
|
||||
.action(async (bucketName: string, dirName: string, workspace: string, cmd) => {
|
||||
const { minio } = prepareTools()
|
||||
const storage = await createMinioBackupStorage(minio, bucketName, dirName)
|
||||
return await backup(transactorUrl, workspace, storage)
|
||||
const wsId = getWorkspaceId(workspace, productId)
|
||||
const storage = await createMinioBackupStorage(minio, wsId, dirName)
|
||||
return await backup(transactorUrl, wsId, storage)
|
||||
})
|
||||
program
|
||||
.command('backup-s3-restore <bucketName>, <dirName> <workspace> [date]')
|
||||
.description('dump workspace transactions and minio resources')
|
||||
.action(async (bucketName, dirName, workspace, date, cmd) => {
|
||||
.action(async (bucketName: string, dirName: string, workspace: string, date, cmd) => {
|
||||
const { minio } = prepareTools()
|
||||
const storage = await createMinioBackupStorage(minio, bucketName, dirName)
|
||||
return await restore(transactorUrl, workspace, storage, parseInt(date ?? '-1'))
|
||||
const wsId = getWorkspaceId(bucketName, productId)
|
||||
const storage = await createMinioBackupStorage(minio, wsId, dirName)
|
||||
return await restore(transactorUrl, wsId, storage, parseInt(date ?? '-1'))
|
||||
})
|
||||
program
|
||||
.command('backup-s3-list <bucketName> <dirName>')
|
||||
.description('list snaphost ids for backup')
|
||||
.action(async (bucketName, dirName, cmd) => {
|
||||
.action(async (bucketName: string, dirName: string, cmd) => {
|
||||
const { minio } = prepareTools()
|
||||
const storage = await createMinioBackupStorage(minio, bucketName, dirName)
|
||||
const wsId = getWorkspaceId(bucketName, productId)
|
||||
const storage = await createMinioBackupStorage(minio, wsId, dirName)
|
||||
return await backupList(storage)
|
||||
})
|
||||
|
||||
program
|
||||
.command('restore-workspace <workspace> <dirName>')
|
||||
.description('restore workspace transactions and minio resources from previous dump.')
|
||||
.action(async (workspace, dirName, cmd) => {
|
||||
.action(async (workspace: string, dirName: string, cmd) => {
|
||||
const { mongodbUri, minio, txes, migrateOperations } = prepareTools()
|
||||
return await restoreWorkspace(
|
||||
mongodbUri,
|
||||
workspace,
|
||||
getWorkspaceId(workspace, productId),
|
||||
dirName,
|
||||
minio,
|
||||
getElasticUrl(),
|
||||
@ -324,9 +327,9 @@ export function devTool (
|
||||
program
|
||||
.command('diff-workspace <workspace>')
|
||||
.description('restore workspace transactions and minio resources from previous dump.')
|
||||
.action(async (workspace, cmd) => {
|
||||
.action(async (workspace: string, cmd) => {
|
||||
const { mongodbUri, txes } = prepareTools()
|
||||
return await diffWorkspace(mongodbUri, workspace, txes)
|
||||
return await diffWorkspace(mongodbUri, getWorkspaceId(workspace, productId), txes)
|
||||
})
|
||||
|
||||
program
|
||||
@ -343,7 +346,7 @@ export function devTool (
|
||||
}
|
||||
|
||||
console.log(`clearing ${workspace} history:`)
|
||||
await clearTelegramHistory(mongodbUri, workspace, telegramDB, minio)
|
||||
await clearTelegramHistory(mongodbUri, getWorkspaceId(workspace, productId), telegramDB, minio)
|
||||
})
|
||||
})
|
||||
|
||||
@ -351,7 +354,7 @@ export function devTool (
|
||||
.command('clear-telegram-all-history')
|
||||
.description('clear telegram history')
|
||||
.action(async (cmd) => {
|
||||
const { mongodbUri, minio, productId } = prepareTools()
|
||||
const { mongodbUri, minio } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
const telegramDB = process.env.TELEGRAM_DATABASE
|
||||
if (telegramDB === undefined) {
|
||||
@ -363,7 +366,7 @@ export function devTool (
|
||||
|
||||
for (const w of workspaces) {
|
||||
console.log(`clearing ${w.workspace} history:`)
|
||||
await clearTelegramHistory(mongodbUri, w.workspace, telegramDB, minio)
|
||||
await clearTelegramHistory(mongodbUri, getWorkspaceId(w.workspace, productId), telegramDB, minio)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -371,17 +374,17 @@ export function devTool (
|
||||
program
|
||||
.command('rebuild-elastic [workspace]')
|
||||
.description('rebuild elastic index')
|
||||
.action(async (workspace, cmd) => {
|
||||
const { mongodbUri, minio, productId } = prepareTools()
|
||||
.action(async (workspace: string, cmd) => {
|
||||
const { mongodbUri, minio } = prepareTools()
|
||||
return await withDatabase(mongodbUri, async (db) => {
|
||||
if (workspace === undefined) {
|
||||
const workspaces = await listWorkspaces(db, productId)
|
||||
|
||||
for (const w of workspaces) {
|
||||
await rebuildElastic(mongodbUri, w.workspace, minio, getElasticUrl())
|
||||
await rebuildElastic(mongodbUri, getWorkspaceId(w.workspace, productId), minio, getElasticUrl())
|
||||
}
|
||||
} else {
|
||||
await rebuildElastic(mongodbUri, workspace, minio, getElasticUrl())
|
||||
await rebuildElastic(mongodbUri, getWorkspaceId(workspace, productId), minio, getElasticUrl())
|
||||
console.log('rebuild end')
|
||||
}
|
||||
})
|
||||
@ -390,56 +393,63 @@ export function devTool (
|
||||
program
|
||||
.command('import-xml <workspace> <fileName>')
|
||||
.description('Import Talants.')
|
||||
.action(async (workspace, fileName, cmd) => {
|
||||
.action(async (workspace: string, fileName: string, cmd) => {
|
||||
const { mongodbUri, minio } = prepareTools()
|
||||
return await importXml(transactorUrl, workspace, minio, fileName, mongodbUri, getElasticUrl())
|
||||
return await importXml(
|
||||
transactorUrl,
|
||||
getWorkspaceId(workspace, productId),
|
||||
minio,
|
||||
fileName,
|
||||
mongodbUri,
|
||||
getElasticUrl()
|
||||
)
|
||||
})
|
||||
|
||||
program
|
||||
.command('import-lead-csv <workspace> <fileName>')
|
||||
.description('Import LEAD csv customer organizations')
|
||||
.action(async (workspace, fileName, cmd) => {
|
||||
return await importLead(transactorUrl, workspace, fileName)
|
||||
.action(async (workspace: string, fileName: string, cmd) => {
|
||||
return await importLead(transactorUrl, getWorkspaceId(workspace, productId), fileName)
|
||||
})
|
||||
|
||||
program
|
||||
.command('import-lead-csv2 <workspace> <fileName>')
|
||||
.description('Import LEAD csv customer organizations')
|
||||
.action(async (workspace, fileName, cmd) => {
|
||||
return await importLead2(transactorUrl, workspace, fileName)
|
||||
.action(async (workspace: string, fileName: string, cmd) => {
|
||||
return await importLead2(transactorUrl, getWorkspaceId(workspace, productId), fileName)
|
||||
})
|
||||
|
||||
program
|
||||
.command('import-talant-csv <workspace> <fileName>')
|
||||
.description('Import Talant csv')
|
||||
.action(async (workspace, fileName, cmd) => {
|
||||
.action(async (workspace: string, fileName: string, cmd) => {
|
||||
const rekoniUrl = process.env.REKONI_URL
|
||||
if (rekoniUrl === undefined) {
|
||||
console.log('Please provide REKONI_URL environment variable')
|
||||
exit(1)
|
||||
}
|
||||
return await importTalants(transactorUrl, workspace, fileName, rekoniUrl)
|
||||
return await importTalants(transactorUrl, getWorkspaceId(workspace, productId), fileName, rekoniUrl)
|
||||
})
|
||||
|
||||
program
|
||||
.command('import-org-csv <workspace> <fileName>')
|
||||
.description('Import Organizations csv')
|
||||
.action(async (workspace, fileName, cmd) => {
|
||||
return await importOrgs(transactorUrl, workspace, fileName)
|
||||
.action(async (workspace: string, fileName: string, cmd) => {
|
||||
return await importOrgs(transactorUrl, getWorkspaceId(workspace, productId), fileName)
|
||||
})
|
||||
|
||||
program
|
||||
.command('lead-duplicates <workspace>')
|
||||
.description('Find and remove duplicate organizations.')
|
||||
.action(async (workspace, cmd) => {
|
||||
return await removeDuplicates(transactorUrl, workspace)
|
||||
.action(async (workspace: string, cmd) => {
|
||||
return await removeDuplicates(transactorUrl, getWorkspaceId(workspace, productId))
|
||||
})
|
||||
|
||||
program
|
||||
.command('generate-token <name> <workspace>')
|
||||
.command('generate-token <name> <workspace> <productId>')
|
||||
.description('generate token')
|
||||
.action(async (name, workspace) => {
|
||||
console.log(generateToken(name, workspace))
|
||||
.action(async (name: string, workspace: string, productId) => {
|
||||
console.log(generateToken(name, getWorkspaceId(workspace, productId)))
|
||||
})
|
||||
program
|
||||
.command('decode-token <token>')
|
||||
@ -450,14 +460,21 @@ export function devTool (
|
||||
program
|
||||
.command('update-recruit <workspace>')
|
||||
.description('process pdf documents inside minio and update resumes with skills, etc.')
|
||||
.action(async (workspace) => {
|
||||
.action(async (workspace: string) => {
|
||||
const rekoniUrl = process.env.REKONI_URL
|
||||
if (rekoniUrl === undefined) {
|
||||
console.log('Please provide REKONI_URL environment variable')
|
||||
exit(1)
|
||||
}
|
||||
const { mongodbUri, minio } = prepareTools()
|
||||
return await updateCandidates(transactorUrl, workspace, minio, mongodbUri, getElasticUrl(), rekoniUrl)
|
||||
return await updateCandidates(
|
||||
transactorUrl,
|
||||
getWorkspaceId(workspace, productId),
|
||||
minio,
|
||||
mongodbUri,
|
||||
getElasticUrl(),
|
||||
rekoniUrl
|
||||
)
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { BucketItem, Client, ItemBucketMetadata } from 'minio'
|
||||
|
||||
export type MinioWorkspaceItem = BucketItem & { metaData: ItemBucketMetadata }
|
||||
|
||||
export async function listMinioObjects (minio: Client, dbName: string): Promise<MinioWorkspaceItem[]> {
|
||||
const items: MinioWorkspaceItem[] = []
|
||||
const list = await minio.listObjects(dbName, undefined, true)
|
||||
await new Promise((resolve) => {
|
||||
list.on('data', (data) => {
|
||||
items.push({ metaData: {}, ...data })
|
||||
})
|
||||
list.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return items
|
||||
}
|
@ -16,7 +16,8 @@
|
||||
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import contact, { Channel, ChannelProvider, EmployeeAccount } from '@hcengineering/contact'
|
||||
import { Ref, TxOperations, WithLookup } from '@hcengineering/core'
|
||||
import { Ref, TxOperations, WithLookup, WorkspaceId } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import attachment from '@hcengineering/model-attachment'
|
||||
import recruit from '@hcengineering/model-recruit'
|
||||
import { Candidate } from '@hcengineering/recruit'
|
||||
@ -24,11 +25,9 @@ import { ReconiDocument } from '@hcengineering/rekoni'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import tags, { findTagCategory } from '@hcengineering/tags'
|
||||
import { Client } from 'minio'
|
||||
import got from 'got'
|
||||
import { ElasticTool } from './elastic'
|
||||
import { findOrUpdateAttached } from './utils'
|
||||
import { readMinioData } from './workspace'
|
||||
|
||||
export async function recognize (rekoniUrl: string, data: string, token: string): Promise<ReconiDocument | undefined> {
|
||||
const { body }: { body?: ReconiDocument } = await got.post(rekoniUrl + '/recognize?format=pdf', {
|
||||
@ -87,18 +86,18 @@ export async function addChannel (
|
||||
|
||||
export async function updateCandidates (
|
||||
transactorUrl: string,
|
||||
dbName: string,
|
||||
minio: Client,
|
||||
workspaceId: WorkspaceId,
|
||||
minio: MinioService,
|
||||
mongoUrl: string,
|
||||
elasticUrl: string,
|
||||
rekoniUrl: string
|
||||
): Promise<void> {
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
const connection = await connect(transactorUrl, workspaceId)
|
||||
|
||||
const tool = new ElasticTool(mongoUrl, dbName, minio, elasticUrl)
|
||||
const tool = new ElasticTool(mongoUrl, workspaceId, minio, elasticUrl)
|
||||
const done = await tool.connect()
|
||||
|
||||
const token = generateToken('anticrm@hc.engineering', dbName)
|
||||
const token = generateToken('anticrm@hc.engineering', workspaceId)
|
||||
try {
|
||||
const client = new TxOperations(connection, 'recruit:account:candidate-importer' as Ref<EmployeeAccount>)
|
||||
|
||||
@ -115,10 +114,10 @@ export async function updateCandidates (
|
||||
if (a.type.includes('application/pdf')) {
|
||||
console.log('processing', c.name, a.name, `(${cpos}, ${candidates.length})`)
|
||||
try {
|
||||
const buffer = Buffer.concat(await readMinioData(minio, dbName, a.file)).toString('base64')
|
||||
const buffer = Buffer.concat(await minio.read(workspaceId, a.file)).toString('base64')
|
||||
const document = await recognize(rekoniUrl, buffer, token)
|
||||
if (document !== undefined) {
|
||||
await updateAvatar(c, document, minio, dbName, client, tool)
|
||||
await updateAvatar(c, document, minio, workspaceId, client, tool)
|
||||
|
||||
// Update candidate values if applicable
|
||||
if (isUndef(c.city) && document.city !== undefined) {
|
||||
@ -203,8 +202,8 @@ export async function updateContacts (
|
||||
async function updateAvatar (
|
||||
c: WithLookup<Candidate>,
|
||||
document: ReconiDocument,
|
||||
minio: Client,
|
||||
dbName: string,
|
||||
minio: MinioService,
|
||||
workspaceId: WorkspaceId,
|
||||
client: TxOperations,
|
||||
tool: ElasticTool
|
||||
): Promise<void> {
|
||||
@ -221,7 +220,7 @@ async function updateAvatar (
|
||||
const attachId = `${c._id}.${document.avatarName}` as Ref<Attachment>
|
||||
// Upload new avatar for candidate
|
||||
const data = Buffer.from(document.avatar, 'base64')
|
||||
await minio.putObject(dbName, attachId, data, data.length, {
|
||||
await minio.put(workspaceId, attachId, data, data.length, {
|
||||
'Content-Type': document.avatarFormat
|
||||
})
|
||||
|
||||
|
@ -14,12 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { DOMAIN_TX, Ref } from '@hcengineering/core'
|
||||
import { DOMAIN_TX, Ref, WorkspaceId } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
|
||||
import contact, { DOMAIN_CHANNEL } from '@hcengineering/model-contact'
|
||||
import { DOMAIN_TELEGRAM } from '@hcengineering/model-telegram'
|
||||
import { getWorkspaceDB } from '@hcengineering/mongo'
|
||||
import telegram, { SharedTelegramMessage, SharedTelegramMessages } from '@hcengineering/telegram'
|
||||
import { Client } from 'minio'
|
||||
import { Document, MongoClient, UpdateFilter } from 'mongodb'
|
||||
|
||||
const LastMessages = 'last-msgs'
|
||||
@ -29,14 +30,14 @@ const LastMessages = 'last-msgs'
|
||||
*/
|
||||
export async function clearTelegramHistory (
|
||||
mongoUrl: string,
|
||||
workspace: string,
|
||||
workspaceId: WorkspaceId,
|
||||
tgDb: string,
|
||||
minio: Client
|
||||
minio: MinioService
|
||||
): Promise<void> {
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
const workspaceDB = client.db(workspace)
|
||||
const workspaceDB = getWorkspaceDB(client, workspaceId)
|
||||
const telegramDB = client.db(tgDb)
|
||||
|
||||
const sharedMessages = await workspaceDB
|
||||
@ -89,12 +90,12 @@ export async function clearTelegramHistory (
|
||||
workspaceDB.collection(DOMAIN_ATTACHMENT).deleteMany({
|
||||
attachedToClass: telegram.class.Message
|
||||
}),
|
||||
minio.removeObjects(workspace, Array.from(attachments))
|
||||
minio.remove(workspaceId, Array.from(attachments))
|
||||
])
|
||||
|
||||
console.log('clearing telegram service data...')
|
||||
await telegramDB.collection(LastMessages).deleteMany({
|
||||
workspace
|
||||
workspace: workspaceId
|
||||
})
|
||||
} finally {
|
||||
await client.close()
|
||||
|
@ -15,17 +15,17 @@
|
||||
//
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, { DOMAIN_TX, Tx } from '@hcengineering/core'
|
||||
import core, { DOMAIN_TX, Tx, WorkspaceId } from '@hcengineering/core'
|
||||
import { MinioService, MinioWorkspaceItem } from '@hcengineering/minio'
|
||||
import { MigrateOperation } from '@hcengineering/model'
|
||||
import { getWorkspaceDB } from '@hcengineering/mongo'
|
||||
import { upgradeModel } from '@hcengineering/server-tool'
|
||||
import { existsSync } from 'fs'
|
||||
import { mkdir, open, readFile, writeFile } from 'fs/promises'
|
||||
import { Client } from 'minio'
|
||||
import { Document, MongoClient } from 'mongodb'
|
||||
import { join } from 'path'
|
||||
import { rebuildElastic } from './elastic'
|
||||
import { generateModelDiff, printDiff } from './mdiff'
|
||||
import { listMinioObjects, MinioWorkspaceItem } from './minio'
|
||||
|
||||
interface CollectionInfo {
|
||||
name: string
|
||||
@ -41,11 +41,16 @@ interface WorkspaceInfo {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function dumpWorkspace (mongoUrl: string, dbName: string, fileName: string, minio: Client): Promise<void> {
|
||||
export async function dumpWorkspace (
|
||||
mongoUrl: string,
|
||||
workspaceId: WorkspaceId,
|
||||
fileName: string,
|
||||
minio: MinioService
|
||||
): Promise<void> {
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
const db = client.db(dbName)
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
|
||||
console.log('dumping transactions...')
|
||||
|
||||
@ -66,19 +71,19 @@ export async function dumpWorkspace (mongoUrl: string, dbName: string, fileName:
|
||||
}
|
||||
|
||||
console.log('Dump minio objects')
|
||||
if (await minio.bucketExists(dbName)) {
|
||||
workspaceInfo.minioData.push(...(await listMinioObjects(minio, dbName)))
|
||||
if (await minio.exists(workspaceId)) {
|
||||
workspaceInfo.minioData.push(...(await minio.list(workspaceId)))
|
||||
const minioDbLocation = fileName + '.minio'
|
||||
if (!existsSync(minioDbLocation)) {
|
||||
await mkdir(minioDbLocation)
|
||||
}
|
||||
for (const d of workspaceInfo.minioData) {
|
||||
const stat = await minio.statObject(dbName, d.name)
|
||||
const stat = await minio.stat(workspaceId, d.name)
|
||||
d.metaData = stat.metaData
|
||||
|
||||
const fileHandle = await open(join(minioDbLocation, d.name), 'w')
|
||||
|
||||
const chunks: Buffer[] = await readMinioData(minio, dbName, d.name)
|
||||
const chunks: Buffer[] = await minio.read(workspaceId, d.name)
|
||||
for (const b of chunks) {
|
||||
await fileHandle.write(b)
|
||||
}
|
||||
@ -92,41 +97,21 @@ export async function dumpWorkspace (mongoUrl: string, dbName: string, fileName:
|
||||
}
|
||||
}
|
||||
|
||||
export async function readMinioData (minio: Client, dbName: string, name: string): Promise<Buffer[]> {
|
||||
const data = await minio.getObject(dbName, name)
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
await new Promise((resolve) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
const b = chunk as Buffer
|
||||
chunks.push(b)
|
||||
}
|
||||
})
|
||||
|
||||
data.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return chunks
|
||||
}
|
||||
|
||||
export async function restoreWorkspace (
|
||||
mongoUrl: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
fileName: string,
|
||||
minio: Client,
|
||||
minio: MinioService,
|
||||
elasticUrl: string,
|
||||
transactorUrl: string,
|
||||
rawTxes: Tx[],
|
||||
migrateOperations: MigrateOperation[]
|
||||
): Promise<void> {
|
||||
console.log('Restoring workspace', mongoUrl, dbName, fileName)
|
||||
console.log('Restoring workspace', mongoUrl, workspaceId, fileName)
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
const db = client.db(dbName)
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
|
||||
const workspaceInfo = JSON.parse((await readFile(fileName + '.workspace.json')).toString()) as WorkspaceInfo
|
||||
|
||||
@ -148,12 +133,12 @@ export async function restoreWorkspace (
|
||||
}
|
||||
}
|
||||
|
||||
if (await minio.bucketExists(dbName)) {
|
||||
const objectNames = (await listMinioObjects(minio, dbName)).map((i) => i.name)
|
||||
await minio.removeObjects(dbName, objectNames)
|
||||
await minio.removeBucket(dbName)
|
||||
if (await minio.exists(workspaceId)) {
|
||||
const objectNames = (await minio.list(workspaceId)).map((i) => i.name)
|
||||
await minio.remove(workspaceId, objectNames)
|
||||
await minio.delete(workspaceId)
|
||||
}
|
||||
await minio.makeBucket(dbName, 'k8s')
|
||||
await minio.make(workspaceId)
|
||||
|
||||
const minioDbLocation = fileName + '.minio'
|
||||
console.log('Restore minio objects', workspaceInfo.minioData.length)
|
||||
@ -162,7 +147,7 @@ export async function restoreWorkspace (
|
||||
const file = await open(join(minioDbLocation, d.name), 'r')
|
||||
const stream = file.createReadStream()
|
||||
promises.push(
|
||||
minio.putObject(dbName, d.name, stream, d.size, d.metaData).then(async () => {
|
||||
minio.put(workspaceId, d.name, stream, d.size, d.metaData).then(async () => {
|
||||
await file.close()
|
||||
})
|
||||
)
|
||||
@ -172,19 +157,19 @@ export async function restoreWorkspace (
|
||||
}
|
||||
}
|
||||
|
||||
await upgradeModel(transactorUrl, dbName, rawTxes, migrateOperations)
|
||||
await upgradeModel(transactorUrl, workspaceId, rawTxes, migrateOperations)
|
||||
|
||||
await rebuildElastic(mongoUrl, dbName, minio, elasticUrl)
|
||||
await rebuildElastic(mongoUrl, workspaceId, minio, elasticUrl)
|
||||
} finally {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function diffWorkspace (mongoUrl: string, dbName: string, rawTxes: Tx[]): Promise<void> {
|
||||
export async function diffWorkspace (mongoUrl: string, workspace: WorkspaceId, rawTxes: Tx[]): Promise<void> {
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
const db = client.db(dbName)
|
||||
const db = getWorkspaceDB(client, workspace)
|
||||
|
||||
console.log('diffing transactions...')
|
||||
|
||||
|
@ -108,7 +108,8 @@ export default plugin(coreId, {
|
||||
},
|
||||
status: {
|
||||
ObjectNotFound: '' as StatusCode<{ _id: Ref<Doc> }>,
|
||||
ItemNotFound: '' as StatusCode<{ _id: Ref<Doc>, _localId: string }>
|
||||
ItemNotFound: '' as StatusCode<{ _id: Ref<Doc>, _localId: string }>,
|
||||
InvalidProduct: '' as StatusCode<{ productId: string }>
|
||||
},
|
||||
version: {
|
||||
Model: '' as Ref<Version>
|
||||
|
@ -76,3 +76,30 @@ export function toFindResult<T extends Doc> (docs: T[], total?: number): FindRes
|
||||
const length = total ?? docs.length
|
||||
return Object.assign(docs, { total: length })
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface WorkspaceId {
|
||||
name: string
|
||||
productId: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Combine workspace with productId, if not equal ''
|
||||
*/
|
||||
export function getWorkspaceId (workspace: string, productId: string = ''): WorkspaceId {
|
||||
return {
|
||||
name: workspace,
|
||||
productId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function toWorkspaceString (id: WorkspaceId, sep = '@'): string {
|
||||
return id.name + (id.productId === '' ? '' : sep + id.productId)
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export interface AnySvelteComponentWithProps {
|
||||
|
||||
export interface Action {
|
||||
label: IntlString
|
||||
icon: Asset | AnySvelteComponent
|
||||
icon?: Asset | AnySvelteComponent
|
||||
action: (props: any, ev: Event) => Promise<void>
|
||||
inline?: boolean
|
||||
link?: string
|
||||
|
@ -33,7 +33,7 @@
|
||||
{
|
||||
name: 'workspace',
|
||||
i18n: login.string.Workspace,
|
||||
rule: /^[0-9a-z#%&^\-@!)(]{3,63}$/,
|
||||
rule: /^[0-9a-z#%&^\-!)(]{3,63}$/,
|
||||
ruleDescr: login.string.WorksaceNameRule
|
||||
}
|
||||
]
|
||||
|
@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import core, { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { AttributesBar, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
||||
import setting, { settingId } from '@hcengineering/setting'
|
||||
import { Button, getCurrentLocation, Label, navigate } from '@hcengineering/ui'
|
||||
@ -21,16 +22,18 @@
|
||||
|
||||
export let object: Doc
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let to: Ref<Class<Doc>> | undefined
|
||||
export let to: Ref<Class<Doc>> | undefined = core.class.Doc
|
||||
export let ignoreKeys: string[] = []
|
||||
export let allowedCollections: string[] = []
|
||||
export let readonly = false
|
||||
export let showLabel: IntlString | undefined = undefined
|
||||
export let defaultCollapsed = false
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let keys: KeyedAttribute[] = []
|
||||
let collapsed: boolean = false
|
||||
let collapsed: boolean = defaultCollapsed
|
||||
|
||||
function updateKeys (_class: Ref<Class<Doc>>, ignoreKeys: string[], to: Ref<Class<Doc>> | undefined): void {
|
||||
const filtredKeys = getFiltredKeys(hierarchy, _class, ignoreKeys, to)
|
||||
@ -39,9 +42,10 @@
|
||||
|
||||
$: updateKeys(_class, ignoreKeys, to)
|
||||
|
||||
$: label = hierarchy.getClass(_class).label
|
||||
$: label = showLabel ?? hierarchy.getClass(_class).label
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="attrbar-header"
|
||||
class:collapsed
|
||||
|
@ -137,33 +137,42 @@
|
||||
return actions
|
||||
}
|
||||
|
||||
function getWorkspaceItems () {
|
||||
return workspaces.map((w) => ({
|
||||
label: getEmbeddedLabel(w.workspace),
|
||||
action: async () => {
|
||||
const loginInfo = (await selectWorkspace(w.workspace))[1]
|
||||
navigateToWorkspace(w.workspace, loginInfo)
|
||||
},
|
||||
isSubmenuRightClicking: true,
|
||||
component: Menu,
|
||||
props: {
|
||||
actions: [
|
||||
{
|
||||
label: workbench.string.OpenInNewTab,
|
||||
action: async () => {
|
||||
const loginInfo = (await selectWorkspace(w.workspace))[1]
|
||||
function getWorkspaceItems (): Action[] {
|
||||
return [
|
||||
...workspaces.map((w) => ({
|
||||
label: getEmbeddedLabel(w.workspace),
|
||||
action: async () => {
|
||||
const loginInfo = (await selectWorkspace(w.workspace))[1]
|
||||
navigateToWorkspace(w.workspace, loginInfo)
|
||||
},
|
||||
isSubmenuRightClicking: true,
|
||||
component: Menu,
|
||||
props: {
|
||||
actions: [
|
||||
{
|
||||
label: workbench.string.OpenInNewTab,
|
||||
action: async () => {
|
||||
const loginInfo = (await selectWorkspace(w.workspace))[1]
|
||||
|
||||
if (!loginInfo) {
|
||||
return
|
||||
if (!loginInfo) {
|
||||
return
|
||||
}
|
||||
setLoginInfo(loginInfo)
|
||||
const url = locationToUrl({ path: [workbenchId, w.workspace] })
|
||||
window.open(url, '_blank')?.focus()
|
||||
}
|
||||
setLoginInfo(loginInfo)
|
||||
const url = locationToUrl({ path: [workbenchId, w.workspace] })
|
||||
window.open(url, '_blank')?.focus()
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
})),
|
||||
{
|
||||
label: getEmbeddedLabel('...'),
|
||||
action: async () => {
|
||||
navigate({ path: [login.component.LoginApp, 'selectWorkspace'] })
|
||||
},
|
||||
isSubmenuRightClicking: false
|
||||
}
|
||||
}))
|
||||
]
|
||||
}
|
||||
|
||||
let actions: Action[] = []
|
||||
|
@ -43,7 +43,7 @@
|
||||
"@hcengineering/account": "~0.6.0",
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"mongodb": "^4.9.0",
|
||||
"mongodb": "^4.11.0",
|
||||
"koa": "^2.13.1",
|
||||
"koa-router": "^10.1.1",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
|
@ -20,4 +20,4 @@ import { serveAccount } from '.'
|
||||
|
||||
const txes = JSON.parse(JSON.stringify(builder.getTxes())) as Tx[]
|
||||
|
||||
serveAccount(getMethods(version, txes, migrateOperations, ''))
|
||||
serveAccount(getMethods(version, txes, migrateOperations))
|
||||
|
@ -28,7 +28,7 @@ import { Db, MongoClient } from 'mongodb'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function serveAccount (methods: Record<string, AccountMethod>): void {
|
||||
export function serveAccount (methods: Record<string, AccountMethod>, productId = ''): void {
|
||||
const ACCOUNT_PORT = parseInt(process.env.ACCOUNT_PORT ?? '3000')
|
||||
const dbUri = process.env.MONGO_URL
|
||||
if (dbUri === undefined) {
|
||||
@ -71,9 +71,9 @@ export function serveAccount (methods: Record<string, AccountMethod>): void {
|
||||
const token = extractToken(ctx.request.headers)
|
||||
|
||||
const request = ctx.request.body as any
|
||||
const method = (methods as { [key: string]: (db: Db, request: Request<any>, token?: string) => Response<any> })[
|
||||
request.method
|
||||
]
|
||||
const method = (
|
||||
methods as { [key: string]: (db: Db, productId: string, request: Request<any>, token?: string) => Response<any> }
|
||||
)[request.method]
|
||||
if (method === undefined) {
|
||||
const response: Response<void> = {
|
||||
id: request.id,
|
||||
@ -87,7 +87,7 @@ export function serveAccount (methods: Record<string, AccountMethod>): void {
|
||||
client = await MongoClient.connect(dbUri)
|
||||
}
|
||||
const db = client.db(ACCOUNT_DB)
|
||||
const result = await method(db, request, token)
|
||||
const result = await method(db, productId, request, token)
|
||||
console.log(result)
|
||||
ctx.body = result
|
||||
})
|
||||
|
@ -33,12 +33,11 @@
|
||||
"prettier": "^2.7.1",
|
||||
"ts-node": "^10.8.0",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/minio": "~7.0.11"
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"mongodb": "^4.9.0",
|
||||
"mongodb": "^4.11.0",
|
||||
"@hcengineering/server-tool": "~0.6.0",
|
||||
"@hcengineering/server-token": "~0.6.0",
|
||||
"@hcengineering/client": "^0.6.3",
|
||||
@ -47,6 +46,6 @@
|
||||
"dotenv": "~16.0.0",
|
||||
"got": "^11.8.3",
|
||||
"@hcengineering/server-backup": "~0.6.0",
|
||||
"minio": "^7.0.26"
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ interface Config {
|
||||
MinioEndpoint: string
|
||||
MinioAccessKey: string
|
||||
MinioSecretKey: string
|
||||
|
||||
ProductId: string
|
||||
}
|
||||
|
||||
const envMap: { [key in keyof Config]: string } = {
|
||||
@ -36,7 +38,8 @@ const envMap: { [key in keyof Config]: string } = {
|
||||
Interval: 'INTERVAL',
|
||||
MinioEndpoint: 'MINIO_ENDPOINT',
|
||||
MinioAccessKey: 'MINIO_ACCESS_KEY',
|
||||
MinioSecretKey: 'MINIO_SECRET_KEY'
|
||||
MinioSecretKey: 'MINIO_SECRET_KEY',
|
||||
ProductId: 'PRODUCT_ID'
|
||||
}
|
||||
|
||||
const required: Array<keyof Config> = [
|
||||
@ -60,7 +63,8 @@ const config: Config = (() => {
|
||||
Interval: parseInt(process.env[envMap.Interval] ?? '3600'),
|
||||
MinioEndpoint: process.env[envMap.MinioEndpoint],
|
||||
MinioAccessKey: process.env[envMap.MinioAccessKey],
|
||||
MinioSecretKey: process.env[envMap.MinioSecretKey]
|
||||
MinioSecretKey: process.env[envMap.MinioSecretKey],
|
||||
ProductId: process.env[envMap.ProductId] ?? ''
|
||||
}
|
||||
|
||||
const missingEnv = required.filter((key) => params[key] === undefined).map((key) => envMap[key])
|
||||
|
@ -13,12 +13,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { getWorkspaceId } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import { backup, createMinioBackupStorage } from '@hcengineering/server-backup'
|
||||
import got from 'got'
|
||||
import { Client as MinioClient } from 'minio'
|
||||
import config from './config'
|
||||
import serverToken from '@hcengineering/server-token'
|
||||
import got from 'got'
|
||||
import config from './config'
|
||||
|
||||
async function getWorkspaces (): Promise<string[]> {
|
||||
const { body }: { body: { error?: string, result?: any[] } } = await got.post(config.AccountsURL, {
|
||||
@ -37,7 +38,7 @@ async function getWorkspaces (): Promise<string[]> {
|
||||
}
|
||||
|
||||
export class PlatformWorker {
|
||||
minio?: MinioClient
|
||||
minio!: MinioService
|
||||
|
||||
async close (): Promise<void> {}
|
||||
|
||||
@ -51,7 +52,7 @@ export class PlatformWorker {
|
||||
minioPort = parseInt(sp[1])
|
||||
}
|
||||
|
||||
this.minio = new MinioClient({
|
||||
this.minio = new MinioService({
|
||||
endPoint: minioEndpoint,
|
||||
port: minioPort,
|
||||
useSSL: false,
|
||||
@ -78,8 +79,8 @@ export class PlatformWorker {
|
||||
for (const ws of workspaces) {
|
||||
console.log('\n\nBACKUP WORKSPACE ', ws)
|
||||
try {
|
||||
const storage = await createMinioBackupStorage(this.minio as MinioClient, 'backups', ws)
|
||||
await backup(config.TransactorURL, ws, storage)
|
||||
const storage = await createMinioBackupStorage(this.minio, getWorkspaceId('backups', config.ProductId), ws)
|
||||
await backup(config.TransactorURL, getWorkspaceId(ws, config.ProductId), storage)
|
||||
} catch (err: any) {
|
||||
console.error('\n\nFAILED to BACKUP', ws, err)
|
||||
}
|
||||
|
@ -34,8 +34,7 @@
|
||||
"ts-node": "^10.8.0",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@types/minio": "~7.0.11"
|
||||
"@types/ws": "^8.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
@ -49,6 +48,6 @@
|
||||
"y-protocols": "~1.0.5",
|
||||
"ws": "^8.10.0",
|
||||
"yjs": "^13.5.42",
|
||||
"minio": "^7.0.26"
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ import serverToken from '@hcengineering/server-token'
|
||||
import { metricsContext } from './metrics'
|
||||
import { start } from './server'
|
||||
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import config from './config'
|
||||
import { Client as MinioClient } from 'minio'
|
||||
|
||||
setMetadata(serverToken.metadata.Secret, config.Secret)
|
||||
|
||||
@ -32,7 +32,7 @@ if (sp.length > 1) {
|
||||
minioPort = parseInt(sp[1])
|
||||
}
|
||||
|
||||
const minio = new MinioClient({
|
||||
const minio = new MinioService({
|
||||
endPoint: minioEndpoint,
|
||||
port: minioPort,
|
||||
useSSL: false,
|
||||
|
@ -5,14 +5,14 @@ import { createServer, IncomingMessage } from 'http'
|
||||
import WebSocket, { WebSocketServer } from 'ws'
|
||||
import { setupWSConnection } from './yserver'
|
||||
|
||||
import { Client as MinioClient } from 'minio'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
/**
|
||||
* @public
|
||||
* @param sessionFactory -
|
||||
* @param port -
|
||||
* @param host -
|
||||
*/
|
||||
export function start (ctx: MeasureContext, port: number, minio: MinioClient, host?: string): () => void {
|
||||
export function start (ctx: MeasureContext, port: number, minio: MinioService, host?: string): () => void {
|
||||
console.log(`starting server on port ${port} ...`)
|
||||
|
||||
const wss = new WebSocketServer({
|
||||
|
@ -8,8 +8,7 @@ import { createEncoder, length, toUint8Array, writeVarUint, writeVarUint8Array }
|
||||
import { Token } from '@hcengineering/server-token'
|
||||
import WebSocket from 'ws'
|
||||
|
||||
import { Client as MinioClient } from 'minio'
|
||||
import { Readable } from 'stream'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
|
||||
const wsReadyStateConnecting = 0
|
||||
const wsReadyStateOpen = 1
|
||||
@ -27,31 +26,13 @@ export interface YPersistence {
|
||||
documentId: string,
|
||||
ydoc: WSSharedDoc,
|
||||
token: Token,
|
||||
minio: MinioClient,
|
||||
minio: MinioService,
|
||||
initialContentId: string
|
||||
) => Promise<void>
|
||||
writeState: (documentId: string, ydoc: WSSharedDoc, token: Token) => Promise<void>
|
||||
provider: any
|
||||
}
|
||||
|
||||
async function loadData (data: Readable): Promise<Buffer> {
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
await new Promise((resolve) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
const b = chunk as Buffer
|
||||
chunks.push(b)
|
||||
}
|
||||
})
|
||||
|
||||
data.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return Buffer.concat(chunks)
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -62,30 +43,27 @@ persistence = {
|
||||
documentId: string,
|
||||
ydoc: WSSharedDoc,
|
||||
token: Token,
|
||||
minio: MinioClient,
|
||||
minio: MinioService,
|
||||
initialContentId: string
|
||||
): Promise<void> => {
|
||||
try {
|
||||
ydoc.minio = minio
|
||||
|
||||
let minioDocument: Readable | undefined
|
||||
let minioDocument: Buffer | undefined
|
||||
try {
|
||||
minioDocument = await minio.getObject(token.workspace, documentId)
|
||||
minioDocument = Buffer.concat(await minio.read(token.workspace, documentId))
|
||||
} catch (err: any) {
|
||||
if (initialContentId !== undefined && initialContentId.length > 0) {
|
||||
minioDocument = await minio.getObject(token.workspace, initialContentId)
|
||||
minioDocument = Buffer.concat(await minio.read(token.workspace, initialContentId))
|
||||
}
|
||||
}
|
||||
|
||||
if (minioDocument !== undefined) {
|
||||
const buffer = await loadData(minioDocument)
|
||||
if (buffer.length > 0) {
|
||||
try {
|
||||
const uint8arr = new Uint8Array(buffer)
|
||||
applyUpdate(ydoc, uint8arr)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
if (minioDocument !== undefined && minioDocument.length > 0) {
|
||||
try {
|
||||
const uint8arr = new Uint8Array(minioDocument)
|
||||
applyUpdate(ydoc, uint8arr)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
@ -97,7 +75,7 @@ persistence = {
|
||||
const newUpdates = encodeStateAsUpdate(ydoc)
|
||||
const buffer = Buffer.from(newUpdates.buffer)
|
||||
|
||||
await ydoc?.minio?.putObject(token.workspace, documentId, buffer)
|
||||
await ydoc?.minio?.put(token.workspace, documentId, buffer)
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
}
|
||||
@ -147,7 +125,7 @@ class WSSharedDoc extends Doc {
|
||||
conns = new Map<WebSocket, ConnectionEntry>()
|
||||
awareness: Awareness
|
||||
|
||||
minio?: MinioClient
|
||||
minio?: MinioService
|
||||
|
||||
/**
|
||||
* @param {{ added: Array<number>, updated: Array<number>, removed: Array<number> }} changes
|
||||
@ -201,7 +179,7 @@ export function getYDoc (
|
||||
docId: string,
|
||||
token: Token,
|
||||
gc = true,
|
||||
minio: MinioClient,
|
||||
minio: MinioService,
|
||||
initialContentId: string
|
||||
): WSSharedDoc {
|
||||
let doc = docs.get(docId)
|
||||
@ -300,7 +278,7 @@ export function setupWSConnection (
|
||||
req: any,
|
||||
documentId: string,
|
||||
token: Token,
|
||||
minio: MinioClient,
|
||||
minio: MinioService,
|
||||
initialContentId: string,
|
||||
gc = true
|
||||
): void {
|
||||
|
@ -31,7 +31,6 @@
|
||||
"@types/express-fileupload": "^1.1.7",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/minio": "~7.0.11",
|
||||
"esbuild": "^0.15.13",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
@ -55,7 +54,6 @@
|
||||
"@hcengineering/server-token": "~0.6.0",
|
||||
"@hcengineering/attachment": "~0.6.1",
|
||||
"@hcengineering/contrib": "~0.6.0",
|
||||
"minio": "^7.0.26",
|
||||
"body-parser": "~1.19.1",
|
||||
"compression": "~1.7.4",
|
||||
"sharp": "~0.30.7"
|
||||
|
@ -33,8 +33,7 @@
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/minio": "~7.0.11"
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
@ -47,7 +46,6 @@
|
||||
"@hcengineering/mongo": "~0.6.1",
|
||||
"@hcengineering/elastic": "~0.6.0",
|
||||
"elastic-apm-node": "~3.26.0",
|
||||
"minio": "^7.0.26",
|
||||
"@hcengineering/server-contact": "~0.6.1",
|
||||
"@hcengineering/server-contact-resources": "~0.6.0",
|
||||
"@hcengineering/server-notification": "^0.6.0",
|
||||
@ -78,6 +76,7 @@
|
||||
"@hcengineering/server-hr": "~0.6.0",
|
||||
"@hcengineering/server-hr-resources": "~0.6.0",
|
||||
"@hcengineering/server-token": "~0.6.0",
|
||||
"@hcengineering/middleware": "~0.6.0"
|
||||
"@hcengineering/middleware": "~0.6.0",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ setMetadata(serverToken.metadata.Secret, serverSecret)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
console.log(`starting server on ${serverPort}`)
|
||||
const shutdown = start(url, elasticUrl, minioConf, serverPort)
|
||||
const shutdown = start(url, elasticUrl, minioConf, serverPort, '')
|
||||
|
||||
const close = (): void => {
|
||||
console.trace('Exiting from server')
|
||||
|
@ -13,7 +13,14 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { DOMAIN_BLOB, DOMAIN_FULLTEXT_BLOB, DOMAIN_MODEL, DOMAIN_TRANSIENT, DOMAIN_TX } from '@hcengineering/core'
|
||||
import {
|
||||
DOMAIN_BLOB,
|
||||
DOMAIN_FULLTEXT_BLOB,
|
||||
DOMAIN_MODEL,
|
||||
DOMAIN_TRANSIENT,
|
||||
DOMAIN_TX,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { createElasticAdapter, createElasticBackupDataAdapter } from '@hcengineering/elastic'
|
||||
import { ModifiedMiddleware, PrivateMiddleware } from '@hcengineering/middleware'
|
||||
import { createMongoAdapter, createMongoTxAdapter } from '@hcengineering/mongo'
|
||||
@ -49,7 +56,7 @@ import { serverTelegramId } from '@hcengineering/server-telegram'
|
||||
import { Token } from '@hcengineering/server-token'
|
||||
import { serverTrackerId } from '@hcengineering/server-tracker'
|
||||
import { BroadcastCall, ClientSession, start as startJsonRpc } from '@hcengineering/server-ws'
|
||||
import { Client as MinioClient } from 'minio'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -59,6 +66,7 @@ export function start (
|
||||
fullTextUrl: string,
|
||||
minioConf: MinioConfig,
|
||||
port: number,
|
||||
productId: string,
|
||||
host?: string
|
||||
): () => void {
|
||||
addLocation(serverAttachmentId, () => import('@hcengineering/server-attachment-resources'))
|
||||
@ -81,7 +89,7 @@ export function start (
|
||||
|
||||
return startJsonRpc(
|
||||
getMetricsContext(),
|
||||
(workspace: string) => {
|
||||
(workspace: WorkspaceId) => {
|
||||
const conf: DbConfiguration = {
|
||||
domains: {
|
||||
[DOMAIN_TX]: 'MongoTx',
|
||||
@ -122,7 +130,7 @@ export function start (
|
||||
url: fullTextUrl
|
||||
},
|
||||
storageFactory: () =>
|
||||
new MinioClient({
|
||||
new MinioService({
|
||||
...minioConf,
|
||||
port: 9000,
|
||||
useSSL: false
|
||||
@ -138,6 +146,7 @@ export function start (
|
||||
return new ClientSession(broadcast, token, pipeline)
|
||||
},
|
||||
port,
|
||||
productId,
|
||||
host
|
||||
)
|
||||
}
|
||||
|
@ -1408,6 +1408,11 @@
|
||||
"packageName": "@hcengineering/apm",
|
||||
"projectFolder": "tools/apm",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/minio",
|
||||
"projectFolder": "server/minio",
|
||||
"shouldPublish": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ export async function OnAttachmentDelete (
|
||||
})
|
||||
|
||||
storageFx(async (adapter, bucket) => {
|
||||
await adapter.removeObject(bucket, attach.file)
|
||||
await adapter.remove(bucket, [attach.file])
|
||||
})
|
||||
|
||||
return []
|
||||
|
@ -41,7 +41,7 @@ import { workbenchId } from '@hcengineering/workbench'
|
||||
export async function channelHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const channel = doc as ChunterSpace
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${chunterId}/${channel._id}">${channel.name}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${chunterId}/${channel._id}">${channel.name}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,6 +32,7 @@
|
||||
"@hcengineering/contact": "~0.6.8",
|
||||
"@hcengineering/view": "^0.6.1",
|
||||
"@hcengineering/login": "~0.6.1",
|
||||
"@hcengineering/workbench": "~0.6.2"
|
||||
"@hcengineering/workbench": "~0.6.2",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -14,13 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { Doc, Tx, TxCreateDoc, TxRemoveDoc, TxUpdateDoc } from '@hcengineering/core'
|
||||
import type { TriggerControl, MinioClient, BucketItem } from '@hcengineering/server-core'
|
||||
import contact, { Contact, contactId, formatName, Organization, Person } from '@hcengineering/contact'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import core, { Doc, Tx, TxCreateDoc, TxRemoveDoc, TxUpdateDoc } from '@hcengineering/core'
|
||||
import login from '@hcengineering/login'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import type { TriggerControl } from '@hcengineering/server-core'
|
||||
import view from '@hcengineering/view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -51,13 +51,14 @@ export async function OnContactDelete (tx: Tx, { findAll, hierarchy, storageFx }
|
||||
}
|
||||
|
||||
storageFx(async (adapter, bucket) => {
|
||||
await adapter.removeObject(bucket, avatar)
|
||||
await adapter.remove(bucket, [avatar])
|
||||
|
||||
const extra = await listMinioObjects(adapter, bucket, avatar)
|
||||
if (extra.size > 0) {
|
||||
for (const e of extra.entries()) {
|
||||
await adapter.removeObject(bucket, e[1].name)
|
||||
}
|
||||
const extra = await adapter.list(bucket, avatar)
|
||||
if (extra.length > 0) {
|
||||
await adapter.remove(
|
||||
bucket,
|
||||
Array.from(extra.entries()).map((it) => it[1].name)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@ -70,9 +71,9 @@ export async function OnContactDelete (tx: Tx, { findAll, hierarchy, storageFx }
|
||||
export function personHTMLPresenter (doc: Doc, control: TriggerControl): string {
|
||||
const person = doc as Person
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${contactId}#${view.component.EditDoc}|${person._id}|${
|
||||
person._class
|
||||
}">${formatName(person.name)}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${contactId}#${view.component.EditDoc}|${
|
||||
person._id
|
||||
}|${person._class}">${formatName(person.name)}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +90,7 @@ export function personTextPresenter (doc: Doc): string {
|
||||
export function organizationHTMLPresenter (doc: Doc, control: TriggerControl): string {
|
||||
const organization = doc as Organization
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${contactId}#${view.component.EditDoc}|${organization._id}|${organization._class}">${organization.name}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${contactId}#${view.component.EditDoc}|${organization._id}|${organization._class}">${organization.name}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,17 +113,3 @@ export default async () => ({
|
||||
OrganizationTextPresenter: organizationTextPresenter
|
||||
}
|
||||
})
|
||||
|
||||
async function listMinioObjects (client: MinioClient, db: string, prefix: string): Promise<Map<string, BucketItem>> {
|
||||
const items = new Map<string, BucketItem>()
|
||||
const list = await client.listObjects(db, prefix, true)
|
||||
await new Promise((resolve) => {
|
||||
list.on('data', (data) => {
|
||||
items.set(data.name, { ...data })
|
||||
})
|
||||
list.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import { workbenchId } from '@hcengineering/workbench'
|
||||
export async function productHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const product = doc as Product
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${inventoryId}/Products/#${view.component.EditDoc}|${product._id}|${product._class}">${product.name}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${inventoryId}/Products/#${view.component.EditDoc}|${product._id}|${product._class}">${product.name}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ import { addAssigneeNotification } from '@hcengineering/server-task-resources'
|
||||
export async function leadHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const lead = doc as Lead
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${leadId}/${lead.space}/#${view.component.EditDoc}|${lead._id}|${lead._class}">${lead.title}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${leadId}/${lead.space}/#${view.component.EditDoc}|${lead._id}|${lead._class}">${lead.title}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,7 +39,7 @@ import { workbenchId } from '@hcengineering/workbench'
|
||||
export async function vacancyHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const vacancy = doc as Vacancy
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${recruitId}/${vacancy._id}/#${recruit.component.EditVacancy}|${vacancy._id}|${vacancy._class}">${vacancy.name}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${recruitId}/${vacancy._id}/#${recruit.component.EditVacancy}|${vacancy._id}|${vacancy._class}">${vacancy.name}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +56,7 @@ export async function vacancyTextPresenter (doc: Doc): Promise<string> {
|
||||
export async function applicationHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const applicant = doc as Applicant
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${recruitId}/${applicant.space}/#${view.component.EditDoc}|${applicant._id}|${applicant._class}">APP-${applicant.number}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${recruitId}/${applicant.space}/#${view.component.EditDoc}|${applicant._id}|${applicant._class}">APP-${applicant.number}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,7 +31,7 @@ import { workbenchId } from '@hcengineering/workbench'
|
||||
export async function issueHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
|
||||
const issue = doc as Issue
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${taskId}/${issue.space}/#${view.component.EditDoc}|${issue._id}|${issue._class}">Task-${issue.number}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${taskId}/${issue.space}/#${view.component.EditDoc}|${issue._id}|${issue._class}">Task-${issue.number}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,7 @@ export async function issueHTMLPresenter (doc: Doc, control: TriggerControl): Pr
|
||||
const issueName = `${team?.identifier ?? '?'}-${issue.number}`
|
||||
|
||||
const front = getMetadata(login.metadata.FrontUrl) ?? ''
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace}/${trackerId}/${issue.space}/#${tracker.component.EditIssue}|${issue._id}|${issue._class}">${issueName}</a>`
|
||||
return `<a href="${front}/${workbenchId}/${control.workspace.name}/${trackerId}/${issue.space}/#${tracker.component.EditIssue}|${issue._id}|${issue._class}">${issueName}</a>`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,13 +24,11 @@
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/minio": "~7.0.11",
|
||||
"@types/ws": "^8.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"mongodb": "^4.9.0",
|
||||
"mongodb": "^4.11.0",
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"minio": "^7.0.26",
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"@hcengineering/contact": "~0.6.8",
|
||||
"@hcengineering/client-resources": "~0.6.4",
|
||||
|
@ -21,7 +21,7 @@ import { getAccount, getMethods, getWorkspace } from '..'
|
||||
|
||||
const DB_NAME = 'test_accounts'
|
||||
|
||||
const methods = getMethods(version, builder.getTxes(), migrateOperations, '')
|
||||
const methods = getMethods(version, builder.getTxes(), migrateOperations)
|
||||
|
||||
describe('server', () => {
|
||||
const dbUri = process.env.MONGODB_URI ?? 'mongodb://localhost:27017'
|
||||
@ -46,7 +46,7 @@ describe('server', () => {
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
}
|
||||
|
||||
const result = await methods.createWorkspace(db, request)
|
||||
const result = await methods.createWorkspace(db, '', request)
|
||||
expect(result.result).toBeDefined()
|
||||
workspace = result.result as string
|
||||
})
|
||||
@ -57,12 +57,12 @@ describe('server', () => {
|
||||
params: ['andrey2', '123']
|
||||
}
|
||||
|
||||
const result = await methods.createAccount(db, request)
|
||||
const result = await methods.createAccount(db, '', request)
|
||||
expect(result.result).toBeDefined()
|
||||
})
|
||||
|
||||
it('should not create, duplicate account', async () => {
|
||||
await methods.createAccount(db, {
|
||||
await methods.createAccount(db, '', {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
@ -72,20 +72,20 @@ describe('server', () => {
|
||||
params: ['andrey', '123']
|
||||
}
|
||||
|
||||
const result = await methods.createAccount(db, request)
|
||||
const result = await methods.createAccount(db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
it('should login', async () => {
|
||||
await methods.createAccount(db, {
|
||||
await methods.createAccount(db, '', {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
await methods.createWorkspace(db, {
|
||||
await methods.createWorkspace(db, '', {
|
||||
method: 'createWorkspace',
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
})
|
||||
await methods.assignWorkspace(db, {
|
||||
await methods.assignWorkspace(db, '', {
|
||||
method: 'assignWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
@ -95,7 +95,7 @@ describe('server', () => {
|
||||
params: ['andrey', '123', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(db, request)
|
||||
const result = await methods.login(db, '', request)
|
||||
expect(result.result).toBeDefined()
|
||||
})
|
||||
|
||||
@ -105,7 +105,7 @@ describe('server', () => {
|
||||
params: ['andrey', '123555', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(db, request)
|
||||
const result = await methods.login(db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
@ -115,7 +115,7 @@ describe('server', () => {
|
||||
params: ['andrey1', '123555', workspace]
|
||||
}
|
||||
|
||||
const result = await methods.login(db, request)
|
||||
const result = await methods.login(db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
@ -125,34 +125,34 @@ describe('server', () => {
|
||||
params: ['andrey', '123', 'non-existent-workspace']
|
||||
}
|
||||
|
||||
const result = await methods.login(db, request)
|
||||
const result = await methods.login(db, '', request)
|
||||
expect(result.error).toBeDefined()
|
||||
})
|
||||
|
||||
it('do remove workspace', async () => {
|
||||
await methods.createAccount(db, {
|
||||
await methods.createAccount(db, '', {
|
||||
method: 'createAccount',
|
||||
params: ['andrey', '123']
|
||||
})
|
||||
await methods.createWorkspace(db, {
|
||||
await methods.createWorkspace(db, '', {
|
||||
method: 'createWorkspace',
|
||||
params: [workspace, 'ООО Рога и Копыта']
|
||||
})
|
||||
await methods.assignWorkspace(db, {
|
||||
await methods.assignWorkspace(db, '', {
|
||||
method: 'assignWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
|
||||
// Check we had one
|
||||
expect((await getAccount(db, 'andrey'))?.workspaces.length).toEqual(1)
|
||||
expect((await getWorkspace(db, workspace))?.accounts.length).toEqual(1)
|
||||
expect((await getWorkspace(db, '', workspace))?.accounts.length).toEqual(1)
|
||||
|
||||
await methods.removeWorkspace(db, {
|
||||
await methods.removeWorkspace(db, '', {
|
||||
method: 'removeWorkspace',
|
||||
params: ['andrey', workspace]
|
||||
})
|
||||
expect((await getAccount(db, 'andrey'))?.workspaces.length).toEqual(0)
|
||||
expect((await getWorkspace(db, workspace))?.accounts.length).toEqual(0)
|
||||
expect((await getWorkspace(db, '', workspace))?.accounts.length).toEqual(0)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
@ -21,7 +21,7 @@ import contact, {
|
||||
Employee,
|
||||
getAvatarColorForId
|
||||
} from '@hcengineering/contact'
|
||||
import core, { AccountRole, Data, Ref, Tx, TxOperations, Version } from '@hcengineering/core'
|
||||
import core, { AccountRole, Data, getWorkspaceId, Ref, Tx, TxOperations, Version } from '@hcengineering/core'
|
||||
import { MigrateOperation } from '@hcengineering/model'
|
||||
import platform, {
|
||||
getMetadata,
|
||||
@ -37,7 +37,7 @@ import platform, {
|
||||
import { decodeToken, generateToken } from '@hcengineering/server-token'
|
||||
import toolPlugin, { connect, initModel, upgradeModel } from '@hcengineering/server-tool'
|
||||
import { pbkdf2Sync, randomBytes } from 'crypto'
|
||||
import { Binary, Db, ObjectId } from 'mongodb'
|
||||
import { Binary, Db, Filter, ObjectId } from 'mongodb'
|
||||
|
||||
const WORKSPACE_COLLECTION = 'workspace'
|
||||
const ACCOUNT_COLLECTION = 'account'
|
||||
@ -121,6 +121,7 @@ export interface LoginInfo {
|
||||
*/
|
||||
export interface WorkspaceLoginInfo extends LoginInfo {
|
||||
workspace: string
|
||||
productId: string
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,16 +155,24 @@ export async function getAccount (db: Db, email: string): Promise<Account | null
|
||||
return await db.collection(ACCOUNT_COLLECTION).findOne<Account>({ email })
|
||||
}
|
||||
|
||||
function withProductId (productId: string, query: Filter<Workspace>): Filter<Workspace> {
|
||||
return productId === ''
|
||||
? {
|
||||
$or: [
|
||||
{ productId: '', ...query },
|
||||
{ productId: { $exists: false }, ...query }
|
||||
]
|
||||
}
|
||||
: { productId, ...query }
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
* @param db -
|
||||
* @param workspace -
|
||||
* @returns
|
||||
*/
|
||||
export async function getWorkspace (db: Db, workspace: string): Promise<Workspace | null> {
|
||||
return await db.collection(WORKSPACE_COLLECTION).findOne<Workspace>({
|
||||
workspace
|
||||
})
|
||||
export async function getWorkspace (db: Db, productId: string, workspace: string): Promise<Workspace | null> {
|
||||
return await db.collection(WORKSPACE_COLLECTION).findOne<Workspace>(withProductId(productId, { workspace }))
|
||||
}
|
||||
|
||||
function toAccountInfo (account: Account): AccountInfo {
|
||||
@ -191,12 +200,12 @@ async function getAccountInfo (db: Db, email: string, password: string): Promise
|
||||
* @param workspace -
|
||||
* @returns
|
||||
*/
|
||||
export async function login (db: Db, email: string, password: string): Promise<LoginInfo> {
|
||||
export async function login (db: Db, productId: string, email: string, password: string): Promise<LoginInfo> {
|
||||
await getAccountInfo(db, email, password)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, '')
|
||||
token: generateToken(email, getWorkspaceId('', productId))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -204,13 +213,18 @@ export async function login (db: Db, email: string, password: string): Promise<L
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function selectWorkspace (db: Db, token: string, workspace: string): Promise<WorkspaceLoginInfo> {
|
||||
export async function selectWorkspace (
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
workspace: string
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
const { email } = decodeToken(token)
|
||||
const accountInfo = await getAccount(db, email)
|
||||
if (accountInfo === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
|
||||
}
|
||||
const workspaceInfo = await getWorkspace(db, workspace)
|
||||
const workspaceInfo = await getWorkspace(db, productId, workspace)
|
||||
|
||||
if (workspaceInfo !== null) {
|
||||
const workspaces = accountInfo.workspaces
|
||||
@ -220,8 +234,9 @@ export async function selectWorkspace (db: Db, token: string, workspace: string)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, workspace),
|
||||
workspace
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId)),
|
||||
workspace,
|
||||
productId
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -265,13 +280,19 @@ export async function useInvite (db: Db, inviteId: ObjectId): Promise<void> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function join (db: Db, email: string, password: string, inviteId: ObjectId): Promise<WorkspaceLoginInfo> {
|
||||
export async function join (
|
||||
db: Db,
|
||||
productId: string,
|
||||
email: string,
|
||||
password: string,
|
||||
inviteId: ObjectId
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
const invite = await getInvite(db, inviteId)
|
||||
const workspace = await checkInvite(invite, email)
|
||||
await assignWorkspace(db, email, workspace)
|
||||
await assignWorkspace(db, productId, email, workspace)
|
||||
|
||||
const token = (await login(db, email, password)).token
|
||||
const result = await selectWorkspace(db, token, workspace)
|
||||
const token = (await login(db, productId, email, password)).token
|
||||
const result = await selectWorkspace(db, productId, token, workspace)
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
@ -281,6 +302,7 @@ export async function join (db: Db, email: string, password: string, inviteId: O
|
||||
*/
|
||||
export async function signUpJoin (
|
||||
db: Db,
|
||||
productId: string,
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
@ -289,11 +311,11 @@ export async function signUpJoin (
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
const invite = await getInvite(db, inviteId)
|
||||
const workspace = await checkInvite(invite, email)
|
||||
await createAccount(db, email, password, first, last)
|
||||
await assignWorkspace(db, email, workspace)
|
||||
await createAccount(db, productId, email, password, first, last)
|
||||
await assignWorkspace(db, productId, email, workspace)
|
||||
|
||||
const token = (await login(db, email, password)).token
|
||||
const result = await selectWorkspace(db, token, workspace)
|
||||
const token = (await login(db, productId, email, password)).token
|
||||
const result = await selectWorkspace(db, productId, token, workspace)
|
||||
await useInvite(db, inviteId)
|
||||
return result
|
||||
}
|
||||
@ -303,6 +325,7 @@ export async function signUpJoin (
|
||||
*/
|
||||
export async function createAccount (
|
||||
db: Db,
|
||||
productId: string,
|
||||
email: string,
|
||||
password: string,
|
||||
first: string,
|
||||
@ -328,7 +351,7 @@ export async function createAccount (
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, '')
|
||||
token: generateToken(email, getWorkspaceId('', productId))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -337,13 +360,7 @@ export async function createAccount (
|
||||
* @public
|
||||
*/
|
||||
export async function listWorkspaces (db: Db, productId: string): Promise<Workspace[]> {
|
||||
if (productId === '') {
|
||||
return await db
|
||||
.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
.find({ $or: [{ productId: { $exists: false } }, { productId: '' }] })
|
||||
.toArray()
|
||||
}
|
||||
return await db.collection<Workspace>(WORKSPACE_COLLECTION).find({ productId }).toArray()
|
||||
return await db.collection<Workspace>(WORKSPACE_COLLECTION).find(withProductId(productId, {})).toArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -360,12 +377,12 @@ export async function createWorkspace (
|
||||
version: Data<Version>,
|
||||
txes: Tx[],
|
||||
migrationOperation: MigrateOperation[],
|
||||
productId: string,
|
||||
db: Db,
|
||||
productId: string,
|
||||
workspace: string,
|
||||
organisation: string
|
||||
): Promise<string> {
|
||||
if ((await getWorkspace(db, workspace)) !== null) {
|
||||
if ((await getWorkspace(db, productId, workspace)) !== null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceAlreadyExists, { workspace }))
|
||||
}
|
||||
const result = await db
|
||||
@ -377,7 +394,7 @@ export async function createWorkspace (
|
||||
productId
|
||||
})
|
||||
.then((e) => e.insertedId.toHexString())
|
||||
await initModel(getTransactor(), workspace, txes, migrationOperation)
|
||||
await initModel(getTransactor(), getWorkspaceId(workspace, productId), txes, migrationOperation)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -392,7 +409,7 @@ export async function upgradeWorkspace (
|
||||
db: Db,
|
||||
workspace: string
|
||||
): Promise<string> {
|
||||
const ws = await getWorkspace(db, workspace)
|
||||
const ws = await getWorkspace(db, productId, workspace)
|
||||
if (ws === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace }))
|
||||
}
|
||||
@ -407,7 +424,7 @@ export async function upgradeWorkspace (
|
||||
$set: { version }
|
||||
}
|
||||
)
|
||||
await upgradeModel(getTransactor(), workspace, txes, migrationOperation)
|
||||
await upgradeModel(getTransactor(), getWorkspaceId(workspace, productId), txes, migrationOperation)
|
||||
return `${version.major}.${version.minor}.${version.patch}`
|
||||
}
|
||||
|
||||
@ -415,16 +432,17 @@ export async function upgradeWorkspace (
|
||||
* @public
|
||||
*/
|
||||
export const createUserWorkspace =
|
||||
(version: Data<Version>, txes: Tx[], migrationOperation: MigrateOperation[], productId: string) =>
|
||||
async (db: Db, token: string, workspace: string): Promise<LoginInfo> => {
|
||||
(version: Data<Version>, txes: Tx[], migrationOperation: MigrateOperation[]) =>
|
||||
async (db: Db, productId: string, token: string, workspace: string): Promise<LoginInfo> => {
|
||||
const { email } = decodeToken(token)
|
||||
await createWorkspace(version, txes, migrationOperation, productId, db, workspace, '')
|
||||
await assignWorkspace(db, email, workspace)
|
||||
await setRole(email, workspace, AccountRole.Owner)
|
||||
await createWorkspace(version, txes, migrationOperation, db, productId, workspace, '')
|
||||
await assignWorkspace(db, productId, email, workspace)
|
||||
await setRole(email, workspace, productId, AccountRole.Owner)
|
||||
const result = {
|
||||
endpoint: getEndpoint(),
|
||||
email,
|
||||
token: generateToken(email, workspace)
|
||||
token: generateToken(email, getWorkspaceId(workspace, productId)),
|
||||
productId
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -434,15 +452,18 @@ export const createUserWorkspace =
|
||||
*/
|
||||
export async function getInviteLink (
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
exp: number,
|
||||
emailMask: string,
|
||||
limit: number
|
||||
): Promise<ObjectId> {
|
||||
const { workspace } = decodeToken(token)
|
||||
const wsPromise = await getWorkspace(db, workspace)
|
||||
const wsPromise = await getWorkspace(db, productId, workspace.name)
|
||||
if (wsPromise === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace }))
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace: workspace.name })
|
||||
)
|
||||
}
|
||||
const result = await db.collection(INVITE_COLLECTION).insertOne({
|
||||
workspace,
|
||||
@ -456,24 +477,23 @@ export async function getInviteLink (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getUserWorkspaces (db: Db, token: string): Promise<Workspace[]> {
|
||||
export async function getUserWorkspaces (db: Db, productId: string, token: string): Promise<Workspace[]> {
|
||||
const { email } = decodeToken(token)
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) return []
|
||||
return await db
|
||||
.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
.find({
|
||||
_id: { $in: account.workspaces }
|
||||
})
|
||||
.find(withProductId(productId, { _id: { $in: account.workspaces } }))
|
||||
.toArray()
|
||||
}
|
||||
|
||||
async function getWorkspaceAndAccount (
|
||||
db: Db,
|
||||
productId: string,
|
||||
email: string,
|
||||
workspace: string
|
||||
): Promise<{ accountId: ObjectId, workspaceId: ObjectId }> {
|
||||
const wsPromise = await getWorkspace(db, workspace)
|
||||
const wsPromise = await getWorkspace(db, productId, workspace)
|
||||
if (wsPromise === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace }))
|
||||
}
|
||||
@ -489,8 +509,8 @@ async function getWorkspaceAndAccount (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function setRole (email: string, workspace: string, role: AccountRole): Promise<void> {
|
||||
const connection = await connect(getTransactor(), workspace, email)
|
||||
export async function setRole (email: string, workspace: string, productId: string, role: AccountRole): Promise<void> {
|
||||
const connection = await connect(getTransactor(), getWorkspaceId(workspace, productId), email)
|
||||
try {
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
|
||||
@ -510,8 +530,8 @@ export async function setRole (email: string, workspace: string, role: AccountRo
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function assignWorkspace (db: Db, email: string, workspace: string): Promise<void> {
|
||||
const { workspaceId, accountId } = await getWorkspaceAndAccount(db, email, workspace)
|
||||
export async function assignWorkspace (db: Db, productId: string, email: string, workspace: string): Promise<void> {
|
||||
const { workspaceId, accountId } = await getWorkspaceAndAccount(db, productId, email, workspace)
|
||||
// Add account into workspace.
|
||||
await db.collection(WORKSPACE_COLLECTION).updateOne({ _id: workspaceId }, { $addToSet: { accounts: accountId } })
|
||||
|
||||
@ -520,7 +540,7 @@ export async function assignWorkspace (db: Db, email: string, workspace: string)
|
||||
|
||||
const account = await db.collection<Account>(ACCOUNT_COLLECTION).findOne({ _id: accountId })
|
||||
|
||||
if (account !== null) await createEmployeeAccount(account, workspace)
|
||||
if (account !== null) await createEmployeeAccount(account, productId, workspace)
|
||||
}
|
||||
|
||||
async function createEmployee (ops: TxOperations, name: string, email: string): Promise<Ref<Employee>> {
|
||||
@ -542,8 +562,8 @@ async function createEmployee (ops: TxOperations, name: string, email: string):
|
||||
return id
|
||||
}
|
||||
|
||||
async function createEmployeeAccount (account: Account, workspace: string): Promise<void> {
|
||||
const connection = await connect(getTransactor(), workspace, account.email)
|
||||
async function createEmployeeAccount (account: Account, productId: string, workspace: string): Promise<void> {
|
||||
const connection = await connect(getTransactor(), getWorkspaceId(workspace, productId), account.email)
|
||||
try {
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
|
||||
@ -584,7 +604,13 @@ async function createEmployeeAccount (account: Account, workspace: string): Prom
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function changePassword (db: Db, token: string, oldPassword: string, password: string): Promise<void> {
|
||||
export async function changePassword (
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
oldPassword: string,
|
||||
password: string
|
||||
): Promise<void> {
|
||||
const { email } = decodeToken(token)
|
||||
const account = await getAccountInfo(db, email, oldPassword)
|
||||
|
||||
@ -597,7 +623,7 @@ export async function changePassword (db: Db, token: string, oldPassword: string
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function replacePassword (db: Db, email: string, password: string): Promise<void> {
|
||||
export async function replacePassword (db: Db, productId: string, email: string, password: string): Promise<void> {
|
||||
const account = await getAccount(db, email)
|
||||
|
||||
if (account === null) {
|
||||
@ -612,7 +638,7 @@ export async function replacePassword (db: Db, email: string, password: string):
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function changeName (db: Db, token: string, first: string, last: string): Promise<void> {
|
||||
export async function changeName (db: Db, productId: string, token: string, first: string, last: string): Promise<void> {
|
||||
const { email } = decodeToken(token)
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) {
|
||||
@ -630,13 +656,15 @@ export async function changeName (db: Db, token: string, first: string, last: st
|
||||
|
||||
const promises: Promise<void>[] = []
|
||||
for (const ws of workspaces) {
|
||||
promises.push(updateEmployeeAccount(account, ws.workspace))
|
||||
if (ws.productId === productId) {
|
||||
promises.push(updateEmployeeAccount(account, ws.workspace, ws.productId))
|
||||
}
|
||||
}
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
async function updateEmployeeAccount (account: Account, workspace: string): Promise<void> {
|
||||
const connection = await connect(getTransactor(), workspace, account.email)
|
||||
async function updateEmployeeAccount (account: Account, workspace: string, productId: string): Promise<void> {
|
||||
const connection = await connect(getTransactor(), getWorkspaceId(workspace, productId), account.email)
|
||||
try {
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
|
||||
@ -663,8 +691,8 @@ async function updateEmployeeAccount (account: Account, workspace: string): Prom
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function removeWorkspace (db: Db, email: string, workspace: string): Promise<void> {
|
||||
const { workspaceId, accountId } = await getWorkspaceAndAccount(db, email, workspace)
|
||||
export async function removeWorkspace (db: Db, productId: string, email: string, workspace: string): Promise<void> {
|
||||
const { workspaceId, accountId } = await getWorkspaceAndAccount(db, productId, email, workspace)
|
||||
|
||||
// Add account into workspace.
|
||||
await db.collection(WORKSPACE_COLLECTION).updateOne({ _id: workspaceId }, { $pull: { accounts: accountId } })
|
||||
@ -676,18 +704,23 @@ export async function removeWorkspace (db: Db, email: string, workspace: string)
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function checkJoin (db: Db, token: string, inviteId: ObjectId): Promise<WorkspaceLoginInfo> {
|
||||
export async function checkJoin (
|
||||
db: Db,
|
||||
productId: string,
|
||||
token: string,
|
||||
inviteId: ObjectId
|
||||
): Promise<WorkspaceLoginInfo> {
|
||||
const { email } = decodeToken(token)
|
||||
const invite = await getInvite(db, inviteId)
|
||||
const workspace = await checkInvite(invite, email)
|
||||
return await selectWorkspace(db, token, workspace)
|
||||
return await selectWorkspace(db, productId, token, workspace)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function dropWorkspace (db: Db, workspace: string): Promise<void> {
|
||||
const ws = await getWorkspace(db, workspace)
|
||||
export async function dropWorkspace (db: Db, productId: string, workspace: string): Promise<void> {
|
||||
const ws = await getWorkspace(db, productId, workspace)
|
||||
if (ws === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace }))
|
||||
}
|
||||
@ -700,7 +733,7 @@ export async function dropWorkspace (db: Db, workspace: string): Promise<void> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function dropAccount (db: Db, email: string): Promise<void> {
|
||||
export async function dropAccount (db: Db, productId: string, email: string): Promise<void> {
|
||||
const account = await getAccount(db, email)
|
||||
if (account === null) {
|
||||
throw new PlatformError(new Status(Severity.ERROR, accountPlugin.status.AccountNotFound, { account: email }))
|
||||
@ -708,12 +741,12 @@ export async function dropAccount (db: Db, email: string): Promise<void> {
|
||||
|
||||
const workspaces = await db
|
||||
.collection<Workspace>(WORKSPACE_COLLECTION)
|
||||
.find({ _id: { $in: account.workspaces } })
|
||||
.find(withProductId(productId, { _id: { $in: account.workspaces } }))
|
||||
.toArray()
|
||||
|
||||
await Promise.all(
|
||||
workspaces.map(async (ws) => {
|
||||
return await deactivateEmployeeAccount(account.email, ws.workspace)
|
||||
return await deactivateEmployeeAccount(account.email, ws.workspace, productId)
|
||||
})
|
||||
)
|
||||
|
||||
@ -726,7 +759,7 @@ export async function dropAccount (db: Db, email: string): Promise<void> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function leaveWorkspace (db: Db, token: string, email: string): Promise<void> {
|
||||
export async function leaveWorkspace (db: Db, productId: string, token: string, email: string): Promise<void> {
|
||||
const tokenData = decodeToken(token)
|
||||
|
||||
const currentAccount = await getAccount(db, tokenData.email)
|
||||
@ -736,14 +769,14 @@ export async function leaveWorkspace (db: Db, token: string, email: string): Pro
|
||||
)
|
||||
}
|
||||
|
||||
const workspace = await getWorkspace(db, tokenData.workspace)
|
||||
const workspace = await getWorkspace(db, productId, tokenData.workspace.name)
|
||||
if (workspace === null) {
|
||||
throw new PlatformError(
|
||||
new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace: tokenData.workspace })
|
||||
new Status(Severity.ERROR, accountPlugin.status.WorkspaceNotFound, { workspace: tokenData.workspace.name })
|
||||
)
|
||||
}
|
||||
|
||||
await deactivateEmployeeAccount(email, workspace.workspace)
|
||||
await deactivateEmployeeAccount(email, workspace.workspace, workspace.productId)
|
||||
|
||||
const account = tokenData.email !== email ? await getAccount(db, email) : currentAccount
|
||||
if (account !== null) {
|
||||
@ -756,8 +789,8 @@ export async function leaveWorkspace (db: Db, token: string, email: string): Pro
|
||||
}
|
||||
}
|
||||
|
||||
async function deactivateEmployeeAccount (email: string, workspace: string): Promise<void> {
|
||||
const connection = await connect(getTransactor(), workspace, email)
|
||||
async function deactivateEmployeeAccount (email: string, workspace: string, productId: string): Promise<void> {
|
||||
const connection = await connect(getTransactor(), getWorkspaceId(workspace, productId), email)
|
||||
try {
|
||||
const ops = new TxOperations(connection, core.account.System)
|
||||
|
||||
@ -779,19 +812,27 @@ async function deactivateEmployeeAccount (email: string, workspace: string): Pro
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AccountMethod = (db: Db, request: Request<any[]>, token?: string) => Promise<Response<any>>
|
||||
export type AccountMethod = (
|
||||
db: Db,
|
||||
productId: string,
|
||||
request: Request<any[]>,
|
||||
token?: string
|
||||
) => Promise<Response<any>>
|
||||
|
||||
function wrap (f: (db: Db, ...args: any[]) => Promise<any>): AccountMethod {
|
||||
return async function (db: Db, request: Request<any[]>, token?: string): Promise<Response<any>> {
|
||||
function wrap (f: (db: Db, productId: string, ...args: any[]) => Promise<any>): AccountMethod {
|
||||
return async function (db: Db, productId: string, request: Request<any[]>, token?: string): Promise<Response<any>> {
|
||||
if (token !== undefined) request.params.unshift(token)
|
||||
return await f(db, ...request.params)
|
||||
return await f(db, productId, ...request.params)
|
||||
.then((result) => ({ id: request.id, result }))
|
||||
.catch((err) => ({
|
||||
error:
|
||||
err instanceof PlatformError
|
||||
? new Status(Severity.ERROR, platform.status.Forbidden, {})
|
||||
: new Status(Severity.ERROR, platform.status.InternalServerError, {})
|
||||
}))
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
return {
|
||||
error:
|
||||
err instanceof PlatformError
|
||||
? new Status(Severity.ERROR, platform.status.Forbidden, {})
|
||||
: new Status(Severity.ERROR, platform.status.InternalServerError, {})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -801,8 +842,7 @@ function wrap (f: (db: Db, ...args: any[]) => Promise<any>): AccountMethod {
|
||||
export function getMethods (
|
||||
version: Data<Version>,
|
||||
txes: Tx[],
|
||||
migrateOperations: MigrateOperation[],
|
||||
productId: string
|
||||
migrateOperations: MigrateOperation[]
|
||||
): Record<string, AccountMethod> {
|
||||
return {
|
||||
login: wrap(login),
|
||||
@ -814,7 +854,7 @@ export function getMethods (
|
||||
getInviteLink: wrap(getInviteLink),
|
||||
getAccountInfo: wrap(getAccountInfo),
|
||||
createAccount: wrap(createAccount),
|
||||
createWorkspace: wrap(createUserWorkspace(version, txes, migrateOperations, productId)),
|
||||
createWorkspace: wrap(createUserWorkspace(version, txes, migrateOperations)),
|
||||
assignWorkspace: wrap(assignWorkspace),
|
||||
removeWorkspace: wrap(removeWorkspace),
|
||||
leaveWorkspace: wrap(leaveWorkspace),
|
||||
|
@ -25,8 +25,7 @@
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/node": "~16.11.12",
|
||||
"@types/minio": "~7.0.11"
|
||||
"@types/node": "~16.11.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
@ -37,6 +36,6 @@
|
||||
"@hcengineering/model": "~0.6.0",
|
||||
"tar-stream": "^2.2.0",
|
||||
"@hcengineering/server-tool": "~0.6.0",
|
||||
"minio": "^7.0.26"
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -22,13 +22,14 @@ import core, {
|
||||
Domain,
|
||||
DOMAIN_MODEL,
|
||||
DOMAIN_TRANSIENT,
|
||||
Ref
|
||||
Ref,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import { createGzip } from 'node:zlib'
|
||||
import { join } from 'path'
|
||||
import { extract, Pack, pack } from 'tar-stream'
|
||||
import { createGunzip, gunzipSync, gzipSync } from 'zlib'
|
||||
import { connect } from '@hcengineering/server-tool'
|
||||
import { BackupStorage } from './storage'
|
||||
export * from './storage'
|
||||
|
||||
@ -72,6 +73,7 @@ export interface BackupSnapshot {
|
||||
export interface BackupInfo {
|
||||
workspace: string
|
||||
version: string
|
||||
productId: string
|
||||
snapshots: BackupSnapshot[]
|
||||
}
|
||||
|
||||
@ -107,8 +109,8 @@ async function loadDigest (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function backup (transactorUrl: string, dbName: string, storage: BackupStorage): Promise<void> {
|
||||
const connection = (await connect(transactorUrl, dbName, undefined, {
|
||||
export async function backup (transactorUrl: string, workspaceId: WorkspaceId, storage: BackupStorage): Promise<void> {
|
||||
const connection = (await connect(transactorUrl, workspaceId, undefined, {
|
||||
mode: 'backup'
|
||||
})) as unknown as CoreClient & BackupClient
|
||||
try {
|
||||
@ -118,7 +120,8 @@ export async function backup (transactorUrl: string, dbName: string, storage: Ba
|
||||
.filter((it) => it !== DOMAIN_TRANSIENT && it !== DOMAIN_MODEL)
|
||||
|
||||
let backupInfo: BackupInfo = {
|
||||
workspace: dbName,
|
||||
workspace: workspaceId.name,
|
||||
productId: workspaceId.productId,
|
||||
version: '0.6',
|
||||
snapshots: []
|
||||
}
|
||||
@ -128,7 +131,8 @@ export async function backup (transactorUrl: string, dbName: string, storage: Ba
|
||||
backupInfo = JSON.parse(gunzipSync(await storage.loadFile(infoFile)).toString())
|
||||
}
|
||||
|
||||
backupInfo.workspace = dbName
|
||||
backupInfo.workspace = workspaceId.name
|
||||
backupInfo.productId = workspaceId.productId
|
||||
|
||||
const snapshot: BackupSnapshot = {
|
||||
date: Date.now(),
|
||||
@ -282,7 +286,7 @@ export async function backupList (storage: BackupStorage): Promise<void> {
|
||||
*/
|
||||
export async function restore (
|
||||
transactorUrl: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
storage: BackupStorage,
|
||||
date: number
|
||||
): Promise<void> {
|
||||
@ -311,7 +315,7 @@ export async function restore (
|
||||
Object.keys(s.domains).forEach((it) => domains.add(it as Domain))
|
||||
}
|
||||
|
||||
const connection = (await connect(transactorUrl, dbName, undefined, {
|
||||
const connection = (await connect(transactorUrl, workspaceId, undefined, {
|
||||
mode: 'backup',
|
||||
model: 'upgrade'
|
||||
})) as unknown as CoreClient & BackupClient
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { WorkspaceId } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { createReadStream, createWriteStream, existsSync } from 'fs'
|
||||
import { mkdir, readFile, writeFile } from 'fs/promises'
|
||||
import { Client as MinioClient } from 'minio'
|
||||
import { dirname, join } from 'path'
|
||||
import { PassThrough, Readable, Writable } from 'stream'
|
||||
|
||||
@ -51,40 +52,25 @@ class FileStorage implements BackupStorage {
|
||||
}
|
||||
|
||||
class MinioStorage implements BackupStorage {
|
||||
constructor (readonly client: MinioClient, readonly bucketName: string, readonly root: string) {}
|
||||
constructor (readonly client: MinioService, readonly workspaceId: WorkspaceId, readonly root: string) {}
|
||||
async loadFile (name: string): Promise<Buffer> {
|
||||
const data = await this.client.getObject(this.bucketName, join(this.root, name))
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
await new Promise((resolve) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
const b = chunk as Buffer
|
||||
chunks.push(b)
|
||||
}
|
||||
})
|
||||
|
||||
data.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return Buffer.concat(chunks)
|
||||
const data = await this.client.read(this.workspaceId, join(this.root, name))
|
||||
return Buffer.concat(data)
|
||||
}
|
||||
|
||||
async write (name: string): Promise<Writable> {
|
||||
const wr = new PassThrough()
|
||||
void this.client.putObject(this.bucketName, join(this.root, name), wr)
|
||||
void this.client.put(this.workspaceId, join(this.root, name), wr)
|
||||
return wr
|
||||
}
|
||||
|
||||
async load (name: string): Promise<Readable> {
|
||||
return await this.client.getObject(this.bucketName, join(this.root, name))
|
||||
return await this.client.get(this.workspaceId, join(this.root, name))
|
||||
}
|
||||
|
||||
async exists (name: string): Promise<boolean> {
|
||||
try {
|
||||
await this.client.statObject(this.bucketName, join(this.root, name))
|
||||
await this.client.stat(this.workspaceId, join(this.root, name))
|
||||
return true
|
||||
} catch (err) {
|
||||
return false
|
||||
@ -92,7 +78,7 @@ class MinioStorage implements BackupStorage {
|
||||
}
|
||||
|
||||
async writeFile (name: string, data: string | Buffer): Promise<void> {
|
||||
void this.client.putObject(this.bucketName, join(this.root, name), data, data.length)
|
||||
void this.client.put(this.workspaceId, join(this.root, name), data, data.length)
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,12 +96,12 @@ export async function createFileBackupStorage (fileName: string): Promise<Backup
|
||||
* @public
|
||||
*/
|
||||
export async function createMinioBackupStorage (
|
||||
client: MinioClient,
|
||||
bucketName: string,
|
||||
client: MinioService,
|
||||
workspaceId: WorkspaceId,
|
||||
root: string
|
||||
): Promise<BackupStorage> {
|
||||
if (!(await client.bucketExists(bucketName))) {
|
||||
await client.makeBucket(bucketName, 'k8s')
|
||||
if (!(await client.exists(workspaceId))) {
|
||||
await client.make(workspaceId)
|
||||
}
|
||||
return new MinioStorage(client, bucketName, root)
|
||||
return new MinioStorage(client, workspaceId, root)
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
"@hcengineering/platform-rig": "~0.6.0",
|
||||
"@types/heft-jest": "^1.0.3",
|
||||
"@types/node": "~16.11.12",
|
||||
"@types/minio": "~7.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
@ -30,6 +29,6 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"minio": "^7.0.26"
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,10 @@ import {
|
||||
Ref,
|
||||
StorageIterator,
|
||||
Tx,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { Client } from 'minio'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -65,9 +66,9 @@ export interface TxAdapter extends DbAdapter {
|
||||
export type DbAdapterFactory = (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
db: string,
|
||||
workspaceId: WorkspaceId,
|
||||
modelDb: ModelDb,
|
||||
storage?: Client
|
||||
storage?: MinioService
|
||||
) => Promise<DbAdapter>
|
||||
|
||||
/**
|
||||
@ -129,6 +130,10 @@ class InMemoryAdapter implements DbAdapter {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createInMemoryAdapter (hierarchy: Hierarchy, url: string, db: string): Promise<DbAdapter> {
|
||||
export async function createInMemoryAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
workspaceId: WorkspaceId
|
||||
): Promise<DbAdapter> {
|
||||
return new InMemoryAdapter(hierarchy)
|
||||
}
|
||||
|
@ -45,10 +45,11 @@ import core, {
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxResult,
|
||||
TxUpdateDoc
|
||||
TxUpdateDoc,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import type { Client as MinioClient } from 'minio'
|
||||
import { DbAdapter, DbAdapterConfiguration, TxAdapter } from './adapter'
|
||||
import { FullTextIndex } from './fulltext'
|
||||
import serverCore from './plugin'
|
||||
@ -62,12 +63,12 @@ export interface DbConfiguration {
|
||||
adapters: Record<string, DbAdapterConfiguration>
|
||||
domains: Record<string, string>
|
||||
defaultAdapter: string
|
||||
workspace: string
|
||||
workspace: WorkspaceId
|
||||
fulltextAdapter: {
|
||||
factory: FullTextAdapterFactory
|
||||
url: string
|
||||
}
|
||||
storageFactory?: () => MinioClient
|
||||
storageFactory?: () => MinioService
|
||||
}
|
||||
|
||||
class TServerStorage implements ServerStorage {
|
||||
@ -83,9 +84,9 @@ class TServerStorage implements ServerStorage {
|
||||
hierarchy: Hierarchy,
|
||||
private readonly triggers: Triggers,
|
||||
private readonly fulltextAdapter: FullTextAdapter,
|
||||
readonly storageAdapter: MinioClient | undefined,
|
||||
readonly storageAdapter: MinioService | undefined,
|
||||
readonly modelDb: ModelDb,
|
||||
private readonly workspace: string,
|
||||
private readonly workspace: WorkspaceId,
|
||||
options?: ServerStorageOptions
|
||||
) {
|
||||
this.hierarchy = hierarchy
|
||||
|
@ -30,13 +30,12 @@ import type {
|
||||
Storage,
|
||||
Timestamp,
|
||||
Tx,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { Hierarchy, TxFactory } from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import type { Resource } from '@hcengineering/platform'
|
||||
import type { Client as MinioClient, BucketItem } from 'minio'
|
||||
|
||||
export { MinioClient, BucketItem }
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -98,7 +97,7 @@ export interface Pipeline extends LowLevelStorage {
|
||||
* @public
|
||||
*/
|
||||
export interface TriggerControl {
|
||||
workspace: string
|
||||
workspace: WorkspaceId
|
||||
txFactory: TxFactory
|
||||
findAll: Storage['findAll']
|
||||
hierarchy: Hierarchy
|
||||
@ -107,7 +106,7 @@ export interface TriggerControl {
|
||||
fulltextFx: (f: (adapter: FullTextAdapter) => Promise<void>) => void
|
||||
// Since we don't have other storages let's consider adapter is MinioClient
|
||||
// Later can be replaced with generic one with bucket encapsulated inside.
|
||||
storageFx: (f: (adapter: MinioClient, bucket: string) => Promise<void>) => void
|
||||
storageFx: (f: (adapter: MinioService, workspaceId: WorkspaceId) => Promise<void>) => void
|
||||
fx: (f: () => Promise<void>) => void
|
||||
}
|
||||
|
||||
@ -157,7 +156,7 @@ export interface FullTextAdapter {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type FullTextAdapterFactory = (url: string, workspace: string) => Promise<FullTextAdapter>
|
||||
export type FullTextAdapterFactory = (url: string, workspace: WorkspaceId) => Promise<FullTextAdapter>
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
@ -14,13 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Ref, Doc, Class, Account, Space } from '@hcengineering/core'
|
||||
import { createElasticAdapter } from '../adapter'
|
||||
import { Account, Class, Doc, getWorkspaceId, Ref, Space } from '@hcengineering/core'
|
||||
import type { IndexedDoc } from '@hcengineering/server-core'
|
||||
import { createElasticAdapter } from '../adapter'
|
||||
|
||||
describe('client', () => {
|
||||
it('should create document', async () => {
|
||||
const adapter = await createElasticAdapter('http://localhost:9200/', 'ws1')
|
||||
const adapter = await createElasticAdapter('http://localhost:9200/', getWorkspaceId('ws1', ''))
|
||||
const doc: IndexedDoc = {
|
||||
id: 'doc1' as Ref<Doc>,
|
||||
_class: 'class1' as Ref<Class<Doc>>,
|
||||
|
@ -14,12 +14,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Doc, DocumentQuery, Ref, TxResult } from '@hcengineering/core'
|
||||
import { Class, Doc, DocumentQuery, Ref, toWorkspaceString, TxResult, WorkspaceId } from '@hcengineering/core'
|
||||
import type { FullTextAdapter, IndexedDoc } from '@hcengineering/server-core'
|
||||
|
||||
import { Client, errors as esErr } from '@elastic/elasticsearch'
|
||||
class ElasticAdapter implements FullTextAdapter {
|
||||
constructor (private readonly client: Client, private readonly db: string) {}
|
||||
constructor (private readonly client: Client, private readonly workspaceId: WorkspaceId) {}
|
||||
|
||||
async close (): Promise<void> {
|
||||
await this.client.close()
|
||||
@ -95,7 +95,7 @@ class ElasticAdapter implements FullTextAdapter {
|
||||
|
||||
try {
|
||||
const result = await this.client.search({
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
body: {
|
||||
query: request,
|
||||
size: size ?? 200,
|
||||
@ -113,14 +113,14 @@ class ElasticAdapter implements FullTextAdapter {
|
||||
async index (doc: IndexedDoc): Promise<TxResult> {
|
||||
if (doc.data === undefined) {
|
||||
await this.client.index({
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
id: doc.id,
|
||||
type: '_doc',
|
||||
body: doc
|
||||
})
|
||||
} else {
|
||||
await this.client.index({
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
id: doc.id,
|
||||
type: '_doc',
|
||||
pipeline: 'attachment',
|
||||
@ -132,7 +132,7 @@ class ElasticAdapter implements FullTextAdapter {
|
||||
|
||||
async update (id: Ref<Doc>, update: Record<string, any>): Promise<TxResult> {
|
||||
await this.client.update({
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
id,
|
||||
body: {
|
||||
doc: update
|
||||
@ -145,7 +145,7 @@ class ElasticAdapter implements FullTextAdapter {
|
||||
async remove (id: Ref<Doc>): Promise<void> {
|
||||
try {
|
||||
await this.client.delete({
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
id
|
||||
})
|
||||
} catch (e: any) {
|
||||
@ -163,11 +163,11 @@ class ElasticAdapter implements FullTextAdapter {
|
||||
*/
|
||||
export async function createElasticAdapter (
|
||||
url: string,
|
||||
dbName: string
|
||||
workspaceId: WorkspaceId
|
||||
): Promise<FullTextAdapter & { close: () => Promise<void> }> {
|
||||
const client = new Client({
|
||||
node: url
|
||||
})
|
||||
|
||||
return new ElasticAdapter(client, dbName)
|
||||
return new ElasticAdapter(client, workspaceId)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { ApiResponse, Client } from '@elastic/elasticsearch'
|
||||
import core, {
|
||||
Class,
|
||||
Doc,
|
||||
@ -25,16 +26,17 @@ import core, {
|
||||
Ref,
|
||||
Space,
|
||||
StorageIterator,
|
||||
toWorkspaceString,
|
||||
Tx,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { PlatformError, unknownStatus } from '@hcengineering/platform'
|
||||
import { DbAdapter, IndexedDoc } from '@hcengineering/server-core'
|
||||
import { ApiResponse, Client } from '@elastic/elasticsearch'
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
class ElasticDataAdapter implements DbAdapter {
|
||||
constructor (readonly db: string, readonly client: Client) {}
|
||||
constructor (readonly workspaceId: WorkspaceId, readonly client: Client) {}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
@ -64,7 +66,7 @@ class ElasticDataAdapter implements DbAdapter {
|
||||
next: async () => {
|
||||
if (!listRecieved) {
|
||||
const q = {
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
type: '_doc',
|
||||
scroll: '1s',
|
||||
// search_type: 'scan', //if I use search_type then it requires size otherwise it shows 0 result
|
||||
@ -125,7 +127,7 @@ class ElasticDataAdapter implements DbAdapter {
|
||||
const result: Doc[] = []
|
||||
|
||||
const resp = await this.client.search({
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
type: '_doc',
|
||||
body: {
|
||||
query: {
|
||||
@ -159,7 +161,7 @@ class ElasticDataAdapter implements DbAdapter {
|
||||
await this.client.deleteByQuery(
|
||||
{
|
||||
type: '_doc',
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
body: {
|
||||
query: {
|
||||
terms: {
|
||||
@ -174,7 +176,7 @@ class ElasticDataAdapter implements DbAdapter {
|
||||
)
|
||||
|
||||
const operations = part.flatMap((doc) => [
|
||||
{ index: { _index: this.db, _id: doc._id } },
|
||||
{ index: { _index: this.workspaceId, _id: doc._id } },
|
||||
(doc as FullTextData).data
|
||||
])
|
||||
|
||||
@ -188,7 +190,7 @@ class ElasticDataAdapter implements DbAdapter {
|
||||
await this.client.deleteByQuery(
|
||||
{
|
||||
type: '_doc',
|
||||
index: this.db,
|
||||
index: toWorkspaceString(this.workspaceId),
|
||||
body: {
|
||||
query: {
|
||||
terms: {
|
||||
@ -211,10 +213,10 @@ class ElasticDataAdapter implements DbAdapter {
|
||||
export async function createElasticBackupDataAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
db: string
|
||||
workspaceId: WorkspaceId
|
||||
): Promise<DbAdapter> {
|
||||
const client = new Client({
|
||||
node: url
|
||||
})
|
||||
return new ElasticDataAdapter(db, client)
|
||||
return new ElasticDataAdapter(workspaceId, client)
|
||||
}
|
||||
|
@ -26,7 +26,6 @@
|
||||
"@types/express-fileupload": "^1.1.7",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/minio": "~7.0.11",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
@ -48,9 +47,9 @@
|
||||
"@hcengineering/server-token": "~0.6.0",
|
||||
"@hcengineering/attachment": "~0.6.1",
|
||||
"@hcengineering/contrib": "~0.6.0",
|
||||
"minio": "^7.0.26",
|
||||
"body-parser": "~1.19.1",
|
||||
"compression": "~1.7.4",
|
||||
"sharp": "~0.30.7"
|
||||
"sharp": "~0.30.7",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Client } from 'minio'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import serverToken from '@hcengineering/server-token'
|
||||
import { start } from '.'
|
||||
@ -51,7 +51,7 @@ if (minioSecretKey === undefined) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const minio = new Client({
|
||||
const minio = new MinioService({
|
||||
endPoint: minioEndpoint,
|
||||
port: 9000,
|
||||
useSSL: false,
|
||||
|
@ -15,8 +15,9 @@
|
||||
//
|
||||
|
||||
import attachment from '@hcengineering/attachment'
|
||||
import { Account, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import { Account, Doc, Ref, Space, WorkspaceId } from '@hcengineering/core'
|
||||
import { createElasticAdapter } from '@hcengineering/elastic'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import type { IndexedDoc } from '@hcengineering/server-core'
|
||||
import { decodeToken, Token } from '@hcengineering/server-token'
|
||||
import bp from 'body-parser'
|
||||
@ -25,43 +26,22 @@ import cors from 'cors'
|
||||
import express, { Response } from 'express'
|
||||
import fileUpload, { UploadedFile } from 'express-fileupload'
|
||||
import https from 'https'
|
||||
import { BucketItem, Client, ItemBucketMetadata } from 'minio'
|
||||
import { join, resolve } from 'path'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import sharp from 'sharp'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
async function minioUpload (minio: Client, workspace: string, file: UploadedFile): Promise<string> {
|
||||
async function minioUpload (minio: MinioService, workspace: WorkspaceId, file: UploadedFile): Promise<string> {
|
||||
const id = uuid()
|
||||
const meta: ItemBucketMetadata = {
|
||||
const meta: any = {
|
||||
'Content-Type': file.mimetype
|
||||
}
|
||||
|
||||
const resp = await minio.putObject(workspace, id, file.data, file.size, meta)
|
||||
const resp = await minio.put(workspace, id, file.data, file.size, meta)
|
||||
|
||||
console.log(resp)
|
||||
return id
|
||||
}
|
||||
|
||||
async function readMinioData (client: Client, db: string, name: string): Promise<Buffer[]> {
|
||||
const data = await client.getObject(db, name)
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
await new Promise((resolve) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
const b = chunk as Buffer
|
||||
chunks.push(b)
|
||||
}
|
||||
})
|
||||
|
||||
data.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return chunks
|
||||
}
|
||||
|
||||
function getRange (range: string, size: number): [number, number] {
|
||||
const [startStr, endStr] = range.replace(/bytes=/, '').split('-')
|
||||
|
||||
@ -82,14 +62,14 @@ function getRange (range: string, size: number): [number, number] {
|
||||
|
||||
async function getFileRange (
|
||||
range: string,
|
||||
client: Client,
|
||||
workspace: string,
|
||||
client: MinioService,
|
||||
workspace: WorkspaceId,
|
||||
uuid: string,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
const stat = await client.statObject(workspace, uuid)
|
||||
const stat = await client.stat(workspace, uuid)
|
||||
|
||||
const size = stat.size
|
||||
const size: number = stat.size
|
||||
|
||||
const [start, end] = getRange(range, size)
|
||||
|
||||
@ -101,13 +81,8 @@ async function getFileRange (
|
||||
return
|
||||
}
|
||||
|
||||
client.getPartialObject(workspace, uuid, start, end - start + 1, (err, dataStream) => {
|
||||
if (err !== null) {
|
||||
console.log(err)
|
||||
res.status(500).send()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const dataStream = await client.partial(workspace, uuid, start, end - start + 1)
|
||||
res.writeHead(206, {
|
||||
Connection: 'keep-alive',
|
||||
'Content-Range': `bytes ${start}-${end}/${size}`,
|
||||
@ -117,18 +92,17 @@ async function getFileRange (
|
||||
})
|
||||
|
||||
dataStream.pipe(res)
|
||||
})
|
||||
} catch (err: any) {
|
||||
console.log(err)
|
||||
res.status(500).send()
|
||||
}
|
||||
}
|
||||
|
||||
async function getFile (client: Client, workspace: string, uuid: string, res: Response): Promise<void> {
|
||||
const stat = await client.statObject(workspace, uuid)
|
||||
async function getFile (client: MinioService, workspace: WorkspaceId, uuid: string, res: Response): Promise<void> {
|
||||
const stat = await client.stat(workspace, uuid)
|
||||
|
||||
client.getObject(workspace, uuid, (err, dataStream) => {
|
||||
if (err !== null) {
|
||||
console.log(err)
|
||||
res.status(500).send()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const dataStream = await client.get(workspace, uuid)
|
||||
res.status(200)
|
||||
res.set('Cache-Control', 'max-age=604800')
|
||||
|
||||
@ -147,7 +121,10 @@ async function getFile (client: Client, workspace: string, uuid: string, res: Re
|
||||
console.log(err)
|
||||
res.status(500).send()
|
||||
})
|
||||
})
|
||||
} catch (err: any) {
|
||||
console.log(err)
|
||||
res.status(500).send()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,7 +135,7 @@ export function start (
|
||||
config: {
|
||||
transactorEndpoint: string
|
||||
elasticUrl: string
|
||||
minio: Client
|
||||
minio: MinioService
|
||||
accountsUrl: string
|
||||
uploadUrl: string
|
||||
modelVersion: string
|
||||
@ -211,7 +188,7 @@ export function start (
|
||||
const size = req.query.size as 'inline' | 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' | 'full'
|
||||
|
||||
uuid = await getResizeID(size, uuid, config, payload)
|
||||
const stat = await config.minio.statObject(payload.workspace, uuid)
|
||||
const stat = await config.minio.stat(payload.workspace, uuid)
|
||||
|
||||
const fileSize = stat.size
|
||||
|
||||
@ -310,13 +287,14 @@ export function start (
|
||||
const payload = decodeToken(token)
|
||||
const uuid = req.query.file as string
|
||||
|
||||
await config.minio.removeObject(payload.workspace, uuid)
|
||||
await config.minio.remove(payload.workspace, [uuid])
|
||||
|
||||
const extra = await listMinioObjects(config.minio, payload.workspace, uuid)
|
||||
if (extra.size > 0) {
|
||||
for (const e of extra.entries()) {
|
||||
await config.minio.removeObject(payload.workspace, e[1].name)
|
||||
}
|
||||
const extra = await config.minio.list(payload.workspace, uuid)
|
||||
if (extra.length > 0) {
|
||||
await config.minio.remove(
|
||||
payload.workspace,
|
||||
Array.from(extra.entries()).map((it) => it[1].name)
|
||||
)
|
||||
}
|
||||
|
||||
res.status(200).send()
|
||||
@ -365,7 +343,7 @@ export function start (
|
||||
}
|
||||
const id = uuid()
|
||||
const contentType = response.headers['content-type']
|
||||
const meta: ItemBucketMetadata = {
|
||||
const meta = {
|
||||
'Content-Type': contentType
|
||||
}
|
||||
const data: Buffer[] = []
|
||||
@ -375,12 +353,9 @@ export function start (
|
||||
})
|
||||
.on('end', function () {
|
||||
const buffer = Buffer.concat(data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
config.minio.putObject(payload.workspace, id, buffer, 0, meta, async (err, objInfo) => {
|
||||
if (err !== null) {
|
||||
console.log('minio putObject error', err)
|
||||
res.status(500).send(err)
|
||||
} else {
|
||||
config.minio
|
||||
.put(payload.workspace, id, buffer, 0, meta)
|
||||
.then(async (objInfo) => {
|
||||
console.log('uploaded uuid', id)
|
||||
|
||||
if (attachedTo !== undefined) {
|
||||
@ -405,8 +380,13 @@ export function start (
|
||||
contentType,
|
||||
size: buffer.length
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err !== null) {
|
||||
console.log('minio putObject error', err)
|
||||
res.status(500).send(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
.on('error', function (err) {
|
||||
res.status(500).send(err)
|
||||
@ -454,7 +434,7 @@ export function start (
|
||||
}
|
||||
const id = uuid()
|
||||
const contentType = response.headers['content-type']
|
||||
const meta: ItemBucketMetadata = {
|
||||
const meta = {
|
||||
'Content-Type': contentType
|
||||
}
|
||||
const data: Buffer[] = []
|
||||
@ -465,11 +445,9 @@ export function start (
|
||||
.on('end', function () {
|
||||
const buffer = Buffer.concat(data)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
config.minio.putObject(payload.workspace, id, buffer, 0, meta, async (err, objInfo) => {
|
||||
if (err !== null) {
|
||||
console.log('minio putObject error', err)
|
||||
res.status(500).send(err)
|
||||
} else {
|
||||
config.minio
|
||||
.put(payload.workspace, id, buffer, 0, meta)
|
||||
.then(async () => {
|
||||
console.log('uploaded uuid', id)
|
||||
|
||||
if (attachedTo !== undefined) {
|
||||
@ -493,8 +471,11 @@ export function start (
|
||||
contentType,
|
||||
size: buffer.length
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('minio putObject error', err)
|
||||
res.status(500).send(err)
|
||||
})
|
||||
})
|
||||
.on('error', function (err) {
|
||||
res.status(500).send(err)
|
||||
@ -515,7 +496,12 @@ export function start (
|
||||
server.close()
|
||||
}
|
||||
}
|
||||
async function getResizeID (size: string, uuid: string, config: { minio: Client }, payload: Token): Promise<string> {
|
||||
async function getResizeID (
|
||||
size: string,
|
||||
uuid: string,
|
||||
config: { minio: MinioService },
|
||||
payload: Token
|
||||
): Promise<string> {
|
||||
if (size !== undefined && size !== 'full') {
|
||||
let width = 64
|
||||
switch (size) {
|
||||
@ -536,7 +522,7 @@ async function getResizeID (size: string, uuid: string, config: { minio: Client
|
||||
let hasSmall = false
|
||||
const sizeId = uuid + `%size%${width}`
|
||||
try {
|
||||
const d = await config.minio.statObject(payload.workspace, sizeId)
|
||||
const d = await config.minio.stat(payload.workspace, sizeId)
|
||||
hasSmall = d !== undefined && d.size > 0
|
||||
} catch (err) {}
|
||||
if (hasSmall) {
|
||||
@ -544,7 +530,7 @@ async function getResizeID (size: string, uuid: string, config: { minio: Client
|
||||
uuid = sizeId
|
||||
} else {
|
||||
// Let's get data and resize it
|
||||
const data = Buffer.concat(await readMinioData(config.minio, payload.workspace, uuid))
|
||||
const data = Buffer.concat(await config.minio.read(payload.workspace, uuid))
|
||||
|
||||
const dataBuff = await sharp(data)
|
||||
.resize({
|
||||
@ -552,7 +538,7 @@ async function getResizeID (size: string, uuid: string, config: { minio: Client
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer()
|
||||
await config.minio.putObject(payload.workspace, sizeId, dataBuff, {
|
||||
await config.minio.put(payload.workspace, sizeId, dataBuff, dataBuff.length, {
|
||||
'Content-Type': 'image/jpeg'
|
||||
})
|
||||
uuid = sizeId
|
||||
@ -560,21 +546,3 @@ async function getResizeID (size: string, uuid: string, config: { minio: Client
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
async function listMinioObjects (
|
||||
client: Client,
|
||||
db: string,
|
||||
prefix: string
|
||||
): Promise<Map<string, BucketItem & { metaData: ItemBucketMetadata }>> {
|
||||
const items = new Map<string, BucketItem & { metaData: ItemBucketMetadata }>()
|
||||
const list = await client.listObjects(db, prefix, true)
|
||||
await new Promise((resolve) => {
|
||||
list.on('data', (data) => {
|
||||
items.set(data.name, { metaData: {}, ...data })
|
||||
})
|
||||
list.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
7
server/minio/.eslintrc.js
Normal file
7
server/minio/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/config/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
4
server/minio/.npmignore
Normal file
4
server/minio/.npmignore
Normal file
@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
18
server/minio/config/rig.json
Normal file
18
server/minio/config/rig.json
Normal file
@ -0,0 +1,18 @@
|
||||
// The "rig.json" file directs tools to look for their config files in an external package.
|
||||
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
|
||||
/**
|
||||
* (Required) The name of the rig package to inherit from.
|
||||
* It should be an NPM package name with the "-rig" suffix.
|
||||
*/
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
|
||||
/**
|
||||
* (Optional) Selects a config profile from the rig package. The name must consist of
|
||||
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
|
||||
* If omitted, then the "default" profile will be used."
|
||||
*/
|
||||
// "rigProfile": "your-profile-name"
|
||||
}
|
34
server/minio/package.json
Normal file
34
server/minio/package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@hcengineering/minio",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build",
|
||||
"build:watch": "tsc",
|
||||
"test": "heft test",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src && eslint --fix src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "~0.6.0",
|
||||
"@types/heft-jest": "^1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.26.0",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/minio": "~7.0.11",
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"minio": "^7.0.26"
|
||||
}
|
||||
}
|
24
server/minio/src/__tests__/minio.test.ts
Normal file
24
server/minio/src/__tests__/minio.test.ts
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright © 2022 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 { getWorkspaceId } from '@hcengineering/core'
|
||||
import { getBucketId } from '..'
|
||||
|
||||
describe('minio operations', () => {
|
||||
it('check dot', async () => {
|
||||
const wsid = getWorkspaceId('my-workspace', 'product')
|
||||
|
||||
expect(getBucketId(wsid)).toEqual('my-workspace.product')
|
||||
})
|
||||
})
|
119
server/minio/src/index.ts
Normal file
119
server/minio/src/index.ts
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// Copyright © 2022 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 { BucketItem, BucketItemStat, Client, ItemBucketMetadata, UploadedObjectInfo } from 'minio'
|
||||
|
||||
import { Readable as ReadableStream } from 'stream'
|
||||
|
||||
import { toWorkspaceString, WorkspaceId } from '@hcengineering/core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type MinioWorkspaceItem = BucketItem & { metaData: ItemBucketMetadata }
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getBucketId (workspaceId: WorkspaceId): string {
|
||||
return toWorkspaceString(workspaceId, '.')
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class MinioService {
|
||||
client: Client
|
||||
constructor (opt: { endPoint: string, port: number, accessKey: string, secretKey: string, useSSL: boolean }) {
|
||||
this.client = new Client(opt)
|
||||
}
|
||||
|
||||
async exists (workspaceId: WorkspaceId): Promise<boolean> {
|
||||
return await this.client.bucketExists(getBucketId(workspaceId))
|
||||
}
|
||||
|
||||
async make (workspaceId: WorkspaceId): Promise<void> {
|
||||
await this.client.makeBucket(getBucketId(workspaceId), 'k8s')
|
||||
}
|
||||
|
||||
async remove (workspaceId: WorkspaceId, objectNames: string[]): Promise<void> {
|
||||
await this.client.removeObjects(getBucketId(workspaceId), objectNames)
|
||||
}
|
||||
|
||||
async delete (workspaceId: WorkspaceId): Promise<void> {
|
||||
await this.client.removeBucket(getBucketId(workspaceId))
|
||||
}
|
||||
|
||||
async list (workspaceId: WorkspaceId, prefix?: string): Promise<MinioWorkspaceItem[]> {
|
||||
const items = new Map<string, BucketItem & { metaData: ItemBucketMetadata }>()
|
||||
const list = await this.client.listObjects(getBucketId(workspaceId), prefix, true)
|
||||
await new Promise((resolve) => {
|
||||
list.on('data', (data) => {
|
||||
items.set(data.name, { metaData: {}, ...data })
|
||||
})
|
||||
list.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return Array.from(items.values())
|
||||
}
|
||||
|
||||
async stat (workspaceId: WorkspaceId, objectName: string): Promise<BucketItemStat> {
|
||||
return await this.client.statObject(getBucketId(workspaceId), objectName)
|
||||
}
|
||||
|
||||
async get (workspaceId: WorkspaceId, objectName: string): Promise<ReadableStream> {
|
||||
return await this.client.getObject(getBucketId(workspaceId), objectName)
|
||||
}
|
||||
|
||||
async put (
|
||||
workspaceId: WorkspaceId,
|
||||
objectName: string,
|
||||
stream: ReadableStream | Buffer | string,
|
||||
size?: number,
|
||||
metaData?: ItemBucketMetadata
|
||||
): Promise<UploadedObjectInfo> {
|
||||
return await this.client.putObject(getBucketId(workspaceId), objectName, stream, size, metaData)
|
||||
}
|
||||
|
||||
async read (workspaceId: WorkspaceId, name: string): Promise<Buffer[]> {
|
||||
const data = await this.client.getObject(getBucketId(workspaceId), name)
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
await new Promise((resolve) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
const b = chunk as Buffer
|
||||
chunks.push(b)
|
||||
}
|
||||
})
|
||||
|
||||
data.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return chunks
|
||||
}
|
||||
|
||||
async partial (
|
||||
workspaceId: WorkspaceId,
|
||||
objectName: string,
|
||||
offset: number,
|
||||
length?: number
|
||||
): Promise<ReadableStream> {
|
||||
return await this.client.getPartialObject(getBucketId(workspaceId), objectName, offset, length)
|
||||
}
|
||||
}
|
8
server/minio/tsconfig.json
Normal file
8
server/minio/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
}
|
||||
}
|
@ -30,6 +30,6 @@
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"@hcengineering/server-core": "~0.6.1",
|
||||
"mongodb": "^4.9.0"
|
||||
"mongodb": "^4.11.0"
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import core, {
|
||||
FindOptions,
|
||||
FindResult,
|
||||
generateId,
|
||||
getWorkspaceId,
|
||||
Hierarchy,
|
||||
MeasureMetricsContext,
|
||||
ModelDb,
|
||||
@ -37,7 +38,8 @@ import core, {
|
||||
toFindResult,
|
||||
Tx,
|
||||
TxOperations,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import {
|
||||
createServerStorage,
|
||||
@ -88,7 +90,12 @@ class NullDbAdapter implements DbAdapter {
|
||||
async clean (domain: Domain, docs: Ref<Doc>[]): Promise<void> {}
|
||||
}
|
||||
|
||||
async function createNullAdapter (hierarchy: Hierarchy, url: string, db: string, modelDb: ModelDb): Promise<DbAdapter> {
|
||||
async function createNullAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
db: WorkspaceId,
|
||||
modelDb: ModelDb
|
||||
): Promise<DbAdapter> {
|
||||
return new NullDbAdapter()
|
||||
}
|
||||
|
||||
@ -153,7 +160,7 @@ describe('mongo operations', () => {
|
||||
await model.tx(t)
|
||||
}
|
||||
|
||||
const txStorage = await createMongoTxAdapter(hierarchy, mongodbUri, dbId, model)
|
||||
const txStorage = await createMongoTxAdapter(hierarchy, mongodbUri, getWorkspaceId(dbId, ''), model)
|
||||
|
||||
// Put all transactions to Tx
|
||||
for (const t of txes) {
|
||||
@ -184,7 +191,7 @@ describe('mongo operations', () => {
|
||||
factory: createNullFullTextAdapter,
|
||||
url: ''
|
||||
},
|
||||
workspace: dbId
|
||||
workspace: getWorkspaceId(dbId, '')
|
||||
}
|
||||
const serverStorage = await createServerStorage(conf)
|
||||
const ctx = new MeasureMetricsContext('client', {})
|
||||
|
@ -15,3 +15,4 @@
|
||||
//
|
||||
|
||||
export * from './storage'
|
||||
export * from './utils'
|
||||
|
@ -42,12 +42,13 @@ import core, {
|
||||
TxRemoveDoc,
|
||||
TxResult,
|
||||
TxUpdateDoc,
|
||||
WithLookup
|
||||
WithLookup,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import type { DbAdapter, TxAdapter } from '@hcengineering/server-core'
|
||||
import { Collection, Db, Document, Filter, MongoClient, Sort, UpdateFilter } from 'mongodb'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { getMongoClient } from './utils'
|
||||
import { getMongoClient, getWorkspaceDB } from './utils'
|
||||
|
||||
function translateDoc (doc: Doc): Document {
|
||||
return doc as Document
|
||||
@ -779,11 +780,11 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
||||
export async function createMongoAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
modelDb: ModelDb
|
||||
): Promise<DbAdapter> {
|
||||
const client = await getMongoClient(url)
|
||||
const db = client.db(dbName)
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
return new MongoAdapter(db, hierarchy, modelDb, client)
|
||||
}
|
||||
|
||||
@ -793,10 +794,10 @@ export async function createMongoAdapter (
|
||||
export async function createMongoTxAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
modelDb: ModelDb
|
||||
): Promise<TxAdapter> {
|
||||
const client = await getMongoClient(url)
|
||||
const db = client.db(dbName)
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
return new MongoTxAdapter(db, hierarchy, modelDb, client)
|
||||
}
|
||||
|
@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MongoClient, MongoClientOptions } from 'mongodb'
|
||||
import { toWorkspaceString, WorkspaceId } from '@hcengineering/core'
|
||||
import { Db, MongoClient, MongoClientOptions } from 'mongodb'
|
||||
|
||||
let connections: MongoClient[] = []
|
||||
|
||||
@ -40,3 +41,12 @@ export async function getMongoClient (uri: string, options?: MongoClientOptions)
|
||||
connections.push(client)
|
||||
return client
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Construct MongoDB table from workspace.
|
||||
*/
|
||||
export function getWorkspaceDB (client: MongoClient, workspaceId: WorkspaceId): Db {
|
||||
return client.db(toWorkspaceString(workspaceId))
|
||||
}
|
||||
|
@ -27,8 +27,7 @@
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/minio": "~7.0.11"
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
@ -38,8 +37,8 @@
|
||||
"@hcengineering/mongo": "~0.6.1",
|
||||
"@hcengineering/elastic": "~0.6.0",
|
||||
"elastic-apm-node": "~3.26.0",
|
||||
"minio": "^7.0.26",
|
||||
"@hcengineering/server-token": "~0.6.0",
|
||||
"@hcengineering/middleware": "~0.6.0"
|
||||
"@hcengineering/middleware": "~0.6.0",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
//
|
||||
|
||||
import core, {
|
||||
BlobData,
|
||||
Class,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
@ -23,17 +24,17 @@ import core, {
|
||||
Hierarchy,
|
||||
ModelDb,
|
||||
Ref,
|
||||
Space,
|
||||
StorageIterator,
|
||||
Tx,
|
||||
TxResult,
|
||||
BlobData,
|
||||
Space
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { MinioService, MinioWorkspaceItem } from '@hcengineering/minio'
|
||||
import { DbAdapter } from '@hcengineering/server-core'
|
||||
import { BucketItem, Client, ItemBucketMetadata } from 'minio'
|
||||
|
||||
class MinioBlobAdapter implements DbAdapter {
|
||||
constructor (readonly db: string, readonly client: Client) {}
|
||||
constructor (readonly workspaceId: WorkspaceId, readonly client: MinioService) {}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
@ -51,48 +52,14 @@ class MinioBlobAdapter implements DbAdapter {
|
||||
|
||||
async close (): Promise<void> {}
|
||||
|
||||
async listMinioObjects (): Promise<Map<string, BucketItem & { metaData: ItemBucketMetadata }>> {
|
||||
const items = new Map<string, BucketItem & { metaData: ItemBucketMetadata }>()
|
||||
const list = await this.client.listObjects(this.db, undefined, true)
|
||||
await new Promise((resolve) => {
|
||||
list.on('data', (data) => {
|
||||
items.set(data.name, { metaData: {}, ...data })
|
||||
})
|
||||
list.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
async readMinioData (name: string): Promise<Buffer[]> {
|
||||
const data = await this.client.getObject(this.db, name)
|
||||
const chunks: Buffer[] = []
|
||||
|
||||
await new Promise((resolve) => {
|
||||
data.on('readable', () => {
|
||||
let chunk
|
||||
while ((chunk = data.read()) !== null) {
|
||||
const b = chunk as Buffer
|
||||
chunks.push(b)
|
||||
}
|
||||
})
|
||||
|
||||
data.on('end', () => {
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
return chunks
|
||||
}
|
||||
|
||||
find (domain: Domain): StorageIterator {
|
||||
let listRecieved = false
|
||||
let items: (BucketItem & { metaData: ItemBucketMetadata })[]
|
||||
let items: MinioWorkspaceItem[] = []
|
||||
let pos = 0
|
||||
return {
|
||||
next: async () => {
|
||||
if (!listRecieved) {
|
||||
items = Array.from((await this.listMinioObjects()).values())
|
||||
items = await this.client.list(this.workspaceId)
|
||||
listRecieved = true
|
||||
}
|
||||
if (pos < items?.length) {
|
||||
@ -113,8 +80,8 @@ class MinioBlobAdapter implements DbAdapter {
|
||||
async load (domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
|
||||
const result: Doc[] = []
|
||||
for (const item of docs) {
|
||||
const stat = await this.client.statObject(this.db, item)
|
||||
const chunks: Buffer[] = await this.readMinioData(item)
|
||||
const stat = await this.client.stat(this.workspaceId, item)
|
||||
const chunks: Buffer[] = await this.client.read(this.workspaceId, item)
|
||||
const final = Buffer.concat(chunks)
|
||||
const dta: BlobData = {
|
||||
_id: item as Ref<BlobData>,
|
||||
@ -142,12 +109,12 @@ class MinioBlobAdapter implements DbAdapter {
|
||||
const blob = d as unknown as BlobData
|
||||
// Remove existing document
|
||||
try {
|
||||
await this.client.removeObject(this.db, blob._id)
|
||||
await this.client.remove(this.workspaceId, [blob._id])
|
||||
} catch (ee) {
|
||||
// ignore error
|
||||
}
|
||||
const buffer = Buffer.from(blob.base64Data, 'base64')
|
||||
await this.client.putObject(this.db, blob._id, buffer, buffer.length, {
|
||||
await this.client.put(this.workspaceId, blob._id, buffer, buffer.length, {
|
||||
'Content-Type': blob.type,
|
||||
lastModified: new Date(blob.modifiedOn)
|
||||
})
|
||||
@ -155,7 +122,7 @@ class MinioBlobAdapter implements DbAdapter {
|
||||
}
|
||||
|
||||
async clean (domain: Domain, docs: Ref<Doc>[]): Promise<void> {
|
||||
await this.client.removeObjects(this.db, docs)
|
||||
await this.client.remove(this.workspaceId, docs)
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,12 +132,12 @@ class MinioBlobAdapter implements DbAdapter {
|
||||
export async function createMinioDataAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
db: string,
|
||||
workspaceId: WorkspaceId,
|
||||
modelDb: ModelDb,
|
||||
storage?: Client
|
||||
storage?: MinioService
|
||||
): Promise<DbAdapter> {
|
||||
if (storage === undefined) {
|
||||
throw new Error('minio storage adapter require minio')
|
||||
}
|
||||
return new MinioBlobAdapter(db, storage)
|
||||
return new MinioBlobAdapter(workspaceId, storage)
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ import {
|
||||
StorageIterator,
|
||||
toFindResult,
|
||||
Tx,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { DbAdapter } from '@hcengineering/server-core'
|
||||
|
||||
@ -68,7 +69,7 @@ class NullDbAdapter implements DbAdapter {
|
||||
export async function createNullAdapter (
|
||||
hierarchy: Hierarchy,
|
||||
url: string,
|
||||
db: string,
|
||||
workspaceId: WorkspaceId,
|
||||
modelDb: ModelDb
|
||||
): Promise<DbAdapter> {
|
||||
return new NullDbAdapter()
|
||||
|
@ -15,7 +15,6 @@
|
||||
"@hcengineering/platform-rig": "~0.6.0",
|
||||
"@types/heft-jest": "^1.0.3",
|
||||
"@types/node": "~16.11.12",
|
||||
"@types/minio": "~7.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
@ -30,7 +29,6 @@
|
||||
"dependencies": {
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"minio": "^7.0.26",
|
||||
"jwt-simple": "~0.5.6"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { getWorkspaceId, WorkspaceId } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { decode, encode } from 'jwt-simple'
|
||||
import serverPlugin from './plugin'
|
||||
import { encode, decode } from 'jwt-simple'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Token {
|
||||
email: string
|
||||
workspace: string
|
||||
workspace: WorkspaceId
|
||||
extra?: Record<string, string>
|
||||
}
|
||||
|
||||
@ -18,8 +19,8 @@ const getSecret = (): string => {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function generateToken (email: string, workspace: string, extra?: Record<string, string>): string {
|
||||
return encode({ ...(extra ?? {}), email, workspace }, getSecret())
|
||||
export function generateToken (email: string, workspace: WorkspaceId, extra?: Record<string, string>): string {
|
||||
return encode({ ...(extra ?? {}), email, workspace: workspace.name, productId: workspace.productId }, getSecret())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,6 +28,6 @@ export function generateToken (email: string, workspace: string, extra?: Record<
|
||||
*/
|
||||
export function decodeToken (token: string): Token {
|
||||
const value = decode(token, getSecret(), false)
|
||||
const { email, workspace, ...extra } = value
|
||||
return { email, workspace, extra }
|
||||
const { email, workspace, productId, ...extra } = value
|
||||
return { email, workspace: getWorkspaceId(workspace, productId), extra }
|
||||
}
|
||||
|
@ -24,19 +24,19 @@
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5",
|
||||
"@types/minio": "~7.0.11",
|
||||
"@types/ws": "^8.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"mongodb": "^4.9.0",
|
||||
"mongodb": "^4.11.0",
|
||||
"@hcengineering/platform": "^0.6.7",
|
||||
"minio": "^7.0.26",
|
||||
"@hcengineering/core": "^0.6.17",
|
||||
"@hcengineering/contact": "~0.6.8",
|
||||
"@hcengineering/client-resources": "~0.6.4",
|
||||
"@hcengineering/client": "^0.6.3",
|
||||
"ws": "^8.10.0",
|
||||
"@hcengineering/model": "~0.6.0",
|
||||
"@hcengineering/server-token": "~0.6.0"
|
||||
"@hcengineering/server-token": "~0.6.0",
|
||||
"@hcengineering/mongo": "~0.6.1",
|
||||
"@hcengineering/minio": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import client from '@hcengineering/client'
|
||||
import clientResources from '@hcengineering/client-resources'
|
||||
import { Client } from '@hcengineering/core'
|
||||
import { Client, WorkspaceId } from '@hcengineering/core'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
|
||||
@ -25,7 +25,7 @@ import { generateToken } from '@hcengineering/server-token'
|
||||
*/
|
||||
export async function connect (
|
||||
transactorUrl: string,
|
||||
workspace: string,
|
||||
workspace: WorkspaceId,
|
||||
email?: string,
|
||||
extra?: Record<string, string>
|
||||
): Promise<Client> {
|
||||
|
@ -14,9 +14,18 @@
|
||||
//
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
import core, { Client as CoreClient, Domain, DOMAIN_MODEL, DOMAIN_TX, IndexKind, Tx } from '@hcengineering/core'
|
||||
import core, {
|
||||
Client as CoreClient,
|
||||
Domain,
|
||||
DOMAIN_MODEL,
|
||||
DOMAIN_TX,
|
||||
IndexKind,
|
||||
Tx,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { MinioService } from '@hcengineering/minio'
|
||||
import { MigrateOperation } from '@hcengineering/model'
|
||||
import { Client } from 'minio'
|
||||
import { getWorkspaceDB } from '@hcengineering/mongo'
|
||||
import { Db, Document, MongoClient } from 'mongodb'
|
||||
import { connect } from './connect'
|
||||
import toolPlugin from './plugin'
|
||||
@ -29,7 +38,7 @@ export { toolPlugin as default }
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function prepareTools (rawTxes: Tx[]): { mongodbUri: string, minio: Client, txes: Tx[] } {
|
||||
export function prepareTools (rawTxes: Tx[]): { mongodbUri: string, minio: MinioService, txes: Tx[] } {
|
||||
let minioEndpoint = process.env.MINIO_ENDPOINT
|
||||
if (minioEndpoint === undefined) {
|
||||
console.error('please provide minio endpoint')
|
||||
@ -61,7 +70,7 @@ export function prepareTools (rawTxes: Tx[]): { mongodbUri: string, minio: Clien
|
||||
minioPort = parseInt(sp[1])
|
||||
}
|
||||
|
||||
const minio = new Client({
|
||||
const minio = new MinioService({
|
||||
endPoint: minioEndpoint,
|
||||
port: minioPort,
|
||||
useSSL: false,
|
||||
@ -77,7 +86,7 @@ export function prepareTools (rawTxes: Tx[]): { mongodbUri: string, minio: Clien
|
||||
*/
|
||||
export async function initModel (
|
||||
transactorUrl: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
rawTxes: Tx[],
|
||||
migrateOperations: MigrateOperation[]
|
||||
): Promise<void> {
|
||||
@ -89,7 +98,7 @@ export async function initModel (
|
||||
const client = new MongoClient(mongodbUri)
|
||||
try {
|
||||
await client.connect()
|
||||
const db = client.db(dbName)
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
|
||||
console.log('dropping database...')
|
||||
await db.dropDatabase()
|
||||
@ -100,7 +109,7 @@ export async function initModel (
|
||||
console.log(`${result.insertedCount} model transactions inserted.`)
|
||||
|
||||
console.log('creating data...')
|
||||
const connection = await connect(transactorUrl, dbName, undefined, { model: 'upgrade' })
|
||||
const connection = await connect(transactorUrl, workspaceId, undefined, { model: 'upgrade' })
|
||||
try {
|
||||
for (const op of migrateOperations) {
|
||||
await op.upgrade(connection)
|
||||
@ -115,8 +124,8 @@ export async function initModel (
|
||||
await createUpdateIndexes(connection, db)
|
||||
|
||||
console.log('create minio bucket')
|
||||
if (!(await minio.bucketExists(dbName))) {
|
||||
await minio.makeBucket(dbName, 'k8s')
|
||||
if (!(await minio.exists(workspaceId))) {
|
||||
await minio.make(workspaceId)
|
||||
}
|
||||
} finally {
|
||||
await client.close()
|
||||
@ -128,7 +137,7 @@ export async function initModel (
|
||||
*/
|
||||
export async function upgradeModel (
|
||||
transactorUrl: string,
|
||||
dbName: string,
|
||||
workspaceId: WorkspaceId,
|
||||
rawTxes: Tx[],
|
||||
migrateOperations: MigrateOperation[]
|
||||
): Promise<void> {
|
||||
@ -141,7 +150,7 @@ export async function upgradeModel (
|
||||
const client = new MongoClient(mongodbUri)
|
||||
try {
|
||||
await client.connect()
|
||||
const db = client.db(dbName)
|
||||
const db = getWorkspaceDB(client, workspaceId)
|
||||
|
||||
console.log('removing model...')
|
||||
// we're preserving accounts (created by core.account.System).
|
||||
@ -164,7 +173,7 @@ export async function upgradeModel (
|
||||
|
||||
console.log('Apply upgrade operations')
|
||||
|
||||
const connection = await connect(transactorUrl, dbName, undefined, { model: 'upgrade' })
|
||||
const connection = await connect(transactorUrl, workspaceId, undefined, { model: 'upgrade' })
|
||||
|
||||
// Create update indexes
|
||||
await createUpdateIndexes(connection, db)
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
Domain,
|
||||
FindOptions,
|
||||
FindResult,
|
||||
getWorkspaceId,
|
||||
Hierarchy,
|
||||
MeasureMetricsContext,
|
||||
ModelDb,
|
||||
@ -78,11 +79,12 @@ describe('server', () => {
|
||||
clean: async (domain: Domain, docs: Ref<Doc>[]) => {}
|
||||
}),
|
||||
(token, pipeline, broadcast) => new ClientSession(broadcast, token, pipeline),
|
||||
3335
|
||||
3335,
|
||||
''
|
||||
)
|
||||
|
||||
function connect (): WebSocket {
|
||||
const token: string = generateToken('', 'latest')
|
||||
const token: string = generateToken('', getWorkspaceId('latest', ''))
|
||||
return new WebSocket(`ws://localhost:3335/${token}`)
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { MeasureContext, Ref, Space, TxFactory } from '@hcengineering/core'
|
||||
import core, { MeasureContext, Ref, Space, toWorkspaceString, TxFactory, WorkspaceId } from '@hcengineering/core'
|
||||
import { readRequest, Response, serialize, UNAUTHORIZED, unknownError } from '@hcengineering/platform'
|
||||
import type { Pipeline } from '@hcengineering/server-core'
|
||||
import { decodeToken, Token } from '@hcengineering/server-token'
|
||||
@ -46,9 +46,11 @@ class SessionManager {
|
||||
ctx: MeasureContext,
|
||||
ws: WebSocket,
|
||||
token: Token,
|
||||
pipelineFactory: (ws: string) => Promise<Pipeline>
|
||||
pipelineFactory: (ws: WorkspaceId) => Promise<Pipeline>,
|
||||
productId: string
|
||||
): Promise<Session> {
|
||||
const workspace = this.workspaces.get(token.workspace)
|
||||
const wsString = toWorkspaceString(token.workspace, '@')
|
||||
const workspace = this.workspaces.get(wsString)
|
||||
if (workspace === undefined) {
|
||||
return await this.createWorkspace(ctx, pipelineFactory, token, ws)
|
||||
} else {
|
||||
@ -115,7 +117,7 @@ class SessionManager {
|
||||
|
||||
private async createWorkspace (
|
||||
ctx: MeasureContext,
|
||||
pipelineFactory: (ws: string) => Promise<Pipeline>,
|
||||
pipelineFactory: (ws: WorkspaceId) => Promise<Pipeline>,
|
||||
token: Token,
|
||||
ws: WebSocket
|
||||
): Promise<Session> {
|
||||
@ -126,14 +128,21 @@ class SessionManager {
|
||||
sessions: [[session, ws]],
|
||||
upgrade: token.extra?.model === 'upgrade'
|
||||
}
|
||||
this.workspaces.set(token.workspace, workspace)
|
||||
this.workspaces.set(toWorkspaceString(token.workspace), workspace)
|
||||
await this.setStatus(ctx, session, true)
|
||||
return session
|
||||
}
|
||||
|
||||
async close (ctx: MeasureContext, ws: WebSocket, workspaceId: string, code: number, reason: string): Promise<void> {
|
||||
async close (
|
||||
ctx: MeasureContext,
|
||||
ws: WebSocket,
|
||||
workspaceId: WorkspaceId,
|
||||
code: number,
|
||||
reason: string
|
||||
): Promise<void> {
|
||||
if (LOGGING_ENABLED) console.log(`closing websocket, code: ${code}, reason: ${reason}`)
|
||||
const workspace = this.workspaces.get(workspaceId)
|
||||
const wsid = toWorkspaceString(workspaceId)
|
||||
const workspace = this.workspaces.get(wsid)
|
||||
if (workspace === undefined) {
|
||||
console.error(new Error('internal: cannot find sessions'))
|
||||
return
|
||||
@ -149,15 +158,15 @@ class SessionManager {
|
||||
await this.setStatus(ctx, session[0], false)
|
||||
}
|
||||
if (workspace.sessions.length === 0) {
|
||||
if (LOGGING_ENABLED) console.log('no sessions for workspace', workspaceId)
|
||||
this.workspaces.delete(workspaceId)
|
||||
if (LOGGING_ENABLED) console.log('no sessions for workspace', wsid)
|
||||
this.workspaces.delete(wsid)
|
||||
await workspace.pipeline.close().catch((err) => console.error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
broadcast (from: Session | null, workspaceId: string, resp: Response<any>, target?: string): void {
|
||||
const workspace = this.workspaces.get(workspaceId)
|
||||
broadcast (from: Session | null, workspaceId: WorkspaceId, resp: Response<any>, target?: string): void {
|
||||
const workspace = this.workspaces.get(toWorkspaceString(workspaceId))
|
||||
if (workspace === undefined) {
|
||||
console.error(new Error('internal: cannot find sessions'))
|
||||
return
|
||||
@ -210,9 +219,10 @@ async function handleRequest<S extends Session> (
|
||||
*/
|
||||
export function start (
|
||||
ctx: MeasureContext,
|
||||
pipelineFactory: (workspace: string) => Promise<Pipeline>,
|
||||
pipelineFactory: (workspace: WorkspaceId) => Promise<Pipeline>,
|
||||
sessionFactory: (token: Token, pipeline: Pipeline, broadcast: BroadcastCall) => Session,
|
||||
port: number,
|
||||
productId: string,
|
||||
host?: string
|
||||
): () => void {
|
||||
console.log(`starting server on port ${port} ...`)
|
||||
@ -244,12 +254,13 @@ export function start (
|
||||
ws.on('message', (msg: string) => {
|
||||
buffer.push(msg)
|
||||
})
|
||||
const session = await sessions.addSession(ctx, ws, token, pipelineFactory)
|
||||
const session = await sessions.addSession(ctx, ws, token, pipelineFactory, productId)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
ws.on('message', async (msg: string) => await handleRequest(ctx, session, ws, msg))
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
ws.on('close', async (code: number, reason: string) => await sessions.close(ctx, ws, token.workspace, code, reason))
|
||||
|
||||
ws.on('close', (code: number, reason: string) => {
|
||||
void sessions.close(ctx, ws, token.workspace, code, reason)
|
||||
})
|
||||
for (const msg of buffer) {
|
||||
await handleRequest(ctx, session, ws, msg)
|
||||
}
|
||||
@ -261,6 +272,11 @@ export function start (
|
||||
try {
|
||||
const payload = decodeToken(token ?? '')
|
||||
console.log('client connected with payload', payload)
|
||||
|
||||
if (productId !== '' && payload.workspace.productId !== productId) {
|
||||
throw new Error('Invalid workspace product')
|
||||
}
|
||||
|
||||
wss.handleUpgrade(request, socket, head, (ws) => wss.emit('connection', ws, request, payload))
|
||||
} catch (err) {
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
MeasureContext,
|
||||
Ref,
|
||||
Tx,
|
||||
TxResult
|
||||
TxResult,
|
||||
WorkspaceId
|
||||
} from '@hcengineering/core'
|
||||
import { Response } from '@hcengineering/platform'
|
||||
import { Pipeline } from '@hcengineering/server-core'
|
||||
@ -31,4 +32,9 @@ export interface Session {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type BroadcastCall = (from: Session | null, workspaceId: string, resp: Response<any>, target?: string) => void
|
||||
export type BroadcastCall = (
|
||||
from: Session | null,
|
||||
workspaceId: WorkspaceId,
|
||||
resp: Response<any>,
|
||||
target?: string
|
||||
) => void
|
||||
|
Loading…
Reference in New Issue
Block a user