diff --git a/.vscode/launch.json b/.vscode/launch.json index 27a832b073..19b7ded133 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 4d10ac0d3e..5f5e5c231a 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -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 diff --git a/dev/account/package.json b/dev/account/package.json index 7bd4bb86e4..f20f8182a3 100644 --- a/dev/account/package.json +++ b/dev/account/package.json @@ -28,6 +28,7 @@ }, "dependencies": { "@hcengineering/platform": "^0.6.7", + "@hcengineering/core": "^0.6.17", "@hcengineering/server-token": "~0.6.0" } } diff --git a/dev/account/src/account.ts b/dev/account/src/account.ts index fab9ca158f..6ff9c08397 100644 --- a/dev/account/src/account.ts +++ b/dev/account/src/account.ts @@ -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 } } } diff --git a/dev/client-resources/src/connection.ts b/dev/client-resources/src/connection.ts index d3d8fe03a9..0894cbc4ea 100644 --- a/dev/client-resources/src/connection.ts +++ b/dev/client-resources/src/connection.ts @@ -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 ( options: AttachmentOptions, client: TxOperations, - minio: Client, - dbName: string, + minio: MinioService, + workspaceId: WorkspaceId, space: Ref, objectId: Ref, _class: Ref>, @@ -35,7 +35,7 @@ export async function addAttachments ( 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( diff --git a/dev/generator/src/connect.ts b/dev/generator/src/connect.ts index 26c483d17b..a64de986e5 100644 --- a/dev/generator/src/connect.ts +++ b/dev/generator/src/connect.ts @@ -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 { +export async function connect (transactorUrl: string, workspace: WorkspaceId): Promise { console.log('connecting to transactor...') const token = generateToken('anticrm@hc.engineering', workspace) diff --git a/dev/generator/src/index.ts b/dev/generator/src/index.ts index 3a46bc74ec..b741b5e52a 100644 --- a/dev/generator/src/index.ts +++ b/dev/generator/src/index.ts @@ -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 ') + .command('gen ') .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 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: diff --git a/dev/generator/src/issues.ts b/dev/generator/src/issues.ts index c7cb9c83ff..a9457fe986 100644 --- a/dev/generator/src/issues.ts +++ b/dev/generator/src/issues.ts @@ -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 { - const connection = await connect(transactorUrl, dbName) +export async function generateIssues ( + transactorUrl: string, + workspaceId: WorkspaceId, + options: IssueOptions +): Promise { + 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) diff --git a/dev/generator/src/recruit.ts b/dev/generator/src/recruit.ts index 46fa69d230..e933ba42f3 100644 --- a/dev/generator/src/recruit.ts +++ b/dev/generator/src/recruit.ts @@ -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 { - 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[], emoloyeeIds: Ref[] ): Promise { @@ -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[], client: TxOperations, options: RecruitOptions, - minio: Client, - dbName: string, + minio: MinioService, + workspaceId: WorkspaceId, rankGen: Generator ): Promise { const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref @@ -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[], 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 = { @@ -283,7 +284,7 @@ async function genCandidate ( options.attachments, client, minio, - dbName, + workspaceId, recruit.space.CandidatesPublic, candidateId, contact.class.Person, diff --git a/dev/server/src/server.ts b/dev/server/src/server.ts index a0f8f3f4f6..6d5517b3a6 100644 --- a/dev/server/src/server.ts +++ b/dev/server/src/server.ts @@ -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 { factory: createNullFullTextAdapter, url: '' }, - workspace: '' + workspace: getWorkspaceId('') } return createPipeline(conf, []) }, (token, pipeline, broadcast) => new ClientSession(broadcast, token, pipeline), port, + '', host ) } diff --git a/dev/storage/src/storage.ts b/dev/storage/src/storage.ts index c176c2412e..947e09aba5 100644 --- a/dev/storage/src/storage.ts +++ b/dev/storage/src/storage.ts @@ -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 { return new InMemoryTxAdapter(hierarchy) } diff --git a/dev/tool/package.json b/dev/tool/package.json index 9ec5cddcd7..67d1809c78 100644 --- a/dev/tool/package.json +++ b/dev/tool/package.json @@ -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" } } diff --git a/dev/tool/src/__start.ts b/dev/tool/src/__start.ts index eac4bc1896..fc4bb03045 100644 --- a/dev/tool/src/__start.ts +++ b/dev/tool/src/__start.ts @@ -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 migrateOperations: MigrateOperation[] - productId: string } { - return { ...prepareToolsRaw(builder.getTxes()), version, migrateOperations, productId: '' } + return { ...prepareToolsRaw(builder.getTxes()), version, migrateOperations } } -devTool(prepareTools) +devTool(prepareTools, '') diff --git a/dev/tool/src/csv/duplicates.ts b/dev/tool/src/csv/duplicates.ts index c8bae0542e..cd6d539d09 100644 --- a/dev/tool/src/csv/duplicates.ts +++ b/dev/tool/src/csv/duplicates.ts @@ -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 { - const connection = await connect(transactorUrl, dbName) +export async function removeDuplicates (transactorUrl: string, workspaceId: WorkspaceId): Promise { + const connection = await connect(transactorUrl, workspaceId) try { console.log('loading cvs document...') diff --git a/dev/tool/src/csv/lead-importer.ts b/dev/tool/src/csv/lead-importer.ts index 239a965289..344f756cd6 100644 --- a/dev/tool/src/csv/lead-importer.ts +++ b/dev/tool/src/csv/lead-importer.ts @@ -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 ( } } -export async function importLead (transactorUrl: string, dbName: string, csvFile: string): Promise { - const connection = await connect(transactorUrl, dbName) +export async function importLead (transactorUrl: string, workspaceId: WorkspaceId, csvFile: string): Promise { + const connection = await connect(transactorUrl, workspaceId) try { console.log('loading cvs document...') diff --git a/dev/tool/src/csv/lead-importer2.ts b/dev/tool/src/csv/lead-importer2.ts index 53b7cb2a59..1bf71da34f 100644 --- a/dev/tool/src/csv/lead-importer2.ts +++ b/dev/tool/src/csv/lead-importer2.ts @@ -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 { }) } -export async function importLead2 (transactorUrl: string, dbName: string, csvFile: string): Promise { - const connection = await connect(transactorUrl, dbName) +export async function importLead2 (transactorUrl: string, workspaceId: WorkspaceId, csvFile: string): Promise { + const connection = await connect(transactorUrl, workspaceId) try { console.log('loading cvs document...') diff --git a/dev/tool/src/csv/org-importer.ts b/dev/tool/src/csv/org-importer.ts index b7ac641898..c89427ee36 100644 --- a/dev/tool/src/csv/org-importer.ts +++ b/dev/tool/src/csv/org-importer.ts @@ -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 { }) } -export async function importOrgs (transactorUrl: string, dbName: string, csvFile: string): Promise { - const connection = (await connect(transactorUrl, dbName, undefined, { +export async function importOrgs (transactorUrl: string, workspaceId: WorkspaceId, csvFile: string): Promise { + const connection = (await connect(transactorUrl, workspaceId, undefined, { mode: 'backup' })) as unknown as Client & BackupClient try { diff --git a/dev/tool/src/csv/talant-importer.ts b/dev/tool/src/csv/talant-importer.ts index 5bd1189f75..f886c72d48 100644 --- a/dev/tool/src/csv/talant-importer.ts +++ b/dev/tool/src/csv/talant-importer.ts @@ -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 { - 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 { diff --git a/dev/tool/src/elastic.ts b/dev/tool/src/elastic.ts index 9d2b1d6c40..ff7bbb3575 100644 --- a/dev/tool/src/elastic.ts +++ b/dev/tool/src/elastic.ts @@ -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 { - 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 { +async function dropElastic (elasticUrl: string, workspaceId: WorkspaceId): Promise { 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> { 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(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 { @@ -179,29 +155,15 @@ export class ElasticTool { await this.elastic.index(indexedDoc) } - - private async readMinioObject (name: string): Promise { - const data = await this.minio.getObject(this.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() - }) - }) - return Buffer.concat(chunks) - } } -async function restoreElastic (mongoUrl: string, dbName: string, minio: Client, elasticUrl: string): Promise { - const tool = new ElasticTool(mongoUrl, dbName, minio, elasticUrl) +async function restoreElastic ( + mongoUrl: string, + workspaceId: WorkspaceId, + minio: MinioService, + elasticUrl: string +): Promise { + const tool = new ElasticTool(mongoUrl, workspaceId, minio, elasticUrl) const done = await tool.connect() try { const data = await tool.db.collection(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 { +async function createStorage (mongoUrl: string, elasticUrl: string, workspace: WorkspaceId): Promise { 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 { - 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 { - const adapter = await createMongoTxAdapter(hierarchy, url, dbName, modelDb) + const adapter = await createMongoTxAdapter(hierarchy, url, workspaceId, modelDb) return new MongoReadOnlyTxAdapter(adapter) } diff --git a/dev/tool/src/importer.ts b/dev/tool/src/importer.ts index db8d649224..dbccf55811 100644 --- a/dev/tool/src/importer.ts +++ b/dev/tool/src/importer.ts @@ -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 { - 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 }) } diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 1d598a4d19..45f20e26aa 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -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 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 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 ') .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 ') .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 ') .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 ') .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 ') .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 ') .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 , [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 ') .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 ') .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 ') .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 ') .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 ') .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 ') .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 ') .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 ') .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 ') .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 ') + .command('generate-token ') .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 ') @@ -450,14 +460,21 @@ export function devTool ( program .command('update-recruit ') .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) diff --git a/dev/tool/src/minio.ts b/dev/tool/src/minio.ts deleted file mode 100644 index 7ad843fb47..0000000000 --- a/dev/tool/src/minio.ts +++ /dev/null @@ -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 { - 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 -} diff --git a/dev/tool/src/recruit.ts b/dev/tool/src/recruit.ts index 2b46c6b9b2..98ac0dfb88 100644 --- a/dev/tool/src/recruit.ts +++ b/dev/tool/src/recruit.ts @@ -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 { 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 { - 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) @@ -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, document: ReconiDocument, - minio: Client, - dbName: string, + minio: MinioService, + workspaceId: WorkspaceId, client: TxOperations, tool: ElasticTool ): Promise { @@ -221,7 +220,7 @@ async function updateAvatar ( const attachId = `${c._id}.${document.avatarName}` as Ref // 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 }) diff --git a/dev/tool/src/telegram.ts b/dev/tool/src/telegram.ts index 6320a8c057..d53e4909ae 100644 --- a/dev/tool/src/telegram.ts +++ b/dev/tool/src/telegram.ts @@ -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 { 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() diff --git a/dev/tool/src/workspace.ts b/dev/tool/src/workspace.ts index 26409d41e7..ea408a2019 100644 --- a/dev/tool/src/workspace.ts +++ b/dev/tool/src/workspace.ts @@ -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 { +export async function dumpWorkspace ( + mongoUrl: string, + workspaceId: WorkspaceId, + fileName: string, + minio: MinioService +): Promise { 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 { - 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 { - 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 { +export async function diffWorkspace (mongoUrl: string, workspace: WorkspaceId, rawTxes: Tx[]): Promise { const client = new MongoClient(mongoUrl) try { await client.connect() - const db = client.db(dbName) + const db = getWorkspaceDB(client, workspace) console.log('diffing transactions...') diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index e7bbb261c7..36b7ce3240 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -108,7 +108,8 @@ export default plugin(coreId, { }, status: { ObjectNotFound: '' as StatusCode<{ _id: Ref }>, - ItemNotFound: '' as StatusCode<{ _id: Ref, _localId: string }> + ItemNotFound: '' as StatusCode<{ _id: Ref, _localId: string }>, + InvalidProduct: '' as StatusCode<{ productId: string }> }, version: { Model: '' as Ref diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index a46a6256f4..fff002af33 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -76,3 +76,30 @@ export function toFindResult (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) +} diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 1dca496560..e0f8385de9 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -64,7 +64,7 @@ export interface AnySvelteComponentWithProps { export interface Action { label: IntlString - icon: Asset | AnySvelteComponent + icon?: Asset | AnySvelteComponent action: (props: any, ev: Event) => Promise inline?: boolean link?: string diff --git a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte index 1b1a18e1ee..e7b4f177d8 100644 --- a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte +++ b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte @@ -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 } ] diff --git a/plugins/view-resources/src/components/ClassAttributeBar.svelte b/plugins/view-resources/src/components/ClassAttributeBar.svelte index 5c1a7ed955..7332cfaa25 100644 --- a/plugins/view-resources/src/components/ClassAttributeBar.svelte +++ b/plugins/view-resources/src/components/ClassAttributeBar.svelte @@ -13,7 +13,8 @@ // limitations under the License. --> +
({ - 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[] = [] diff --git a/pods/account/package.json b/pods/account/package.json index 55895cb469..cb50c9578c 100644 --- a/pods/account/package.json +++ b/pods/account/package.json @@ -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", diff --git a/pods/account/src/__start.ts b/pods/account/src/__start.ts index fc6932a620..7610e9b701 100644 --- a/pods/account/src/__start.ts +++ b/pods/account/src/__start.ts @@ -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)) diff --git a/pods/account/src/index.ts b/pods/account/src/index.ts index a6a13f1c4e..235f130d17 100644 --- a/pods/account/src/index.ts +++ b/pods/account/src/index.ts @@ -28,7 +28,7 @@ import { Db, MongoClient } from 'mongodb' /** * @public */ -export function serveAccount (methods: Record): void { +export function serveAccount (methods: Record, 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): void { const token = extractToken(ctx.request.headers) const request = ctx.request.body as any - const method = (methods as { [key: string]: (db: Db, request: Request, token?: string) => Response })[ - request.method - ] + const method = ( + methods as { [key: string]: (db: Db, productId: string, request: Request, token?: string) => Response } + )[request.method] if (method === undefined) { const response: Response = { id: request.id, @@ -87,7 +87,7 @@ export function serveAccount (methods: Record): 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 }) diff --git a/pods/backup/package.json b/pods/backup/package.json index 635ef09a8a..0955308bb2 100644 --- a/pods/backup/package.json +++ b/pods/backup/package.json @@ -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" } } diff --git a/pods/backup/src/config.ts b/pods/backup/src/config.ts index 3c9c859437..e9caa797cc 100644 --- a/pods/backup/src/config.ts +++ b/pods/backup/src/config.ts @@ -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 = [ @@ -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]) diff --git a/pods/backup/src/platform.ts b/pods/backup/src/platform.ts index 0dc0bb4bbc..a4b33622d6 100644 --- a/pods/backup/src/platform.ts +++ b/pods/backup/src/platform.ts @@ -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 { const { body }: { body: { error?: string, result?: any[] } } = await got.post(config.AccountsURL, { @@ -37,7 +38,7 @@ async function getWorkspaces (): Promise { } export class PlatformWorker { - minio?: MinioClient + minio!: MinioService async close (): Promise {} @@ -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) } diff --git a/pods/collaborator/package.json b/pods/collaborator/package.json index d69ea80e96..c55f18d723 100644 --- a/pods/collaborator/package.json +++ b/pods/collaborator/package.json @@ -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" } } diff --git a/pods/collaborator/src/__start.ts b/pods/collaborator/src/__start.ts index 4ec560a662..c3f75b0bea 100644 --- a/pods/collaborator/src/__start.ts +++ b/pods/collaborator/src/__start.ts @@ -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, diff --git a/pods/collaborator/src/server.ts b/pods/collaborator/src/server.ts index b6d5b30ff0..49f26ab09e 100644 --- a/pods/collaborator/src/server.ts +++ b/pods/collaborator/src/server.ts @@ -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({ diff --git a/pods/collaborator/src/yserver.ts b/pods/collaborator/src/yserver.ts index 219ba2aab1..ef047b6da8 100644 --- a/pods/collaborator/src/yserver.ts +++ b/pods/collaborator/src/yserver.ts @@ -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 writeState: (documentId: string, ydoc: WSSharedDoc, token: Token) => Promise provider: any } -async function loadData (data: Readable): Promise { - 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 => { 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() awareness: Awareness - minio?: MinioClient + minio?: MinioService /** * @param {{ added: Array, updated: Array, removed: Array }} 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 { diff --git a/pods/front/package.json b/pods/front/package.json index beee50ccb0..a2b805b6b9 100644 --- a/pods/front/package.json +++ b/pods/front/package.json @@ -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" diff --git a/pods/server/package.json b/pods/server/package.json index 765e05ddd5..b5ab1ec608 100644 --- a/pods/server/package.json +++ b/pods/server/package.json @@ -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" } } diff --git a/pods/server/src/__start.ts b/pods/server/src/__start.ts index d2d9d650e1..97e0c51c6c 100644 --- a/pods/server/src/__start.ts +++ b/pods/server/src/__start.ts @@ -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') diff --git a/pods/server/src/server.ts b/pods/server/src/server.ts index ca65d3b494..ab4c265020 100644 --- a/pods/server/src/server.ts +++ b/pods/server/src/server.ts @@ -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 ) } diff --git a/rush.json b/rush.json index 21f3ee8780..26ae148a87 100644 --- a/rush.json +++ b/rush.json @@ -1408,6 +1408,11 @@ "packageName": "@hcengineering/apm", "projectFolder": "tools/apm", "shouldPublish": false + }, + { + "packageName": "@hcengineering/minio", + "projectFolder": "server/minio", + "shouldPublish": true } ] } diff --git a/server-plugins/attachment-resources/src/index.ts b/server-plugins/attachment-resources/src/index.ts index 5c8a983836..77445f1c62 100644 --- a/server-plugins/attachment-resources/src/index.ts +++ b/server-plugins/attachment-resources/src/index.ts @@ -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 [] diff --git a/server-plugins/chunter-resources/src/index.ts b/server-plugins/chunter-resources/src/index.ts index 4f8e3d54e5..ff12eebca6 100644 --- a/server-plugins/chunter-resources/src/index.ts +++ b/server-plugins/chunter-resources/src/index.ts @@ -41,7 +41,7 @@ import { workbenchId } from '@hcengineering/workbench' export async function channelHTMLPresenter (doc: Doc, control: TriggerControl): Promise { const channel = doc as ChunterSpace const front = getMetadata(login.metadata.FrontUrl) ?? '' - return `${channel.name}` + return `${channel.name}` } /** diff --git a/server-plugins/contact-resources/package.json b/server-plugins/contact-resources/package.json index d7a4fe0c2e..219027c982 100644 --- a/server-plugins/contact-resources/package.json +++ b/server-plugins/contact-resources/package.json @@ -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" } } diff --git a/server-plugins/contact-resources/src/index.ts b/server-plugins/contact-resources/src/index.ts index 5c714c0bc7..41ade59147 100644 --- a/server-plugins/contact-resources/src/index.ts +++ b/server-plugins/contact-resources/src/index.ts @@ -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 `${formatName(person.name)}` + return `${formatName(person.name)}` } /** @@ -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 `${organization.name}` + return `${organization.name}` } /** @@ -112,17 +113,3 @@ export default async () => ({ OrganizationTextPresenter: organizationTextPresenter } }) - -async function listMinioObjects (client: MinioClient, db: string, prefix: string): Promise> { - const items = new Map() - 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 -} diff --git a/server-plugins/inventory-resources/src/index.ts b/server-plugins/inventory-resources/src/index.ts index 8205012ecc..db0b7d26c7 100644 --- a/server-plugins/inventory-resources/src/index.ts +++ b/server-plugins/inventory-resources/src/index.ts @@ -27,7 +27,7 @@ import { workbenchId } from '@hcengineering/workbench' export async function productHTMLPresenter (doc: Doc, control: TriggerControl): Promise { const product = doc as Product const front = getMetadata(login.metadata.FrontUrl) ?? '' - return `${product.name}` + return `${product.name}` } /** diff --git a/server-plugins/lead-resources/src/index.ts b/server-plugins/lead-resources/src/index.ts index 8f9fadcf4d..8f6a0189cc 100644 --- a/server-plugins/lead-resources/src/index.ts +++ b/server-plugins/lead-resources/src/index.ts @@ -37,7 +37,7 @@ import { addAssigneeNotification } from '@hcengineering/server-task-resources' export async function leadHTMLPresenter (doc: Doc, control: TriggerControl): Promise { const lead = doc as Lead const front = getMetadata(login.metadata.FrontUrl) ?? '' - return `${lead.title}` + return `${lead.title}` } /** diff --git a/server-plugins/recruit-resources/src/index.ts b/server-plugins/recruit-resources/src/index.ts index 10b8fc4323..5ee9932092 100644 --- a/server-plugins/recruit-resources/src/index.ts +++ b/server-plugins/recruit-resources/src/index.ts @@ -39,7 +39,7 @@ import { workbenchId } from '@hcengineering/workbench' export async function vacancyHTMLPresenter (doc: Doc, control: TriggerControl): Promise { const vacancy = doc as Vacancy const front = getMetadata(login.metadata.FrontUrl) ?? '' - return `${vacancy.name}` + return `${vacancy.name}` } /** @@ -56,7 +56,7 @@ export async function vacancyTextPresenter (doc: Doc): Promise { export async function applicationHTMLPresenter (doc: Doc, control: TriggerControl): Promise { const applicant = doc as Applicant const front = getMetadata(login.metadata.FrontUrl) ?? '' - return `APP-${applicant.number}` + return `APP-${applicant.number}` } /** diff --git a/server-plugins/task-resources/src/index.ts b/server-plugins/task-resources/src/index.ts index 832c677131..8217274b4d 100644 --- a/server-plugins/task-resources/src/index.ts +++ b/server-plugins/task-resources/src/index.ts @@ -31,7 +31,7 @@ import { workbenchId } from '@hcengineering/workbench' export async function issueHTMLPresenter (doc: Doc, control: TriggerControl): Promise { const issue = doc as Issue const front = getMetadata(login.metadata.FrontUrl) ?? '' - return `Task-${issue.number}` + return `Task-${issue.number}` } /** diff --git a/server-plugins/tracker-resources/src/index.ts b/server-plugins/tracker-resources/src/index.ts index 28c6b21e14..6deaea9c3f 100644 --- a/server-plugins/tracker-resources/src/index.ts +++ b/server-plugins/tracker-resources/src/index.ts @@ -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 `${issueName}` + return `${issueName}` } /** diff --git a/server/account/package.json b/server/account/package.json index ac4c33db11..887735f113 100644 --- a/server/account/package.json +++ b/server/account/package.json @@ -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", diff --git a/server/account/src/__tests__/account.test.ts b/server/account/src/__tests__/account.test.ts index 149bfb6d71..af494f2e05 100644 --- a/server/account/src/__tests__/account.test.ts +++ b/server/account/src/__tests__/account.test.ts @@ -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 () => { diff --git a/server/account/src/index.ts b/server/account/src/index.ts index 4599b01a7f..e309e019a4 100644 --- a/server/account/src/index.ts +++ b/server/account/src/index.ts @@ -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({ email }) } +function withProductId (productId: string, query: Filter): Filter { + 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 { - return await db.collection(WORKSPACE_COLLECTION).findOne({ - workspace - }) +export async function getWorkspace (db: Db, productId: string, workspace: string): Promise { + return await db.collection(WORKSPACE_COLLECTION).findOne(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 { +export async function login (db: Db, productId: string, email: string, password: string): Promise { 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 { +export async function selectWorkspace ( + db: Db, + productId: string, + token: string, + workspace: string +): Promise { 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 { /** * @public */ -export async function join (db: Db, email: string, password: string, inviteId: ObjectId): Promise { +export async function join ( + db: Db, + productId: string, + email: string, + password: string, + inviteId: ObjectId +): Promise { 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 { 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 { - if (productId === '') { - return await db - .collection(WORKSPACE_COLLECTION) - .find({ $or: [{ productId: { $exists: false } }, { productId: '' }] }) - .toArray() - } - return await db.collection(WORKSPACE_COLLECTION).find({ productId }).toArray() + return await db.collection(WORKSPACE_COLLECTION).find(withProductId(productId, {})).toArray() } /** @@ -360,12 +377,12 @@ export async function createWorkspace ( version: Data, txes: Tx[], migrationOperation: MigrateOperation[], - productId: string, db: Db, + productId: string, workspace: string, organisation: string ): Promise { - 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 { - 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, txes: Tx[], migrationOperation: MigrateOperation[], productId: string) => - async (db: Db, token: string, workspace: string): Promise => { + (version: Data, txes: Tx[], migrationOperation: MigrateOperation[]) => + async (db: Db, productId: string, token: string, workspace: string): Promise => { 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 { 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 { +export async function getUserWorkspaces (db: Db, productId: string, token: string): Promise { const { email } = decodeToken(token) const account = await getAccount(db, email) if (account === null) return [] return await db .collection(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 { - const connection = await connect(getTransactor(), workspace, email) +export async function setRole (email: string, workspace: string, productId: string, role: AccountRole): Promise { + 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 { - const { workspaceId, accountId } = await getWorkspaceAndAccount(db, email, workspace) +export async function assignWorkspace (db: Db, productId: string, email: string, workspace: string): Promise { + 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_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> { @@ -542,8 +562,8 @@ async function createEmployee (ops: TxOperations, name: string, email: string): return id } -async function createEmployeeAccount (account: Account, workspace: string): Promise { - const connection = await connect(getTransactor(), workspace, account.email) +async function createEmployeeAccount (account: Account, productId: string, workspace: string): Promise { + 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 { +export async function changePassword ( + db: Db, + productId: string, + token: string, + oldPassword: string, + password: string +): Promise { 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 { +export async function replacePassword (db: Db, productId: string, email: string, password: string): Promise { 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 { +export async function changeName (db: Db, productId: string, token: string, first: string, last: string): Promise { 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[] = [] 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 { - const connection = await connect(getTransactor(), workspace, account.email) +async function updateEmployeeAccount (account: Account, workspace: string, productId: string): Promise { + 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 { - const { workspaceId, accountId } = await getWorkspaceAndAccount(db, email, workspace) +export async function removeWorkspace (db: Db, productId: string, email: string, workspace: string): Promise { + 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 { +export async function checkJoin ( + db: Db, + productId: string, + token: string, + inviteId: ObjectId +): Promise { 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 { - const ws = await getWorkspace(db, workspace) +export async function dropWorkspace (db: Db, productId: string, workspace: string): Promise { + 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 { /** * @public */ -export async function dropAccount (db: Db, email: string): Promise { +export async function dropAccount (db: Db, productId: string, email: string): Promise { 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 { const workspaces = await db .collection(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 { /** * @public */ -export async function leaveWorkspace (db: Db, token: string, email: string): Promise { +export async function leaveWorkspace (db: Db, productId: string, token: string, email: string): Promise { 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 { - const connection = await connect(getTransactor(), workspace, email) +async function deactivateEmployeeAccount (email: string, workspace: string, productId: string): Promise { + 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, token?: string) => Promise> +export type AccountMethod = ( + db: Db, + productId: string, + request: Request, + token?: string +) => Promise> -function wrap (f: (db: Db, ...args: any[]) => Promise): AccountMethod { - return async function (db: Db, request: Request, token?: string): Promise> { +function wrap (f: (db: Db, productId: string, ...args: any[]) => Promise): AccountMethod { + return async function (db: Db, productId: string, request: Request, token?: string): Promise> { 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): AccountMethod { export function getMethods ( version: Data, txes: Tx[], - migrateOperations: MigrateOperation[], - productId: string + migrateOperations: MigrateOperation[] ): Record { 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), diff --git a/server/backup/package.json b/server/backup/package.json index 43ecf248f3..641f4e9940 100644 --- a/server/backup/package.json +++ b/server/backup/package.json @@ -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" } } diff --git a/server/backup/src/index.ts b/server/backup/src/index.ts index 5a5e99ba28..f8070c97ad 100644 --- a/server/backup/src/index.ts +++ b/server/backup/src/index.ts @@ -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 { - const connection = (await connect(transactorUrl, dbName, undefined, { +export async function backup (transactorUrl: string, workspaceId: WorkspaceId, storage: BackupStorage): Promise { + 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 { */ export async function restore ( transactorUrl: string, - dbName: string, + workspaceId: WorkspaceId, storage: BackupStorage, date: number ): Promise { @@ -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 diff --git a/server/backup/src/storage.ts b/server/backup/src/storage.ts index 8e52922d85..35d9a37a30 100644 --- a/server/backup/src/storage.ts +++ b/server/backup/src/storage.ts @@ -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 { - 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 { 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 { - 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 { 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 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 { - 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) } diff --git a/server/core/package.json b/server/core/package.json index 278504c129..316abb7c95 100644 --- a/server/core/package.json +++ b/server/core/package.json @@ -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" } } diff --git a/server/core/src/adapter.ts b/server/core/src/adapter.ts index 439532e0a9..c5ed426366 100644 --- a/server/core/src/adapter.ts +++ b/server/core/src/adapter.ts @@ -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 /** @@ -129,6 +130,10 @@ class InMemoryAdapter implements DbAdapter { /** * @public */ -export async function createInMemoryAdapter (hierarchy: Hierarchy, url: string, db: string): Promise { +export async function createInMemoryAdapter ( + hierarchy: Hierarchy, + url: string, + workspaceId: WorkspaceId +): Promise { return new InMemoryAdapter(hierarchy) } diff --git a/server/core/src/storage.ts b/server/core/src/storage.ts index 683a9756db..f97de22fae 100644 --- a/server/core/src/storage.ts +++ b/server/core/src/storage.ts @@ -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 domains: Record 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 diff --git a/server/core/src/types.ts b/server/core/src/types.ts index 14066b6902..995c22b7e4 100644 --- a/server/core/src/types.ts +++ b/server/core/src/types.ts @@ -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 // 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 + storageFx: (f: (adapter: MinioService, workspaceId: WorkspaceId) => Promise) => void fx: (f: () => Promise) => void } @@ -157,7 +156,7 @@ export interface FullTextAdapter { /** * @public */ -export type FullTextAdapterFactory = (url: string, workspace: string) => Promise +export type FullTextAdapterFactory = (url: string, workspace: WorkspaceId) => Promise /** * @public diff --git a/server/elastic/src/__tests__/adapter.test.ts b/server/elastic/src/__tests__/adapter.test.ts index 15015cbff8..52d0a2a90b 100644 --- a/server/elastic/src/__tests__/adapter.test.ts +++ b/server/elastic/src/__tests__/adapter.test.ts @@ -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, _class: 'class1' as Ref>, diff --git a/server/elastic/src/adapter.ts b/server/elastic/src/adapter.ts index 311f97e393..2f222cc058 100644 --- a/server/elastic/src/adapter.ts +++ b/server/elastic/src/adapter.ts @@ -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 { 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 { 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, update: Record): Promise { 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): Promise { 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 Promise }> { const client = new Client({ node: url }) - return new ElasticAdapter(client, dbName) + return new ElasticAdapter(client, workspaceId) } diff --git a/server/elastic/src/backup.ts b/server/elastic/src/backup.ts index 1f6b19260d..6259217c5f 100644 --- a/server/elastic/src/backup.ts +++ b/server/elastic/src/backup.ts @@ -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( _class: Ref>, @@ -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 { const client = new Client({ node: url }) - return new ElasticDataAdapter(db, client) + return new ElasticDataAdapter(workspaceId, client) } diff --git a/server/front/package.json b/server/front/package.json index 8adff9d99b..b24087659c 100644 --- a/server/front/package.json +++ b/server/front/package.json @@ -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" } } diff --git a/server/front/src/__start.ts b/server/front/src/__start.ts index bbe61397e5..06e9837a37 100644 --- a/server/front/src/__start.ts +++ b/server/front/src/__start.ts @@ -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, diff --git a/server/front/src/index.ts b/server/front/src/index.ts index e15fda3869..5e5f83c705 100644 --- a/server/front/src/index.ts +++ b/server/front/src/index.ts @@ -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 { +async function minioUpload (minio: MinioService, workspace: WorkspaceId, file: UploadedFile): Promise { 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 { - 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 { - 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 { - const stat = await client.statObject(workspace, uuid) +async function getFile (client: MinioService, workspace: WorkspaceId, uuid: string, res: Response): Promise { + 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 { +async function getResizeID ( + size: string, + uuid: string, + config: { minio: MinioService }, + payload: Token +): Promise { 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> { - const items = new Map() - 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 -} diff --git a/server/minio/.eslintrc.js b/server/minio/.eslintrc.js new file mode 100644 index 0000000000..7f7cebe58b --- /dev/null +++ b/server/minio/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/server/minio/.npmignore b/server/minio/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/server/minio/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/server/minio/config/rig.json b/server/minio/config/rig.json new file mode 100644 index 0000000000..2fdc07ba20 --- /dev/null +++ b/server/minio/config/rig.json @@ -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" +} diff --git a/server/minio/package.json b/server/minio/package.json new file mode 100644 index 0000000000..6956f6f9d3 --- /dev/null +++ b/server/minio/package.json @@ -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" + } +} diff --git a/server/minio/src/__tests__/minio.test.ts b/server/minio/src/__tests__/minio.test.ts new file mode 100644 index 0000000000..941b6ba8b1 --- /dev/null +++ b/server/minio/src/__tests__/minio.test.ts @@ -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') + }) +}) diff --git a/server/minio/src/index.ts b/server/minio/src/index.ts new file mode 100644 index 0000000000..f07e822d85 --- /dev/null +++ b/server/minio/src/index.ts @@ -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 { + return await this.client.bucketExists(getBucketId(workspaceId)) + } + + async make (workspaceId: WorkspaceId): Promise { + await this.client.makeBucket(getBucketId(workspaceId), 'k8s') + } + + async remove (workspaceId: WorkspaceId, objectNames: string[]): Promise { + await this.client.removeObjects(getBucketId(workspaceId), objectNames) + } + + async delete (workspaceId: WorkspaceId): Promise { + await this.client.removeBucket(getBucketId(workspaceId)) + } + + async list (workspaceId: WorkspaceId, prefix?: string): Promise { + const items = new Map() + 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 { + return await this.client.statObject(getBucketId(workspaceId), objectName) + } + + async get (workspaceId: WorkspaceId, objectName: string): Promise { + return await this.client.getObject(getBucketId(workspaceId), objectName) + } + + async put ( + workspaceId: WorkspaceId, + objectName: string, + stream: ReadableStream | Buffer | string, + size?: number, + metaData?: ItemBucketMetadata + ): Promise { + return await this.client.putObject(getBucketId(workspaceId), objectName, stream, size, metaData) + } + + async read (workspaceId: WorkspaceId, name: string): Promise { + 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 { + return await this.client.getPartialObject(getBucketId(workspaceId), objectName, offset, length) + } +} diff --git a/server/minio/tsconfig.json b/server/minio/tsconfig.json new file mode 100644 index 0000000000..9b47eded03 --- /dev/null +++ b/server/minio/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib" + } +} \ No newline at end of file diff --git a/server/mongo/package.json b/server/mongo/package.json index f0948a593e..8eb56026fa 100644 --- a/server/mongo/package.json +++ b/server/mongo/package.json @@ -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" } } diff --git a/server/mongo/src/__tests__/storage.test.ts b/server/mongo/src/__tests__/storage.test.ts index 4591879573..8af48be63f 100644 --- a/server/mongo/src/__tests__/storage.test.ts +++ b/server/mongo/src/__tests__/storage.test.ts @@ -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[]): Promise {} } -async function createNullAdapter (hierarchy: Hierarchy, url: string, db: string, modelDb: ModelDb): Promise { +async function createNullAdapter ( + hierarchy: Hierarchy, + url: string, + db: WorkspaceId, + modelDb: ModelDb +): Promise { 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', {}) diff --git a/server/mongo/src/index.ts b/server/mongo/src/index.ts index 2480be3c17..de47577bf7 100644 --- a/server/mongo/src/index.ts +++ b/server/mongo/src/index.ts @@ -15,3 +15,4 @@ // export * from './storage' +export * from './utils' diff --git a/server/mongo/src/storage.ts b/server/mongo/src/storage.ts index 797e6c787e..47a8694d7e 100644 --- a/server/mongo/src/storage.ts +++ b/server/mongo/src/storage.ts @@ -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 { 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 { const client = await getMongoClient(url) - const db = client.db(dbName) + const db = getWorkspaceDB(client, workspaceId) return new MongoTxAdapter(db, hierarchy, modelDb, client) } diff --git a/server/mongo/src/utils.ts b/server/mongo/src/utils.ts index cf12beb9c4..c975e7ea55 100644 --- a/server/mongo/src/utils.ts +++ b/server/mongo/src/utils.ts @@ -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)) +} diff --git a/server/server/package.json b/server/server/package.json index af9fae6f5b..cd1d29d51e 100644 --- a/server/server/package.json +++ b/server/server/package.json @@ -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" } } diff --git a/server/server/src/minio.ts b/server/server/src/minio.ts index 4a85e327fe..c789e0f117 100644 --- a/server/server/src/minio.ts +++ b/server/server/src/minio.ts @@ -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( _class: Ref>, @@ -51,48 +52,14 @@ class MinioBlobAdapter implements DbAdapter { async close (): Promise {} - async listMinioObjects (): Promise> { - const items = new Map() - 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 { - 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[]): Promise { 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, @@ -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[]): Promise { - 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 { if (storage === undefined) { throw new Error('minio storage adapter require minio') } - return new MinioBlobAdapter(db, storage) + return new MinioBlobAdapter(workspaceId, storage) } diff --git a/server/server/src/server.ts b/server/server/src/server.ts index 2163f51cce..637ade5091 100644 --- a/server/server/src/server.ts +++ b/server/server/src/server.ts @@ -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 { return new NullDbAdapter() diff --git a/server/token/package.json b/server/token/package.json index feb97b0025..7628e71bd9 100644 --- a/server/token/package.json +++ b/server/token/package.json @@ -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" } } diff --git a/server/token/src/token.ts b/server/token/src/token.ts index 4a9d8a41e2..38ed177b87 100644 --- a/server/token/src/token.ts +++ b/server/token/src/token.ts @@ -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 } @@ -18,8 +19,8 @@ const getSecret = (): string => { /** * @public */ -export function generateToken (email: string, workspace: string, extra?: Record): string { - return encode({ ...(extra ?? {}), email, workspace }, getSecret()) +export function generateToken (email: string, workspace: WorkspaceId, extra?: Record): 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 } } diff --git a/server/tool/package.json b/server/tool/package.json index c152867eae..94ffe2d0b8 100644 --- a/server/tool/package.json +++ b/server/tool/package.json @@ -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" } } diff --git a/server/tool/src/connect.ts b/server/tool/src/connect.ts index 8bb3d697e8..1acc31accb 100644 --- a/server/tool/src/connect.ts +++ b/server/tool/src/connect.ts @@ -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 ): Promise { diff --git a/server/tool/src/index.ts b/server/tool/src/index.ts index d3f28e34df..a9490152ee 100644 --- a/server/tool/src/index.ts +++ b/server/tool/src/index.ts @@ -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 { @@ -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 { @@ -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) diff --git a/server/ws/src/__tests__/server.test.ts b/server/ws/src/__tests__/server.test.ts index 19606da947..4190013fdf 100644 --- a/server/ws/src/__tests__/server.test.ts +++ b/server/ws/src/__tests__/server.test.ts @@ -26,6 +26,7 @@ import { Domain, FindOptions, FindResult, + getWorkspaceId, Hierarchy, MeasureMetricsContext, ModelDb, @@ -78,11 +79,12 @@ describe('server', () => { clean: async (domain: Domain, docs: Ref[]) => {} }), (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}`) } diff --git a/server/ws/src/server.ts b/server/ws/src/server.ts index 8898ad0d97..26691bd54e 100644 --- a/server/ws/src/server.ts +++ b/server/ws/src/server.ts @@ -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 + pipelineFactory: (ws: WorkspaceId) => Promise, + productId: string ): Promise { - 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, + pipelineFactory: (ws: WorkspaceId) => Promise, token: Token, ws: WebSocket ): Promise { @@ -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 { + async close ( + ctx: MeasureContext, + ws: WebSocket, + workspaceId: WorkspaceId, + code: number, + reason: string + ): Promise { 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, target?: string): void { - const workspace = this.workspaces.get(workspaceId) + broadcast (from: Session | null, workspaceId: WorkspaceId, resp: Response, 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 ( */ export function start ( ctx: MeasureContext, - pipelineFactory: (workspace: string) => Promise, + pipelineFactory: (workspace: WorkspaceId) => Promise, 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) => { diff --git a/server/ws/src/types.ts b/server/ws/src/types.ts index e8678e678e..4af50e591a 100644 --- a/server/ws/src/types.ts +++ b/server/ws/src/types.ts @@ -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, target?: string) => void +export type BroadcastCall = ( + from: Session | null, + workspaceId: WorkspaceId, + resp: Response, + target?: string +) => void