├── .env.template ├── .eslintrc.json ├── .github └── workflows │ ├── build-and-deploy-dev.yml │ ├── build-and-deploy.yml │ ├── build-and-publish-preview.yaml │ ├── build.yml │ ├── clean-up-pr-preview.yaml │ ├── code-coverage.yml │ ├── codeql-analysis.yml │ ├── deploy-feature.yml │ ├── deploy-pr-preview.yaml │ ├── e2e-tests.yml │ ├── run-tests.yml │ └── slash-command-dispatcher.yaml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc.template ├── .prettierrc.json ├── .sequelizerc ├── CODEOWNERS ├── Dockerfile ├── LICENSE ├── README.md ├── bin └── delete-files │ ├── Dockerfile │ ├── index.ts │ └── utils.ts ├── infrastructure ├── database-init-scripts │ └── create-db.sql ├── development.Dockerfile ├── docker-compose.yml ├── e2e-docker-compose.yml ├── only-server-docker-compose.yml ├── preview.Dockerfile └── volumes │ ├── dbeaver_logs │ └── .gitignore │ └── dbeaver_workspace │ └── .gitignore ├── migrations ├── 20180809130743-create_folder_table.js ├── 20180809130825-create_users_table.js ├── 20180809131209-create_folder_ancestor_table.js ├── 20180820122915-create_file_table.js ├── 20190214075722-create-plan.js ├── 20190215072131-create-subscription.js ├── 20190225122110-add-mnemonic-storage-column.js ├── 20190311072948-add-hkey-column.js ├── 20190405065451-file-buckets-fixes.js ├── 20190411142950-add-folder-metadata.js ├── 20190417214342-add-2FA-columns.js ├── 20190509094600-create_share_table.js ├── 20190515090823-add_file_timestamps.js ├── 20190709215300-add_user_logincount.js ├── 20191126093100-statistics_table.js ├── 20200213194200-remove-unused-tables-columns.js ├── 20200213234300-more_timestamps.js ├── 20200214101300-bridge_fields.js ├── 20200225130000-uncamelize.js ├── 20200317050000-increase_file_size.js ├── 20200404123030-add-columns-user_activityAt-is-email-activity-sended.js ├── 20200413192000-add-is_folder-column.js ├── 20200421030600-add-column-shares-view.js ├── 20200505045000-folders-files-name-length.js ├── 20200611135400-referral_field.js ├── 20200612074700-sync_date.js ├── 20200620125500-encryption_versioning.js ├── 20200702110000-create_teams_members_table.js ├── 20200709130000-add-column-folder-id_team.js ├── 20200715160002-create_teams_table.js ├── 20200720110000-add-register_from-column.js ├── 20200809130743-create_team-invitations_table.js ├── 20200922134200-users_uuid.js ├── 20200924083100-users_last_resend_date.js ├── 20200924095700-users_referred.js ├── 20200924100500-users-credit.js ├── 20201005153100-name_refactor.js ├── 20201028093605-create_key_server_table.js ├── 20201030134200-add_welcome_column.js ├── 20201102142735-add_columns_teams_members.js ├── 20201106130534-change_strings_teams_members.js ├── 20201109123924-change_type_mnemonic_teams.js ├── 20201110085023-change_types.js ├── 20201111170013-add_columns_teams_invitation.js ├── 20201111174925-change_type_teams_member.js ├── 20201207103900-create_photo_table.js ├── 20210108113800-create_users_photos_table.js ├── 20210108135100-create_previews_table.js ├── 20210111194200-add_column_appsumo.js ├── 20210112114600-create-albums-table.js ├── 20210112124500-add-foreignkeys-userphotos.js ├── 20210112130700-add-foreignkeys-previews.js ├── 20210112134400-add-foreignkey-photos.js ├── 20210112143500-create-photosalbum-jointable.js ├── 20210113154400-add-photo-timestamps.js ├── 20210113154900-add-usersphotos-timestamps.js ├── 20210118165000-create_appsumo_table.js ├── 20210120081100-remove_unused.js ├── 20210121161600-add-album-timestamps.js ├── 20210125123500-add-delete-folder-id.js ├── 20210204142900-add-columns-preview.js ├── 20210205135900-add-photosalbums-timestamps.js ├── 20210210112500-openpgp_fit_size.js ├── 20210211181100-add-preview-bucket.js ├── 20210211181800-add-photo-hash.js ├── 20210215124900-mod-fileId-photo-preview.js ├── 20210309150800-add-total_members-teams.js ├── 20210415144500-add-creation-time-photos.js ├── 20210415161000-add_file_foreign_key.js ├── 20210416092200-create_index_folder_user_id.js ├── 20210416201300-remove_statistics_table.js ├── 20210416202800-keyserver_cascade.js ├── 20210419142100-remove_folder_ancestors.js ├── 20210419151700-remove_unused_columns.js ├── 20210520164800-add-deleted-column-files.js ├── 20210715151336-coupon.js ├── 20210803115504-add-encryption-key-share.js ├── 20210803122725-make-token-unique-share.js ├── 20210804071033-add-bucket-share.js ├── 20210804104524-add-file-token-share.js ├── 20210906090000-add_username_users.js ├── 20210906090001-add_bridge_user_users.js ├── 20210906090002-add_shared_workspace_users.js ├── 20210906090003-add_temp_key_users.js ├── 20210906090550-create_device_table.js ├── 20210906091250-create_backup_table.js ├── 20210906095148-add_backups_bucket_column.js ├── 20210909091644-add_enabled_and_last_backup_at.js ├── 20210920091313-modify_path_column_type.js ├── 20210921083232-create_index_mac_backups.js ├── 20210927101644-add_platform_column.js ├── 20210930165500-remove_icons.js ├── 20211004060909-add_userid_files.js ├── 20211004130500-invitation-guest-members.js ├── 20211008103051-create_plans_table.js ├── 20211019101644-add_modification_time_column.js ├── 20211026055551-remove_user_old_referral_column.js ├── 20211026073048-add_user_referral_code_and_referrer.js ├── 20211026094113-create_index_users_referral_code.js ├── 20211026124013-users_referral_code_uniqueness.js ├── 20211105180000-create_referrals_table.js ├── 20211105180600-create_users_referrals_table.js ├── 20220223084246-create_index_folders_parent_id.js ├── 20220224180600-drop-photos-tables.js ├── 20220307180600-increase-encryption-key-size.js ├── 20220309180600-create-deleted-files-table.js ├── 20220309190600-create-deleted-files-trigger.js ├── 20220309190601-create-clear_orphan_folders_by_user-procedure.js ├── 20220413065816-create_mail_limits_table.js ├── 20220429114425-make-uuid-unique-users.js ├── 20220518130743-create_friend-invitations_table.js ├── 20220519090000-add_avatar_column.js ├── 20220524090000-add_email_verified_column.js ├── 20220623125000-create-send-links-tables.js ├── 20220624130000-update-shares-table-with-columns.js ├── 20220718082248-add-version-to-send-link-item.js ├── 20220722090000-add-columns-folders-files.js ├── 20220823170000-add-column-code-shares.js ├── 20220916100000-update-share-file-delete.js ├── 20220919083117-add-password-to-shared-links.js ├── 20221003080000-create-index-folders-bucket.js ├── 20221003090000-create-thumbnails-table.js ├── 20221010100000-add-column-sendLinks-parentFolder.js ├── 20221109110000-add-unique-columns-files.js ├── 20230103122430-add-plain-name-to-folder.js ├── 20230103122435-add-plain-name-to-file.js ├── 20230103122440-create-index-files-plain-name.js ├── 20230103122445-create-index-folder-plain-name.js ├── 20230105113000-modify-index-folder-plain-name.js ├── 20230302120000-rename-mailType-enum.js ├── 20230303133000-add-deactivateUser-to-mailType-enum.js └── 20240109071854-add-lastPasswordChangedAt-to-users.js ├── package.json ├── sonar-project.properties ├── src ├── app.ts ├── app │ ├── constants.js │ ├── middleware │ │ ├── analytics.ts │ │ ├── basic-auth.js │ │ ├── error-handler.ts │ │ ├── passport.js │ │ ├── resource-sharing.middleware.ts │ │ ├── shared-workspace.js │ │ └── upload-avatar.ts │ ├── models │ │ ├── appsumo.ts │ │ ├── backup.ts │ │ ├── deletedFile.ts │ │ ├── device.ts │ │ ├── file.ts │ │ ├── folder.ts │ │ ├── friendinvitation.ts │ │ ├── index.ts │ │ ├── invitation.ts │ │ ├── keyserver.ts │ │ ├── limit.ts │ │ ├── mailLimit.ts │ │ ├── paidPlans.ts │ │ ├── permissions.ts │ │ ├── plan.ts │ │ ├── privateSharingFolder.ts │ │ ├── privateSharingFolderRole.ts │ │ ├── referral.ts │ │ ├── roles.ts │ │ ├── share.ts │ │ ├── sharingInvites.ts │ │ ├── sharingRoles.ts │ │ ├── sharings.ts │ │ ├── thumbnail.ts │ │ ├── tier.ts │ │ ├── tierLimit.ts │ │ ├── user.ts │ │ ├── userNotificationTokens.ts │ │ └── userReferral.ts │ ├── routes │ │ ├── activation.ts │ │ ├── analytics.js │ │ ├── appsumo.js │ │ ├── auth.ts │ │ ├── backup.js │ │ ├── bridge.ts │ │ ├── desktop.js │ │ ├── gateway.js │ │ ├── guest.js │ │ ├── mobile.js │ │ ├── newsletter.js │ │ ├── plan.js │ │ ├── routes.ts │ │ ├── share.ts │ │ ├── storage.ts │ │ ├── stripe.js │ │ ├── twofactor.js │ │ ├── types.ts │ │ ├── user.js │ │ └── usersReferrals.js │ ├── services │ │ ├── appsumo.js │ │ ├── backups.js │ │ ├── crypt.js │ │ ├── desktop.js │ │ ├── errors │ │ │ ├── FileWithNameAlreadyExistsError.ts │ │ │ ├── FolderWithNameAlreadyExistsError.ts │ │ │ ├── locks.ts │ │ │ └── referrals.ts │ │ ├── featureLimit.js │ │ ├── files.js │ │ ├── folder.js │ │ ├── guest.js │ │ ├── inxt.js │ │ ├── keyserver.js │ │ ├── limit.js │ │ ├── mail.js │ │ ├── mailer │ │ │ └── mailer.service.ts │ │ ├── newsletter.js │ │ ├── plan.js │ │ ├── privateSharing.js │ │ ├── referrals.js │ │ ├── services.js │ │ ├── share.js │ │ ├── stripe.js │ │ ├── thumbnails.js │ │ ├── user.js │ │ ├── usersReferrals.js │ │ └── utils.js │ └── sockets │ │ └── socketServer.js ├── config │ ├── config.ts │ ├── environments │ │ ├── development.js │ │ ├── docker.js │ │ ├── e2e.js │ │ ├── env.ts │ │ ├── production.js │ │ ├── sequelize.js │ │ ├── staging.js │ │ └── test.js │ └── initializers │ │ ├── apn.ts │ │ ├── avatarS3.ts │ │ ├── database.ts │ │ ├── middleware.js │ │ ├── notifications.ts │ │ ├── redis.ts │ │ ├── server.ts │ │ └── swagger.js └── lib │ ├── AesUtil.js │ ├── Validator.ts │ ├── analytics │ ├── Analytics.ts │ ├── AnalyticsService.ts │ ├── types.ts │ └── utils.ts │ ├── logger.ts │ ├── performance │ └── network.ts │ └── recaptcha.ts ├── tests ├── controllers │ ├── auth.test.ts │ ├── share.test.ts │ └── storage.test.ts ├── e2e │ ├── e2e-spec.ts │ ├── jest-e2e.json │ ├── setup.ts │ └── utils.ts └── services │ ├── backups.unit.ts │ ├── crypt.unit.ts │ ├── openpgp.unit.ts │ ├── usersReferrals.unit.ts │ └── utils.unit.ts ├── tsconfig.json └── yarn.lock /.env.template: -------------------------------------------------------------------------------- 1 | APP_SEGMENT_KEY=LT12AvUt4u1NgNA751gYXdJ1Dvf0dmqx 2 | AVATAR_ACCESS_KEY=internxt 3 | AVATAR_BUCKET=avatars 4 | AVATAR_ENDPOINT=http://network-storage:9000 5 | AVATAR_ENDPOINT_REWRITE_FOR_SIGNED_URLS=http://localhost:9000 6 | AVATAR_FORCE_PATH_STYLE=true 7 | AVATAR_REGION= 8 | AVATAR_SECRET_KEY=internxt 9 | CAPTCHA_SECRET=6Lf4_xsURABVAMlnlRzIRtzkiOtdklEsvZfZ9JIk 10 | CRYPTO_SECRET=6KYQBP847D4ATSFA 11 | CRYPTO_SECRET2=8Q8VMUE3BJZV87GT 12 | GATEWAY_USER=user 13 | GATEWAY_PASS=gatewaypass 14 | HOST_DRIVE_WEB=http://localhost:3000 15 | INXT_MAILER_HOST=smtp.sendgrid.net 16 | INXT_MAILER_PASSWORD=4884fD%$fg 17 | INXT_MAILER_PORT=465 18 | INXT_MAILER_USERNAME=internxt 19 | JWT_SECRET=38FTANE5LY90NHYZ 20 | MAGIC_IV=d139cb9a2cd17092e79e1861cf9d7023 21 | MAGIC_SALT=38dce0391b49efba88dbc8c39ebf868f0267eb110bb0012ab27dc52a528d61b1d1ed9d76f400ff58e3240028442b1eab9bb84e111d9dadd997982dbde9dbd25e 22 | NODE_ENV=development 23 | NOTIFICATIONS_URL=http://notifications:4000 24 | NOTIFICATIONS_API_KEY=secret 25 | PAYMENTS_SERVER_URL=http://payments-server:8002 26 | RDS_DBNAME=xCloud 27 | RDS_HOSTNAME=drive-database 28 | RDS_PASSWORD=example 29 | RDS_PORT=5432 30 | RDS_USERNAME=postgres 31 | SENDGRID_API_KEY=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 32 | SENDGRID_FROM= 33 | SENDGRID_NAME= 34 | DRIVE_VERIFY_ACCOUNT_TEMPLATE_ID= 35 | DRIVE_INVITE_FRIEND_TEMPLATE_ID= 36 | SESSION_KEY=t&jJr8N{D:u6fkFK 37 | STORJ_BRIDGE_HTTPS=http://network-api:6382 38 | STORJ_BRIDGE=http://network-api:6382 39 | STRIPE_SK=sk_test_F3Ny2VGUnPga9FtyXkl7mzPc 40 | STRIPE_SK_TEST=sk_test_R3Ny2VG7nPgeJFrtXdt8mrPc 41 | SENDGRID_MODE_SANDBOX=false 42 | 43 | # APN 44 | APN_URL=https://api.sandbox.push.apple.com 45 | APN_BUNDLE_ID= 46 | APN_TEAM_ID= 47 | APN_KEY_ID= 48 | APN_SECRET= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@internxt/eslint-config-internxt", 3 | // Rules needed while migrating from js 4 | "rules": { 5 | "@typescript-eslint/no-var-requires": "off", 6 | "@typescript-eslint/no-explicit-any": "off" 7 | }, 8 | "env": { 9 | "node": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy-dev.yml: -------------------------------------------------------------------------------- 1 | name: build & deploy 2 | on: 3 | push: 4 | branches: ["develop"] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check Out Repo 10 | uses: actions/checkout@v2 11 | with: 12 | registry-url: 'https://npm.pkg.github.com' 13 | - run: echo "registry=https://registry.yarnpkg.com/" > .npmrc 14 | - run: echo "@internxt:registry=https://npm.pkg.github.com" >> .npmrc 15 | # You cannot read packages from other private repos with GITHUB_TOKEN 16 | # You have to use a PAT instead https://github.com/actions/setup-node/issues/49 17 | - run: echo //npm.pkg.github.com/:_authToken=${{ secrets.PERSONAL_ACCESS_TOKEN }} >> .npmrc 18 | - run: echo "always-auth=true" >> .npmrc 19 | - name: Login to DockerHub 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v1 26 | - name: Build and push to drive-server-dev 27 | uses: docker/build-push-action@v2 28 | with: 29 | context: ./ 30 | file: ./Dockerfile 31 | push: true 32 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/drive-server-dev:${{ github.sha }} 33 | deploy: 34 | needs: build 35 | runs-on: ubuntu-latest 36 | environment: 37 | name: develop 38 | steps: 39 | - uses: actions/checkout@master 40 | - name: Update drive-server-dev image 41 | uses: steebchen/kubectl@v2.0.0 42 | with: # defaults to latest kubectl binary version 43 | config: ${{ secrets.KUBE_CONFIG_DATA_DEVELOPMENT }} 44 | command: set image --record deployment/drive-server-dev drive-server-dev=${{ secrets.DOCKERHUB_USERNAME }}/drive-server-dev:${{ github.sha }} 45 | - name: Verify drive-server-dev deployment 46 | uses: steebchen/kubectl@v2.0.0 47 | with: 48 | config: ${{ secrets.KUBE_CONFIG_DATA_DEVELOPMENT }} 49 | version: v1.20.2 # specify kubectl binary version explicitly 50 | command: rollout status deployment/drive-server-dev 51 | 52 | -------------------------------------------------------------------------------- /.github/workflows/build-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: build & deploy 2 | on: 3 | push: 4 | branches: ["master", "feature/cd"] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check Out Repo 10 | uses: actions/checkout@v2 11 | with: 12 | registry-url: 'https://npm.pkg.github.com' 13 | - run: echo "registry=https://registry.yarnpkg.com/" > .npmrc 14 | - run: echo "@internxt:registry=https://npm.pkg.github.com" >> .npmrc 15 | # You cannot read packages from other private repos with GITHUB_TOKEN 16 | # You have to use a PAT instead https://github.com/actions/setup-node/issues/49 17 | - run: echo //npm.pkg.github.com/:_authToken=${{ secrets.PERSONAL_ACCESS_TOKEN }} >> .npmrc 18 | - run: echo "always-auth=true" >> .npmrc 19 | - name: Login to DockerHub 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v1 26 | - name: Build and push to drive-server 27 | uses: docker/build-push-action@v2 28 | with: 29 | context: ./ 30 | file: ./Dockerfile 31 | push: true 32 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/drive-server:${{ github.sha }} 33 | deploy: 34 | needs: build 35 | runs-on: ubuntu-latest 36 | environment: 37 | name: production 38 | steps: 39 | - uses: actions/checkout@master 40 | - name: Update drive-server image 41 | uses: steebchen/kubectl@v2.0.0 42 | with: # defaults to latest kubectl binary version 43 | config: ${{ secrets.KUBE_CONFIG_DATA }} 44 | command: set image --record deployment/drive-server drive-server=${{ secrets.DOCKERHUB_USERNAME }}/drive-server:${{ github.sha }} -n drive 45 | - name: Verify drive-server deployment 46 | uses: steebchen/kubectl@v2.0.0 47 | with: 48 | config: ${{ secrets.KUBE_CONFIG_DATA }} 49 | command: rollout status deployment/drive-server -n drive 50 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish-preview.yaml: -------------------------------------------------------------------------------- 1 | name: Build & Publish Stable Preview 2 | on: 3 | push: 4 | branches: ["master"] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check Out Repo 10 | uses: actions/checkout@v2 11 | with: 12 | registry-url: 'https://npm.pkg.github.com' 13 | - run: echo "registry=https://registry.yarnpkg.com/" > .npmrc 14 | - run: echo "@internxt:registry=https://npm.pkg.github.com" >> .npmrc 15 | # You cannot read packages from other private repos with GITHUB_TOKEN 16 | # You have to use a PAT instead https://github.com/actions/setup-node/issues/49 17 | - run: echo //npm.pkg.github.com/:_authToken=${{ secrets.PERSONAL_ACCESS_TOKEN }} >> .npmrc 18 | - run: echo "always-auth=true" >> .npmrc 19 | - name: Login to DockerHub 20 | uses: docker/login-action@v1 21 | with: 22 | username: ${{ secrets.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.DOCKERHUB_TOKEN }} 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v1 26 | - name: Build and push to ${{ github.event.repository.name }}-dev 27 | uses: docker/build-push-action@v2 28 | with: 29 | context: ./ 30 | file: ./infrastructure/preview.Dockerfile 31 | push: true 32 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}-dev:${{ github.sha }} 33 | dispatch_update_preview_image: 34 | needs: build 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Dispatch Update Preview Image Command 38 | uses: myrotvorets/trigger-repository-dispatch-action@1.0.0 39 | with: 40 | token: ${{ secrets.PAT }} 41 | repo: internxt/environments 42 | type: update-preview-image-command 43 | payload: | 44 | { 45 | "image": { 46 | "name": "${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}", 47 | "newName": "${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}-dev", 48 | "newTag": "${{ github.sha }}" 49 | } 50 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | sonarcloud: 10 | name: SonarCloud 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 16 | - name: SonarCloud Scan 17 | uses: SonarSource/sonarcloud-github-action@master 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 20 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/clean-up-pr-preview.yaml: -------------------------------------------------------------------------------- 1 | name: Clean Up PR Preview 2 | on: 3 | pull_request: 4 | types: [closed] 5 | jobs: 6 | dispatch_cleanup_deployment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Dispatch Cleanup Preview Repository Command 10 | uses: myrotvorets/trigger-repository-dispatch-action@1.0.0 11 | with: 12 | token: ${{ secrets.PAT }} 13 | repo: internxt/environments 14 | type: cleanup-preview-command 15 | payload: | 16 | { 17 | "github": { 18 | "payload": { 19 | "repository": { 20 | "name": "${{ github.event.repository.name }}" 21 | }, 22 | "issue": { 23 | "number": ${{ github.event.number }} 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /.github/workflows/code-coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code coverage 2 | on: 3 | push: 4 | branches: [ master, develop ] 5 | pull_request: 6 | branches: [ master, develop ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | packages: write 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | steps: 18 | - name: Check Out Repo 19 | uses: actions/checkout@v2 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | registry-url: 'https://npm.pkg.github.com' 26 | 27 | - name: Install dependencies 28 | run: yarn 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Run tests 33 | run: yarn run test 34 | 35 | - name: Codecov 36 | uses: codecov/codecov-action@v3.1.0 37 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | name: "CodeQL" 8 | 9 | on: 10 | push: 11 | branches: [ master, develop ] 12 | pull_request: 13 | # The branches below must be a subset of the branches above 14 | branches: [ master, develop ] 15 | 16 | jobs: 17 | analyze: 18 | name: Analyze 19 | runs-on: ubuntu-latest 20 | permissions: 21 | actions: read 22 | contents: read 23 | security-events: write 24 | 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | language: [ 'javascript' ] 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v2 33 | 34 | # Initializes the CodeQL tools for scanning. 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v1 37 | with: 38 | languages: ${{ matrix.language }} 39 | # If you wish to specify custom queries, you can do so here or in a config file. 40 | # By default, queries listed here will override any specified in a config file. 41 | # Prefix the list here with "+" to use these queries and those in the config file. 42 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 43 | 44 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 45 | # If this step fails, then you should remove it and run the build manually (see below) 46 | - name: Autobuild 47 | uses: github/codeql-action/autobuild@v1 48 | 49 | # ℹ️ Command-line programs to run using the OS shell. 50 | # 📚 https://git.io/JvXDl 51 | 52 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 53 | # and modify them (or add more) to build your code if your project 54 | # uses a compiled language 55 | 56 | #- run: | 57 | # make bootstrap 58 | # make release 59 | 60 | - name: Perform CodeQL Analysis 61 | uses: github/codeql-action/analyze@v1 -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | on: 3 | pull_request: 4 | branches: 5 | - "master" 6 | - "develop" 7 | jobs: 8 | run-tests: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [16.x] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | registry-url: 'https://npm.pkg.github.com' 20 | - run: echo "registry=https://registry.yarnpkg.com/" > .npmrc 21 | - run: echo "@internxt:registry=https://npm.pkg.github.com" >> .npmrc 22 | # You cannot read packages from other private repos with GITHUB_TOKEN 23 | # You have to use a PAT instead https://github.com/actions/setup-node/issues/49 24 | - run: echo //npm.pkg.github.com/:_authToken=${{ secrets.PERSONAL_ACCESS_TOKEN }} >> .npmrc 25 | - run: echo "always-auth=true" >> .npmrc 26 | - name: Lint & Test 27 | run: yarn && yarn run test 28 | -------------------------------------------------------------------------------- /.github/workflows/slash-command-dispatcher.yaml: -------------------------------------------------------------------------------- 1 | name: Slash Command Dispatch 2 | on: 3 | issue_comment: 4 | types: [created] 5 | jobs: 6 | slash_command_dispatch: 7 | runs-on: ubuntu-latest 8 | if: ${{ contains(github.event.issue.labels.*.name, 'deployed') || contains(github.event.issue.labels.*.name, 'preview') }} 9 | steps: 10 | - name: Slash Command Dispatch 11 | id: scd 12 | uses: peter-evans/slash-command-dispatch@v4 13 | with: 14 | token: ${{ secrets.PAT }} 15 | commands: update-preview,check-preview 16 | permission: write 17 | repository: internxt/environments 18 | issue-type: pull-request 19 | allow-edits: false 20 | reactions: false 21 | - name: Edit comment with error message 22 | if: steps.scd.outputs.error-message 23 | uses: peter-evans/create-or-update-comment@v4 24 | with: 25 | comment-id: ${{ github.event.comment.id }} 26 | body: | 27 | 28 | > [!CAUTION] 29 | > Couldn't dispatch your command due to error: 30 | > **${{ steps.scd.outputs.error-message }}** -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .env 3 | node_modules 4 | config/environments/development.js 5 | core 6 | yarn-error.log 7 | build 8 | .npmrc 9 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.npmrc.template: -------------------------------------------------------------------------------- 1 | registry=https://registry.yarnpkg.com/ 2 | 3 | @internxt:registry=https://npm.pkg.github.com 4 | //npm.pkg.github.com/:_authToken=TOKEN 5 | always-auth=true -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | "@internxt/prettier-config" 2 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | require('dotenv').config() 3 | 4 | module.exports = { 5 | 'config': path.resolve('build/config/environments', 'sequelize.js'), 6 | 'models-path': path.resolve('build/app', 'models'), 7 | 'seeders-path': path.resolve('build/config', 'seeders'), 8 | } 9 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @apsantiso 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | LABEL author="internxt" 3 | 4 | WORKDIR /drive-server 5 | 6 | # Add useful packages 7 | # RUN apk add git curl 8 | 9 | COPY . . 10 | 11 | # Install deps 12 | RUN yarn && yarn build && yarn --production && yarn cache clean 13 | 14 | # Create prometheus directories 15 | # RUN mkdir -p /mnt/prometheusvol{1,2} 16 | 17 | # Start server 18 | CMD node /drive-server/build/app.js 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drive Server 2 | 3 | ## Prerrequisites 4 | 5 | * Node v16 6 | 7 | ```nvm install 16``` 8 | 9 | * Yarn 10 | 11 | ```npm i -g yarn``` 12 | 13 | # Install 14 | 15 | - Create a `.npmrc` file from the `.npmrc.template` example provided in the repo. 16 | - Replace `TOKEN` with your own [Github Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) with `read:packages` permission **ONLY** 17 | - Use `yarn` to install project dependencies. 18 | 19 | #### Database setup (MariaDB) 20 | 21 | Create schema and configure `config/environments/development.json` 22 | 23 | Run `yarn run migrate` to create tables. 24 | 25 | #### Start app 26 | 27 | Run `yarn start` to start server in production mode. 28 | 29 | Run `yarn run dev` to start with nodemon and development environment. 30 | -------------------------------------------------------------------------------- /bin/delete-files/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:14 2 | LABEL author="internxt" 3 | 4 | WORKDIR /drive-server 5 | 6 | # Add useful packages 7 | RUN apk add git curl 8 | 9 | COPY ../ . 10 | 11 | # Install deps 12 | RUN yarn && yarn build && yarn cache clean 13 | 14 | # Create prometheus directories 15 | RUN mkdir -p /mnt/prometheusvol{1,2} 16 | 17 | # Start server 18 | CMD node /drive-server/build/bin/delete-files/index.js 19 | -------------------------------------------------------------------------------- /bin/delete-files/utils.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@internxt/lib'; 2 | import { Op } from 'sequelize'; 3 | import axios, { AxiosRequestConfig } from 'axios'; 4 | import { sign } from 'jsonwebtoken'; 5 | import { FileAttributes, FileModel, FileStatus } from '../../src/app/models/file'; 6 | 7 | type Timer = { start: () => void, end: () => number } 8 | 9 | export function signToken(duration: string, secret: string) { 10 | return sign( 11 | {}, 12 | Buffer.from(secret, 'base64').toString('utf8'), 13 | { 14 | algorithm: 'RS256', 15 | expiresIn: duration 16 | } 17 | ); 18 | } 19 | 20 | export const createTimer = (): Timer => { 21 | let timeStart: [number, number]; 22 | 23 | return { 24 | start: () => { 25 | timeStart = process.hrtime(); 26 | }, 27 | end: () => { 28 | const NS_PER_SEC = 1e9; 29 | const NS_TO_MS = 1e6; 30 | const diff = process.hrtime(timeStart); 31 | 32 | return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS; 33 | } 34 | }; 35 | }; 36 | 37 | export function getFilesToDelete( 38 | files: FileModel, 39 | limit: number, 40 | updatedAt: Date, 41 | lastId: number 42 | ): Promise { 43 | return files.findAll({ 44 | limit, 45 | raw: true, 46 | where: { 47 | status: FileStatus.DELETED, 48 | id: { 49 | [Op.lt]: lastId 50 | }, 51 | updatedAt: { 52 | [Op.gte]: updatedAt 53 | }, 54 | }, 55 | order: [['id', 'DESC']] 56 | }).then(res => { 57 | return res as unknown as FileAttributes[]; 58 | }); 59 | } 60 | 61 | export type DeleteFilesResponse = { 62 | message: { 63 | confirmed: string[], 64 | notConfirmed: string[] 65 | } 66 | } 67 | 68 | export function deleteFiles(endpoint: string, fileIds: string[], jwt: string): Promise { 69 | const params: AxiosRequestConfig = { 70 | headers: { 71 | 'Content-Type': 'application/json', 72 | 'Authorization': `Bearer ${jwt}` 73 | }, 74 | data: { 75 | files: fileIds 76 | } 77 | }; 78 | 79 | return axios.delete(endpoint, params) 80 | .then((res) => res.data) 81 | .catch((err) => { 82 | throw new Error(request.extractMessageFromError(err)); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /infrastructure/database-init-scripts/create-db.sql: -------------------------------------------------------------------------------- 1 | create database xCloud; -------------------------------------------------------------------------------- /infrastructure/development.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /usr/app 4 | 5 | COPY package*.json ./ 6 | 7 | COPY .npmrc ./ 8 | 9 | RUN yarn 10 | 11 | COPY . ./ 12 | 13 | CMD yarn migrate && yarn dev -------------------------------------------------------------------------------- /infrastructure/preview.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /usr/app 4 | 5 | COPY package*.json ./ 6 | 7 | COPY .npmrc ./ 8 | 9 | RUN yarn 10 | 11 | COPY . ./ 12 | 13 | CMD yarn migrate && yarn dev -------------------------------------------------------------------------------- /infrastructure/volumes/dbeaver_logs/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /infrastructure/volumes/dbeaver_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /migrations/20180809130743-create_folder_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('folders', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | parentId: { 11 | type: Sequelize.INTEGER 12 | }, 13 | name: { 14 | type: Sequelize.STRING 15 | }, 16 | bucket: { 17 | type: Sequelize.STRING 18 | }, 19 | user_id: { 20 | type: Sequelize.INTEGER 21 | }, 22 | hierarchy_level: { 23 | type: Sequelize.INTEGER 24 | } 25 | }); 26 | }, 27 | 28 | down: (queryInterface) => { 29 | return queryInterface.dropTable('folders'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/20180809130825-create_users_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('users', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | userId: { 11 | type: Sequelize.STRING 12 | }, 13 | name: { 14 | type: Sequelize.STRING 15 | }, 16 | lastname: { 17 | type: Sequelize.STRING 18 | }, 19 | email: { 20 | type: Sequelize.STRING, 21 | allowNull: false 22 | }, 23 | password: { 24 | type: Sequelize.BLOB('medium'), 25 | allowNull: false 26 | }, 27 | mnemonic: { 28 | type: Sequelize.BLOB('medium') 29 | }, 30 | isFreeTier: { 31 | type: Sequelize.BOOLEAN 32 | }, 33 | root_folder_id: { 34 | type: Sequelize.INTEGER, 35 | references: { 36 | model: 'folders', 37 | key: 'id' 38 | } 39 | } 40 | }); 41 | }, 42 | 43 | down: (queryInterface) => { 44 | return queryInterface.dropTable('users'); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /migrations/20180809131209-create_folder_ancestor_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('foldersancestors', { 4 | folder_id: { 5 | type: Sequelize.INTEGER 6 | }, 7 | ancestor_id: { 8 | type: Sequelize.INTEGER 9 | } 10 | }); 11 | 12 | await queryInterface.addConstraint('foldersancestors', { 13 | type: 'PRIMARY KEY', 14 | fields: ['folder_id', 'ancestor_id'] 15 | }); 16 | 17 | await queryInterface.addConstraint('foldersancestors', { 18 | type: 'UNIQUE', 19 | fields: ['folder_id', 'ancestor_id'], 20 | name: 'foldersancestors_folder_id_ancestor_id_unique' 21 | }); 22 | 23 | await queryInterface.addConstraint('foldersancestors', { 24 | type: 'FOREIGN KEY', 25 | fields: ['folder_id'], 26 | name: 'foldersancestors_ibfk_1', 27 | references: { 28 | table: 'folders', 29 | field: 'id' 30 | }, 31 | onDelete: 'cascade', 32 | onUpdate: 'cascade' 33 | }); 34 | 35 | await queryInterface.addConstraint('foldersancestors', { 36 | type: 'FOREIGN KEY', 37 | fields: ['ancestor_id'], 38 | name: 'foldersancestors_ibfk_2', 39 | references: { 40 | table: 'folders', 41 | field: 'id' 42 | }, 43 | onDelete: 'cascade', 44 | onUpdate: 'cascade' 45 | }); 46 | }, 47 | 48 | down: async (queryInterface) => { 49 | await queryInterface.dropTable('foldersancestors'); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /migrations/20180820122915-create_file_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('files', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | bucketId: { 11 | type: Sequelize.STRING 12 | }, 13 | name: { 14 | type: Sequelize.STRING 15 | }, 16 | type: { 17 | type: Sequelize.STRING 18 | }, 19 | size: { 20 | type: Sequelize.INTEGER 21 | }, 22 | folder_id: { 23 | type: Sequelize.INTEGER 24 | } 25 | }); 26 | }, 27 | 28 | down: (queryInterface) => { 29 | return queryInterface.dropTable('files'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/20190214075722-create-plan.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('plans', { 4 | id: { 5 | allowNull: false, 6 | autoIncrement: true, 7 | primaryKey: true, 8 | type: Sequelize.INTEGER 9 | }, 10 | name: { 11 | type: Sequelize.STRING 12 | }, 13 | price_eur: { 14 | type: Sequelize.DECIMAL(10, 2) 15 | }, 16 | space_gb: { 17 | type: Sequelize.INTEGER 18 | }, 19 | stripe_plan_id: { 20 | type: Sequelize.STRING 21 | } 22 | }); 23 | }, 24 | down: (queryInterface) => { 25 | return queryInterface.dropTable('plans'); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /migrations/20190215072131-create-subscription.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('subscriptions', { 4 | user: { 5 | allowNull: false, 6 | primaryKey: true, 7 | type: Sequelize.INTEGER, 8 | references: { 9 | model: 'users', 10 | key: 'id' 11 | } 12 | }, 13 | plan: { 14 | allowNull: false, 15 | primaryKey: true, 16 | type: Sequelize.INTEGER, 17 | references: { 18 | model: 'plans', 19 | key: 'id' 20 | } 21 | }, 22 | creation_time: { 23 | type: Sequelize.DATE 24 | }, 25 | stripe_customer_id: { 26 | type: Sequelize.STRING 27 | }, 28 | expiration: { 29 | type: Sequelize.DATE 30 | }, 31 | is_active: { 32 | type: Sequelize.BOOLEAN 33 | }, 34 | createdAt: { 35 | allowNull: false, 36 | type: Sequelize.DATE 37 | }, 38 | updatedAt: { 39 | allowNull: false, 40 | type: Sequelize.DATE 41 | } 42 | }); 43 | }, 44 | down: (queryInterface) => { 45 | return queryInterface.dropTable('subscriptions'); 46 | } 47 | }; 48 | 49 | // sequelize model:generate --name subscription --attributes user:integer,plan:integer,creation_time:date,stripe_customer_id:string,expiration:date,is_active:boolean 50 | -------------------------------------------------------------------------------- /migrations/20190225122110-add-mnemonic-storage-column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'storeMnemonic', { 4 | type: Sequelize.BOOLEAN, 5 | allowNull: true 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'storeMnemonic'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20190311072948-add-hkey-column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'hKey', { 4 | type: Sequelize.BLOB('medium'), 5 | allowNull: false 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'hKey'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20190405065451-file-buckets-fixes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return Promise.all([ 4 | queryInterface.addColumn('files', 'fileId', { 5 | type: Sequelize.STRING 6 | }), 7 | queryInterface.addColumn('files', 'bucket', { 8 | type: Sequelize.STRING 9 | }) 10 | ]); 11 | }, 12 | 13 | down: (queryInterface) => { 14 | return Promise.all([ 15 | queryInterface.removeColumn('files', 'fileId'), 16 | queryInterface.removeColumn('files', 'bucket') 17 | ]); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20190411142950-add-folder-metadata.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('icons', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING 12 | } 13 | }); 14 | 15 | await queryInterface.addColumn('folders', 'icon_id', { 16 | type: Sequelize.INTEGER, 17 | references: { 18 | model: 'icons', 19 | key: 'id' 20 | } 21 | }); 22 | 23 | await queryInterface.addColumn('folders', 'color', { 24 | type: Sequelize.STRING 25 | }); 26 | }, 27 | 28 | down: async (queryInterface) => { 29 | await queryInterface.removeColumn('folders', 'icon_id'); 30 | await queryInterface.removeColumn('folders', 'color'); 31 | await queryInterface.dropTable('icons'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /migrations/20190417214342-add-2FA-columns.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'secret_2FA', { 4 | type: Sequelize.STRING(40) 5 | }); 6 | }, 7 | 8 | down: (queryInterface) => { 9 | return queryInterface.removeColumn('users', 'secret_2FA'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20190509094600-create_share_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('shares', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | token: { 11 | type: Sequelize.STRING 12 | }, 13 | user: { 14 | type: Sequelize.STRING 15 | }, 16 | file: { 17 | type: Sequelize.STRING 18 | }, 19 | mnemonic: { 20 | type: Sequelize.BLOB('medium') 21 | } 22 | }); 23 | }, 24 | 25 | down: (queryInterface) => { 26 | return queryInterface.dropTable('shares'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /migrations/20190515090823-add_file_timestamps.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('files', 'created_at', { type: Sequelize.DATE }); 4 | await queryInterface.addColumn('files', 'updated_at', { type: Sequelize.DATE }); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.removeColumn('files', 'created_at'); 9 | await queryInterface.removeColumn('files', 'updated_at'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20190709215300-add_user_logincount.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'errorLoginCount', { 4 | type: Sequelize.INTEGER, 5 | defaultValue: 0 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'errorLoginCount'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20191126093100-statistics_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('statistics', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING 12 | }, 13 | user: { 14 | type: Sequelize.STRING 15 | }, 16 | userAgent: { 17 | type: Sequelize.STRING 18 | }, 19 | created_at: { 20 | type: Sequelize.DATE 21 | }, 22 | updated_at: { 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | 28 | down: (queryInterface) => { 29 | return queryInterface.dropTable('statistics'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/20200213194200-remove-unused-tables-columns.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.dropTable('subscriptions'); 4 | await queryInterface.dropTable('plans'); 5 | }, 6 | 7 | down: async (queryInterface, Sequelize) => { 8 | await queryInterface.createTable('plans', { 9 | id: { 10 | allowNull: false, 11 | autoIncrement: true, 12 | primaryKey: true, 13 | type: Sequelize.INTEGER 14 | }, 15 | name: { 16 | type: Sequelize.STRING 17 | }, 18 | price_eur: { 19 | type: Sequelize.DECIMAL(10, 2) 20 | }, 21 | space_gb: { 22 | type: Sequelize.INTEGER 23 | }, 24 | stripe_plan_id: { 25 | type: Sequelize.STRING 26 | } 27 | }); 28 | 29 | await queryInterface.createTable('subscriptions', { 30 | user: { 31 | allowNull: false, 32 | primaryKey: true, 33 | type: Sequelize.INTEGER, 34 | references: { 35 | model: 'users', 36 | key: 'id' 37 | } 38 | }, 39 | plan: { 40 | allowNull: false, 41 | primaryKey: true, 42 | type: Sequelize.INTEGER, 43 | references: { 44 | model: 'plans', 45 | key: 'id' 46 | } 47 | }, 48 | creation_time: { 49 | type: Sequelize.DATE 50 | }, 51 | stripe_customer_id: { 52 | type: Sequelize.STRING 53 | }, 54 | expiration: { 55 | type: Sequelize.DATE 56 | }, 57 | is_active: { 58 | type: Sequelize.BOOLEAN 59 | }, 60 | created_at: { 61 | allowNull: false, 62 | type: Sequelize.DATE 63 | }, 64 | updated_at: { 65 | allowNull: false, 66 | type: Sequelize.DATE 67 | } 68 | }); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /migrations/20200213234300-more_timestamps.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('folders', 'created_at', { 4 | type: Sequelize.DATE 5 | }); 6 | await queryInterface.addColumn('folders', 'updated_at', { 7 | type: Sequelize.DATE 8 | }); 9 | await queryInterface.addColumn('users', 'created_at', { type: Sequelize.DATE }); 10 | await queryInterface.addColumn('users', 'updated_at', { type: Sequelize.DATE }); 11 | }, 12 | 13 | down: async (queryInterface) => { 14 | await queryInterface.removeColumn('folders', 'created_at'); 15 | await queryInterface.removeColumn('folders', 'updated_at'); 16 | await queryInterface.removeColumn('users', 'created_at'); 17 | await queryInterface.removeColumn('users', 'updated_at'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20200214101300-bridge_fields.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.removeColumn('files', 'bucketId'); 4 | await queryInterface.changeColumn('files', 'fileId', { type: Sequelize.STRING(24) }); 5 | await queryInterface.changeColumn('files', 'bucket', { type: Sequelize.STRING(24) }); 6 | await queryInterface.changeColumn('folders', 'bucket', { type: Sequelize.STRING(24) }); 7 | await queryInterface.changeColumn('shares', 'file', { type: Sequelize.STRING(24) }); 8 | await queryInterface.changeColumn('users', 'userId', { type: Sequelize.STRING(60) }); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.addColumn('files', 'bucketId', { type: Sequelize.STRING }); 13 | await queryInterface.changeColumn('files', 'fileId', { type: Sequelize.STRING }); 14 | await queryInterface.changeColumn('files', 'bucket', { type: Sequelize.STRING }); 15 | await queryInterface.changeColumn('folders', 'bucket', { type: Sequelize.STRING }); 16 | await queryInterface.changeColumn('shares', 'file', { type: Sequelize.STRING }); 17 | await queryInterface.changeColumn('users', 'userId', { type: Sequelize.STRING }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20200225130000-uncamelize.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.renameColumn('files', 'fileId', 'file_id'); 4 | await queryInterface.renameColumn('folders', 'parentId', 'parent_id'); 5 | await queryInterface.renameColumn('statistics', 'userAgent', 'user_agent'); 6 | await queryInterface.renameColumn('users', 'userId', 'user_id'); 7 | await queryInterface.renameColumn('users', 'isFreeTier', 'is_free_tier'); 8 | await queryInterface.renameColumn('users', 'storeMnemonic', 'store_mnemonic'); 9 | await queryInterface.renameColumn('users', 'hKey', 'h_key'); 10 | await queryInterface.renameColumn('users', 'secret_2FA', 'secret_2_f_a'); 11 | await queryInterface.renameColumn('users', 'errorLoginCount', 'error_login_count'); 12 | }, 13 | 14 | down: async (queryInterface) => { 15 | await queryInterface.renameColumn('files', 'file_id', 'fileId'); 16 | await queryInterface.renameColumn('folders', 'parent_id', 'parentId'); 17 | await queryInterface.renameColumn('statistics', 'user_agent', 'userAgent'); 18 | await queryInterface.renameColumn('users', 'user_id', 'userId'); 19 | await queryInterface.renameColumn('users', 'is_free_tier', 'isFreeTier'); 20 | await queryInterface.renameColumn('users', 'store_mnemonic', 'storeMnemonic'); 21 | await queryInterface.renameColumn('users', 'h_key', 'hKey'); 22 | await queryInterface.renameColumn('users', 'secret_2_f_a', 'secret_2FA'); 23 | await queryInterface.renameColumn('users', 'error_login_count', 'errorLoginCount'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /migrations/20200317050000-increase_file_size.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.changeColumn('files', 'size', { type: Sequelize.BIGINT.UNSIGNED }); 4 | }, 5 | 6 | down: (queryInterface, Sequelize) => { 7 | return queryInterface.changeColumn('files', 'size', { type: Sequelize.INTEGER }); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20200404123030-add-columns-user_activityAt-is-email-activity-sended.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'is_email_activity_sended', { type: Sequelize.BOOLEAN, defaultValue: false }); 4 | }, 5 | down: (queryInterface) => { 6 | return queryInterface.removeColumn('users', 'is_email_activity_sended'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /migrations/20200413192000-add-is_folder-column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('shares', 'is_folder', { 4 | type: Sequelize.BOOLEAN, 5 | defaultValue: false 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('shares', 'is_folder'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20200421030600-add-column-shares-view.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('shares', 'views', { 4 | type: Sequelize.INTEGER, 5 | defaultValue: 1 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('shares', 'views'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20200505045000-folders-files-name-length.js: -------------------------------------------------------------------------------- 1 | // Filename max length on windows/unix is 255 2 | // Encrypted filenames of 255 chars is 512 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn('files', 'name', { type: Sequelize.STRING(512) }); 6 | await queryInterface.changeColumn('folders', 'name', { type: Sequelize.STRING(512) }); 7 | }, 8 | 9 | down: async (queryInterface, Sequelize) => { 10 | await queryInterface.changeColumn('files', 'name', { type: Sequelize.STRING }); 11 | await queryInterface.changeColumn('folders', 'name', { type: Sequelize.STRING }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20200611135400-referral_field.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'referral', { type: Sequelize.STRING, defaultValue: null }); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('users', 'referral'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20200612074700-sync_date.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'sync_date', Sequelize.DATE); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('users', 'sync_date'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20200620125500-encryption_versioning.js: -------------------------------------------------------------------------------- 1 | // Filename max length on windows/unix is 255 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.addColumn('files', 'encrypt_version', { type: Sequelize.STRING(20) }); 5 | await queryInterface.addColumn('folders', 'encrypt_version', { type: Sequelize.STRING(20) }); 6 | }, 7 | 8 | down: async (queryInterface) => { 9 | await queryInterface.removeColumn('files', 'encrypt_version'); 10 | await queryInterface.removeColumn('folders', 'encrypt_version'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20200702110000-create_teams_members_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('teamsmembers', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | id_team: { 11 | type: Sequelize.STRING 12 | }, 13 | user: { 14 | type: Sequelize.STRING 15 | }, 16 | is_active: { 17 | type: Sequelize.BOOLEAN 18 | } 19 | }); 20 | }, 21 | 22 | down: (queryInterface) => { 23 | return queryInterface.dropTable('teamsmembers'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /migrations/20200709130000-add-column-folder-id_team.js: -------------------------------------------------------------------------------- 1 | // Filename max length on windows/unix is 255 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.addColumn('folders', 'id_team', { type: Sequelize.INTEGER }); 5 | }, 6 | 7 | down: (queryInterface) => { 8 | return queryInterface.removeColumn('folders', 'id_team'); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /migrations/20200715160002-create_teams_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('teams', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | user: { 11 | type: Sequelize.STRING 12 | }, 13 | name: { 14 | type: Sequelize.STRING 15 | }, 16 | bridge_user: { 17 | type: Sequelize.STRING 18 | }, 19 | bridge_password: { 20 | type: Sequelize.STRING 21 | }, 22 | bridge_email: { 23 | type: Sequelize.STRING 24 | } 25 | }); 26 | }, 27 | 28 | down: (queryInterface) => { 29 | return queryInterface.dropTable('teams'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/20200720110000-add-register_from-column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('statistics', 'action', { type: Sequelize.STRING(40) }); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('statistics', 'action'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20200809130743-create_team-invitations_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('teamsinvitations', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | id_team: { 11 | type: Sequelize.INTEGER 12 | }, 13 | user: { 14 | type: Sequelize.STRING 15 | }, 16 | token: { 17 | type: Sequelize.STRING 18 | }, 19 | is_used: { 20 | type: Sequelize.BOOLEAN 21 | } 22 | }); 23 | }, 24 | 25 | down: (queryInterface) => { 26 | return queryInterface.dropTable('teamsinvitations'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /migrations/20200922134200-users_uuid.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'uuid', { 4 | unique: true, 5 | type: Sequelize.STRING(36) 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'uuid'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20200924083100-users_last_resend_date.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'last_resend', Sequelize.DATE); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('users', 'last_resend'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20200924095700-users_referred.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'referred', { type: Sequelize.STRING(36) }); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('users', 'referred'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20200924100500-users-credit.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'credit', { 4 | type: Sequelize.INTEGER, 5 | defaultValue: 0 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'credit'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20201005153100-name_refactor.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.renameColumn('teams', 'bridge_email', 'bridge_mnemonic'); 4 | await queryInterface.renameColumn('teams', 'user', 'admin'); 5 | await queryInterface.removeColumn('teamsinvitations', 'is_used'); 6 | await queryInterface.removeColumn('teamsmembers', 'is_active'); 7 | }, 8 | 9 | down: async (queryInterface, Sequelize) => { 10 | await queryInterface.addColumn('teamsmembers', 'is_active', { 11 | type: Sequelize.BOOLEAN 12 | }); 13 | await queryInterface.addColumn('teamsinvitations', 'is_used', { 14 | type: Sequelize.BOOLEAN 15 | }); 16 | await queryInterface.renameColumn('teams', 'admin', 'user'); 17 | await queryInterface.renameColumn('teams', 'bridge_mnemonic', 'bridge_email'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20201028093605-create_key_server_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('keyserver', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | user_id: { 11 | type: Sequelize.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'users', 15 | key: 'id' 16 | } 17 | }, 18 | public_key: { 19 | type: Sequelize.STRING(920), 20 | allowNull: false 21 | }, 22 | private_key: { 23 | type: Sequelize.STRING(1356), 24 | allowNull: false 25 | }, 26 | revocation_key: { 27 | type: Sequelize.STRING(476), 28 | allowNull: false 29 | }, 30 | encrypt_version: { 31 | type: Sequelize.STRING, 32 | allowNull: true 33 | }, 34 | 35 | created_at: { 36 | type: Sequelize.DATE 37 | }, 38 | updated_at: { 39 | type: Sequelize.DATE 40 | } 41 | 42 | }); 43 | }, 44 | 45 | down: (queryInterface) => { 46 | return queryInterface.dropTable('keyserver'); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /migrations/20201030134200-add_welcome_column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'welcome_pack', { 4 | type: Sequelize.BOOLEAN, 5 | defaultValue: false 6 | }); 7 | }, 8 | down: (queryInterface) => { 9 | return queryInterface.removeColumn('users', 'welcome_pack'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20201102142735-add_columns_teams_members.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('teamsmembers', 'bridge_password', { type: Sequelize.STRING }); 4 | await queryInterface.addColumn('teamsmembers', 'bridge_mnemonic', { type: Sequelize.STRING }); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.removeColumn('teamsmembers', 'bridge_password'); 9 | await queryInterface.removeColumn('teamsmembers', 'bridge_mnemonic'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20201106130534-change_strings_teams_members.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.changeColumn('teamsmembers', 'bridge_password', { 4 | type: Sequelize.STRING(528) 5 | }); 6 | await queryInterface.changeColumn('teamsmembers', 'bridge_mnemonic', { 7 | type: Sequelize.STRING(428) 8 | }); 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.changeColumn('teamsmembers', 'bridge_password', { type: Sequelize.STRING }); 13 | await queryInterface.changeColumn('teamsmembers', 'bridge_mnemonic', { type: Sequelize.STRING }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20201109123924-change_type_mnemonic_teams.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.changeColumn('teams', 'bridge_mnemonic', { type: Sequelize.STRING(900) }); 4 | }, 5 | 6 | down: (queryInterface, Sequelize) => { 7 | return queryInterface.changeColumn('teams', 'bridge_mnemonic', { type: Sequelize.STRING }); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20201110085023-change_types.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.changeColumn('teamsmembers', 'bridge_mnemonic', { type: Sequelize.STRING(2000) }); 4 | await queryInterface.changeColumn('teams', 'bridge_mnemonic', { type: Sequelize.STRING(2000) }); 5 | }, 6 | 7 | down: async (queryInterface, Sequelize) => { 8 | await queryInterface.changeColumn('teamsmembers', 'bridge_mnemonic', { type: Sequelize.STRING }); 9 | await queryInterface.changeColumn('teams', 'bridge_mnemonic', { type: Sequelize.STRING }); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20201111170013-add_columns_teams_invitation.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('teamsinvitations', 'bridge_password', { type: Sequelize.STRING(2000) }); 4 | await queryInterface.addColumn('teamsinvitations', 'mnemonic', { type: Sequelize.STRING(2000) }); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.removeColumn('teamsinvitations', 'bridge_password'); 9 | await queryInterface.removeColumn('teamsinvitations', 'mnemonic'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20201111174925-change_type_teams_member.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.changeColumn('teamsmembers', 'bridge_password', { type: Sequelize.STRING(2000) }); 4 | }, 5 | 6 | down: (queryInterface, Sequelize) => { 7 | return queryInterface.changeColumn('teamsmembers', 'bridge_password', { type: Sequelize.STRING }); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20201207103900-create_photo_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('photos', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | photo_id: { 11 | type: Sequelize.STRING(24) 12 | }, 13 | name: { 14 | type: Sequelize.STRING(512) 15 | }, 16 | type: { 17 | type: Sequelize.STRING(50) 18 | }, 19 | size: { 20 | type: Sequelize.BIGINT.UNSIGNED 21 | }, 22 | bucket_id: { 23 | type: Sequelize.STRING(24) 24 | } 25 | }); 26 | }, 27 | 28 | down: (queryInterface) => { 29 | return queryInterface.dropTable('photos'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /migrations/20210108113800-create_users_photos_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('usersphotos', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | user_id: { 11 | type: Sequelize.INTEGER(60), 12 | references: { 13 | model: 'users', 14 | key: 'id' 15 | } 16 | } 17 | }); 18 | }, 19 | 20 | down: (queryInterface) => { 21 | return queryInterface.dropTable('usersphotos'); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /migrations/20210108135100-create_previews_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('previews', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING(512) 12 | }, 13 | type: { 14 | type: Sequelize.STRING(50) 15 | }, 16 | size: { 17 | type: Sequelize.BIGINT.UNSIGNED 18 | } 19 | }); 20 | }, 21 | 22 | down: (queryInterface) => { 23 | return queryInterface.dropTable('previews'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /migrations/20210111194200-add_column_appsumo.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'register_completed', { 4 | type: Sequelize.BOOLEAN, 5 | defaultValue: true 6 | }); 7 | }, 8 | down: (queryInterface) => { 9 | return queryInterface.removeColumn('users', 'register_completed'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210112114600-create-albums-table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('albums', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | name: { 11 | type: Sequelize.STRING(512) 12 | }, 13 | user_id: { 14 | type: Sequelize.INTEGER, 15 | references: { 16 | model: 'usersphotos', 17 | key: 'id' 18 | } 19 | } 20 | }); 21 | }, 22 | 23 | down: (queryInterface) => { 24 | return queryInterface.dropTable('albums'); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /migrations/20210112124500-add-foreignkeys-userphotos.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('usersphotos', 'root_album_id', { 4 | type: Sequelize.STRING(24) 5 | }); 6 | await queryInterface.addColumn('usersphotos', 'root_preview_id', { 7 | type: Sequelize.STRING(24) 8 | }); 9 | }, 10 | 11 | down: async (queryInterface) => { 12 | await queryInterface.removeColumn('usersphotos', 'root_album_id'); 13 | await queryInterface.removeColumn('usersphotos', 'root_preview_id'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20210112130700-add-foreignkeys-previews.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('previews', 'preview_id', { 4 | type: Sequelize.STRING(24) 5 | }); 6 | await queryInterface.addColumn('previews', 'photo_id', { 7 | type: Sequelize.INTEGER, 8 | references: { 9 | model: 'photos', 10 | key: 'id' 11 | } 12 | }); 13 | }, 14 | 15 | down: async (queryInterface) => { 16 | await queryInterface.removeColumn('previews', 'preview_id'); 17 | await queryInterface.removeColumn('previews', 'photo_id'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20210112134400-add-foreignkey-photos.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('photos', 'user_id', { 4 | type: Sequelize.INTEGER, 5 | references: { 6 | model: 'usersphotos', 7 | key: 'id' 8 | } 9 | }); 10 | }, 11 | 12 | down: (queryInterface) => { 13 | return queryInterface.removeColumn('photos', 'user_id'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20210112143500-create-photosalbum-jointable.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('photosalbums', { 4 | photo_id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | references: { 8 | model: 'photos', 9 | key: 'id' 10 | } 11 | }, 12 | album_id: { 13 | type: Sequelize.INTEGER, 14 | primaryKey: true, 15 | references: { 16 | model: 'albums', 17 | key: 'id' 18 | } 19 | } 20 | }); 21 | }, 22 | 23 | down: (queryInterface) => { 24 | return queryInterface.dropTable('photosalbums'); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /migrations/20210113154400-add-photo-timestamps.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('photos', 'created_at', { type: Sequelize.DATE }); 4 | await queryInterface.addColumn('photos', 'updated_at', { type: Sequelize.DATE }); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.removeColumn('photos', 'created_at'); 9 | await queryInterface.removeColumn('photos', 'updated_at'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210113154900-add-usersphotos-timestamps.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('usersphotos', 'created_at', { type: Sequelize.DATE }); 4 | await queryInterface.addColumn('usersphotos', 'updated_at', { type: Sequelize.DATE }); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.removeColumn('usersphotos', 'created_at'); 9 | await queryInterface.removeColumn('usersphotos', 'updated_at'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210118165000-create_appsumo_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('appsumo', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | user_id: { 11 | type: Sequelize.INTEGER, 12 | references: { 13 | model: 'users', 14 | key: 'id' 15 | }, 16 | onDelete: 'CASCADE' 17 | }, 18 | plan_id: { 19 | type: Sequelize.STRING, 20 | allowNull: false 21 | }, 22 | uuid: { 23 | type: Sequelize.STRING(36), 24 | allowNull: false 25 | }, 26 | invoice_item_uuid: { 27 | type: Sequelize.STRING(36), 28 | allowNull: false 29 | }, 30 | created_at: { 31 | allowNull: false, 32 | type: Sequelize.DATE 33 | }, 34 | updated_at: { 35 | allowNull: false, 36 | type: Sequelize.DATE 37 | } 38 | }); 39 | }, 40 | 41 | down: (queryInterface) => { 42 | return queryInterface.dropTable('appsumo'); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /migrations/20210120081100-remove_unused.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface) => { 3 | return queryInterface.removeColumn('users', 'referred'); 4 | }, 5 | 6 | down: (queryInterface, Sequelize) => { 7 | return queryInterface.addColumn('users', 'referred', { type: Sequelize.STRING(36) }); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20210121161600-add-album-timestamps.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('albums', 'created_at', { type: Sequelize.DATE }); 4 | await queryInterface.addColumn('albums', 'updated_at', { type: Sequelize.DATE }); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.removeColumn('albums', 'created_at'); 9 | await queryInterface.removeColumn('albums', 'updated_at'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210125123500-add-delete-folder-id.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('usersphotos', 'delete_folder_id', { type: Sequelize.INTEGER }); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('usersphotos', 'delete_folder_id'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20210204142900-add-columns-preview.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('previews', 'hash', { type: Sequelize.STRING }); 4 | await queryInterface.addColumn('previews', 'created_at', { type: Sequelize.DATE }); 5 | await queryInterface.addColumn('previews', 'updated_at', { type: Sequelize.DATE }); 6 | }, 7 | 8 | down: async (queryInterface) => { 9 | await queryInterface.removeColumn('previews', 'hash'); 10 | await queryInterface.removeColumn('previews', 'created_at'); 11 | await queryInterface.removeColumn('previews', 'updated_at'); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20210205135900-add-photosalbums-timestamps.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('photosalbums', 'created_at', { type: Sequelize.DATE }); 4 | await queryInterface.addColumn('photosalbums', 'updated_at', { type: Sequelize.DATE }); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.removeColumn('photosalbums', 'created_at'); 9 | await queryInterface.removeColumn('photosalbums', 'updated_at'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210210112500-openpgp_fit_size.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.changeColumn('keyserver', 'public_key', { type: Sequelize.STRING(1024) }); 4 | await queryInterface.changeColumn('keyserver', 'private_key', { type: Sequelize.STRING(2000) }); 5 | await queryInterface.changeColumn('keyserver', 'revocation_key', { type: Sequelize.STRING(1024) }); 6 | }, 7 | 8 | down: async (queryInterface, Sequelize) => { 9 | await queryInterface.changeColumn('keyserver', 'public_key', { type: Sequelize.STRING(920) }); 10 | await queryInterface.changeColumn('keyserver', 'private_key', { type: Sequelize.STRING(1356) }); 11 | await queryInterface.changeColumn('keyserver', 'revocation_key', { type: Sequelize.STRING(476) }); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20210211181100-add-preview-bucket.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('previews', 'bucket_id', { type: Sequelize.STRING(24) }); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('previews', 'bucket_id'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20210211181800-add-photo-hash.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('photos', 'hash', { type: Sequelize.STRING }); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('photos', 'hash'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20210215124900-mod-fileId-photo-preview.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.renameColumn('photos', 'photo_id', 'file_id'); 4 | await queryInterface.renameColumn('previews', 'preview_id', 'file_id'); 5 | }, 6 | 7 | down: async (queryInterface) => { 8 | await queryInterface.renameColumn('photos', 'file_id', 'photo_id'); 9 | await queryInterface.renameColumn('previews', 'file_id', 'preview_id'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210309150800-add-total_members-teams.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('teams', 'total_members', { type: Sequelize.INTEGER }); 4 | }, 5 | 6 | down: (queryInterface) => { 7 | return queryInterface.removeColumn('teams', 'total_members'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20210415144500-add-creation-time-photos.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('photos', 'creation_time', { 4 | type: Sequelize.DATE, 5 | allowNull: false 6 | }); 7 | await queryInterface.addColumn('photos', 'device', { type: Sequelize.STRING(10) }); 8 | }, 9 | 10 | down: async (queryInterface) => { 11 | await queryInterface.removeColumn('photos', 'creation_time'); 12 | await queryInterface.removeColumn('photos', 'device'); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/20210415161000-add_file_foreign_key.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface) => { 3 | return queryInterface.addConstraint('files', { 4 | type: 'FOREIGN KEY', 5 | fields: ['folder_id'], 6 | name: 'files_folder_id_foreign_id_fk', 7 | references: { 8 | table: 'folders', 9 | field: 'id' 10 | }, 11 | onDelete: 'cascade', 12 | onUpdate: 'cascade' 13 | }); 14 | }, 15 | 16 | down: (queryInterface) => { 17 | return queryInterface.removeConstraint('files', 'files_folder_id_foreign_id_fk'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20210416092200-create_index_folder_user_id.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface) => { 3 | return queryInterface.addIndex('folders', { 4 | fields: ['user_id'], 5 | name: 'user_id_folders_index' 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeIndex('folders', 'user_id_folders_index'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20210416201300-remove_statistics_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | return queryInterface.dropTable('statistics'); 4 | }, 5 | 6 | down: (queryInterface, Sequelize) => { 7 | return queryInterface.createTable('statistics', { 8 | id: { 9 | type: Sequelize.INTEGER, 10 | primaryKey: true, 11 | allowNull: false, 12 | autoIncrement: true 13 | }, 14 | name: { 15 | type: Sequelize.STRING 16 | }, 17 | user: { 18 | type: Sequelize.STRING 19 | }, 20 | user_agent: { 21 | type: Sequelize.STRING 22 | }, 23 | created_at: { 24 | type: Sequelize.DATE 25 | }, 26 | updated_at: { 27 | type: Sequelize.DATE 28 | }, 29 | action: { 30 | type: Sequelize.STRING(40) 31 | } 32 | }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /migrations/20210416202800-keyserver_cascade.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | // Default constraint name is different in each 4 | const foreignKeys = await queryInterface.getForeignKeyReferencesForTable('keyserver'); 5 | const userIdForeignKey = foreignKeys.find((constraint) => constraint.columnName === 'user_id'); 6 | 7 | await queryInterface.removeConstraint('keyserver', userIdForeignKey.constraintName); 8 | await queryInterface.addConstraint('keyserver', { 9 | type: 'FOREIGN KEY', 10 | fields: ['user_id'], 11 | name: 'keyserver_ibfk_1', 12 | references: { 13 | table: 'users', 14 | field: 'id' 15 | }, 16 | onDelete: 'cascade', 17 | onUpdate: 'cascade' 18 | }); 19 | }, 20 | 21 | down: async (queryInterface) => { 22 | await queryInterface.removeConstraint('keyserver', 'keyserver_ibfk_1'); 23 | await queryInterface.addConstraint('keyserver', { 24 | type: 'FOREIGN KEY', 25 | fields: ['user_id'], 26 | name: 'keyserver_ibfk_1', 27 | references: { 28 | table: 'users', 29 | field: 'id' 30 | } 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /migrations/20210419142100-remove_folder_ancestors.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.removeConstraint('foldersancestors', 'foldersancestors_ibfk_1'); 4 | await queryInterface.removeConstraint('foldersancestors', 'foldersancestors_ibfk_2'); 5 | await queryInterface.removeColumn('folders', 'hierarchy_level'); 6 | return queryInterface.dropTable('foldersancestors'); 7 | }, 8 | 9 | down: async (queryInterface, Sequelize) => { 10 | await queryInterface.addColumn('folders', 'hierarchy_level', { 11 | type: Sequelize.INTEGER 12 | }); 13 | await queryInterface.createTable('foldersancestors', { 14 | folder_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | ancestor_id: { 18 | type: Sequelize.INTEGER 19 | } 20 | }); 21 | 22 | await queryInterface.addConstraint('foldersancestors', { 23 | type: 'PRIMARY KEY', 24 | fields: ['folder_id', 'ancestor_id'] 25 | }); 26 | 27 | await queryInterface.addConstraint('foldersancestors', { 28 | type: 'UNIQUE', 29 | fields: ['folder_id', 'ancestor_id'], 30 | name: 'foldersancestors_folder_id_ancestor_id_unique' 31 | }); 32 | 33 | await queryInterface.addConstraint('foldersancestors', { 34 | type: 'FOREIGN KEY', 35 | fields: ['folder_id'], 36 | name: 'foldersancestors_ibfk_1', 37 | references: { 38 | table: 'folders', 39 | field: 'id' 40 | }, 41 | onDelete: 'cascade', 42 | onUpdate: 'cascade' 43 | }); 44 | 45 | await queryInterface.addConstraint('foldersancestors', { 46 | type: 'FOREIGN KEY', 47 | fields: ['ancestor_id'], 48 | name: 'foldersancestors_ibfk_2', 49 | references: { 50 | table: 'folders', 51 | field: 'id' 52 | }, 53 | onDelete: 'cascade', 54 | onUpdate: 'cascade' 55 | }); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /migrations/20210419151700-remove_unused_columns.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.removeColumn('folders', 'id_team'); 4 | return queryInterface.removeColumn('users', 'is_free_tier'); 5 | }, 6 | 7 | down: async (queryInterface, Sequelize) => { 8 | await queryInterface.addColumn('folders', 'id_team', { type: Sequelize.INTEGER }); 9 | await queryInterface.addColumn('users', 'is_free_tier', { type: Sequelize.BOOLEAN }); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210520164800-add-deleted-column-files.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('files', 'deleted', { 4 | type: Sequelize.BOOLEAN, 5 | defaultValue: false, 6 | allowNull: false 7 | }); 8 | 9 | await queryInterface.addColumn('files', 'deleted_at', { type: Sequelize.DATE }); 10 | }, 11 | 12 | down: async (queryInterface) => { 13 | await queryInterface.removeColumn('files', 'deleted'); 14 | await queryInterface.removeColumn('files', 'deleted_at'); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/20210715151336-coupon.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('coupons', { 4 | code: { 5 | type: Sequelize.STRING, 6 | primaryKey: true, 7 | allowNull: false 8 | }, 9 | email: { 10 | type: Sequelize.STRING 11 | }, 12 | times_reedemed: { 13 | type: Sequelize.INTEGER 14 | } 15 | }); 16 | }, 17 | 18 | down: async (queryInterface) => { 19 | return queryInterface.dropTable('coupons'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /migrations/20210803115504-add-encryption-key-share.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.addColumn('shares', 'encryption_key', { 6 | type: Sequelize.STRING(64), 7 | allowNull: false 8 | }); 9 | }, 10 | 11 | down: (queryInterface) => { 12 | return queryInterface.removeColumn('shares', 'encryption_key') 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/20210803122725-make-token-unique-share.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface) => { 5 | return queryInterface.addConstraint('shares', { 6 | type: 'UNIQUE', 7 | fields: ['token'], 8 | name: 'token_UNIQUE' 9 | }); 10 | }, 11 | 12 | down: (queryInterface) => { 13 | return queryInterface.removeConstraint('shares', 'token_UNIQUE'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20210804071033-add-bucket-share.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.addColumn('shares', 'bucket', { 6 | type: Sequelize.STRING(24), 7 | allowNull: false 8 | }); 9 | }, 10 | 11 | down: (queryInterface) => { 12 | return queryInterface.removeColumn('shares', 'bucket') 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/20210804104524-add-file-token-share.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.addColumn('shares', 'file_token', { 6 | type: Sequelize.STRING(64), 7 | allowNull: false 8 | }) 9 | }, 10 | 11 | down: (queryInterface) => { 12 | return queryInterface.removeColumn('shares', 'file_token'); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/20210906090000-add_username_users.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('users', 'username', { 4 | type: Sequelize.STRING, 5 | unique: true 6 | }); 7 | }, 8 | 9 | down: async (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'username'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20210906090001-add_bridge_user_users.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('users', 'bridge_user', { 4 | type: Sequelize.STRING 5 | }); 6 | }, 7 | 8 | down: async (queryInterface) => { 9 | return queryInterface.removeColumn('users', 'bridge_user'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /migrations/20210906090002-add_shared_workspace_users.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'shared_workspace', { type: Sequelize.BOOLEAN, defaultValue: false }); 4 | }, 5 | down: (queryInterface) => { 6 | return queryInterface.removeColumn('users', 'shared_workspace'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /migrations/20210906090003-add_temp_key_users.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'temp_key', { type: Sequelize.STRING(256) }); 4 | }, 5 | down: (queryInterface) => { 6 | return queryInterface.removeColumn('users', 'temp_key'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /migrations/20210906090550-create_device_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('devices', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | mac: { 11 | type: Sequelize.STRING, 12 | allowNull: false 13 | }, 14 | userId: { 15 | type: Sequelize.INTEGER, 16 | references: { 17 | model: 'users', 18 | key: 'id' 19 | } 20 | }, 21 | name: { 22 | type: Sequelize.STRING 23 | }, 24 | createdAt: { 25 | type: Sequelize.DATE 26 | }, 27 | updatedAt: { 28 | type: Sequelize.DATE 29 | } 30 | }); 31 | }, 32 | 33 | down: (queryInterface) => { 34 | return queryInterface.dropTable('devices'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /migrations/20210906091250-create_backup_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('backups', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | path: { 11 | type: Sequelize.STRING 12 | }, 13 | fileId: { 14 | type: Sequelize.STRING(24) 15 | }, 16 | deviceId: { 17 | type: Sequelize.INTEGER, 18 | references: { 19 | model: 'devices', 20 | key: 'id' 21 | } 22 | }, 23 | userId: { 24 | type: Sequelize.INTEGER, 25 | references: { 26 | model: 'users', 27 | key: 'id' 28 | } 29 | }, 30 | interval: { 31 | type: Sequelize.INTEGER 32 | }, 33 | size: { 34 | type: Sequelize.BIGINT.UNSIGNED 35 | }, 36 | bucket: { 37 | type: Sequelize.STRING(24) 38 | }, 39 | createdAt: { 40 | type: Sequelize.DATE 41 | }, 42 | updatedAt: { 43 | type: Sequelize.DATE 44 | }, 45 | encrypt_version: { 46 | type: Sequelize.STRING 47 | }, 48 | hash: { 49 | type: Sequelize.STRING 50 | } 51 | }); 52 | }, 53 | 54 | down: (queryInterface) => { 55 | return queryInterface.dropTable('backups'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /migrations/20210906095148-add_backups_bucket_column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.addColumn('users', 'backups_bucket', { 4 | type: Sequelize.STRING, 5 | allowNull: true 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'backups_bucket'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20210909091644-add_enabled_and_last_backup_at.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('backups', 'enabled', { 4 | type: Sequelize.BOOLEAN, 5 | defaultValue: true 6 | }); 7 | await queryInterface.addColumn('backups', 'lastBackupAt', { 8 | type: Sequelize.DATE 9 | }); 10 | }, 11 | 12 | down: async (queryInterface) => { 13 | await queryInterface.removeColumn('backups', 'enabled'); 14 | await queryInterface.removeColumn('backups', 'lastBackupAt'); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/20210920091313-modify_path_column_type.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return Promise.all([ 4 | queryInterface.changeColumn('backups', 'path', { 5 | type: Sequelize.TEXT, 6 | allowNull: true, 7 | }), 8 | ]); 9 | }, 10 | 11 | down: (queryInterface, Sequelize) => { 12 | return Promise.all([ 13 | queryInterface.changeColumn('backups', 'path', { 14 | type: Sequelize.STRING, 15 | allowNull: true, 16 | }), 17 | ]); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20210921083232-create_index_mac_backups.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface) => { 3 | return queryInterface.addIndex('devices', { 4 | fields: ['mac', 'userId'], 5 | name: 'mac_device_index' 6 | }); 7 | }, 8 | 9 | down: (queryInterface) => { 10 | return queryInterface.removeIndex('devices', 'mac_device_index'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20210927101644-add_platform_column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('devices', 'platform', { 4 | type: Sequelize.STRING(20), 5 | allowNull: true 6 | }); 7 | }, 8 | 9 | down: async (queryInterface) => { 10 | await queryInterface.removeColumn('devices', 'platform'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20211004060909-add_userid_files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.addColumn('files', 'user_id', { 6 | type: Sequelize.INTEGER, 7 | allowNull: true, 8 | references: { 9 | model: 'users', 10 | key: 'id' 11 | }, 12 | onDelete: 'CASCADE' 13 | }); 14 | }, 15 | 16 | down: async (queryInterface) => { 17 | await queryInterface.removeColumn('files', 'user_id'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20211004130500-invitation-guest-members.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('invitations', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true 9 | }, 10 | host: { 11 | type: Sequelize.INTEGER, 12 | allowNull: false, 13 | references: { 14 | model: 'users', 15 | key: 'id' 16 | }, 17 | onDelete: 'cascade', 18 | onUpdate: 'cascade' 19 | }, 20 | guest: { 21 | type: Sequelize.INTEGER, 22 | allowNull: false, 23 | references: { 24 | model: 'users', 25 | key: 'id' 26 | }, 27 | onDelete: 'cascade', 28 | onUpdate: 'cascade' 29 | }, 30 | invite_id: { 31 | type: Sequelize.STRING(216), 32 | allowNull: false 33 | }, 34 | accepted: { 35 | type: Sequelize.BOOLEAN, 36 | allowNull: false, 37 | defaultValue: false 38 | }, 39 | created_at: { 40 | type: Sequelize.DATE 41 | }, 42 | updated_at: { 43 | type: Sequelize.DATE 44 | } 45 | }); 46 | }, 47 | down: (queryInterface) => { 48 | return queryInterface.dropTable('invitations'); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /migrations/20211008103051-create_plans_table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.createTable('plans', { 6 | id: { 7 | type: Sequelize.INTEGER, 8 | primaryKey: true, 9 | allowNull: false, 10 | autoIncrement: true 11 | }, 12 | user_id: { 13 | type: Sequelize.INTEGER, 14 | references: { 15 | model: 'users', 16 | key: 'id' 17 | }, 18 | onUpdate: 'CASCADE', 19 | onDelete: 'CASCADE' 20 | }, 21 | name: { 22 | type: Sequelize.STRING 23 | }, 24 | type: { 25 | type: Sequelize.ENUM('subscription', 'one_time') 26 | }, 27 | created_at: { 28 | type: Sequelize.DATE 29 | }, 30 | updated_at: { 31 | type: Sequelize.DATE 32 | }, 33 | limit: { 34 | type: Sequelize.BIGINT.UNSIGNED, 35 | allowNull: false, 36 | defaultValue: 0 37 | } 38 | }); 39 | 40 | await queryInterface.addIndex('plans', ['name'], { 41 | name: 'plans_name_idx' 42 | }); 43 | }, 44 | 45 | down: async (queryInterface) => { 46 | await queryInterface.removeIndex('plans', 'plans_name_idx'); 47 | return queryInterface.dropTable('plans'); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /migrations/20211019101644-add_modification_time_column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('files', 'modification_time', { 4 | type: Sequelize.DATE, 5 | allowNull: false, 6 | defaultValue: Sequelize.fn('now') 7 | }); 8 | }, 9 | 10 | down: async (queryInterface) => { 11 | await queryInterface.removeColumn('files', 'modification_time'); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20211026055551-remove_user_old_referral_column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | return queryInterface.removeColumn('users', 'referral'); 4 | }, 5 | 6 | down: async (queryInterface, Sequelize) => { 7 | return queryInterface.addColumn('users', 'referral', { type: Sequelize.STRING, defaultValue: null }); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/20211026073048-add_user_referral_code_and_referrer.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('users', 'referral_code', { 4 | type: Sequelize.STRING, 5 | allowNull: true 6 | }); 7 | 8 | await queryInterface.addColumn('users', 'referrer', { 9 | type: Sequelize.STRING, 10 | allowNull: true 11 | }); 12 | }, 13 | 14 | down: async (queryInterface) => { 15 | await queryInterface.removeColumn('users', 'referrer'); 16 | 17 | await queryInterface.removeColumn('users', 'referral_code'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/20211026094113-create_index_users_referral_code.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | return queryInterface.addIndex('users', { 4 | fields: ['referral_code'], 5 | name: 'referral_code_index' 6 | }); 7 | }, 8 | 9 | down: async (queryInterface) => { 10 | return queryInterface.removeIndex('users', 'referral_code_index'); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20211026124013-users_referral_code_uniqueness.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | return queryInterface.changeColumn('users', 'referral_code', { 4 | type: Sequelize.STRING, 5 | unique: true, 6 | allowNull: false 7 | }); 8 | }, 9 | 10 | down: async (queryInterface, Sequelize) => { 11 | return queryInterface.changeColumn('users', 'referral_code', { 12 | type: Sequelize.STRING, 13 | unique: false, 14 | allowNull: true 15 | }); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /migrations/20211105180000-create_referrals_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('referrals', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | autoIncrement: true, 8 | allowNull: false 9 | }, 10 | key: { 11 | type: Sequelize.STRING, 12 | allowNull: false, 13 | unique: true 14 | }, 15 | type: { 16 | type: Sequelize.ENUM('storage'), 17 | allowNull: false 18 | }, 19 | credit: { 20 | type: Sequelize.BIGINT.UNSIGNED, 21 | allowNull: false 22 | }, 23 | steps: { 24 | type: Sequelize.INTEGER, 25 | allowNull: false 26 | }, 27 | enabled: { 28 | type: Sequelize.BOOLEAN, 29 | allowNull: false, 30 | defaultValue: true 31 | }, 32 | created_at: { 33 | type: Sequelize.DATE 34 | }, 35 | updated_at: { 36 | type: Sequelize.DATE 37 | } 38 | }); 39 | }, 40 | 41 | down: async (queryInterface) => { 42 | await queryInterface.dropTable('referrals'); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /migrations/20211105180600-create_users_referrals_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('users_referrals', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | autoIncrement: true, 8 | allowNull: false 9 | }, 10 | user_id: { 11 | type: Sequelize.INTEGER, 12 | references: { 13 | model: 'users', 14 | key: 'id' 15 | }, 16 | onUpdate: 'CASCADE', 17 | onDelete: 'CASCADE', 18 | allowNull: false 19 | }, 20 | referral_id: { 21 | type: Sequelize.INTEGER, 22 | references: { 23 | model: 'referrals', 24 | key: 'id' 25 | }, 26 | onUpdate: 'CASCADE', 27 | onDelete: 'CASCADE', 28 | allowNull: false 29 | }, 30 | referred: { 31 | type: Sequelize.STRING, 32 | allowNull: true 33 | }, 34 | start_date: { 35 | type: Sequelize.DATE, 36 | allowNull: false 37 | }, 38 | expiration_date: { 39 | type: Sequelize.DATE, 40 | allowNull: true 41 | }, 42 | applied: { 43 | type: Sequelize.BOOLEAN, 44 | allowNull: false, 45 | defaultValue: false 46 | }, 47 | created_at: { 48 | type: Sequelize.DATE 49 | }, 50 | updated_at: { 51 | type: Sequelize.DATE 52 | } 53 | }); 54 | }, 55 | 56 | down: async (queryInterface) => { 57 | await queryInterface.dropTable('users_referrals'); 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /migrations/20220223084246-create_index_folders_parent_id.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.addIndex('folders', ['parent_id'], { 6 | name: 'folders_parent_id_idx' 7 | }) 8 | }, 9 | 10 | down: async (queryInterface, Sequelize) => { 11 | await queryInterface.removeIndex('folders', 'folders_parent_id_idx'); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20220307180600-increase-encryption-key-size.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.changeColumn('shares', 'encryption_key', { 4 | type: Sequelize.STRING(400) 5 | }); 6 | }, 7 | 8 | down: async (queryInterface, Sequelize) => { 9 | await queryInterface.changeColumn('shares', 'encryption_key', { 10 | type: Sequelize.STRING(64) 11 | }); 12 | }, 13 | 14 | }; -------------------------------------------------------------------------------- /migrations/20220309180600-create-deleted-files-table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable('deleted_files', { 4 | file_id: { 5 | type: Sequelize.STRING(24), 6 | }, 7 | user_id: { 8 | type: Sequelize.INTEGER(11), 9 | }, 10 | folder_id: { 11 | type: Sequelize.INTEGER(11), 12 | }, 13 | bucket: { 14 | type: Sequelize.STRING(24), 15 | }, 16 | }); 17 | }, 18 | 19 | down: async (queryInterface) => { 20 | await queryInterface.dropTable('deleted_files'); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /migrations/20220309190600-create-deleted-files-trigger.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.sequelize.query(` 4 | CREATE OR REPLACE FUNCTION insert_deleted_files() 5 | RETURNS trigger AS 6 | $$ 7 | BEGIN 8 | INSERT INTO deleted_files(file_id, user_id, folder_id, bucket) select file_id, user_id, folder_id, bucket from files where files.folder_id = OLD.id; 9 | RETURN OLD; 10 | END; 11 | $$ 12 | LANGUAGE 'plpgsql';`); 13 | await queryInterface.sequelize.query( 14 | `DROP TRIGGER IF EXISTS copy_deleted_files_on_delete ON folders;`, 15 | ); 16 | await queryInterface.sequelize.query( 17 | `CREATE TRIGGER copy_deleted_files_on_delete BEFORE DELETE on folders 18 | FOR EACH ROW 19 | EXECUTE PROCEDURE insert_deleted_files()`, 20 | ); 21 | }, 22 | 23 | down: async (queryInterface) => { 24 | await queryInterface.sequelize.query( 25 | `DROP TRIGGER IF EXISTS copy_deleted_files_on_delete ON folders;`, 26 | ); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /migrations/20220309190601-create-clear_orphan_folders_by_user-procedure.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.sequelize.query( 4 | ` 5 | CREATE OR REPLACE PROCEDURE clear_orphan_folders_by_user(IN userid int, OUT total_left integer) 6 | LANGUAGE 'plpgsql' 7 | AS $$ 8 | BEGIN 9 | delete from folders where parent_id is not null and parent_id not in (select id from folders where user_id = userid) and user_id = userid; 10 | select count(*) from folders where parent_id is not null and parent_id not in (select id from folders where user_id = userid) and user_id = userid 11 | into total_left; 12 | END;$$ 13 | `, 14 | ); 15 | }, 16 | 17 | down: async (queryInterface) => { 18 | await queryInterface.sequelize.query('drop procedure xCloud.clear_orphan_folders_by_user;'); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /migrations/20220413065816-create_mail_limits_table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.createTable('mail_limits', { 6 | id: { 7 | type: Sequelize.INTEGER, 8 | primaryKey: true, 9 | allowNull: false, 10 | autoIncrement: true 11 | }, 12 | user_id: { 13 | type: Sequelize.INTEGER, 14 | allowNull: false, 15 | references: { 16 | model: 'users', 17 | key: 'id' 18 | }, 19 | onUpdate: 'CASCADE', 20 | onDelete: 'CASCADE' 21 | }, 22 | mail_type: { 23 | type: Sequelize.ENUM('invite_friend', 'reset_password', 'remove_account', 'email_verification'), 24 | allowNull: false 25 | }, 26 | attempts_count: { 27 | type: Sequelize.INTEGER(10), 28 | allowNull: false, 29 | defaultValue: 0 30 | }, 31 | attempts_limit: { 32 | type: Sequelize.INTEGER(10), 33 | allowNull: false, 34 | defaultValue: 0 35 | }, 36 | last_mail_sent: { 37 | type: Sequelize.DATE, 38 | allowNull: false, 39 | defaultValue: new Date(0) 40 | }, 41 | }); 42 | }, 43 | 44 | down: async (queryInterface) => { 45 | await queryInterface.dropTable('mail_limits'); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /migrations/20220429114425-make-uuid-unique-users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface) => { 5 | return queryInterface.addConstraint('users', { 6 | type: 'UNIQUE', 7 | fields: ['uuid'], 8 | name: 'uuid_UNIQUE', 9 | }); 10 | }, 11 | 12 | down: (queryInterface) => { 13 | return queryInterface.removeConstraint('users', 'uuid_UNIQUE'); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20220518130743-create_friend-invitations_table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: (queryInterface, Sequelize) => { 3 | return queryInterface.createTable('friend_invitations', { 4 | id: { 5 | type: Sequelize.INTEGER, 6 | primaryKey: true, 7 | allowNull: false, 8 | autoIncrement: true, 9 | }, 10 | host: { 11 | type: Sequelize.INTEGER, 12 | references: { 13 | model: 'users', 14 | key: 'id', 15 | }, 16 | onUpdate: 'CASCADE', 17 | onDelete: 'CASCADE', 18 | allowNull: false, 19 | }, 20 | guest_email: { 21 | type: Sequelize.STRING, 22 | allowNull: false, 23 | }, 24 | accepted: { 25 | type: Sequelize.BOOLEAN, 26 | allowNull: false, 27 | defaultValue: false, 28 | }, 29 | created_at: { 30 | type: Sequelize.DATE, 31 | }, 32 | updated_at: { 33 | type: Sequelize.DATE, 34 | }, 35 | }); 36 | }, 37 | 38 | down: (queryInterface) => { 39 | return queryInterface.dropTable('friend_invitations'); 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /migrations/20220519090000-add_avatar_column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('users', 'avatar', { 4 | type: Sequelize.STRING, 5 | allowNull: true, 6 | }); 7 | }, 8 | 9 | down: async (queryInterface) => { 10 | return queryInterface.removeColumn('users', 'avatar'); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/20220524090000-add_email_verified_column.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('users', 'email_verified', { 4 | type: Sequelize.BOOLEAN, 5 | allowNull: false, 6 | defaultValue: false, 7 | }); 8 | }, 9 | 10 | down: async (queryInterface) => { 11 | return queryInterface.removeColumn('users', 'email_verified'); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20220624130000-update-shares-table-with-columns.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.addColumn('shares', 'user_id', { 4 | type: Sequelize.INTEGER, 5 | allowNull: true, 6 | }); 7 | await queryInterface.addColumn('shares', 'file_id', { 8 | type: Sequelize.INTEGER, 9 | allowNull: true, 10 | }); 11 | await queryInterface.addColumn('shares', 'folder_id', { 12 | type: Sequelize.INTEGER, 13 | allowNull: true, 14 | }); 15 | await queryInterface.addColumn('shares', 'times_valid', { 16 | type: Sequelize.INTEGER, 17 | defaultValue: -1, 18 | }); 19 | await queryInterface.addColumn('shares', 'active', { 20 | type: Sequelize.BOOLEAN, 21 | defaultValue: true, 22 | }); 23 | await queryInterface.addColumn('shares', 'created_at', { 24 | type: Sequelize.DATE, 25 | defaultValue: Sequelize.fn('now'), 26 | }); 27 | await queryInterface.addColumn('shares', 'updated_at', { 28 | type: Sequelize.DATE, 29 | defaultValue: Sequelize.fn('now'), 30 | }); 31 | 32 | await queryInterface.sequelize.query( 33 | 'UPDATE shares set user_id = (select id from users where users.email = shares.user);', 34 | ); 35 | }, 36 | 37 | down: async (queryInterface) => { 38 | await queryInterface.removeColumn('shares', 'user_id'); 39 | await queryInterface.removeColumn('shares', 'file_id'); 40 | await queryInterface.removeColumn('shares', 'folder_id'); 41 | await queryInterface.removeColumn('shares', 'times_valid'); 42 | await queryInterface.removeColumn('shares', 'active'); 43 | await queryInterface.removeColumn('shares', 'created_at'); 44 | await queryInterface.removeColumn('shares', 'updated_at'); 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /migrations/20220718082248-add-version-to-send-link-item.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async up(queryInterface, Sequelize) { 5 | await queryInterface.addColumn('send_links_items', 'version', { 6 | type: Sequelize.INTEGER, 7 | allowNull: false, 8 | defaultValue: 1, 9 | }); 10 | }, 11 | 12 | async down(queryInterface) { 13 | return queryInterface.removeColumn('send_links_items', 'version'); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20220722090000-add-columns-folders-files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async up(queryInterface, Sequelize) { 5 | // When SELECT required use 0 or 1, but in setter use true or false 6 | await queryInterface.addColumn('folders', 'deleted', { 7 | type: Sequelize.BOOLEAN, 8 | allowNull: false, 9 | defaultValue: false, 10 | }); 11 | await queryInterface.addColumn('folders', 'deleted_at', { 12 | type: Sequelize.DATE, 13 | allowNull: true, 14 | }); 15 | }, 16 | 17 | async down(queryInterface) { 18 | await queryInterface.removeColumn('folders', 'deleted'); 19 | await queryInterface.removeColumn('folders', 'deleted_at'); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /migrations/20220823170000-add-column-code-shares.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async up(queryInterface, Sequelize) { 5 | await queryInterface.addColumn('shares', 'code', { 6 | type: Sequelize.STRING, 7 | allowNull: true, 8 | }); 9 | }, 10 | 11 | async down(queryInterface) { 12 | await queryInterface.removeColumn('shares', 'code'); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/20220916100000-update-share-file-delete.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async up(queryInterface, Sequelize) { 5 | await queryInterface.addConstraint('shares', { 6 | type: 'FOREIGN KEY', 7 | fields: ['file_id'], 8 | name: 'shares_file_id_fkey', 9 | references: { 10 | table: 'files', 11 | field: 'id', 12 | }, 13 | onDelete: 'CASCADE', 14 | onUpdate: 'CASCADE', 15 | }); 16 | await queryInterface.addConstraint('shares', { 17 | type: 'FOREIGN KEY', 18 | fields: ['folder_id'], 19 | name: 'shares_folder_id_fkey', 20 | references: { 21 | table: 'folders', 22 | field: 'id', 23 | }, 24 | onDelete: 'CASCADE', 25 | onUpdate: 'CASCADE', 26 | }); 27 | }, 28 | 29 | async down(queryInterface) { 30 | await queryInterface.removeConstraint('shares', 'shares_file_id_fkey'); 31 | await queryInterface.removeConstraint('shares', 'shares_folder_id_fkey'); 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /migrations/20220919083117-add-password-to-shared-links.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async up(queryInterface, Sequelize) { 5 | await queryInterface.addColumn('shares', 'hashed_password', { 6 | type: Sequelize.TEXT, 7 | allowNull: true, 8 | }); 9 | 10 | await queryInterface.addColumn('send_links', 'hashed_password', { 11 | type: Sequelize.TEXT, 12 | allowNull: true, 13 | }); 14 | }, 15 | 16 | async down(queryInterface) { 17 | await queryInterface.removeColumn('shares', 'hashed_password'); 18 | 19 | await queryInterface.removeColumn('send_links', 'hashed_password'); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /migrations/20221003080000-create-index-folders-bucket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface) => { 5 | return queryInterface.addIndex('folders', { 6 | fields: ['bucket'], 7 | name: 'bucket_index', 8 | }); 9 | }, 10 | 11 | down: async (queryInterface) => { 12 | return queryInterface.removeIndex('folders', 'bucket_index'); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/20221003090000-create-thumbnails-table.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable( 4 | 'thumbnails', 5 | { 6 | id: { 7 | type: Sequelize.INTEGER, 8 | primaryKey: true, 9 | autoIncrement: true, 10 | allowNull: false, 11 | }, 12 | file_id: { 13 | type: Sequelize.INTEGER, 14 | allowNull: false, 15 | }, 16 | max_width: { 17 | type: Sequelize.INTEGER, 18 | allowNull: false, 19 | }, 20 | max_height: { 21 | type: Sequelize.INTEGER, 22 | allowNull: false, 23 | }, 24 | type: { 25 | type: Sequelize.STRING, 26 | allowNull: false, 27 | }, 28 | size: { 29 | type: Sequelize.BIGINT.UNSIGNED, 30 | }, 31 | bucket_id: { 32 | type: Sequelize.STRING(24), 33 | }, 34 | bucket_file: { 35 | type: Sequelize.STRING(24), 36 | }, 37 | encrypt_version: { 38 | type: Sequelize.STRING(20), 39 | }, 40 | created_at: { 41 | type: Sequelize.DATE, 42 | defaultValue: Sequelize.fn('now'), 43 | }, 44 | updated_at: { 45 | type: Sequelize.DATE, 46 | defaultValue: Sequelize.fn('now'), 47 | }, 48 | }, 49 | { 50 | uniqueKeys: { 51 | unique_thumbnail: { 52 | fields: ['file_id', 'max_width', 'max_height', 'type'], 53 | }, 54 | }, 55 | }, 56 | ); 57 | 58 | await queryInterface.addConstraint('thumbnails', { 59 | type: 'FOREIGN KEY', 60 | fields: ['file_id'], 61 | name: 'thumbnails_file_id_fkey', 62 | references: { 63 | table: 'files', 64 | field: 'id', 65 | }, 66 | onDelete: 'CASCADE', 67 | onUpdate: 'CASCADE', 68 | }); 69 | }, 70 | 71 | down: async (queryInterface) => { 72 | await queryInterface.removeConstraint( 73 | 'thumbnails', 74 | 'thumbnails_file_id_fkey', 75 | ); 76 | await queryInterface.dropTable('thumbnails'); 77 | }, 78 | }; 79 | -------------------------------------------------------------------------------- /migrations/20221010100000-add-column-sendLinks-parentFolder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | async up(queryInterface, Sequelize) { 5 | await queryInterface.addColumn('send_links_items', 'parent_folder', { 6 | type: Sequelize.UUID, 7 | allowNull: true, 8 | }); 9 | }, 10 | 11 | async down(queryInterface) { 12 | await queryInterface.removeColumn('send_links_items', 'parent_folder'); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/20221109110000-add-unique-columns-files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.addIndex('files', ['name', 'type', 'folder_id'], { 6 | name: 'files_name_type_folderid_deleted_unique', 7 | unique: true, 8 | where: { deleted: { [Sequelize.Op.eq]: false } }, 9 | }); 10 | }, 11 | 12 | down: (queryInterface) => { 13 | return queryInterface.removeIndex( 14 | 'files', 15 | 'files_name_type_folderid_deleted_unique', 16 | ); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /migrations/20230103122430-add-plain-name-to-folder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | async up(queryInterface, Sequelize) { 6 | await queryInterface.addColumn('folders', 'plain_name', { 7 | type: Sequelize.STRING(650), 8 | allowNull: true, 9 | }); 10 | }, 11 | 12 | async down(queryInterface) { 13 | await queryInterface.removeColumn('folders', 'plain_name'); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20230103122435-add-plain-name-to-file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | async up(queryInterface, Sequelize) { 6 | await queryInterface.addColumn('files', 'plain_name', { 7 | type: Sequelize.STRING(650), 8 | allowNull: true, 9 | }); 10 | }, 11 | 12 | async down(queryInterface) { 13 | await queryInterface.removeColumn('files', 'plain_name'); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20230103122440-create-index-files-plain-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | up: (queryInterface, Sequelize) => { 6 | return queryInterface.addIndex('files', ['plain_name', 'type', 'folder_id'], { 7 | name: 'files_plainname_type_folderid_deleted_key', 8 | unique: true, 9 | where: { deleted: { [Sequelize.Op.eq]: false } }, 10 | }); 11 | }, 12 | 13 | down: (queryInterface) => { 14 | return queryInterface.removeIndex('files', 'files_plainname_type_folderid_deleted_key'); 15 | }, 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /migrations/20230103122445-create-index-folder-plain-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | up: (queryInterface) => { 6 | return queryInterface.addIndex('folders', { 7 | fields: ['plain_name', 'parent_id'], 8 | name: 'folders_plainname_parentid_key', 9 | unique: true, 10 | }); 11 | }, 12 | 13 | down: (queryInterface) => { 14 | return queryInterface.removeIndex('folders', 'folders_plainname_parentid_key'); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/20230105113000-modify-index-folder-plain-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | up: async (queryInterface, Sequelize) => { 6 | await queryInterface.removeIndex('folders', 'folders_plainname_parentid_key'); 7 | await queryInterface.addIndex('folders', { 8 | fields: ['plain_name', 'parent_id'], 9 | name: 'folders_plainname_parentid_key', 10 | unique: true, 11 | where: { deleted: { [Sequelize.Op.eq]: false } }, 12 | }); 13 | }, 14 | 15 | down: async (queryInterface) => { 16 | await queryInterface.removeIndex('folders', 'folders_plainname_parentid_key'); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /migrations/20230302120000-rename-mailType-enum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.sequelize.query('ALTER TYPE enum_mail_limits_mail_type RENAME TO mail_type;'); 4 | }, 5 | down: async (queryInterface) => { 6 | await queryInterface.sequelize.query('ALTER TYPE mail_type RENAME TO enum_mail_limits_mail_type;'); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /migrations/20230303133000-add-deactivateUser-to-mailType-enum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface) => { 3 | await queryInterface.sequelize.query( 4 | `ALTER TYPE mail_type ADD VALUE 'deactivate_user';`, 5 | ); 6 | }, 7 | down: async (queryInterface) => { 8 | await queryInterface.sequelize.query( 9 | ` 10 | ALTER TYPE mail_type RENAME TO mail_type_old; 11 | CREATE TYPE mail_type AS ENUM('invite_friend', 'reset_password', 'remove_account'); 12 | ALTER TABLE mail_limits ALTER COLUMN mail_type TYPE mail_type USING mail_type::text::mail_type; 13 | DROP TYPE mail_type_old; 14 | `, 15 | ); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /migrations/20240109071854-add-lastPasswordChangedAt-to-users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** @type {import('sequelize-cli').Migration} */ 4 | module.exports = { 5 | async up(queryInterface, Sequelize) { 6 | await queryInterface.addColumn('users', 'last_password_changed_at', { 7 | type: Sequelize.DATE, 8 | allowNull: true, 9 | defaultValue: null, 10 | }); 11 | }, 12 | 13 | async down(queryInterface) { 14 | await queryInterface.removeColumn('users', 'last_password_changed_at'); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=internxt_drive-server 2 | sonar.organization=internxt 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=drive-server 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | #sonar.sources=. 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | #sonar.sourceEncoding=UTF-8 13 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | import Server from './config/initializers/server'; 4 | 5 | import Routes from './app/routes/routes'; 6 | import { configureHttp } from './lib/performance/network'; 7 | const Services = require('./app/services/services'); 8 | const Middleware = require('./config/initializers/middleware'); 9 | const SocketServer = require('./app/sockets/socketServer'); 10 | 11 | configureHttp(); 12 | const App = new Server(); 13 | 14 | App.start(() => { 15 | App.initMiddleware(Middleware); 16 | App.initModels(); 17 | App.initServices(Services); 18 | App.initRoutes(Routes); 19 | App.initSocketServer(SocketServer); 20 | }); 21 | 22 | module.exports = App; 23 | -------------------------------------------------------------------------------- /src/app/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | MAX_FREE_PLAN_BYTES: 10737418240, 3 | FREE_PLAN_BYTES: 2147483648, 4 | SYNC_KEEPALIVE_INTERVAL_MS: 60 * 1000, // 60 seconds 5 | SHARE_TOKEN_LENGTH: 64, 6 | RECENTS_LIMIT: 15, 7 | INDIVIDUAL_FREE_TIER_PLAN_ID: 'free_000000', 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/middleware/analytics.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import { page as pageTracking } from '../../lib/analytics/AnalyticsService'; 3 | 4 | export function page(req: Request, res: Response, next: NextFunction) { 5 | try { 6 | pageTracking(req).catch(() => null); 7 | } 8 | catch(err) { 9 | // NO OP 10 | } 11 | next(); 12 | } -------------------------------------------------------------------------------- /src/app/middleware/basic-auth.js: -------------------------------------------------------------------------------- 1 | const build = (user, pass) => (req, res, next) => { 2 | const receivedAuth = req.headers.authorization; 3 | const auth = `Basic ${Buffer.from(`${user}:${pass}`).toString('base64')}`; 4 | 5 | if (receivedAuth !== auth) { 6 | return res.status(401).json({ error: 'Unauthorized' }); 7 | } 8 | 9 | return next(); 10 | }; 11 | 12 | module.exports = { 13 | build, 14 | }; 15 | -------------------------------------------------------------------------------- /src/app/middleware/error-handler.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | 3 | import { UserAttributes } from '../models/user'; 4 | 5 | type RequestId = string; 6 | 7 | /** 8 | * DO NOT REMOVE next function as this is required by Express to 9 | * treat this as a 'catch all' function 10 | * @param err Error thrown by some handler 11 | * @param req Express Request 12 | * @param res Express Response 13 | * @param next Express NextFunction 14 | * @returns 15 | */ 16 | export default function errorHandler( 17 | err: Error & { status?: number; message?: string; expose?: boolean }, 18 | req: Request & { user?: UserAttributes; id?: RequestId; logger?: any }, 19 | res: Response, 20 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 21 | next: NextFunction, 22 | ) { 23 | const { path, user } = req; 24 | const handlerPath = '/' + path.split('/').slice(2).join('/'); 25 | 26 | if (err.status !== 401) { 27 | if (user) { 28 | req.logger?.error( 29 | '%s ERROR for user %s: %s Stack: %s. Payload %s', 30 | handlerPath, 31 | user.email, 32 | err.message, 33 | err.stack, 34 | req.body ? JSON.stringify(req.body) : 'NO PAYLOAD', 35 | { headers: req.headers ? JSON.stringify(req.headers) : 'NO HEADERS' }, 36 | ); 37 | } else { 38 | req.logger?.error('%s ERROR %s Stack: %s', handlerPath, err.message, err.stack, { 39 | headers: req.headers ? JSON.stringify(req.headers) : 'NO HEADERS', 40 | }); 41 | } 42 | } 43 | 44 | if (res.headersSent) { 45 | return; 46 | } 47 | 48 | const status = err.status ?? 500; 49 | 50 | const message = err.expose ? err.message : 'Internal Server Error'; 51 | 52 | return res.status(status).send({ error: message }); 53 | } 54 | -------------------------------------------------------------------------------- /src/app/middleware/passport.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const passport = require('passport'); 3 | 4 | const passportAuth = passport.authenticate('jwt', { session: false }); 5 | 6 | function Sign(data, secret, expires = false) { 7 | const token = expires ? 8 | jwt.sign({ email: data, iat: getDefaultIAT() }, secret, { expiresIn: '14d' }) : 9 | jwt.sign({ email: data, iat: getDefaultIAT() }, secret); 10 | return token; 11 | } 12 | 13 | function SignWithFutureIAT(data, secret) { 14 | return jwt.sign({ email: data, iat: getFutureIAT() }, secret, { expiresIn: '14d' }); 15 | } 16 | 17 | function SignNewTokenWithFutureIAT(data, secret, expires = false) { 18 | const futureIat = getFutureIAT(); 19 | return expires 20 | ? jwt.sign(getNewTokenPayload(data, futureIat), secret, { expiresIn: '14d' }) 21 | : jwt.sign(getNewTokenPayload(data, futureIat), secret); 22 | } 23 | 24 | function SignNewToken(data, secret, expires = false) { 25 | const token = expires ? 26 | jwt.sign(getNewTokenPayload(data), secret, { expiresIn: '14d' }) : 27 | jwt.sign(getNewTokenPayload(data), secret); 28 | return token; 29 | } 30 | 31 | function getNewTokenPayload(userData, customIat) { 32 | return { 33 | payload: { 34 | uuid: userData.uuid, 35 | email: userData.email, 36 | name: userData.name, 37 | lastname: userData.lastname, 38 | username: userData.username, 39 | sharedWorkspace: true, 40 | networkCredentials: { 41 | user: userData.bridgeUser, 42 | pass: userData.userId, 43 | }, 44 | }, 45 | iat: customIat ?? getDefaultIAT(), 46 | }; 47 | } 48 | 49 | function getDefaultIAT() { 50 | return Math.floor(Date.now() / 1000); 51 | } 52 | 53 | function getFutureIAT() { 54 | return Math.floor(Date.now() / 1000) + 60; 55 | } 56 | 57 | module.exports = { 58 | passportAuth, 59 | Sign, 60 | SignNewToken, 61 | SignWithFutureIAT, 62 | SignNewTokenWithFutureIAT, 63 | }; 64 | -------------------------------------------------------------------------------- /src/app/middleware/shared-workspace.js: -------------------------------------------------------------------------------- 1 | const build = (Service) => async (req, res, next) => { 2 | try { 3 | const { user } = req; 4 | const isGuest = user.email !== user.bridgeUser; 5 | 6 | if (!isGuest) { 7 | req.behalfUser = req.user; 8 | } else { 9 | req.behalfUser = await Service.Guest.getHost(user.bridgeUser); 10 | } 11 | next(); 12 | } catch (err) { 13 | next(err); 14 | } 15 | }; 16 | 17 | module.exports = { 18 | build, 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/middleware/upload-avatar.ts: -------------------------------------------------------------------------------- 1 | import multer from 'multer'; 2 | import multerS3 from 'multer-s3'; 3 | import AvatarS3 from '../../config/initializers/avatarS3'; 4 | import * as uuid from 'uuid'; 5 | 6 | const uploadAvatar = multer({ 7 | storage: multerS3({ 8 | s3: AvatarS3.getInstance(), 9 | bucket: process.env.AVATAR_BUCKET as string, 10 | key: function (req, file, cb) { 11 | cb(null, uuid.v4()); 12 | }, 13 | }), 14 | limits: { 15 | fileSize: 1024 * 1024, 16 | }, 17 | }).single('avatar'); 18 | 19 | export default uploadAvatar; 20 | -------------------------------------------------------------------------------- /src/app/models/appsumo.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface AppSumoAttributes { 4 | id: number; 5 | userId: number; 6 | planId: string; 7 | uuid: string; 8 | invoiceItemUuid: string; 9 | } 10 | 11 | export type AppSumoModel = ModelDefined; 12 | 13 | export default (database: Sequelize): AppSumoModel => { 14 | const AppSumo: AppSumoModel = database.define( 15 | 'AppSumo', 16 | { 17 | id: { 18 | type: DataTypes.INTEGER, 19 | primaryKey: true, 20 | allowNull: false, 21 | autoIncrement: true, 22 | }, 23 | userId: { 24 | type: DataTypes.INTEGER, 25 | references: { 26 | model: 'users', 27 | key: 'id', 28 | }, 29 | }, 30 | planId: { 31 | type: DataTypes.STRING, 32 | allowNull: false, 33 | }, 34 | uuid: { 35 | type: DataTypes.STRING(36), 36 | allowNull: false, 37 | }, 38 | invoiceItemUuid: { 39 | type: DataTypes.STRING(36), 40 | allowNull: false, 41 | }, 42 | }, 43 | { 44 | timestamps: true, 45 | underscored: true, 46 | tableName: 'appsumo', 47 | }, 48 | ); 49 | 50 | return AppSumo; 51 | }; 52 | -------------------------------------------------------------------------------- /src/app/models/backup.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface BackupAttributes { 4 | id: number; 5 | userId: number; 6 | planId: string; 7 | uuid: string; 8 | invoiceItemUuid: string; 9 | createdAt: Date; 10 | } 11 | 12 | export type BackupModel = ModelDefined; 13 | 14 | export default (database: Sequelize): BackupModel => { 15 | const Backup: BackupModel = database.define( 16 | 'backup', 17 | { 18 | id: { 19 | type: DataTypes.INTEGER, 20 | primaryKey: true, 21 | allowNull: false, 22 | autoIncrement: true, 23 | }, 24 | path: { 25 | type: DataTypes.TEXT, 26 | }, 27 | fileId: { 28 | type: DataTypes.STRING(24), 29 | }, 30 | deviceId: { 31 | type: DataTypes.INTEGER, 32 | }, 33 | userId: { 34 | type: DataTypes.INTEGER, 35 | }, 36 | hash: { 37 | type: DataTypes.STRING, 38 | }, 39 | interval: { 40 | type: DataTypes.INTEGER, 41 | }, 42 | size: { 43 | type: DataTypes.BIGINT.UNSIGNED, 44 | }, 45 | bucket: { 46 | type: DataTypes.STRING(24), 47 | }, 48 | lastBackupAt: { 49 | type: DataTypes.DATE, 50 | }, 51 | enabled: { 52 | type: DataTypes.BOOLEAN, 53 | }, 54 | // TODO: Is this required? 55 | created_at: { 56 | type: DataTypes.VIRTUAL, 57 | get() { 58 | return this.getDataValue('createdAt'); 59 | }, 60 | }, 61 | encrypt_version: { 62 | type: DataTypes.STRING, 63 | }, 64 | }, 65 | { 66 | timestamps: true, 67 | }, 68 | ); 69 | 70 | return Backup; 71 | }; 72 | -------------------------------------------------------------------------------- /src/app/models/deletedFile.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | export interface Attributes { 4 | id: number; 5 | fileId: string; 6 | userId: number; 7 | folderId: string; 8 | bucket: string; 9 | } 10 | 11 | export type DeletedFileModel = ModelDefined; 12 | 13 | export default (database: Sequelize): DeletedFileModel => { 14 | const DeletedFile: DeletedFileModel = database.define( 15 | 'deleted_files', 16 | { 17 | id: { 18 | type: DataTypes.INTEGER, 19 | primaryKey: true, 20 | allowNull: false, 21 | autoIncrement: true, 22 | }, 23 | fileId: { 24 | type: DataTypes.STRING, 25 | allowNull: false, 26 | }, 27 | userId: { 28 | type: DataTypes.INTEGER, 29 | allowNull: false 30 | }, 31 | folderId: { 32 | primaryKey: true, 33 | type: DataTypes.INTEGER, 34 | allowNull: false 35 | }, 36 | bucket: { 37 | type: DataTypes.STRING, 38 | allowNull: false, 39 | }, 40 | }, 41 | { 42 | tableName: 'deleted_files', 43 | underscored: true, 44 | timestamps: false, 45 | }, 46 | ); 47 | 48 | return DeletedFile; 49 | }; 50 | -------------------------------------------------------------------------------- /src/app/models/device.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface DeviceAttributes { 4 | id: number; 5 | mac: string; 6 | userId: number; 7 | name: string; 8 | createdAt: string; 9 | platform: string; 10 | } 11 | 12 | export type DeviceModel = ModelDefined; 13 | 14 | export default (database: Sequelize): DeviceModel => { 15 | const Device: DeviceModel = database.define( 16 | 'device', 17 | { 18 | id: { 19 | type: DataTypes.INTEGER, 20 | primaryKey: true, 21 | allowNull: false, 22 | autoIncrement: true, 23 | }, 24 | mac: { 25 | type: DataTypes.STRING, 26 | allowNull: false, 27 | }, 28 | userId: { 29 | type: DataTypes.INTEGER, 30 | }, 31 | name: { 32 | type: DataTypes.STRING, 33 | }, 34 | created_at: { 35 | type: DataTypes.VIRTUAL, 36 | get() { 37 | return this.getDataValue('createdAt'); 38 | }, 39 | }, 40 | platform: { 41 | type: DataTypes.STRING(20), 42 | allowNull: true, 43 | }, 44 | }, 45 | { 46 | timestamps: true, 47 | indexes: [{ fields: ['userId', 'mac'], name: 'mac_device_index' }], 48 | }, 49 | ); 50 | 51 | return Device; 52 | }; 53 | -------------------------------------------------------------------------------- /src/app/models/friendinvitation.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface FriendInvitationAttributes { 4 | id: number; 5 | host: number; 6 | guestEmail: string; 7 | accepted: boolean; 8 | } 9 | 10 | export type FriendInvitationModel = ModelDefined; 11 | 12 | export default (database: Sequelize): FriendInvitationModel => { 13 | const FriendInvitation: FriendInvitationModel = database.define( 14 | 'FriendInvitation', 15 | { 16 | id: { 17 | type: DataTypes.INTEGER, 18 | primaryKey: true, 19 | allowNull: false, 20 | autoIncrement: true, 21 | }, 22 | host: { 23 | type: DataTypes.INTEGER, 24 | references: { 25 | model: 'users', 26 | key: 'id', 27 | }, 28 | allowNull: false, 29 | }, 30 | guestEmail: { 31 | type: DataTypes.STRING, 32 | allowNull: false, 33 | }, 34 | accepted: { 35 | type: DataTypes.BOOLEAN, 36 | allowNull: false, 37 | defaultValue: false, 38 | }, 39 | }, 40 | { 41 | timestamps: true, 42 | underscored: true, 43 | tableName: 'friend_invitations', 44 | }, 45 | ); 46 | 47 | return FriendInvitation; 48 | }; 49 | -------------------------------------------------------------------------------- /src/app/models/invitation.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface InvitationAttributes { 4 | id: number; 5 | host: number; 6 | guest: number; 7 | inviteId: string; 8 | accepted: boolean; 9 | } 10 | 11 | export type InvitationModel = ModelDefined; 12 | 13 | export default (database: Sequelize): InvitationModel => { 14 | const Invitation: InvitationModel = database.define( 15 | 'Invitation', 16 | { 17 | id: { 18 | type: DataTypes.INTEGER, 19 | primaryKey: true, 20 | allowNull: false, 21 | autoIncrement: true, 22 | }, 23 | host: { 24 | type: DataTypes.INTEGER, 25 | references: { 26 | model: 'users', 27 | key: 'id', 28 | }, 29 | allowNull: false, 30 | }, 31 | guest: { 32 | type: DataTypes.INTEGER, 33 | references: { 34 | model: 'users', 35 | key: 'id', 36 | }, 37 | allowNull: false, 38 | }, 39 | inviteId: { 40 | type: DataTypes.STRING(216), 41 | allowNull: false, 42 | }, 43 | accepted: { 44 | type: DataTypes.BOOLEAN, 45 | allowNull: false, 46 | defaultValue: false, 47 | }, 48 | }, 49 | { 50 | timestamps: true, 51 | underscored: true, 52 | tableName: 'invitations', 53 | }, 54 | ); 55 | 56 | return Invitation; 57 | }; 58 | -------------------------------------------------------------------------------- /src/app/models/keyserver.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface KeyServerAttributes { 4 | id: number; 5 | userId: number; 6 | publicKey: string; 7 | privateKey: string; 8 | revocationKey: string; 9 | encryptVersion: string; 10 | } 11 | 12 | export type KeyServerModel = ModelDefined; 13 | 14 | export default (database: Sequelize): KeyServerModel => { 15 | const KeyServer: KeyServerModel = database.define( 16 | 'keyserver', 17 | { 18 | id: { 19 | type: DataTypes.INTEGER, 20 | primaryKey: true, 21 | allowNull: false, 22 | autoIncrement: true, 23 | }, 24 | user_id: { 25 | type: DataTypes.INTEGER, 26 | references: { 27 | model: 'users', 28 | key: 'id', 29 | }, 30 | allowNull: false, 31 | }, 32 | public_key: { 33 | type: DataTypes.STRING(920), 34 | allowNull: false, 35 | }, 36 | private_key: { 37 | type: DataTypes.STRING(1356), 38 | allowNull: false, 39 | }, 40 | revocation_key: { 41 | type: DataTypes.STRING(476), 42 | allowNull: false, 43 | }, 44 | encrypt_version: { 45 | type: DataTypes.STRING, 46 | }, 47 | }, 48 | { 49 | timestamps: true, 50 | underscored: true, 51 | freezeTableName: true, 52 | }, 53 | ); 54 | 55 | return KeyServer; 56 | }; 57 | -------------------------------------------------------------------------------- /src/app/models/limit.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, ModelDefined, Sequelize } from 'sequelize'; 2 | 3 | export interface LimitAttributes { 4 | id: string; 5 | label: string; 6 | type: string; 7 | value: string; 8 | createdAt: Date; 9 | updatedAt: Date; 10 | } 11 | 12 | export type LimitModel = ModelDefined; 13 | 14 | export default (database: Sequelize): LimitModel => { 15 | const Limit: LimitModel = database.define( 16 | 'limits', 17 | { 18 | id: { 19 | type: DataTypes.UUID, 20 | primaryKey: true, 21 | defaultValue: DataTypes.UUIDV4, 22 | }, 23 | label: { 24 | type: DataTypes.STRING, 25 | allowNull: false, 26 | }, 27 | type: { 28 | type: DataTypes.STRING, 29 | allowNull: false, 30 | }, 31 | value: { 32 | type: DataTypes.STRING, 33 | allowNull: false, 34 | }, 35 | createdAt: { 36 | type: DataTypes.DATE, 37 | allowNull: false, 38 | }, 39 | updatedAt: { 40 | type: DataTypes.DATE, 41 | allowNull: false, 42 | }, 43 | }, 44 | { 45 | tableName: 'limits', 46 | timestamps: true, 47 | underscored: true, 48 | }, 49 | ); 50 | 51 | return Limit; 52 | }; 53 | -------------------------------------------------------------------------------- /src/app/models/mailLimit.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | export enum MailTypes { 4 | InviteFriend = 'invite_friend', 5 | ResetPassword = 'reset_password', 6 | RemoveAccount = 'remove_account', 7 | EmailVerification = 'email_verification', 8 | DeactivateUser = 'deactivate_user', 9 | } 10 | 11 | interface Attributes { 12 | id: number; 13 | userId: number; 14 | mailType: MailTypes; 15 | attemptsCount: number; 16 | attemptsLimit: number; 17 | lastMailSent: Date; 18 | } 19 | 20 | export type MailLimitModel = ModelDefined; 21 | 22 | export default (database: Sequelize): MailLimitModel => { 23 | const MailLimit: MailLimitModel = database.define( 24 | 'mail_limits', 25 | { 26 | id: { 27 | type: DataTypes.INTEGER, 28 | primaryKey: true, 29 | allowNull: false, 30 | autoIncrement: true, 31 | }, 32 | userId: { 33 | type: DataTypes.INTEGER, 34 | allowNull: false 35 | }, 36 | mailType: { 37 | type: DataTypes.ENUM( 38 | MailTypes.InviteFriend, 39 | MailTypes.RemoveAccount, 40 | MailTypes.ResetPassword, 41 | MailTypes.EmailVerification 42 | ), 43 | allowNull: false 44 | }, 45 | attemptsCount: { 46 | type: DataTypes.INTEGER, 47 | allowNull: false, 48 | defaultValue: 0 49 | }, 50 | attemptsLimit: { 51 | type: DataTypes.INTEGER, 52 | allowNull: false, 53 | defaultValue: 0 54 | }, 55 | lastMailSent: { 56 | type: DataTypes.DATE, 57 | allowNull: false, 58 | defaultValue: new Date(0) 59 | } 60 | }, 61 | { 62 | tableName: 'mail_limits', 63 | underscored: true, 64 | timestamps: false 65 | }, 66 | ); 67 | 68 | return MailLimit; 69 | }; 70 | -------------------------------------------------------------------------------- /src/app/models/paidPlans.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, ModelDefined, Sequelize } from 'sequelize'; 2 | 3 | export interface PaidPlansAttributes { 4 | id: string; 5 | planId: string; 6 | tierId: string; 7 | createdAt: Date; 8 | } 9 | 10 | export type PaidPlansModel = ModelDefined; 11 | 12 | export default (database: Sequelize): PaidPlansModel => { 13 | const PaidPlans: PaidPlansModel = database.define( 14 | 'paidPlans', 15 | { 16 | id: { 17 | type: DataTypes.INTEGER, 18 | primaryKey: true, 19 | autoIncrement: true, 20 | }, 21 | planId: { 22 | type: DataTypes.STRING, 23 | allowNull: false, 24 | }, 25 | tierId: { 26 | type: DataTypes.UUID, 27 | allowNull: false, 28 | }, 29 | createdAt: { 30 | type: DataTypes.DATE, 31 | allowNull: false, 32 | }, 33 | }, 34 | { 35 | tableName: 'paid_plans', 36 | timestamps: true, 37 | underscored: true, 38 | }, 39 | ); 40 | 41 | return PaidPlans; 42 | }; 43 | -------------------------------------------------------------------------------- /src/app/models/permissions.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, DataTypes, ModelDefined } from 'sequelize'; 2 | 3 | interface PermissionsAttributes { 4 | id: string; 5 | name: string; 6 | roleId: string; 7 | createdAt: Date; 8 | updatedAt: Date; 9 | } 10 | 11 | export type PermissionModel = ModelDefined< 12 | PermissionsAttributes, 13 | PermissionsAttributes 14 | >; 15 | 16 | export default (database: Sequelize): PermissionModel => { 17 | const Permissions: PermissionModel = database.define( 18 | 'permissions', 19 | { 20 | id: { 21 | type: DataTypes.UUIDV4, 22 | primaryKey: true, 23 | allowNull: false 24 | }, 25 | name: { 26 | type: DataTypes.STRING, 27 | allowNull: false, 28 | }, 29 | roleId: { 30 | type: DataTypes.UUIDV4, 31 | allowNull: false, 32 | references: { 33 | model: 'roles', 34 | key: 'id', 35 | } 36 | }, 37 | }, 38 | { 39 | tableName: 'permissions', 40 | underscored: true, 41 | timestamps: true 42 | }, 43 | ); 44 | 45 | return Permissions; 46 | }; 47 | -------------------------------------------------------------------------------- /src/app/models/plan.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | enum PlanTypes { 4 | subscription = 'subscription', 5 | oneTime = 'one_time', 6 | } 7 | 8 | interface Attributes { 9 | id: number; 10 | userId: number; 11 | name: string; 12 | type: PlanTypes; 13 | createdAt: Date; 14 | updatedAt: Date; 15 | limit: number; 16 | } 17 | 18 | export type PlanModel = ModelDefined; 19 | 20 | export default (database: Sequelize): PlanModel => { 21 | const Plan: PlanModel = database.define( 22 | 'plan', 23 | { 24 | id: { 25 | type: DataTypes.INTEGER, 26 | primaryKey: true, 27 | allowNull: false, 28 | autoIncrement: true, 29 | }, 30 | userId: { 31 | type: DataTypes.INTEGER, 32 | }, 33 | name: { 34 | type: DataTypes.STRING, 35 | }, 36 | type: { 37 | type: DataTypes.ENUM('subscription', 'one_time'), 38 | }, 39 | createdAt: { 40 | type: DataTypes.DATE, 41 | }, 42 | updatedAt: { 43 | type: DataTypes.DATE, 44 | }, 45 | limit: { 46 | type: DataTypes.BIGINT.UNSIGNED, 47 | allowNull: false, 48 | defaultValue: 0, 49 | }, 50 | }, 51 | { 52 | tableName: 'plans', 53 | timestamps: true, 54 | underscored: true, 55 | indexes: [ 56 | { 57 | unique: false, 58 | fields: ['name'], 59 | }, 60 | ], 61 | }, 62 | ); 63 | 64 | return Plan; 65 | }; 66 | -------------------------------------------------------------------------------- /src/app/models/privateSharingFolder.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface Attributes { 4 | id: string; 5 | folderId: string; 6 | ownerId: string; 7 | sharedWith: string; 8 | encryptionKey: string; 9 | createdAt: Date; 10 | updatedAt: Date; 11 | } 12 | 13 | export type PrivateSharingFolderModel = ModelDefined; 14 | 15 | export default (database: Sequelize): PrivateSharingFolderModel => { 16 | const PrivateSharingFolder: PrivateSharingFolderModel = database.define( 17 | 'privateSharingFolder', 18 | { 19 | id: { 20 | type: DataTypes.UUIDV4, 21 | primaryKey: true, 22 | allowNull: false, 23 | }, 24 | folderId: { 25 | type: DataTypes.UUIDV4, 26 | allowNull: false 27 | }, 28 | ownerId: { 29 | type: DataTypes.UUIDV4, 30 | allowNull: false 31 | }, 32 | sharedWith: { 33 | type: DataTypes.UUIDV4, 34 | allowNull: false 35 | }, 36 | encryptionKey: { 37 | type: DataTypes.STRING, 38 | allowNull: false 39 | }, 40 | }, 41 | { 42 | tableName: 'private_sharing_folder', 43 | underscored: true, 44 | timestamps: true 45 | }, 46 | ); 47 | 48 | return PrivateSharingFolder; 49 | }; 50 | -------------------------------------------------------------------------------- /src/app/models/privateSharingFolderRole.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface Attributes { 4 | id: number; 5 | userId: string; 6 | folderId: string; 7 | roleId: string; 8 | createdAt: Date; 9 | updatedAt: Date; 10 | } 11 | 12 | export type PrivateSharingFolderRoleModel = ModelDefined; 13 | 14 | export default (database: Sequelize): PrivateSharingFolderRoleModel => { 15 | const PrivateSharingFolderRole: PrivateSharingFolderRoleModel = database.define( 16 | 'privateSharingFolderRole', 17 | { 18 | id: { 19 | type: DataTypes.UUIDV4, 20 | primaryKey: true, 21 | allowNull: false, 22 | }, 23 | userId: { 24 | type: DataTypes.STRING, 25 | allowNull: false 26 | }, 27 | folderId: { 28 | type: DataTypes.UUIDV4, 29 | allowNull: false 30 | }, 31 | roleId: { 32 | type: DataTypes.UUIDV4, 33 | allowNull: false 34 | }, 35 | }, 36 | { 37 | tableName: 'private_sharing_folder_roles', 38 | underscored: true, 39 | timestamps: true 40 | }, 41 | ); 42 | 43 | return PrivateSharingFolderRole; 44 | }; 45 | -------------------------------------------------------------------------------- /src/app/models/referral.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | export interface ReferralAttributes { 4 | id: number; 5 | key: string; 6 | type: 'storage'; 7 | credit: number; 8 | steps: number; 9 | enabled: boolean; 10 | } 11 | 12 | export type ReferralModel = ModelDefined; 13 | 14 | export default (database: Sequelize): ReferralModel => { 15 | const Referral: ReferralModel = database.define( 16 | 'referrals', 17 | { 18 | id: { 19 | type: DataTypes.INTEGER, 20 | primaryKey: true, 21 | allowNull: false, 22 | autoIncrement: true, 23 | }, 24 | key: { 25 | type: DataTypes.STRING, 26 | unique: true, 27 | allowNull: false, 28 | }, 29 | type: { 30 | type: DataTypes.ENUM('storage'), 31 | allowNull: false, 32 | }, 33 | credit: { 34 | type: DataTypes.INTEGER, 35 | allowNull: false, 36 | }, 37 | steps: { 38 | type: DataTypes.INTEGER, 39 | allowNull: false, 40 | }, 41 | enabled: { 42 | type: DataTypes.BOOLEAN, 43 | allowNull: false, 44 | defaultValue: true, 45 | }, 46 | }, 47 | { 48 | tableName: 'referrals', 49 | timestamps: true, 50 | underscored: true, 51 | }, 52 | ); 53 | 54 | return Referral; 55 | }; 56 | -------------------------------------------------------------------------------- /src/app/models/roles.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface RoleAttributes { 4 | id: string; 5 | name: string; 6 | createdAt: Date; 7 | updatedAt: Date; 8 | } 9 | 10 | export type RoleModel = ModelDefined< 11 | RoleAttributes, 12 | RoleAttributes 13 | >; 14 | 15 | export default (database: Sequelize): RoleModel => { 16 | const Roles: RoleModel = database.define( 17 | 'roles', 18 | { 19 | id: { 20 | type: DataTypes.UUIDV4, 21 | primaryKey: true, 22 | allowNull: false 23 | }, 24 | name: { 25 | type: DataTypes.STRING, 26 | allowNull: false, 27 | }, 28 | }, 29 | { 30 | tableName: 'roles', 31 | underscored: true, 32 | timestamps: true 33 | }, 34 | ); 35 | 36 | return Roles; 37 | }; 38 | -------------------------------------------------------------------------------- /src/app/models/share.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface ShareAttributes { 4 | id: number; 5 | token: string; 6 | mnemonic: string; 7 | user: number; 8 | file: string; 9 | encryptionKey: string; 10 | bucket: string; 11 | fileToken: string; 12 | isFolder: boolean; 13 | views: number; 14 | is_folder: boolean; 15 | active: boolean; 16 | hashed_password: string; 17 | userId: string; 18 | } 19 | 20 | export type ShareModel = ModelDefined; 21 | 22 | export default (database: Sequelize): ShareModel => { 23 | const Share: ShareModel = database.define( 24 | 'shares', 25 | { 26 | id: { 27 | type: DataTypes.INTEGER, 28 | primaryKey: true, 29 | allowNull: false, 30 | autoIncrement: true, 31 | }, 32 | token: { 33 | type: DataTypes.STRING, 34 | unique: true, 35 | }, 36 | mnemonic: { 37 | type: DataTypes.BLOB, 38 | }, 39 | user: { 40 | type: DataTypes.INTEGER, 41 | }, 42 | file: DataTypes.STRING(24), 43 | encryptionKey: { 44 | type: DataTypes.STRING(64), 45 | allowNull: false, 46 | }, 47 | bucket: { 48 | type: DataTypes.STRING(24), 49 | allowNull: false, 50 | }, 51 | fileToken: { 52 | type: DataTypes.STRING(64), 53 | allowNull: false, 54 | }, 55 | is_folder: { 56 | type: DataTypes.BOOLEAN, 57 | defaultValue: false, 58 | }, 59 | active: { 60 | type: DataTypes.BOOLEAN, 61 | defaultValue: true, 62 | }, 63 | views: { 64 | type: DataTypes.INTEGER, 65 | defaultValue: 1, 66 | }, 67 | hashed_password: { 68 | type: DataTypes.TEXT, 69 | allowNull: true, 70 | }, 71 | code: { 72 | type: DataTypes.TEXT, 73 | allowNull: true, 74 | }, 75 | userId: { 76 | type: DataTypes.INTEGER 77 | } 78 | }, 79 | { 80 | underscored: true, 81 | timestamps: false, 82 | }, 83 | ); 84 | 85 | return Share; 86 | }; 87 | -------------------------------------------------------------------------------- /src/app/models/sharingInvites.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | export interface SharingInviteAttributes { 4 | id: string; 5 | itemId: string; 6 | itemType: 'file' | 'folder'; 7 | ownerId: string; 8 | sharedWith: string 9 | encryptionKey: string; 10 | encryptionAlgorithm: string; 11 | createdAt: Date; 12 | updatedAt: Date; 13 | type: 'SELF' | 'OWNER'; 14 | roleId: string 15 | } 16 | 17 | export type SharingInvitesModel = ModelDefined; 18 | 19 | export default (database: Sequelize): SharingInvitesModel => { 20 | const SharingInvites: SharingInvitesModel = database.define( 21 | 'sharingInvites', 22 | { 23 | id: { 24 | type: DataTypes.UUIDV4, 25 | defaultValue: DataTypes.UUIDV4, 26 | primaryKey: true, 27 | allowNull: false, 28 | }, 29 | itemId: { 30 | type: DataTypes.UUIDV4, 31 | allowNull: false 32 | }, 33 | itemType: { 34 | type: DataTypes.STRING, 35 | allowNull: false 36 | }, 37 | sharedWith: { 38 | type: DataTypes.STRING(36), 39 | allowNull: false, 40 | references: { 41 | model: 'users', 42 | key: 'uuid' 43 | } 44 | }, 45 | encryptionKey: { 46 | type: DataTypes.STRING(800), 47 | allowNull: false 48 | }, 49 | encryptionAlgorithm: { 50 | type: DataTypes.STRING, 51 | allowNull: false, 52 | }, 53 | roleId: { 54 | type: DataTypes.UUIDV4, 55 | allowNull: false, 56 | references: { 57 | model: 'roles', 58 | key: 'id' 59 | } 60 | }, 61 | type: { 62 | type: DataTypes.STRING, 63 | allowNull: false 64 | }, 65 | }, 66 | { 67 | tableName: 'sharing_invites', 68 | underscored: true, 69 | timestamps: true 70 | }, 71 | ); 72 | 73 | return SharingInvites; 74 | }; 75 | -------------------------------------------------------------------------------- /src/app/models/sharingRoles.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | export interface SharingRoleAttributes { 4 | id: string; 5 | sharingId: string; 6 | roleId: string; 7 | createdAt: Date; 8 | updatedAt: Date; 9 | } 10 | export type SharingRolesModel = ModelDefined; 11 | 12 | export default (database: Sequelize): SharingRolesModel => { 13 | const SharingRoles: SharingRolesModel = database.define( 14 | 'sharingRoles', 15 | { 16 | id: { 17 | type: DataTypes.UUIDV4, 18 | defaultValue: DataTypes.UUIDV4, 19 | primaryKey: true, 20 | allowNull: false, 21 | }, 22 | sharingId: { 23 | type: DataTypes.UUIDV4, 24 | allowNull: false, 25 | references: { 26 | model: 'sharings', 27 | key: 'id' 28 | }, 29 | }, 30 | roleId: { 31 | type: DataTypes.UUIDV4, 32 | allowNull: false, 33 | references: { 34 | model: 'roles', 35 | key: 'id' 36 | } 37 | }, 38 | }, 39 | { 40 | tableName: 'sharing_roles', 41 | underscored: true, 42 | timestamps: true 43 | }, 44 | ); 45 | 46 | return SharingRoles; 47 | }; 48 | -------------------------------------------------------------------------------- /src/app/models/sharings.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | export interface SharingAttributes { 4 | id: string; 5 | itemId: string; 6 | itemType: 'file' | 'folder'; 7 | ownerId: string; 8 | sharedWith: string; 9 | encryptionKey: string; 10 | encryptionAlgorithm: string; 11 | createdAt: Date; 12 | updatedAt: Date; 13 | } 14 | 15 | export type SharingsModel = ModelDefined; 16 | 17 | export default (database: Sequelize): SharingsModel => { 18 | const Sharings: SharingsModel = database.define( 19 | 'sharings', 20 | { 21 | id: { 22 | type: DataTypes.UUIDV4, 23 | defaultValue: DataTypes.UUIDV4, 24 | primaryKey: true, 25 | allowNull: false, 26 | }, 27 | itemId: { 28 | type: DataTypes.UUIDV4, 29 | allowNull: false 30 | }, 31 | itemType: { 32 | type: DataTypes.STRING, 33 | allowNull: false 34 | }, 35 | ownerId: { 36 | type: DataTypes.STRING(36), 37 | allowNull: false, 38 | references: { 39 | model: 'users', 40 | key: 'uuid' 41 | } 42 | }, 43 | sharedWith: { 44 | type: DataTypes.STRING(36), 45 | allowNull: false, 46 | references: { 47 | model: 'users', 48 | key: 'uuid' 49 | } 50 | }, 51 | encryptionKey: { 52 | type: DataTypes.STRING(800), 53 | allowNull: false 54 | }, 55 | encryptionAlgorithm: { 56 | type: DataTypes.STRING, 57 | allowNull: false, 58 | }, 59 | }, 60 | { 61 | tableName: 'sharings', 62 | underscored: true, 63 | timestamps: true 64 | }, 65 | ); 66 | 67 | return Sharings; 68 | }; 69 | -------------------------------------------------------------------------------- /src/app/models/thumbnail.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | export interface ThumbnailAttributes { 4 | id: number; 5 | file_id: number; 6 | type: string; 7 | size: number; 8 | bucket_id: string; 9 | bucket_file: string; 10 | encrypt_version: string; 11 | created_at: Date; 12 | updated_at: Date; 13 | } 14 | 15 | export type ThumbnailModel = ModelDefined; 16 | 17 | export default (database: Sequelize): ThumbnailModel => { 18 | const Thumbnail: ThumbnailModel = database.define( 19 | 'thumbnail', 20 | { 21 | id: { 22 | type: DataTypes.INTEGER, 23 | primaryKey: true, 24 | autoIncrement: true, 25 | allowNull: false 26 | }, 27 | file_id: { 28 | type: DataTypes.INTEGER, 29 | allowNull: false 30 | }, 31 | max_width: { 32 | type: DataTypes.INTEGER, 33 | allowNull: false 34 | }, 35 | max_height: { 36 | type: DataTypes.INTEGER, 37 | allowNull: false 38 | }, 39 | type: { 40 | type: DataTypes.STRING, 41 | allowNull: false 42 | }, 43 | size: { 44 | type: DataTypes.BIGINT.UNSIGNED 45 | }, 46 | bucket_id: { 47 | type: DataTypes.STRING(24) 48 | }, 49 | bucket_file: { 50 | type: DataTypes.STRING(24) 51 | }, 52 | encrypt_version: { 53 | type: DataTypes.STRING(20) 54 | }, 55 | created_at: { 56 | type: DataTypes.VIRTUAL, 57 | get() { 58 | return this.getDataValue('created_at'); 59 | }, 60 | }, 61 | updated_at: { 62 | type: DataTypes.VIRTUAL, 63 | get() { 64 | return this.getDataValue('updated_at'); 65 | }, 66 | }, 67 | }, 68 | { 69 | timestamps: true, 70 | underscored: true, 71 | }, 72 | ); 73 | 74 | return Thumbnail; 75 | }; 76 | -------------------------------------------------------------------------------- /src/app/models/tier.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, ModelDefined, Sequelize } from 'sequelize'; 2 | 3 | export interface TierAttributes { 4 | id: string; 5 | label: string; 6 | context?: string; 7 | createdAt: Date; 8 | updatedAt: Date; 9 | } 10 | 11 | export type TierModel = ModelDefined; 12 | 13 | export default (database: Sequelize): TierModel => { 14 | const Tier: TierModel = database.define( 15 | 'tiers', 16 | { 17 | id: { 18 | type: DataTypes.UUID, 19 | primaryKey: true, 20 | defaultValue: DataTypes.UUIDV4, 21 | }, 22 | label: { 23 | type: DataTypes.STRING, 24 | allowNull: false, 25 | }, 26 | context: { 27 | type: DataTypes.STRING, 28 | allowNull: false, 29 | }, 30 | createdAt: { 31 | type: DataTypes.DATE, 32 | allowNull: false, 33 | }, 34 | updatedAt: { 35 | type: DataTypes.DATE, 36 | allowNull: false, 37 | }, 38 | }, 39 | { 40 | tableName: 'tiers', 41 | timestamps: true, 42 | underscored: true, 43 | }, 44 | ); 45 | 46 | return Tier; 47 | }; 48 | -------------------------------------------------------------------------------- /src/app/models/tierLimit.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, ModelDefined, Sequelize } from 'sequelize'; 2 | 3 | export interface TierLimitsAttributes { 4 | id: string; 5 | tierId: string; 6 | limitId: string; 7 | createdAt: Date; 8 | updatedAt: Date; 9 | } 10 | 11 | export type TierLimitsModel = ModelDefined; 12 | 13 | export default (database: Sequelize): TierLimitsModel => { 14 | const TierLimits: TierLimitsModel = database.define( 15 | 'tierLimits', 16 | { 17 | id: { 18 | type: DataTypes.UUID, 19 | primaryKey: true, 20 | defaultValue: DataTypes.UUIDV4, 21 | }, 22 | tierId: { 23 | type: DataTypes.UUID, 24 | allowNull: false, 25 | }, 26 | limitId: { 27 | type: DataTypes.UUID, 28 | allowNull: false, 29 | }, 30 | createdAt: { 31 | type: DataTypes.DATE, 32 | allowNull: false, 33 | }, 34 | updatedAt: { 35 | type: DataTypes.DATE, 36 | allowNull: false, 37 | }, 38 | }, 39 | { 40 | tableName: 'tiers_limits', 41 | timestamps: true, 42 | underscored: true, 43 | }, 44 | ); 45 | 46 | return TierLimits; 47 | }; 48 | -------------------------------------------------------------------------------- /src/app/models/userNotificationTokens.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, ModelDefined, Sequelize } from 'sequelize'; 2 | 3 | export interface UserNotificationTokenAttributes { 4 | id: string; 5 | userId: string; 6 | token: string; 7 | type: 'macos' | 'android' | 'ios'; 8 | createdAt: Date; 9 | updatedAt: Date; 10 | } 11 | 12 | export type UserNotificationTokenModel = ModelDefined; 13 | 14 | export default (database: Sequelize): UserNotificationTokenModel => { 15 | const UserNotificationToken: UserNotificationTokenModel = database.define( 16 | 'user_notification_tokens', 17 | { 18 | id: { 19 | type: DataTypes.UUID, 20 | primaryKey: true, 21 | defaultValue: DataTypes.UUIDV4, 22 | }, 23 | userId: { 24 | type: DataTypes.STRING(36), 25 | allowNull: false, 26 | references: { 27 | model: 'users', 28 | key: 'uuid', 29 | }, 30 | }, 31 | token: { 32 | type: DataTypes.STRING, 33 | allowNull: false, 34 | }, 35 | type: { 36 | type: DataTypes.ENUM('macos', 'android', 'ios'), 37 | allowNull: false, 38 | }, 39 | createdAt: { 40 | field: 'created_at', 41 | type: DataTypes.DATE, 42 | allowNull: false, 43 | defaultValue: DataTypes.NOW, 44 | }, 45 | updatedAt: { 46 | field: 'updated_at', 47 | type: DataTypes.DATE, 48 | allowNull: false, 49 | defaultValue: DataTypes.NOW, 50 | }, 51 | }, 52 | { 53 | tableName: 'user_notification_tokens', 54 | timestamps: true, 55 | underscored: true, 56 | }, 57 | ); 58 | 59 | return UserNotificationToken; 60 | }; 61 | -------------------------------------------------------------------------------- /src/app/models/userReferral.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, ModelDefined, DataTypes } from 'sequelize'; 2 | 3 | interface Attributes { 4 | id: number; 5 | key: string; 6 | type: 'storage'; 7 | credit: number; 8 | steps: number; 9 | enabled: boolean; 10 | } 11 | 12 | export type UserReferralModel = ModelDefined; 13 | 14 | export default (database: Sequelize): UserReferralModel => { 15 | const UserReferral: UserReferralModel = database.define( 16 | 'users_referrals', 17 | { 18 | id: { 19 | type: DataTypes.INTEGER, 20 | primaryKey: true, 21 | allowNull: false, 22 | autoIncrement: true, 23 | }, 24 | user_id: { 25 | type: DataTypes.INTEGER, 26 | references: { 27 | model: 'users', 28 | key: 'id', 29 | }, 30 | }, 31 | referral_id: { 32 | type: DataTypes.INTEGER, 33 | references: { 34 | model: 'referrals', 35 | key: 'id', 36 | }, 37 | }, 38 | referred: { 39 | type: DataTypes.STRING, 40 | allowNull: true, 41 | }, 42 | start_date: { 43 | type: DataTypes.DATE, 44 | allowNull: false, 45 | defaultValue: DataTypes.NOW, 46 | }, 47 | expiration_date: { 48 | type: DataTypes.DATE, 49 | allowNull: true, 50 | }, 51 | applied: { 52 | type: DataTypes.BOOLEAN, 53 | allowNull: false, 54 | defaultValue: false, 55 | }, 56 | }, 57 | { 58 | tableName: 'users_referrals', 59 | timestamps: true, 60 | underscored: true, 61 | }, 62 | ); 63 | 64 | return UserReferral; 65 | }; 66 | -------------------------------------------------------------------------------- /src/app/routes/analytics.js: -------------------------------------------------------------------------------- 1 | const { passportAuth } = require('../middleware/passport'); 2 | const { page } = require('../middleware/analytics'); 3 | const AnalyticsService = require('../../lib/analytics/AnalyticsService'); 4 | 5 | module.exports = (Router) => { 6 | Router.post('/data', passportAuth, (req, res) => { 7 | res.status(200).send(); 8 | try { 9 | const { actionName } = req.body; 10 | AnalyticsService.actions[actionName](req); 11 | } 12 | catch(err) { 13 | // NO OP 14 | }; 15 | }); 16 | 17 | Router.post('/data/p', page, (req, res) => { 18 | res.status(200).send(); 19 | }); 20 | 21 | Router.post('/data/t', (req, res) => { 22 | res.status(200).send(); 23 | try { 24 | const { actionName } = req.body; 25 | AnalyticsService.actions[actionName](req); 26 | } 27 | catch(err) { 28 | // NO OP 29 | }; 30 | }); 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/routes/bridge.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from 'express'; 2 | 3 | import { passportAuth } from '../middleware/passport'; 4 | import { UserAttributes } from '../models/user'; 5 | 6 | type AuthorizedRequest = Request & { user: UserAttributes }; 7 | 8 | class BridgeController { 9 | private service: any; 10 | 11 | constructor(service: any) { 12 | this.service = service; 13 | } 14 | 15 | async getUsage(req: Request, res: Response) { 16 | const usage = await this.service.User.getUsage((req as AuthorizedRequest).user); 17 | 18 | res.status(200).send(usage); 19 | } 20 | 21 | async getLimit(req: Request, res: Response) { 22 | const { 23 | bridgeUser, 24 | userId: bridgePass, 25 | updatedAt, 26 | uuid 27 | } = (req as AuthorizedRequest).user; 28 | 29 | const limit = await this.service.Limit.getLimit( 30 | bridgeUser, 31 | bridgePass, 32 | uuid, 33 | updatedAt 34 | ); 35 | 36 | res.status(200).send(limit); 37 | } 38 | } 39 | 40 | export default (router: Router, service: any) => { 41 | const controller = new BridgeController(service); 42 | 43 | router.get('/usage', passportAuth, controller.getUsage.bind(controller)); 44 | router.get('/limit', passportAuth, controller.getLimit.bind(controller)); 45 | }; 46 | -------------------------------------------------------------------------------- /src/app/routes/desktop.js: -------------------------------------------------------------------------------- 1 | const { passportAuth } = require('../middleware/passport'); 2 | const logger = require('../../lib/logger').default; 3 | const Logger = logger.getInstance(); 4 | 5 | function logError(context, user, err) { 6 | Logger.error( 7 | `[DESKTOP/${context}]: Error for user %s (UUID %s): %s. %s`, 8 | user.email, 9 | user.uuid, 10 | err.message, 11 | err.stack || 'NO STACK.' 12 | ); 13 | } 14 | 15 | module.exports = (Router, Service) => { 16 | Router.get('/storage/tree', passportAuth, (req, res) => { 17 | res.status(426).send({ error: 'Outdated desktop version' }); 18 | }); 19 | 20 | Router.get('/desktop/tree', passportAuth, (req, res) => { 21 | res.status(426).send({ error: 'Outdated desktop version' }); 22 | }); 23 | 24 | /** 25 | * TODO 26 | */ 27 | Router.get('/desktop/list/:index', passportAuth, (req, res) => { 28 | const { user } = req; 29 | const index = parseInt(req.params.index, 10); 30 | const deleted = req.query?.trash === 'true'; 31 | 32 | if (Number.isNaN(index)) { 33 | return res.status(400).send({ error: 'Bad Index' }); 34 | } 35 | 36 | return Service.Folder.GetFoldersPaginationWithoutSharesNorThumbnails( 37 | user, 38 | index, 39 | { deleted } 40 | ).then((result) => { 41 | res.status(200).send(result); 42 | }).catch((err) => { 43 | logError('LIST', user, err); 44 | res.status(500).send({ error: 'Internal Server Error' }); 45 | }); 46 | }); 47 | 48 | /** 49 | * TODO 50 | */ 51 | Router.post('/desktop/folders', passportAuth, (req, res) => { 52 | const folders = req.body; 53 | const { user } = req; 54 | 55 | Service.Desktop.CreateChildren(user, folders) 56 | .then((result) => { 57 | res.status(201).json(result); 58 | }) 59 | .catch((err) => { 60 | logError('FOLDERS', user, err); 61 | res.status(500).json({ error: 'Internal Server Error' }); 62 | }); 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /src/app/routes/guest.js: -------------------------------------------------------------------------------- 1 | const bip39 = require('bip39'); 2 | const AesUtil = require('../../lib/AesUtil'); 3 | const Logger = require('../../lib/logger').default; 4 | const { passportAuth } = require('../middleware/passport'); 5 | 6 | module.exports = (Router, Service) => { 7 | const logger = Logger.getInstance(); 8 | 9 | Router.post('/guest/invite', passportAuth, async (req, res) => { 10 | const sharedKey = req.headers['internxt-mnemonic']; 11 | 12 | if (!sharedKey) { 13 | return res.status(400).send({ error: 'Missing key' }); 14 | } 15 | 16 | const sharedKeyEncrypted = AesUtil.encrypt(bip39.mnemonicToEntropy(sharedKey)); 17 | 18 | const guestUser = req.body.guest && req.body.guest.toLowerCase(); 19 | 20 | if (!guestUser) { 21 | return res.status(400).send({ error: 'Missing guest user' }); 22 | } 23 | 24 | try { 25 | await Service.Guest.invite(req.user, guestUser, sharedKeyEncrypted); 26 | await Service.Mail.sendGuestInvitation(req.user, guestUser); 27 | 28 | return res.status(200).send({}); 29 | } catch (err) { 30 | logger.error('Error inviting user. %s is inviting %s: %s', req.user.email, req.body.guest, err.message); 31 | 32 | return res.status(500).send({ error: err.message || 'Internal server error' }); 33 | } 34 | }); 35 | 36 | Router.post('/guest/accept', passportAuth, async (req, res) => { 37 | try { 38 | const { payload } = req.body; 39 | 40 | await Service.Guest.acceptInvitation(req.user, payload); 41 | return res.status(200).send({}); 42 | } catch (err) { 43 | logger.error('Error accepting invitation for user %s: %s', req.user.email, err.message); 44 | 45 | return res.status(500).send({ error: err.message }); 46 | } 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /src/app/routes/mobile.js: -------------------------------------------------------------------------------- 1 | const bip39 = require('bip39'); 2 | 3 | module.exports = (Router, Service, App) => { 4 | Router.get('/bits', (req, res) => { 5 | const newBits = bip39.generateMnemonic(256); 6 | const eNewBits = App.services.Crypt.encryptText(newBits); 7 | res.status(200).send({ bits: eNewBits }); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /src/app/routes/newsletter.js: -------------------------------------------------------------------------------- 1 | const passport = require('../middleware/passport'); 2 | const Logger = require('../../lib/logger').default; 3 | 4 | const { passportAuth } = passport; 5 | 6 | module.exports = (Router, Service) => { 7 | const logger = Logger.getInstance(); 8 | 9 | Router.post('/newsletter/subscribe', passportAuth, async (req, res) => { 10 | const { email } = req.body; 11 | const { user } = req; 12 | const mailerLiteGroupId = '51650193869768251'; 13 | 14 | try { 15 | await Service.Newsletter.subscribe(email, mailerLiteGroupId); 16 | 17 | await Service.UsersReferrals.applyUserReferral(user.id, 'subscribe-to-newsletter'); 18 | 19 | res.status(200).send({ message: 'Subscribed to newsletter!' }); 20 | } catch (err) { 21 | const errMessage = `Error subscribing to newsletter email '${email}': ${err}`; 22 | 23 | logger.error(errMessage); 24 | res.status(400).send({ error: errMessage }); 25 | } 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /src/app/routes/plan.js: -------------------------------------------------------------------------------- 1 | const createHttpError = require('http-errors'); 2 | const passport = require('../middleware/passport'); 3 | const logger = require('../../lib/logger').default; 4 | 5 | const Logger = logger.getInstance(); 6 | 7 | const { passportAuth } = passport; 8 | 9 | module.exports = (Router, Service) => { 10 | Router.get('/plan/individual', passportAuth, async (req, res) => { 11 | try { 12 | const { user } = req; 13 | 14 | const appSumoPlan = await Service.AppSumo.GetDetails(user).catch(() => null); 15 | 16 | if (appSumoPlan && appSumoPlan.planId !== 'internxt_free1') { 17 | const result = { 18 | isAppSumo: true, 19 | price: 0, 20 | details: appSumoPlan, 21 | }; 22 | 23 | return res.status(200).send(result); 24 | } 25 | 26 | const plan = await Service.Plan.getIndividualPlan(user.bridgeUser, user.userId); 27 | 28 | if (!plan) { 29 | throw createHttpError(404, 'Individual plan not found'); 30 | } 31 | 32 | return res.status(200).json(plan); 33 | } catch (error) { 34 | const errorMessage = error.message || ''; 35 | 36 | Logger.error(`Error getting stripe individual plan ${req.user.email}: ${error.message}`); 37 | return res.status(500).send({ error: errorMessage }); 38 | } 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /src/app/routes/types.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { UserAttributes } from '../models/user'; 3 | 4 | export type AuthorizedUser = Request & { user: UserAttributes }; 5 | -------------------------------------------------------------------------------- /src/app/services/errors/FileWithNameAlreadyExistsError.ts: -------------------------------------------------------------------------------- 1 | export class FileWithNameAlreadyExistsError extends Error { 2 | constructor(message: string) { 3 | super(message); 4 | 5 | Object.setPrototypeOf(this, FileWithNameAlreadyExistsError.prototype); 6 | } 7 | } 8 | 9 | export class FileAlreadyExistsError extends Error { 10 | constructor(message: string) { 11 | super(message); 12 | 13 | Object.setPrototypeOf(this, FileAlreadyExistsError.prototype); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/services/errors/FolderWithNameAlreadyExistsError.ts: -------------------------------------------------------------------------------- 1 | export class FolderWithNameAlreadyExistsError extends Error { 2 | constructor(message: string) { 3 | super(message); 4 | 5 | Object.setPrototypeOf(this, FolderWithNameAlreadyExistsError.prototype); 6 | } 7 | } 8 | 9 | export class FolderAlreadyExistsError extends Error { 10 | constructor(message: string) { 11 | super(message); 12 | 13 | Object.setPrototypeOf(this, FolderAlreadyExistsError.prototype); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/services/errors/locks.ts: -------------------------------------------------------------------------------- 1 | export class LockNotAvaliableError extends Error { 2 | constructor(folderId: number) { 3 | super(`Lock for ${folderId} is not avaliable`); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/errors/referrals.ts: -------------------------------------------------------------------------------- 1 | export class ReferralsNotAvailableError extends Error { 2 | constructor() { 3 | super('Referrals program not available for this user'); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/app/services/featureLimit.js: -------------------------------------------------------------------------------- 1 | const { INDIVIDUAL_FREE_TIER_PLAN_ID } = require('../constants'); 2 | 3 | module.exports = (Model, App) => { 4 | const getTierByPlanId = async (planId) => { 5 | return Model.paidPlans.findOne({ 6 | where: { planId }, 7 | include: [Model.tiers], 8 | }); 9 | }; 10 | 11 | const getIndividualFreeTier = async () => { 12 | return getTierByPlanId(INDIVIDUAL_FREE_TIER_PLAN_ID); 13 | }; 14 | 15 | return { 16 | Name: 'FeatureLimits', 17 | getTierByPlanId, 18 | getIndividualFreeTier, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/app/services/keyserver.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('sequelize'); 2 | 3 | const { Op } = sequelize; 4 | 5 | module.exports = (Model) => { 6 | const getKeys = (user) => 7 | Model.keyserver.findOne({ 8 | where: { 9 | user_id: { [Op.eq]: user.id }, 10 | encrypt_version: { [Op.eq]: 'ecc' } 11 | }, 12 | }); 13 | 14 | const keysExists = async (user) => { 15 | if (!user) { 16 | return false; 17 | } 18 | const keys = await getKeys(user); 19 | return !!keys; 20 | }; 21 | 22 | const addKeysLogin = (userData, publicKey, privateKey, revocationKey) => 23 | new Promise((resolve, reject) => { 24 | Model.keyserver 25 | .create({ 26 | user_id: userData.id, 27 | public_key: publicKey, 28 | private_key: privateKey, 29 | revocation_key: revocationKey, 30 | encrypt_version: 'ecc', 31 | }) 32 | .then((userKeys) => { 33 | resolve(userKeys); 34 | }) 35 | .catch(() => { 36 | reject(Error('Error querying database')); 37 | }); 38 | }); 39 | 40 | const removeKeys = (userId) => { 41 | Model.keyserver.destroy({ where: { user_id: { [Op.eq]: userId } } }); 42 | }; 43 | 44 | return { 45 | Name: 'KeyServer', 46 | addKeysLogin, 47 | keysExists, 48 | getKeys, 49 | removeKeys, 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /src/app/services/limit.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require('axios'); 2 | const CryptService = require('./crypt'); 3 | const { default: Redis } = require('../../config/initializers/redis'); 4 | 5 | module.exports = (Model, App) => { 6 | const cryptService = CryptService(Model, App); 7 | 8 | const getLimit = async (userEmail, userId, userUuid, userUpdatedAt) => { 9 | if (userUpdatedAt) { 10 | const updatedAtTime = new Date(userUpdatedAt).getTime(); 11 | Redis.getInstance(); 12 | const limitRecord = await Redis.getLimit(userUuid); 13 | 14 | if (limitRecord && limitRecord.cachedAt > updatedAtTime) { 15 | console.log('Cache hit for limit on user', userUuid); 16 | 17 | return { maxSpaceBytes: limitRecord.limit }; 18 | } 19 | } 20 | 21 | const pwd = userId; 22 | const pwdHash = cryptService.hashSha256(pwd); 23 | const credential = Buffer.from(`${userEmail}:${pwdHash}`).toString('base64'); 24 | 25 | const response = await axios 26 | .get(`${App.config.get('STORJ_BRIDGE')}/limit`, { 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | Authorization: `Basic ${credential}`, 30 | }, 31 | }); 32 | 33 | if (userUuid) { 34 | await Redis.setLimit(userUuid, response.data.maxSpaceBytes); 35 | } 36 | 37 | return response.data; 38 | }; 39 | 40 | const expireLimit = async (userUuid) => { 41 | Redis.getInstance(); 42 | 43 | await Redis.expireLimit(userUuid); 44 | }; 45 | 46 | return { 47 | Name: 'Limit', 48 | getLimit, 49 | expireLimit, 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /src/app/services/mailer/mailer.service.ts: -------------------------------------------------------------------------------- 1 | import sendgrid from '@sendgrid/mail'; 2 | 3 | export class MailerService { 4 | constructor() { 5 | const sendgridApiKey = process.env.SENDGRID_API_KEY || ''; 6 | sendgrid.setApiKey(sendgridApiKey); 7 | } 8 | 9 | send(email: string, templateId: string, context: any) { 10 | const msg = { 11 | to: email, 12 | from: { 13 | email: process.env.SENDGRID_FROM || 'hello@internxt.com', 14 | name: process.env.SENDGRID_NAME || 'Internxt', 15 | }, 16 | subject: '', 17 | text: 'email text', 18 | html: 'email text', 19 | personalizations: [ 20 | { 21 | to: [ 22 | { 23 | email, 24 | }, 25 | ], 26 | dynamic_template_data: context, 27 | }, 28 | ], 29 | template_id: templateId, 30 | mail_settings: { 31 | sandbox_mode: { 32 | enable: process.env.SENDGRID_MODE_SANDBOX === 'true' || false, 33 | }, 34 | }, 35 | }; 36 | return sendgrid.send(msg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/services/newsletter.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require('axios'); 2 | const GROUP_ID = process.env.MAILERLITE_GROUP_ID; 3 | module.exports = () => { 4 | const subscribe = async (email, groupId = GROUP_ID) => { 5 | await axios.post( 6 | `https://api.mailerlite.com/api/v2/groups/${groupId}/subscribers`, 7 | { email, resubscribe: true, autoresponders: true }, 8 | { 9 | headers: { 10 | Accept: 'application/json', 11 | 'X-MailerLite-ApiDocs': 'true', 12 | 'Content-Type': 'application/json', 13 | 'X-MailerLite-ApiKey': process.env.MAILERLITE_API_KEY, 14 | }, 15 | }, 16 | ); 17 | }; 18 | 19 | return { 20 | Name: 'Newsletter', 21 | subscribe, 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/app/services/privateSharing.js: -------------------------------------------------------------------------------- 1 | class PrivateSharingFolderRoleNotFound extends Error { 2 | constructor() { 3 | super('Role inside this resource not found'); 4 | 5 | Object.setPrototypeOf(this, PrivateSharingFolderRoleNotFound.prototype); 6 | } 7 | } 8 | 9 | class PrivateSharingFolderPermissionsNotFound extends Error { 10 | constructor() { 11 | super('Permissions inside this resource not found'); 12 | 13 | Object.setPrototypeOf(this, PrivateSharingFolderPermissionsNotFound.prototype); 14 | } 15 | } 16 | 17 | module.exports = (Model) => { 18 | const FindUserPermissionsInsidePrivateSharing = async (sharedWithId, itemId) => { 19 | const permissions = await Model.permissions.findAll({ 20 | include: [ 21 | { 22 | model: Model.roles, 23 | include: [ 24 | { 25 | model: Model.sharingRoles, 26 | include: [ 27 | { 28 | model: Model.sharings, 29 | where: { 30 | sharedWith: sharedWithId, 31 | itemId, 32 | } 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ] 39 | }); 40 | 41 | if (!permissions) { 42 | throw new PrivateSharingFolderPermissionsNotFound(); 43 | } 44 | 45 | return permissions.map(p => p.get({ plain: true })); 46 | }; 47 | 48 | const CanUserPerformAction = async (sharedWith, resourceId, action) => { 49 | const permissions = await FindUserPermissionsInsidePrivateSharing(sharedWith.uuid, resourceId); 50 | 51 | for (const permission of permissions) { 52 | if (permission.name === action) { 53 | return true; 54 | } 55 | } 56 | return false; 57 | }; 58 | 59 | return { 60 | Name: 'PrivateSharing', 61 | CanUserPerformAction 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /src/app/services/referrals.js: -------------------------------------------------------------------------------- 1 | module.exports = (Model) => { 2 | const create = async (data) => { 3 | return Model.referrals.create({ 4 | key: data.key, 5 | type: data.type, 6 | credit: data.credit, 7 | steps: data.steps, 8 | start_date: data.start_date, 9 | expiration_date: data.expiration_date, 10 | }); 11 | }; 12 | 13 | const getAllEnabled = () => { 14 | return Model.referrals.findAll({ where: { enabled: true } }); 15 | }; 16 | 17 | const getByKey = (key) => { 18 | return Model.referrals.findOne({ where: { key } }); 19 | }; 20 | 21 | return { 22 | Name: 'Referrals', 23 | create, 24 | getAllEnabled, 25 | getByKey, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/app/services/services.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const basename = path.basename(__filename); 6 | 7 | module.exports = (Model, App) => { 8 | const services = {}; 9 | const log = App.logger; 10 | try { 11 | fs.readdirSync(__dirname) 12 | .filter((file) => file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js') 13 | .forEach((file) => { 14 | const service = require(path.join(__dirname, file))(Model, App); 15 | services[service.Name] = service; 16 | }); 17 | log.info('Services loaded'); 18 | 19 | return services; 20 | } catch (error) { 21 | log.error(error); 22 | throw Error(error); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/app/services/thumbnails.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('sequelize'); 2 | const { Op } = sequelize; 3 | 4 | module.exports = (Model, App) => { 5 | const CreateThumbnail = async (user, thumbnail) => { 6 | 7 | const thumbnailExists = await Model.thumbnail.findOne({ 8 | where: { 9 | file_id: { [Op.eq]: thumbnail.file_id }, 10 | type: { [Op.eq]: thumbnail.type }, 11 | }, 12 | }); 13 | 14 | const thumbnailInfo = { 15 | file_id: thumbnail.file_id, 16 | type: thumbnail.type, 17 | max_width: thumbnail.max_width, 18 | max_height: thumbnail.max_height, 19 | size: thumbnail.size, 20 | bucket_id: thumbnail.bucket_id, 21 | bucket_file: thumbnail.bucket_file, 22 | encrypt_version: thumbnail.encrypt_version, 23 | created_at: thumbnail.created_at || new Date(), 24 | updated_at: thumbnail.updated_at || new Date(), 25 | }; 26 | 27 | if (thumbnailExists) { 28 | thumbnailInfo.updated_at = new Date(); 29 | await App.services.Inxt.DeleteFile(user, thumbnailExists.bucket_id, thumbnailExists.bucket_file); 30 | await Model.thumbnail.update( 31 | thumbnailInfo, 32 | { 33 | where: { 34 | file_id: { [Op.eq]: thumbnail.file_id }, 35 | max_width: { [Op.eq]: thumbnail.max_width }, 36 | max_height: { [Op.eq]: thumbnail.max_height }, 37 | type: { [Op.eq]: thumbnail.type }, 38 | } 39 | }, 40 | ); 41 | return await Model.thumbnail.findOne({ 42 | where: { 43 | file_id: { [Op.eq]: thumbnail.file_id }, 44 | max_width: { [Op.eq]: thumbnail.max_width }, 45 | max_height: { [Op.eq]: thumbnail.max_height }, 46 | type: { [Op.eq]: thumbnail.type }, 47 | }, 48 | }); 49 | } else { 50 | return Model.thumbnail.create(thumbnailInfo); 51 | } 52 | }; 53 | 54 | return { 55 | Name: 'Thumbnails', 56 | CreateThumbnail, 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /src/app/services/utils.js: -------------------------------------------------------------------------------- 1 | const getNewMoveName = (originalName, i) => `${originalName} (${i})`; 2 | 3 | module.exports = () => { 4 | const IsBucketId = (targetId) => { 5 | const bucketIdPattern = /^[a-z0-9]{24}$/; 6 | const isString = typeof targetId === 'string'; 7 | return isString && !!bucketIdPattern.exec(targetId); 8 | }; 9 | 10 | const IsDatabaseId = (targetId) => { 11 | const isString = typeof targetId === 'string'; 12 | const isNumber = typeof targetId === 'number'; 13 | 14 | let isValidDatabaseId = false; 15 | 16 | if (IsBucketId(targetId)) { 17 | isValidDatabaseId = false; 18 | } else if (isString) { 19 | isValidDatabaseId = !!/^[0-9]+$/.exec(targetId); 20 | } else if (isNumber && targetId <= Number.MAX_SAFE_INTEGER) { 21 | isValidDatabaseId = !!/^[0-9]+$/.exec(targetId.toString()); 22 | } else { 23 | isValidDatabaseId = false; 24 | } 25 | 26 | return isValidDatabaseId; 27 | }; 28 | 29 | const FileNameParts = (filename) => { 30 | const pattern = /^(\.?.*?\.?)(\.([^.]*))?$/; 31 | const matches = filename.match(pattern); 32 | 33 | return { name: matches[1], ext: matches[3] ? matches[3] : null }; 34 | }; 35 | 36 | const isToday = (date) => { 37 | const otherDate = new Date(date); 38 | const todayDate = new Date(); 39 | 40 | if ( 41 | otherDate.getDate() === todayDate.getDate() && 42 | otherDate.getMonth() === todayDate.getMonth() && 43 | otherDate.getYear() === todayDate.getYear() 44 | ) { 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | }; 50 | 51 | return { 52 | Name: 'Utils', 53 | IsBucketId, 54 | IsDatabaseId, 55 | FileNameParts, 56 | getNewMoveName, 57 | isToday 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/app/sockets/socketServer.js: -------------------------------------------------------------------------------- 1 | const io = require('socket.io'); 2 | 3 | module.exports = (App) => { 4 | io(App.instance, { 5 | path: '/api/sockets', 6 | cors: { 7 | origin: '*', 8 | }, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/config/config.ts: -------------------------------------------------------------------------------- 1 | import nconf from 'nconf'; 2 | 3 | const development = require('./environments/development.js').data; 4 | const test = require('./environments/test.js').data; 5 | const staging = require('./environments/staging.js').data; 6 | const production = require('./environments/production.js').data; 7 | const e2e = require('./environments/e2e.js').data; 8 | 9 | const environments: any = { development, test, staging, e2e, production }; 10 | 11 | export default class Config { 12 | private static instance: Config; 13 | 14 | static getInstance(): Config { 15 | if (!Config.instance) { 16 | Config.instance = new Config(); 17 | } 18 | 19 | return Config.instance; 20 | } 21 | 22 | constructor() { 23 | // eslint-disable-next-line global-require 24 | nconf.argv(); 25 | nconf.env(); 26 | nconf.required(['NODE_ENV']); 27 | nconf.use('conf', { 28 | type: 'literal', 29 | // eslint-disable-next-line global-require 30 | store: environments[nconf.get('NODE_ENV')], 31 | }); 32 | nconf.required(['server:port']); 33 | } 34 | 35 | get(key: string) { 36 | return nconf.get(key); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/config/environments/development.js: -------------------------------------------------------------------------------- 1 | exports.data = { 2 | server: { 3 | port: 8000, 4 | }, 5 | database: { 6 | host: process.env.RDS_HOSTNAME, 7 | name: process.env.RDS_DBNAME, 8 | user: process.env.RDS_USERNAME, 9 | password: process.env.RDS_PASSWORD, 10 | sequelizeConfig: { 11 | host: process.env.RDS_HOSTNAME, 12 | dialect: 'postgres', 13 | port: process.env.RDS_PORT, 14 | }, 15 | }, 16 | secrets: { 17 | JWT: process.env.JWT_SECRET || 'asdf1234', 18 | CRYPTO: 'asdf1234', 19 | CAPTCHA: process.env.CAPTCHA_SECRET, 20 | CRYPTO_SECRET: process.env.CRYPTO_SECRET || 'ASDFGHJKL1234567', 21 | STRIPE_SK: process.env.STRIPE_SK, 22 | CAPTCHA_SECRET: process.env.CAPTCHA_SECRET, 23 | MAGIC_SALT: process.env.MAGIC_SALT, 24 | MAGIC_IV: process.env.MAGIC_IV, 25 | CRYPTO_SECRET2: process.env.CRYPTO_SECRET2, 26 | }, 27 | logger: { 28 | level: 'info', 29 | }, 30 | STORJ_BRIDGE: 'https://api.internxt.com', 31 | }; 32 | -------------------------------------------------------------------------------- /src/config/environments/docker.js: -------------------------------------------------------------------------------- 1 | exports.data = { 2 | server: { 3 | port: 8000, 4 | }, 5 | database: { 6 | name: process.env.RDS_DBNAME, 7 | user: process.env.RDS_USERNAME, 8 | password: process.env.RDS_PASSWORD, 9 | sequelizeConfig: { 10 | dialect: 'postgres', 11 | port: 3306, 12 | host: process.env.RDS_HOSTNAME, 13 | }, 14 | }, 15 | secrets: { 16 | JWT: process.env.JWT_SECRET || 'asdf1234', 17 | CRYPTO: 'asdf1234', 18 | CAPTCHA: process.env.CAPTCHA_SECRET, 19 | CRYPTO_SECRET: process.env.CRYPTO_SECRET || 'ASDFGHJKL1234567', 20 | STRIPE_SK: process.env.STRIPE_SK, 21 | CAPTCHA_SECRET: process.env.CAPTCHA_SECRET, 22 | MAGIC_SALT: process.env.MAGIC_SALT, 23 | MAGIC_IV: process.env.MAGIC_IV, 24 | CRYPTO_SECRET2: process.env.CRYPTO_SECRET2, 25 | }, 26 | logger: { 27 | level: 2, 28 | }, 29 | STORJ_BRIDGE: process.env.STORJ_BRIDGE, 30 | }; 31 | -------------------------------------------------------------------------------- /src/config/environments/e2e.js: -------------------------------------------------------------------------------- 1 | exports.data = { 2 | server: { 3 | port: 5555, 4 | }, 5 | database: { 6 | name: process.env.RDS_DBNAME, 7 | user: process.env.RDS_USERNAME || 'root', 8 | password: process.env.RDS_PASSWORD || '', 9 | sequelizeConfig: { 10 | dialect: 'postgres', 11 | port: process.env.RDS_PORT, 12 | host: process.env.RDS_HOSTNAME || 'localhost', 13 | }, 14 | }, 15 | secrets: { 16 | JWT: process.env.JWT_SECRET || 'asdf1234', 17 | CRYPTO: 'asdf1234', 18 | CAPTCHA: process.env.CAPTCHA_SECRET, 19 | CRYPTO_SECRET: process.env.CRYPTO_SECRET || 'ASDFGHJKL1234567', 20 | STRIPE_SK: process.env.STRIPE_SK, 21 | CAPTCHA_SECRET: process.env.CAPTCHA_SECRET, 22 | MAGIC_SALT: process.env.MAGIC_SALT, 23 | MAGIC_IV: process.env.MAGIC_IV, 24 | CRYPTO_SECRET2: process.env.CRYPTO_SECRET2, 25 | }, 26 | logger: { 27 | level: 0, 28 | }, 29 | STORJ_BRIDGE: 'https://api.internxt.com', 30 | }; 31 | -------------------------------------------------------------------------------- /src/config/environments/env.ts: -------------------------------------------------------------------------------- 1 | export function isProduction(): boolean { 2 | return process.env.NODE_ENV === 'staging'; 3 | } 4 | -------------------------------------------------------------------------------- /src/config/environments/production.js: -------------------------------------------------------------------------------- 1 | exports.data = { 2 | server: { 3 | port: 8000, 4 | }, 5 | database: { 6 | host: process.env.RDS_HOSTNAME, 7 | name: process.env.RDS_DBNAME, 8 | user: process.env.RDS_USERNAME, 9 | password: process.env.RDS_PASSWORD, 10 | sequelizeConfig: { 11 | dialect: 'postgres', 12 | port: process.env.RDS_PORT, 13 | dialectOptions: { 14 | ssl: { 15 | require: true, 16 | rejectUnauthorized: false, 17 | }, 18 | }, 19 | replication: { 20 | read: [ 21 | { host: process.env.RDS_HOSTNAME, username: process.env.RDS_USERNAME, password: process.env.RDS_PASSWORD }, 22 | { host: process.env.RDS_HOSTNAME2, username: process.env.RDS_USERNAME, password: process.env.RDS_PASSWORD }, 23 | { host: process.env.RDS_HOSTNAME3, username: process.env.RDS_USERNAME, password: process.env.RDS_PASSWORD }, 24 | ], 25 | write: { 26 | host: process.env.RDS_HOSTNAME, 27 | username: process.env.RDS_USERNAME, 28 | password: process.env.RDS_PASSWORD, 29 | }, 30 | }, 31 | }, 32 | }, 33 | secrets: { 34 | JWT: process.env.JWT_SECRET || 'asdf1234', 35 | CRYPTO: 'asdf1234', 36 | CAPTCHA: process.env.CAPTCHA_SECRET, 37 | CRYPTO_SECRET: process.env.CRYPTO_SECRET || 'ASDFGHJKL1234567', 38 | STRIPE_SK: process.env.STRIPE_SK, 39 | MAGIC_SALT: process.env.MAGIC_SALT, 40 | MAGIC_IV: process.env.MAGIC_IV, 41 | CRYPTO_SECRET2: process.env.CRYPTO_SECRET2, 42 | }, 43 | logger: { 44 | level: 4, 45 | }, 46 | STORJ_BRIDGE: 'https://api.internxt.com', 47 | }; 48 | -------------------------------------------------------------------------------- /src/config/environments/sequelize.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | dialect: 'postgres', 4 | host: process.env.RDS_HOSTNAME, 5 | database: process.env.RDS_DBNAME, 6 | username: process.env.RDS_USERNAME, 7 | password: process.env.RDS_PASSWORD, 8 | port: process.env.RDS_PORT, 9 | logging: true, 10 | }, 11 | e2e: { 12 | dialect: 'postgres', 13 | host: process.env.RDS_HOSTNAME, 14 | database: process.env.RDS_DBNAME, 15 | username: process.env.RDS_USERNAME, 16 | password: process.env.RDS_PASSWORD, 17 | port: process.env.RDS_PORT, 18 | logging: true, 19 | }, 20 | test: { 21 | username: 'root', 22 | password: null, 23 | database: 'drive_test', 24 | host: '127.0.0.1', 25 | dialect: 'postgres', 26 | dialectOptions: { 27 | ssl: { 28 | require: true, 29 | rejectUnauthorized: false, 30 | }, 31 | }, 32 | }, 33 | staging: { 34 | host: process.env.RDS_HOSTNAME, 35 | database: process.env.RDS_DBNAME, 36 | username: process.env.RDS_USERNAME, 37 | password: process.env.RDS_PASSWORD, 38 | port: process.env.RDS_PORT, 39 | dialect: 'postgres', 40 | dialectOptions: { 41 | ssl: { 42 | require: true, 43 | rejectUnauthorized: false, 44 | }, 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /src/config/environments/staging.js: -------------------------------------------------------------------------------- 1 | exports.data = { 2 | server: { 3 | port: 8000, 4 | }, 5 | database: { 6 | host: process.env.RDS_HOSTNAME, 7 | name: process.env.RDS_DBNAME, 8 | user: process.env.RDS_USERNAME, 9 | password: process.env.RDS_PASSWORD, 10 | sequelizeConfig: { 11 | dialect: 'postgres', 12 | port: process.env.RDS_PORT, 13 | dialectOptions: { 14 | ssl: { 15 | require: true, 16 | rejectUnauthorized: false, 17 | }, 18 | }, 19 | replication: { 20 | read: [ 21 | { host: process.env.RDS_HOSTNAME, username: process.env.RDS_USERNAME, password: process.env.RDS_PASSWORD }, 22 | { host: process.env.RDS_HOSTNAME2, username: process.env.RDS_USERNAME, password: process.env.RDS_PASSWORD }, 23 | { host: process.env.RDS_HOSTNAME3, username: process.env.RDS_USERNAME, password: process.env.RDS_PASSWORD }, 24 | ], 25 | write: { 26 | host: process.env.RDS_HOSTNAME, 27 | username: process.env.RDS_USERNAME, 28 | password: process.env.RDS_PASSWORD, 29 | }, 30 | }, 31 | }, 32 | }, 33 | secrets: { 34 | JWT: process.env.JWT_SECRET || 'asdf1234', 35 | CRYPTO: 'asdf1234', 36 | CAPTCHA: process.env.CAPTCHA_SECRET, 37 | CRYPTO_SECRET: process.env.CRYPTO_SECRET || 'ASDFGHJKL1234567', 38 | STRIPE_SK: process.env.STRIPE_SK, 39 | MAGIC_SALT: process.env.MAGIC_SALT, 40 | MAGIC_IV: process.env.MAGIC_IV, 41 | CRYPTO_SECRET2: process.env.CRYPTO_SECRET2, 42 | }, 43 | logger: { 44 | level: 4, 45 | }, 46 | STORJ_BRIDGE: 'https://api.internxt.com', 47 | }; 48 | -------------------------------------------------------------------------------- /src/config/environments/test.js: -------------------------------------------------------------------------------- 1 | exports.data = { 2 | server: { 3 | port: 3007, 4 | }, 5 | database: { 6 | name: 'drive_test', 7 | user: 'developer', 8 | password: 'asdf1234', 9 | host: 'localhost', 10 | }, 11 | secrets: { 12 | JWT: 'asdf1234', 13 | CRYPTO_SECRET: 'asdf1234', 14 | MAGIC_IV: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 15 | MAGIC_SALT: 16 | 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\ 17 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 18 | }, 19 | logger: { 20 | level: 'emerg', 21 | }, 22 | STORJ_BRIDGE: 'http://localhost:6382', 23 | }; 24 | -------------------------------------------------------------------------------- /src/config/initializers/avatarS3.ts: -------------------------------------------------------------------------------- 1 | import AWS from 'aws-sdk'; 2 | 3 | export default class AvatarS3 { 4 | private static instance: AWS.S3; 5 | 6 | static getInstance(): AWS.S3 { 7 | if (AvatarS3.instance) { 8 | return AvatarS3.instance; 9 | } 10 | AvatarS3.instance = new AWS.S3({ 11 | endpoint: process.env.AVATAR_ENDPOINT as string, 12 | region: process.env.AVATAR_REGION, 13 | credentials: { 14 | accessKeyId: process.env.AVATAR_ACCESS_KEY as string, 15 | secretAccessKey: process.env.AVATAR_SECRET_KEY as string, 16 | }, 17 | s3ForcePathStyle: process.env.AVATAR_FORCE_PATH_STYLE === 'true', 18 | }); 19 | 20 | return AvatarS3.instance; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/config/initializers/swagger.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const routes = path.join(process.cwd(), 'app/routes/index.js'); 4 | 5 | const options = { 6 | swaggerDefinition: { 7 | info: { 8 | title: 'Drive Server', // Title (required) 9 | version: '1.0.0', // Version (required) 10 | }, 11 | }, 12 | apis: [routes], // Path to the API docs 13 | }; 14 | 15 | // Initialize swagger-jsdoc -> returns validated swagger spec in json format 16 | module.exports = options; 17 | -------------------------------------------------------------------------------- /src/lib/Validator.ts: -------------------------------------------------------------------------------- 1 | export default class Validator { 2 | public static isValidString(string: unknown): boolean { 3 | return typeof string === 'string' && string.length > 0; 4 | } 5 | 6 | public static isInvalidString(string: unknown): boolean { 7 | return !Validator.isValidString(string); 8 | } 9 | 10 | public static isInvalidPositiveNumber(number: unknown): boolean { 11 | return isNaN(Number(number)) || Number(number) <= 0; 12 | } 13 | 14 | public static isInvalidUnsignedNumber(number: unknown): boolean { 15 | return isNaN(Number(number)) || Number(number) < 0; 16 | } 17 | 18 | static isEmpty(field: unknown) { 19 | return !field; 20 | } 21 | } -------------------------------------------------------------------------------- /src/lib/analytics/Analytics.ts: -------------------------------------------------------------------------------- 1 | import AnalyticsRudder from '@rudderstack/rudder-sdk-node'; 2 | import { logError } from './utils'; 3 | 4 | class Analytics { 5 | analytics!: AnalyticsRudder; 6 | 7 | constructor() { 8 | try { 9 | this.analytics = new AnalyticsRudder( 10 | process.env.ANALYTICS_RUDDER_KEY || '', 11 | process.env.ANALYTICS_RUDDER_PLAN_URL || '', 12 | ); 13 | } catch (err: any) { 14 | logError(`Error initializing analytics: ${err.message}`); 15 | } 16 | } 17 | 18 | track(params: any) { 19 | try { 20 | this.analytics.track(params); 21 | } catch (err: unknown) { 22 | logError(err); 23 | } 24 | } 25 | 26 | identify(params: any) { 27 | try { 28 | this.analytics.identify(params); 29 | } catch (err: unknown) { 30 | logError(err); 31 | } 32 | } 33 | 34 | page(params: any) { 35 | try { 36 | this.analytics.page(params); 37 | } catch (err: unknown) { 38 | logError(err); 39 | } 40 | } 41 | } 42 | 43 | export default new Analytics(); 44 | -------------------------------------------------------------------------------- /src/lib/analytics/types.ts: -------------------------------------------------------------------------------- 1 | export interface Location { 2 | country: string; 3 | region: string; 4 | city: string; 5 | timezone: string; 6 | } 7 | 8 | interface App { 9 | name?: string; 10 | version?: string; 11 | } 12 | 13 | interface Campaign { 14 | name?: string; 15 | source?: string; 16 | medium?: string; 17 | term?: string; 18 | content?: string; 19 | } 20 | 21 | export interface Context { 22 | app: App; 23 | campaign?: Campaign; 24 | ip: string; 25 | location?: Location; 26 | userAgent: string; 27 | locale: string; 28 | } 29 | 30 | export interface User { 31 | uuid: string; 32 | appsumoDetails: boolean; 33 | sharedWorkspace: boolean; 34 | name: string; 35 | lastname: string; 36 | } 37 | 38 | export interface ReqUser { 39 | user: { uuid: string }; 40 | } 41 | 42 | export enum TrackName { 43 | DeactivationRequest = 'Deactivation Requested', 44 | SignUp = 'User Signup', 45 | InvitationSent = 'Invitation Sent', 46 | DeactivationConfirmed = 'Deactivation Confirmed', 47 | ReferralRedeemed = 'Referral Redeemed', 48 | InvitationAccepted = 'Invitation Accepted', 49 | UploadCompleted = 'Upload Completed', 50 | ShareLinkCopied = 'Share Link Copied', 51 | FileDeleted = 'File Deleted', 52 | SharedLinkItemDownloaded = 'Shared Link Downloaded', 53 | DownloadCompleted = 'Download Completed', 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | import os from 'os'; 3 | import { isProduction } from '../config/environments/env'; 4 | 5 | const { splat, combine, printf, timestamp } = winston.format; 6 | 7 | const serverHostname = os.hostname(); 8 | const colorize = winston.format.colorize({ all: true }); 9 | 10 | const winstonProdOptions: winston.LoggerOptions = { 11 | level: 'info', 12 | exitOnError: true, 13 | handleExceptions: true, 14 | format: combine( 15 | timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), 16 | splat(), 17 | printf((info) => { 18 | return JSON.stringify({ 19 | hostname: serverHostname, 20 | requestId: (info.meta && info.meta.requestId) ?? '0', 21 | timestamp: info.timestamp, 22 | level: info.level, 23 | message: info.message, 24 | meta: info.meta ?? {}, 25 | }); 26 | }), 27 | ), 28 | transports: [new winston.transports.Console()], 29 | }; 30 | 31 | const winstonDevOptions: winston.LoggerOptions = { 32 | level: 'info', 33 | exitOnError: true, 34 | handleExceptions: true, 35 | format: combine( 36 | timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), 37 | splat(), 38 | printf((info) => { 39 | const message = colorize.colorize(info.level, `${info.level}: ${info.message}`); 40 | return `${info.timestamp} ${serverHostname} ${message}`; 41 | }), 42 | ), 43 | transports: [new winston.transports.Console()], 44 | }; 45 | 46 | type LoggerConfig = { 47 | level: string; 48 | } 49 | 50 | export default class Logger { 51 | private static instance: winston.Logger; 52 | 53 | static getInstance(config?: LoggerConfig): winston.Logger { 54 | if (!Logger.instance) { 55 | Logger.instance = loggerInstance(config); 56 | } 57 | 58 | return Logger.instance; 59 | } 60 | } 61 | 62 | const loggerInstance = (config?: LoggerConfig): winston.Logger => { 63 | return winston.createLogger(isProduction() ? winstonProdOptions : {...winstonDevOptions, ...config}); 64 | }; 65 | -------------------------------------------------------------------------------- /src/lib/performance/network.ts: -------------------------------------------------------------------------------- 1 | import Agent from 'agentkeepalive'; 2 | import axios from 'axios'; 3 | 4 | function createHttpAgent() { 5 | return new Agent({ 6 | keepAlive: true, 7 | maxSockets: 100, 8 | maxFreeSockets: 10, 9 | timeout: 10000, 10 | freeSocketTimeout: 30000, 11 | }); 12 | } 13 | 14 | function createHttpsAgent() { 15 | return new Agent.HttpsAgent({ 16 | keepAlive: true, 17 | maxSockets: 100, 18 | maxFreeSockets: 10, 19 | timeout: 10000, 20 | freeSocketTimeout: 30000, 21 | }); 22 | } 23 | 24 | export function configureHttp(): void { 25 | axios.defaults.httpAgent = createHttpAgent(); 26 | axios.defaults.httpsAgent = createHttpsAgent(); 27 | } -------------------------------------------------------------------------------- /src/lib/recaptcha.ts: -------------------------------------------------------------------------------- 1 | // Google ReCaptcha V3 2 | import axios from 'axios'; 3 | import { encode } from 'querystring'; 4 | import { isProduction } from '../config/environments/env'; 5 | 6 | const GOOGLE_RECAPTCHA_V3_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify'; 7 | 8 | export async function verify(captcha: any, remoteip?: string) { 9 | if (!isProduction()) { 10 | return {}; 11 | } 12 | const body = { 13 | secret: process.env.RECAPTCHA_V3, 14 | response: captcha, 15 | remoteip, 16 | }; 17 | 18 | return axios 19 | .post(GOOGLE_RECAPTCHA_V3_ENDPOINT, encode(body), { 20 | headers: { 21 | 'Content-Type': 'application/x-www-form-urlencoded', 22 | }, 23 | }) 24 | .then((res: any) => { 25 | if (!res.data.success) { 26 | throw Error(res.data['error-codes']); 27 | } 28 | 29 | const scoreThreshold = process.env.RECAPTCHA_V3_SCORE_THRESHOLD || 0.5; 30 | const { score } = res.data; 31 | 32 | if (score < scoreThreshold) { 33 | throw Error(`Score ${score} under ${scoreThreshold}`); 34 | } 35 | 36 | return res.data; 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /tests/e2e/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/e2e/setup.ts: -------------------------------------------------------------------------------- 1 | import { Express } from 'express'; 2 | 3 | export function applicationInitialization(app: Express): Promise { 4 | const TRIES_UNTIL_DRIVE_SERVER_IS_READY = 10; 5 | const INTERVAL_FOR_RETRY = 3000; 6 | 7 | return new Promise((resolve, reject) => { 8 | let tries = 0; 9 | const interval = setInterval(async () => { 10 | try { 11 | app.post('/api/data/t'); 12 | clearInterval(interval); 13 | resolve(); 14 | } catch (err) { 15 | tries += 1; 16 | 17 | if (tries > TRIES_UNTIL_DRIVE_SERVER_IS_READY) { 18 | clearInterval(interval); 19 | reject(new Error('Too many tries to connect to Drive Server')); 20 | } else { 21 | // eslint-disable-next-line no-console 22 | console.log(`Drive Server not ready yet, waiting: ${INTERVAL_FOR_RETRY / 1000} more seconds`); 23 | } 24 | } 25 | }, INTERVAL_FOR_RETRY); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /tests/e2e/utils.ts: -------------------------------------------------------------------------------- 1 | import { aes } from '@internxt/lib'; 2 | const bip39 = require('bip39'); 3 | const server = require('../../src/app'); 4 | 5 | export function encryptFilename(filename: string, folderId: number): string { 6 | const aesInit = { 7 | iv: 'd139cb9a2cd17092e79e1861cf9d7023', 8 | // eslint-disable-next-line max-len 9 | salt: '38dce0391b49efba88dbc8c39ebf868f0267eb110bb0012ab27dc52a528d61b1d1ed9d76f400ff58e3240028442b1eab9bb84e111d9dadd997982dbde9dbd25e', 10 | }; 11 | const CRYPTO_KEY = '8Q8VMUE3BJZV87GT'; 12 | 13 | return aes.encrypt(filename, `${CRYPTO_KEY}-${folderId}`, aesInit); 14 | } 15 | 16 | export async function createTestUser(email: string): Promise { 17 | const cryptService = server.services.Crypt; 18 | 19 | const randomPassword = cryptService.RandomPassword(email); 20 | const encryptedPassword = cryptService.passToHash({ password: randomPassword }); 21 | 22 | const encryptedHash = cryptService.encryptText(encryptedPassword.hash); 23 | const encryptedSalt = cryptService.encryptText(encryptedPassword.salt); 24 | 25 | const newMnemonic = bip39.generateMnemonic(256); 26 | const encryptedMnemonic = cryptService.encryptTextWithKey(newMnemonic, randomPassword); 27 | 28 | const userCreated = await server.services.User.FindOrCreate({ 29 | email: email, 30 | name: 'test', 31 | lastname: 'e2e', 32 | referrer: null, 33 | uuid: null, 34 | credit: 0, 35 | welcomePack: true, 36 | registerCompleted: false, 37 | username: email, 38 | sharedWorkspace: false, 39 | bridgeUser: email, 40 | password: encryptedHash, 41 | mnemonic: encryptedMnemonic, 42 | salt: encryptedSalt, 43 | }); 44 | 45 | return await server.services.User.InitializeUser(userCreated); 46 | } 47 | 48 | export async function deleteTestUser(userId: number): Promise { 49 | const user = await server.models.users.findOne({ where: { id: userId } }); 50 | await user.destroy(); 51 | } 52 | 53 | export async function delay(seconds: number): Promise { 54 | return new Promise((resolve) => { 55 | setTimeout(() => { 56 | resolve(); 57 | }, seconds * 1000); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /tests/services/backups.unit.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { stub } from 'sinon'; 3 | 4 | const initBackups = require('../../src/app/services/backups'); 5 | 6 | const App = { 7 | services: { 8 | Crypt: { 9 | encryptName(name, bucket) {}, 10 | }, 11 | User: { 12 | async FindUserObjByEmail(email) { 13 | return { backupsBucket: '' }; 14 | }, 15 | }, 16 | }, 17 | }; 18 | 19 | const Model = { 20 | folder: { 21 | async findOne() { 22 | return { 23 | bucket: '', 24 | }; 25 | }, 26 | async update() { 27 | return {}; 28 | }, 29 | async create() { 30 | return {}; 31 | }, 32 | }, 33 | }; 34 | 35 | const backupsService = initBackups(Model, App); 36 | 37 | const findOneStub = stub(Model.folder, 'findOne'); 38 | const encryptNameStub = stub(App.services.Crypt, 'encryptName'); 39 | const FindUserObjByEmailStub = stub(App.services.User, 'FindUserObjByEmail'); 40 | 41 | describe('# backups', () => { 42 | it('renameDeviceAsFolder()', async () => { 43 | const bucket = 'bucket'; 44 | findOneStub.returns(Promise.resolve({ bucket, update() {} })); 45 | 46 | await backupsService.renameDeviceAsFolder({ id: 'foo' }); 47 | 48 | expect(encryptNameStub.calledOnceWith(undefined, bucket)).to.be.true; 49 | }); 50 | it('createDeviceAsFolder()', async () => { 51 | const bucket = 'bucket'; 52 | FindUserObjByEmailStub.resolves({ backupsBucket: bucket }); 53 | findOneStub.returns(Promise.resolve(null)); 54 | 55 | await backupsService.createDeviceAsFolder({ id: 'foo', email: 'bar' }); 56 | 57 | expect(encryptNameStub.calledOnceWith(undefined, 'bucket')).to.be.true; 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/services/openpgp.unit.ts: -------------------------------------------------------------------------------- 1 | import * as openpgp from 'openpgp'; 2 | import { expect } from 'chai'; 3 | import { describe, it } from 'mocha'; 4 | const { encrypt } = require('../../src/lib/AesUtil'); 5 | 6 | describe('# openpgp', () => { 7 | it('should have correct length', async () => { 8 | const randomKey = await openpgp.generateKey({ 9 | userIDs: [{ email: 'inxt@inxt.com' }], 10 | curve: 'ed25519' 11 | }); 12 | 13 | const privateKey = Buffer.from(randomKey.privateKey).toString('base64'); 14 | const publicKey = Buffer.from(randomKey.publicKey).toString('base64'); 15 | const revocationCertificate = Buffer.from(randomKey.revocationCertificate).toString('base64'); 16 | 17 | const encryptedPrivateKey1 = encrypt(privateKey, '1234'); 18 | const encryptedPrivateKey2 = encrypt(privateKey, '1234123412341234'); 19 | 20 | expect(encryptedPrivateKey1.length).to.be.equals(encryptedPrivateKey2.length); 21 | expect(publicKey.length).to.be.lessThan(920); 22 | expect(revocationCertificate.length).to.be.lessThan(476); 23 | }); 24 | 25 | it('should have consistent lengths', async () => { 26 | const randomKey1 = await openpgp.generateKey({ 27 | userIDs: [{ email: 'inxt@inxt.com' }], 28 | curve: 'ed25519' 29 | }); 30 | 31 | const randomKey2 = await openpgp.generateKey({ 32 | userIDs: [{ email: 'inxt@inxt.com' }], 33 | curve: 'ed25519' 34 | }); 35 | 36 | const randomKey3 = await openpgp.generateKey({ 37 | userIDs: [{ email: 'inxt@inxt.com' }], 38 | curve: 'ed25519' 39 | }); 40 | 41 | expect(randomKey1.publicKey.length).to.be.equals(randomKey2.publicKey.length); 42 | expect(randomKey2.publicKey.length).to.be.equals(randomKey3.publicKey.length); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/services/utils.unit.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import Server from '../../src/config/initializers/server'; 5 | 6 | const utilsService = require('../../src/app/services/utils'); 7 | 8 | const App = new Server(); 9 | const utils = utilsService(null, App); 10 | 11 | const validBucketIds = ['aaaaaaaaaaaaaaaaaaaaaaaa', '154785478541256987458987']; 12 | 13 | const validDatabaseIds = [0, 999, 54125, 14, 12566]; 14 | 15 | describe('# Utils', () => { 16 | describe('# isBucketId', () => { 17 | validBucketIds.forEach((bucketId) => { 18 | it(`bucket id ${bucketId} should be valid`, () => { 19 | expect(utils.IsBucketId(bucketId)).to.be.true; 20 | }); 21 | }); 22 | 23 | validDatabaseIds.forEach((databaseId) => { 24 | it(`database id ${databaseId} should NOT be valid bucket id`, () => { 25 | expect(utils.IsBucketId(databaseId)).to.be.false; 26 | }); 27 | }); 28 | }); 29 | 30 | describe('# isDatabaseId', () => { 31 | validBucketIds.forEach((bucketId) => { 32 | it(`bucket id ${bucketId} should NOT be valid database id`, () => { 33 | expect(utils.IsDatabaseId(bucketId)).to.be.false; 34 | }); 35 | }); 36 | 37 | validDatabaseIds.forEach((databaseId) => { 38 | it(`database id ${databaseId} should be valid`, () => { 39 | expect(utils.IsDatabaseId(databaseId)).to.be.true; 40 | }); 41 | }); 42 | 43 | it('invalid databaseIds', () => { 44 | expect(utils.IsDatabaseId(Number.MAX_VALUE)).to.be.false; 45 | expect(utils.IsDatabaseId(Number.MAX_SAFE_INTEGER)).to.be.true; 46 | }); 47 | }); 48 | }); 49 | --------------------------------------------------------------------------------