瀏覽代碼

feat: script to mass delete accounts from CSV source (#913)

Karol Sójko 1 年之前
父節點
當前提交
a6dea50d74
共有 79 個文件被更改,包括 1652 次插入0 次删除
  1. 697 0
      .pnp.cjs
  2. 二進制
      .yarn/cache/@aws-sdk-client-s3-npm-3.445.0-fd17753af1-0a75a875cc.zip
  3. 二進制
      .yarn/cache/@aws-sdk-client-sso-npm-3.445.0-201a856f2d-e0a8dc55fe.zip
  4. 二進制
      .yarn/cache/@aws-sdk-client-sts-npm-3.445.0-94a7ac045f-f6d8ba39c0.zip
  5. 二進制
      .yarn/cache/@aws-sdk-core-npm-3.445.0-7420fe10a0-49d3719dba.zip
  6. 二進制
      .yarn/cache/@aws-sdk-credential-provider-env-npm-3.433.0-c654acd3b0-f1742342e4.zip
  7. 二進制
      .yarn/cache/@aws-sdk-credential-provider-ini-npm-3.445.0-ce6e456dae-491bafcbd5.zip
  8. 二進制
      .yarn/cache/@aws-sdk-credential-provider-node-npm-3.445.0-e46f520013-de0f42a581.zip
  9. 二進制
      .yarn/cache/@aws-sdk-credential-provider-process-npm-3.433.0-1f04178c18-ae5c357a48.zip
  10. 二進制
      .yarn/cache/@aws-sdk-credential-provider-sso-npm-3.445.0-55379ab4a6-674ca0b198.zip
  11. 二進制
      .yarn/cache/@aws-sdk-credential-provider-web-identity-npm-3.433.0-d9c6f05357-fd5dc00316.zip
  12. 二進制
      .yarn/cache/@aws-sdk-middleware-bucket-endpoint-npm-3.433.0-75f3e083f1-4b8be28542.zip
  13. 二進制
      .yarn/cache/@aws-sdk-middleware-expect-continue-npm-3.433.0-686c619b17-5a90a82c97.zip
  14. 二進制
      .yarn/cache/@aws-sdk-middleware-flexible-checksums-npm-3.433.0-aedc837cff-895134e75f.zip
  15. 二進制
      .yarn/cache/@aws-sdk-middleware-host-header-npm-3.433.0-2d83f070b1-9cb0d75e5a.zip
  16. 二進制
      .yarn/cache/@aws-sdk-middleware-location-constraint-npm-3.433.0-c994175ec3-b34361ae53.zip
  17. 二進制
      .yarn/cache/@aws-sdk-middleware-logger-npm-3.433.0-89cf073fb9-0a0e7b0cc7.zip
  18. 二進制
      .yarn/cache/@aws-sdk-middleware-recursion-detection-npm-3.433.0-27e67d3aaa-98d69e0466.zip
  19. 二進制
      .yarn/cache/@aws-sdk-middleware-sdk-s3-npm-3.440.0-c747652356-445be8121d.zip
  20. 二進制
      .yarn/cache/@aws-sdk-middleware-sdk-sts-npm-3.433.0-2d279e516c-7c4d1768d3.zip
  21. 二進制
      .yarn/cache/@aws-sdk-middleware-signing-npm-3.433.0-3c0b9eb352-d7167473ee.zip
  22. 二進制
      .yarn/cache/@aws-sdk-middleware-ssec-npm-3.433.0-975479b90c-c080a66e5a.zip
  23. 二進制
      .yarn/cache/@aws-sdk-middleware-user-agent-npm-3.438.0-e2a53e4959-1113259e54.zip
  24. 二進制
      .yarn/cache/@aws-sdk-region-config-resolver-npm-3.433.0-bc89f96cf1-450c73dcc7.zip
  25. 二進制
      .yarn/cache/@aws-sdk-signature-v4-multi-region-npm-3.437.0-38d95938ff-c537a28b20.zip
  26. 二進制
      .yarn/cache/@aws-sdk-token-providers-npm-3.438.0-4b94ec7c93-9c4f56de35.zip
  27. 二進制
      .yarn/cache/@aws-sdk-types-npm-3.433.0-a08a292a43-527cda15da.zip
  28. 二進制
      .yarn/cache/@aws-sdk-util-endpoints-npm-3.438.0-6888f0e906-811dc75ece.zip
  29. 二進制
      .yarn/cache/@aws-sdk-util-user-agent-browser-npm-3.433.0-2f197d08d3-7b88a0a8f1.zip
  30. 二進制
      .yarn/cache/@aws-sdk-util-user-agent-node-npm-3.437.0-25f3a8fa22-0390fbea8a.zip
  31. 二進制
      .yarn/cache/@smithy-abort-controller-npm-2.0.12-284507d2d9-ade23e7e6d.zip
  32. 二進制
      .yarn/cache/@smithy-config-resolver-npm-2.0.16-7b859ffe5a-94665f8960.zip
  33. 二進制
      .yarn/cache/@smithy-credential-provider-imds-npm-2.0.18-4843469616-76665cb083.zip
  34. 二進制
      .yarn/cache/@smithy-eventstream-codec-npm-2.0.12-ae100efef3-e2be231894.zip
  35. 二進制
      .yarn/cache/@smithy-eventstream-serde-browser-npm-2.0.12-af51c5ffaa-6dbd214f75.zip
  36. 二進制
      .yarn/cache/@smithy-eventstream-serde-config-resolver-npm-2.0.12-d12ed9f499-869959136f.zip
  37. 二進制
      .yarn/cache/@smithy-eventstream-serde-node-npm-2.0.12-b20645b33e-fb4fab3b5d.zip
  38. 二進制
      .yarn/cache/@smithy-eventstream-serde-universal-npm-2.0.12-b1fd809aea-5de9fe4eab.zip
  39. 二進制
      .yarn/cache/@smithy-fetch-http-handler-npm-2.2.4-a07dc01282-0151a1b7f4.zip
  40. 二進制
      .yarn/cache/@smithy-hash-blob-browser-npm-2.0.12-8ec60a052a-d0147b2860.zip
  41. 二進制
      .yarn/cache/@smithy-hash-node-npm-2.0.12-dc19684da2-73d8a5121a.zip
  42. 二進制
      .yarn/cache/@smithy-hash-stream-node-npm-2.0.12-837568259a-bcc24b615b.zip
  43. 二進制
      .yarn/cache/@smithy-invalid-dependency-npm-2.0.12-fc7b4dbf99-4749da004a.zip
  44. 二進制
      .yarn/cache/@smithy-md5-js-npm-2.0.12-ff265e11d5-aca6a60871.zip
  45. 二進制
      .yarn/cache/@smithy-middleware-content-length-npm-2.0.14-dad35dc8cc-0a2d091368.zip
  46. 二進制
      .yarn/cache/@smithy-middleware-endpoint-npm-2.1.3-ebc2051146-a82b2b1aed.zip
  47. 二進制
      .yarn/cache/@smithy-middleware-retry-npm-2.0.18-9fe8673f23-e18d073352.zip
  48. 二進制
      .yarn/cache/@smithy-middleware-serde-npm-2.0.12-5c1625ab21-18406d2203.zip
  49. 二進制
      .yarn/cache/@smithy-middleware-stack-npm-2.0.6-fcbeb7e7fc-f98dcd7688.zip
  50. 二進制
      .yarn/cache/@smithy-node-config-provider-npm-2.1.3-1ef774bf1f-f53d0cda8c.zip
  51. 二進制
      .yarn/cache/@smithy-node-http-handler-npm-2.1.8-8ac787c2b2-aca079234e.zip
  52. 二進制
      .yarn/cache/@smithy-property-provider-npm-2.0.13-04595bfd4f-5b9469d17e.zip
  53. 二進制
      .yarn/cache/@smithy-protocol-http-npm-3.0.8-3bec477c37-014df5fe50.zip
  54. 二進制
      .yarn/cache/@smithy-querystring-builder-npm-2.0.12-1035f75405-e3ba93e719.zip
  55. 二進制
      .yarn/cache/@smithy-querystring-parser-npm-2.0.12-7c25c8b3f7-e491478c97.zip
  56. 二進制
      .yarn/cache/@smithy-service-error-classification-npm-2.0.5-e08d76788e-fcd3e267de.zip
  57. 二進制
      .yarn/cache/@smithy-shared-ini-file-loader-npm-2.2.2-90c60eb131-ee3b6a9b0f.zip
  58. 二進制
      .yarn/cache/@smithy-smithy-client-npm-2.1.12-2961adb1b3-78ad4c6599.zip
  59. 二進制
      .yarn/cache/@smithy-types-npm-2.4.0-79f798587a-d8998f754c.zip
  60. 二進制
      .yarn/cache/@smithy-url-parser-npm-2.0.12-da5f841dc2-636e2548f7.zip
  61. 二進制
      .yarn/cache/@smithy-util-defaults-mode-browser-npm-2.0.16-b18594bcc4-388444a93e.zip
  62. 二進制
      .yarn/cache/@smithy-util-defaults-mode-node-npm-2.0.21-2a982d91e7-f0efa3e351.zip
  63. 二進制
      .yarn/cache/@smithy-util-endpoints-npm-1.0.2-924b9d70eb-7438acafe9.zip
  64. 二進制
      .yarn/cache/@smithy-util-middleware-npm-2.0.5-bd5abccaea-13dc6d8ee9.zip
  65. 二進制
      .yarn/cache/@smithy-util-retry-npm-2.0.5-4f31bcfd57-83019b0b92.zip
  66. 二進制
      .yarn/cache/@smithy-util-stream-npm-2.0.17-7d45860ae5-3d55c712db.zip
  67. 二進制
      .yarn/cache/@smithy-util-waiter-npm-2.0.12-0d6eb3dd54-bc5a5d1ce2.zip
  68. 43 0
      packages/auth/bin/delete_accounts.ts
  69. 11 0
      packages/auth/docker/entrypoint-delete-accounts.js
  70. 7 0
      packages/auth/docker/entrypoint.sh
  71. 1 0
      packages/auth/package.json
  72. 28 0
      packages/auth/src/Bootstrap/Container.ts
  73. 3 0
      packages/auth/src/Bootstrap/Types.ts
  74. 5 0
      packages/auth/src/Domain/CSV/CSVFileReaderInterface.ts
  75. 72 0
      packages/auth/src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile.spec.ts
  76. 47 0
      packages/auth/src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile.ts
  77. 4 0
      packages/auth/src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFileDTO.ts
  78. 32 0
      packages/auth/src/Infra/S3/S3CsvFileReader.ts
  79. 702 0
      yarn.lock

文件差異過大導致無法顯示
+ 697 - 0
.pnp.cjs


二進制
.yarn/cache/@aws-sdk-client-s3-npm-3.445.0-fd17753af1-0a75a875cc.zip


二進制
.yarn/cache/@aws-sdk-client-sso-npm-3.445.0-201a856f2d-e0a8dc55fe.zip


二進制
.yarn/cache/@aws-sdk-client-sts-npm-3.445.0-94a7ac045f-f6d8ba39c0.zip


二進制
.yarn/cache/@aws-sdk-core-npm-3.445.0-7420fe10a0-49d3719dba.zip


二進制
.yarn/cache/@aws-sdk-credential-provider-env-npm-3.433.0-c654acd3b0-f1742342e4.zip


二進制
.yarn/cache/@aws-sdk-credential-provider-ini-npm-3.445.0-ce6e456dae-491bafcbd5.zip


二進制
.yarn/cache/@aws-sdk-credential-provider-node-npm-3.445.0-e46f520013-de0f42a581.zip


二進制
.yarn/cache/@aws-sdk-credential-provider-process-npm-3.433.0-1f04178c18-ae5c357a48.zip


二進制
.yarn/cache/@aws-sdk-credential-provider-sso-npm-3.445.0-55379ab4a6-674ca0b198.zip


二進制
.yarn/cache/@aws-sdk-credential-provider-web-identity-npm-3.433.0-d9c6f05357-fd5dc00316.zip


二進制
.yarn/cache/@aws-sdk-middleware-bucket-endpoint-npm-3.433.0-75f3e083f1-4b8be28542.zip


二進制
.yarn/cache/@aws-sdk-middleware-expect-continue-npm-3.433.0-686c619b17-5a90a82c97.zip


二進制
.yarn/cache/@aws-sdk-middleware-flexible-checksums-npm-3.433.0-aedc837cff-895134e75f.zip


二進制
.yarn/cache/@aws-sdk-middleware-host-header-npm-3.433.0-2d83f070b1-9cb0d75e5a.zip


二進制
.yarn/cache/@aws-sdk-middleware-location-constraint-npm-3.433.0-c994175ec3-b34361ae53.zip


二進制
.yarn/cache/@aws-sdk-middleware-logger-npm-3.433.0-89cf073fb9-0a0e7b0cc7.zip


二進制
.yarn/cache/@aws-sdk-middleware-recursion-detection-npm-3.433.0-27e67d3aaa-98d69e0466.zip


二進制
.yarn/cache/@aws-sdk-middleware-sdk-s3-npm-3.440.0-c747652356-445be8121d.zip


二進制
.yarn/cache/@aws-sdk-middleware-sdk-sts-npm-3.433.0-2d279e516c-7c4d1768d3.zip


二進制
.yarn/cache/@aws-sdk-middleware-signing-npm-3.433.0-3c0b9eb352-d7167473ee.zip


二進制
.yarn/cache/@aws-sdk-middleware-ssec-npm-3.433.0-975479b90c-c080a66e5a.zip


二進制
.yarn/cache/@aws-sdk-middleware-user-agent-npm-3.438.0-e2a53e4959-1113259e54.zip


二進制
.yarn/cache/@aws-sdk-region-config-resolver-npm-3.433.0-bc89f96cf1-450c73dcc7.zip


二進制
.yarn/cache/@aws-sdk-signature-v4-multi-region-npm-3.437.0-38d95938ff-c537a28b20.zip


二進制
.yarn/cache/@aws-sdk-token-providers-npm-3.438.0-4b94ec7c93-9c4f56de35.zip


二進制
.yarn/cache/@aws-sdk-types-npm-3.433.0-a08a292a43-527cda15da.zip


二進制
.yarn/cache/@aws-sdk-util-endpoints-npm-3.438.0-6888f0e906-811dc75ece.zip


二進制
.yarn/cache/@aws-sdk-util-user-agent-browser-npm-3.433.0-2f197d08d3-7b88a0a8f1.zip


二進制
.yarn/cache/@aws-sdk-util-user-agent-node-npm-3.437.0-25f3a8fa22-0390fbea8a.zip


二進制
.yarn/cache/@smithy-abort-controller-npm-2.0.12-284507d2d9-ade23e7e6d.zip


二進制
.yarn/cache/@smithy-config-resolver-npm-2.0.16-7b859ffe5a-94665f8960.zip


二進制
.yarn/cache/@smithy-credential-provider-imds-npm-2.0.18-4843469616-76665cb083.zip


二進制
.yarn/cache/@smithy-eventstream-codec-npm-2.0.12-ae100efef3-e2be231894.zip


二進制
.yarn/cache/@smithy-eventstream-serde-browser-npm-2.0.12-af51c5ffaa-6dbd214f75.zip


二進制
.yarn/cache/@smithy-eventstream-serde-config-resolver-npm-2.0.12-d12ed9f499-869959136f.zip


二進制
.yarn/cache/@smithy-eventstream-serde-node-npm-2.0.12-b20645b33e-fb4fab3b5d.zip


二進制
.yarn/cache/@smithy-eventstream-serde-universal-npm-2.0.12-b1fd809aea-5de9fe4eab.zip


二進制
.yarn/cache/@smithy-fetch-http-handler-npm-2.2.4-a07dc01282-0151a1b7f4.zip


二進制
.yarn/cache/@smithy-hash-blob-browser-npm-2.0.12-8ec60a052a-d0147b2860.zip


二進制
.yarn/cache/@smithy-hash-node-npm-2.0.12-dc19684da2-73d8a5121a.zip


二進制
.yarn/cache/@smithy-hash-stream-node-npm-2.0.12-837568259a-bcc24b615b.zip


二進制
.yarn/cache/@smithy-invalid-dependency-npm-2.0.12-fc7b4dbf99-4749da004a.zip


二進制
.yarn/cache/@smithy-md5-js-npm-2.0.12-ff265e11d5-aca6a60871.zip


二進制
.yarn/cache/@smithy-middleware-content-length-npm-2.0.14-dad35dc8cc-0a2d091368.zip


二進制
.yarn/cache/@smithy-middleware-endpoint-npm-2.1.3-ebc2051146-a82b2b1aed.zip


二進制
.yarn/cache/@smithy-middleware-retry-npm-2.0.18-9fe8673f23-e18d073352.zip


二進制
.yarn/cache/@smithy-middleware-serde-npm-2.0.12-5c1625ab21-18406d2203.zip


二進制
.yarn/cache/@smithy-middleware-stack-npm-2.0.6-fcbeb7e7fc-f98dcd7688.zip


二進制
.yarn/cache/@smithy-node-config-provider-npm-2.1.3-1ef774bf1f-f53d0cda8c.zip


二進制
.yarn/cache/@smithy-node-http-handler-npm-2.1.8-8ac787c2b2-aca079234e.zip


二進制
.yarn/cache/@smithy-property-provider-npm-2.0.13-04595bfd4f-5b9469d17e.zip


二進制
.yarn/cache/@smithy-protocol-http-npm-3.0.8-3bec477c37-014df5fe50.zip


二進制
.yarn/cache/@smithy-querystring-builder-npm-2.0.12-1035f75405-e3ba93e719.zip


二進制
.yarn/cache/@smithy-querystring-parser-npm-2.0.12-7c25c8b3f7-e491478c97.zip


二進制
.yarn/cache/@smithy-service-error-classification-npm-2.0.5-e08d76788e-fcd3e267de.zip


二進制
.yarn/cache/@smithy-shared-ini-file-loader-npm-2.2.2-90c60eb131-ee3b6a9b0f.zip


二進制
.yarn/cache/@smithy-smithy-client-npm-2.1.12-2961adb1b3-78ad4c6599.zip


二進制
.yarn/cache/@smithy-types-npm-2.4.0-79f798587a-d8998f754c.zip


二進制
.yarn/cache/@smithy-url-parser-npm-2.0.12-da5f841dc2-636e2548f7.zip


二進制
.yarn/cache/@smithy-util-defaults-mode-browser-npm-2.0.16-b18594bcc4-388444a93e.zip


二進制
.yarn/cache/@smithy-util-defaults-mode-node-npm-2.0.21-2a982d91e7-f0efa3e351.zip


二進制
.yarn/cache/@smithy-util-endpoints-npm-1.0.2-924b9d70eb-7438acafe9.zip


二進制
.yarn/cache/@smithy-util-middleware-npm-2.0.5-bd5abccaea-13dc6d8ee9.zip


二進制
.yarn/cache/@smithy-util-retry-npm-2.0.5-4f31bcfd57-83019b0b92.zip


二進制
.yarn/cache/@smithy-util-stream-npm-2.0.17-7d45860ae5-3d55c712db.zip


二進制
.yarn/cache/@smithy-util-waiter-npm-2.0.12-0d6eb3dd54-bc5a5d1ce2.zip


+ 43 - 0
packages/auth/bin/delete_accounts.ts

@@ -0,0 +1,43 @@
+import 'reflect-metadata'
+
+import { Logger } from 'winston'
+
+import { ContainerConfigLoader } from '../src/Bootstrap/Container'
+import TYPES from '../src/Bootstrap/Types'
+import { Env } from '../src/Bootstrap/Env'
+import { DeleteAccountsFromCSVFile } from '../src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile'
+
+const inputArgs = process.argv.slice(2)
+const fileName = inputArgs[0]
+const mode = inputArgs[1]
+
+const deleteAccounts = async (deleteAccountsFromCSVFile: DeleteAccountsFromCSVFile): Promise<void> => {
+  await deleteAccountsFromCSVFile.execute({
+    fileName,
+    dryRun: mode !== 'delete',
+  })
+}
+
+const container = new ContainerConfigLoader('worker')
+void container.load().then((container) => {
+  const env: Env = new Env()
+  env.load()
+
+  const logger: Logger = container.get(TYPES.Auth_Logger)
+
+  logger.info('Starting mass accounts deletion from CSV file')
+
+  const deleteAccountsFromCSVFile = container.get<DeleteAccountsFromCSVFile>(TYPES.Auth_DeleteAccountsFromCSVFile)
+
+  Promise.resolve(deleteAccounts(deleteAccountsFromCSVFile))
+    .then(() => {
+      logger.info('Accounts deleted.')
+
+      process.exit(0)
+    })
+    .catch((error) => {
+      logger.error(`Could not delete accounts: ${error.message}`)
+
+      process.exit(1)
+    })
+})

+ 11 - 0
packages/auth/docker/entrypoint-delete-accounts.js

@@ -0,0 +1,11 @@
+'use strict'
+
+const path = require('path')
+
+const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
+
+const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/delete-accounts.js')))
+
+Object.defineProperty(exports, '__esModule', { value: true })
+
+exports.default = index

+ 7 - 0
packages/auth/docker/entrypoint.sh

@@ -40,6 +40,13 @@ case "$COMMAND" in
     node docker/entrypoint-user-email-backup.js $EMAIL
     ;;
 
+  'delete-accounts' )
+    echo "[Docker] Starting Accounts Deleting from CSV..."
+    FILE_NAME=$1 && shift 1
+    MODE=$1 && shift 1
+    node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
+    ;;
+
    * )
     echo "[Docker] Unknown command"
     ;;

+ 1 - 0
packages/auth/package.json

@@ -32,6 +32,7 @@
     "migrate": "yarn build && yarn typeorm migration:run -d dist/src/Bootstrap/DataSource.js"
   },
   "dependencies": {
+    "@aws-sdk/client-s3": "^3.445.0",
     "@aws-sdk/client-sns": "^3.427.0",
     "@aws-sdk/client-sqs": "^3.427.0",
     "@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",

+ 28 - 0
packages/auth/src/Bootstrap/Container.ts

@@ -2,6 +2,7 @@ import * as winston from 'winston'
 import Redis from 'ioredis'
 import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
 import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
+import { S3Client } from '@aws-sdk/client-s3'
 import { Container } from 'inversify'
 import {
   DomainEventHandlerInterface,
@@ -276,6 +277,9 @@ import { UserInvitedToSharedVaultEventHandler } from '../Domain/Handler/UserInvi
 import { TriggerPostSettingUpdateActions } from '../Domain/UseCase/TriggerPostSettingUpdateActions/TriggerPostSettingUpdateActions'
 import { TriggerEmailBackupForUser } from '../Domain/UseCase/TriggerEmailBackupForUser/TriggerEmailBackupForUser'
 import { TriggerEmailBackupForAllUsers } from '../Domain/UseCase/TriggerEmailBackupForAllUsers/TriggerEmailBackupForAllUsers'
+import { CSVFileReaderInterface } from '../Domain/CSV/CSVFileReaderInterface'
+import { S3CsvFileReader } from '../Infra/S3/S3CsvFileReader'
+import { DeleteAccountsFromCSVFile } from '../Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile'
 
 export class ContainerConfigLoader {
   constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -370,6 +374,19 @@ export class ContainerConfigLoader {
       }
       const sqsClient = new SQSClient(sqsConfig)
       container.bind<SQSClient>(TYPES.Auth_SQS).toConstantValue(sqsClient)
+
+      container.bind<S3Client>(TYPES.Auth_S3).toConstantValue(
+        new S3Client({
+          apiVersion: 'latest',
+          region: env.get('S3_AWS_REGION', true),
+        }),
+      )
+
+      container
+        .bind<CSVFileReaderInterface>(TYPES.Auth_CSVFileReader)
+        .toConstantValue(
+          new S3CsvFileReader(env.get('S3_AUTH_SCRIPTS_DATA_BUCKET', true), container.get<S3Client>(TYPES.Auth_S3)),
+        )
     }
 
     container.bind(TYPES.Auth_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
@@ -1251,6 +1268,17 @@ export class ContainerConfigLoader {
           container.get<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser),
         ),
       )
+    if (!isConfiguredForHomeServer) {
+      container
+        .bind<DeleteAccountsFromCSVFile>(TYPES.Auth_DeleteAccountsFromCSVFile)
+        .toConstantValue(
+          new DeleteAccountsFromCSVFile(
+            container.get<CSVFileReaderInterface>(TYPES.Auth_CSVFileReader),
+            container.get<DeleteAccount>(TYPES.Auth_DeleteAccount),
+            container.get<winston.Logger>(TYPES.Auth_Logger),
+          ),
+        )
+    }
 
     // Controller
     container

+ 3 - 0
packages/auth/src/Bootstrap/Types.ts

@@ -3,6 +3,7 @@ const TYPES = {
   Auth_Redis: Symbol.for('Auth_Redis'),
   Auth_SNS: Symbol.for('Auth_SNS'),
   Auth_SQS: Symbol.for('Auth_SQS'),
+  Auth_S3: Symbol.for('Auth_S3'),
   // Mapping
   Auth_SessionTracePersistenceMapper: Symbol.for('Auth_SessionTracePersistenceMapper'),
   Auth_AuthenticatorChallengePersistenceMapper: Symbol.for('Auth_AuthenticatorChallengePersistenceMapper'),
@@ -167,6 +168,7 @@ const TYPES = {
   Auth_TriggerPostSettingUpdateActions: Symbol.for('Auth_TriggerPostSettingUpdateActions'),
   Auth_TriggerEmailBackupForUser: Symbol.for('Auth_TriggerEmailBackupForUser'),
   Auth_TriggerEmailBackupForAllUsers: Symbol.for('Auth_TriggerEmailBackupForAllUsers'),
+  Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'),
   // Handlers
   Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
   Auth_SubscriptionPurchasedEventHandler: Symbol.for('Auth_SubscriptionPurchasedEventHandler'),
@@ -251,6 +253,7 @@ const TYPES = {
   Auth_BaseOfflineController: Symbol.for('Auth_BaseOfflineController'),
   Auth_BaseListedController: Symbol.for('Auth_BaseListedController'),
   Auth_BaseFeaturesController: Symbol.for('Auth_BaseFeaturesController'),
+  Auth_CSVFileReader: Symbol.for('Auth_CSVFileReader'),
 }
 
 export default TYPES

+ 5 - 0
packages/auth/src/Domain/CSV/CSVFileReaderInterface.ts

@@ -0,0 +1,5 @@
+import { Result } from '@standardnotes/domain-core'
+
+export interface CSVFileReaderInterface {
+  getValues(fileName: string): Promise<Result<string[]>>
+}

+ 72 - 0
packages/auth/src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile.spec.ts

@@ -0,0 +1,72 @@
+import { Logger } from 'winston'
+import { Result } from '@standardnotes/domain-core'
+
+import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
+import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
+import { DeleteAccountsFromCSVFile } from './DeleteAccountsFromCSVFile'
+
+describe('DeleteAccountsFromCSVFile', () => {
+  let csvFileReader: CSVFileReaderInterface
+  let deleteAccount: DeleteAccount
+  let logger: Logger
+
+  const createUseCase = () => new DeleteAccountsFromCSVFile(csvFileReader, deleteAccount, logger)
+
+  beforeEach(() => {
+    csvFileReader = {} as jest.Mocked<CSVFileReaderInterface>
+    csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok(['email1']))
+
+    deleteAccount = {} as jest.Mocked<DeleteAccount>
+    deleteAccount.execute = jest.fn().mockResolvedValue(Result.ok(''))
+
+    logger = {} as jest.Mocked<Logger>
+    logger.info = jest.fn()
+  })
+
+  it('should delete accounts', async () => {
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
+
+    expect(result.isFailed()).toBeFalsy()
+  })
+
+  it('should return error if csv file is invalid', async () => {
+    csvFileReader.getValues = jest.fn().mockResolvedValue(Result.fail('Oops'))
+
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should return error if csv file is empty', async () => {
+    csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok([]))
+
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should do nothing on a dry run', async () => {
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({ fileName: 'test.csv', dryRun: true })
+
+    expect(deleteAccount.execute).not.toHaveBeenCalled()
+    expect(result.isFailed()).toBeFalsy()
+  })
+
+  it('should return error if delete account fails', async () => {
+    deleteAccount.execute = jest.fn().mockResolvedValue(Result.fail('Oops'))
+
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+})

+ 47 - 0
packages/auth/src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile.ts

@@ -0,0 +1,47 @@
+import { Result, UseCaseInterface } from '@standardnotes/domain-core'
+import { Logger } from 'winston'
+
+import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
+import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
+import { DeleteAccountsFromCSVFileDTO } from './DeleteAccountsFromCSVFileDTO'
+
+export class DeleteAccountsFromCSVFile implements UseCaseInterface<void> {
+  constructor(
+    private csvFileReader: CSVFileReaderInterface,
+    private deleteAccount: DeleteAccount,
+    private logger: Logger,
+  ) {}
+
+  async execute(dto: DeleteAccountsFromCSVFileDTO): Promise<Result<void>> {
+    const emailsOrError = await this.csvFileReader.getValues(dto.fileName)
+    if (emailsOrError.isFailed()) {
+      return Result.fail(emailsOrError.getError())
+    }
+    const emails = emailsOrError.getValue()
+
+    if (emails.length === 0) {
+      return Result.fail(`No emails found in CSV file ${dto.fileName}`)
+    }
+
+    if (dto.dryRun) {
+      const firstTenEmails = emails.slice(0, 10)
+      this.logger.info(
+        `Dry run mode enabled. Would delete ${emails.length} accounts. First 10 emails: ${firstTenEmails}`,
+      )
+
+      return Result.ok()
+    }
+
+    for (const email of emails) {
+      const deleteAccountOrError = await this.deleteAccount.execute({
+        username: email,
+      })
+
+      if (deleteAccountOrError.isFailed()) {
+        return Result.fail(deleteAccountOrError.getError())
+      }
+    }
+
+    return Result.ok()
+  }
+}

+ 4 - 0
packages/auth/src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFileDTO.ts

@@ -0,0 +1,4 @@
+export interface DeleteAccountsFromCSVFileDTO {
+  fileName: string
+  dryRun: boolean
+}

+ 32 - 0
packages/auth/src/Infra/S3/S3CsvFileReader.ts

@@ -0,0 +1,32 @@
+import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
+import { Result } from '@standardnotes/domain-core'
+
+import { CSVFileReaderInterface } from '../../Domain/CSV/CSVFileReaderInterface'
+
+export class S3CsvFileReader implements CSVFileReaderInterface {
+  constructor(
+    private s3BucketName: string,
+    private s3Client: S3Client,
+  ) {}
+
+  async getValues(fileName: string): Promise<Result<string[]>> {
+    if (this.s3BucketName.length === 0) {
+      return Result.fail('S3 bucket name is not set')
+    }
+
+    const response = await this.s3Client.send(
+      new GetObjectCommand({
+        Bucket: this.s3BucketName,
+        Key: fileName,
+      }),
+    )
+
+    if (response.Body === undefined) {
+      return Result.fail(`Could not find CSV file at path: ${fileName}`)
+    }
+
+    const csvContent = await response.Body.transformToString()
+
+    return Result.ok(csvContent.split('\n').filter((line) => line.length > 0))
+  }
+}

文件差異過大導致無法顯示
+ 702 - 0
yarn.lock


部分文件因文件數量過多而無法顯示