From d48a113a4a56049c6689151425ce247eea1a2e1d Mon Sep 17 00:00:00 2001 From: Andres Date: Sun, 22 Dec 2024 13:55:06 +0100 Subject: [PATCH] add cli command to remove empty decks --- package.json | 1 + pnpm-lock.yaml | 84 +++++++++++++++++-- src/app.module.ts | 4 +- src/command-factory.ts | 10 +++ .../commands/remove-empty-decks.command.ts | 27 ++++++ 5 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 src/command-factory.ts create mode 100644 src/modules/users/commands/remove-empty-decks.command.ts diff --git a/package.json b/package.json index c909b79..52d92ee 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "date-fns": "^2.30.0", "dotenv": "^16.3.1", "jsonwebtoken": "^9.0.2", + "nest-commander": "^3.15.0", "nodemailer": "^6.9.7", "passport": "^0.6.0", "passport-jwt": "^4.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd9e5e8..99adf31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 + nest-commander: + specifier: ^3.15.0 + version: 3.15.0(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.2.9(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.2.9)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@types/inquirer@8.2.10)(typescript@5.2.2) nodemailer: specifier: ^6.9.7 version: 6.9.7 @@ -504,6 +507,11 @@ packages: '@fastify/middie@8.3.0': resolution: {integrity: sha512-h+zBxCzMlkEkh4fM7pZaSGzqS7P9M0Z6rXnWPdUEPfe7x1BCj++wEk/pQ5jpyYY4pF8AknFqb77n7uwh8HdxEA==} + '@fig/complete-commander@3.2.0': + resolution: {integrity: sha512-1Holl3XtRiANVKURZwgpjCnPuV4RsHp+XC0MhgvyAX/avQwj7F2HUItYOvGi/bXjJCkEzgBZmVfCr0HBA+q+Bw==} + peerDependencies: + commander: ^11.1.0 + '@floating-ui/core@1.6.0': resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} @@ -525,6 +533,12 @@ packages: '@floating-ui/vue@1.1.4': resolution: {integrity: sha512-ammH7T3vyCx7pmm9OF19Wc42zrGnUw0QvLoidgypWsCLJMtGXEwY7paYIHO+K+oLC3mbWpzIHzeTVienYenlNg==} + '@golevelup/nestjs-discovery@4.0.1': + resolution: {integrity: sha512-HFXBJayEkYcU/bbxOztozONdWaZR34ZeJ2zRbZIWY8d5K26oPZQTvJ4L0STW3XVRGWtoE0WBpmx2YPNgYvcmJQ==} + peerDependencies: + '@nestjs/common': ^10.x + '@nestjs/core': ^10.x + '@headlessui/tailwindcss@0.2.1': resolution: {integrity: sha512-2+5+NZ+RzMyrVeCZOxdbvkUSssSxGvcUxphkIfSVLpRiKsj+/63T2TOL9dBYMXVfj/CGr6hMxSRInzXv6YY7sA==} engines: {node: '>=10'} @@ -1106,6 +1120,9 @@ packages: '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + '@types/inquirer@8.2.10': + resolution: {integrity: sha512-IdD5NmHyVjWM8SHWo/kPBgtzXatwPkfwzyP3fN1jF2g9BWt5WO+8hL2F4o2GKIYsU40PpqeevuUWvkS/roXJkA==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1187,6 +1204,9 @@ packages: '@types/supertest@2.0.16': resolution: {integrity: sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==} + '@types/through@0.0.33': + resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} + '@types/unist@3.0.2': resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} @@ -1893,6 +1913,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4026,6 +4050,13 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nest-commander@3.15.0: + resolution: {integrity: sha512-o9VEfFj/w2nm+hQi6fnkxL1GAFZW/KmuGcIE7/B/TX0gwm0QVy8svAF75EQm8wrDjcvWS7Cx/ArnkFn2C+iM2w==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@types/inquirer': ^8.1.3 + netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -4425,6 +4456,11 @@ packages: engines: {node: '>=14'} hasBin: true + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + engines: {node: '>=14'} + hasBin: true + pretty-bytes@6.1.1: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} @@ -5571,11 +5607,6 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yaml@2.4.1: - resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} - engines: {node: '>= 14'} - hasBin: true - yaml@2.5.1: resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} engines: {node: '>= 14'} @@ -6039,6 +6070,11 @@ snapshots: path-to-regexp: 6.2.1 reusify: 1.0.4 + '@fig/complete-commander@3.2.0(commander@11.1.0)': + dependencies: + commander: 11.1.0 + prettier: 3.4.2 + '@floating-ui/core@1.6.0': dependencies: '@floating-ui/utils': 0.2.1 @@ -6075,6 +6111,12 @@ snapshots: - '@vue/composition-api' - vue + '@golevelup/nestjs-discovery@4.0.1(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.2.9(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.2.9)(reflect-metadata@0.1.13)(rxjs@7.8.1))': + dependencies: + '@nestjs/common': 10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.2.9(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.2.9)(reflect-metadata@0.1.13)(rxjs@7.8.1) + lodash: 4.17.21 + '@headlessui/tailwindcss@0.2.1(tailwindcss@3.4.10(ts-node@10.9.1(@types/node@20.9.2)(typescript@5.2.2)))': dependencies: tailwindcss: 3.4.10(ts-node@10.9.1(@types/node@20.9.2)(typescript@5.2.2)) @@ -6963,6 +7005,11 @@ snapshots: '@types/http-errors@2.0.4': {} + '@types/inquirer@8.2.10': + dependencies: + '@types/through': 0.0.33 + rxjs: 7.8.1 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -7058,6 +7105,10 @@ snapshots: dependencies: '@types/superagent': 4.1.22 + '@types/through@0.0.33': + dependencies: + '@types/node': 20.9.2 + '@types/unist@3.0.2': {} '@types/validator@13.11.6': {} @@ -7941,6 +7992,8 @@ snapshots: commander@10.0.1: {} + commander@11.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -10911,6 +10964,19 @@ snapshots: neo-async@2.6.2: {} + nest-commander@3.15.0(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.2.9(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.2.9)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@types/inquirer@8.2.10)(typescript@5.2.2): + dependencies: + '@fig/complete-commander': 3.2.0(commander@11.1.0) + '@golevelup/nestjs-discovery': 4.0.1(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/core@10.2.9(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.2.9)(reflect-metadata@0.1.13)(rxjs@7.8.1)) + '@nestjs/common': 10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.2.9(@nestjs/common@10.2.9(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1))(@nestjs/platform-express@10.2.9)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@types/inquirer': 8.2.10 + commander: 11.1.0 + cosmiconfig: 8.3.6(typescript@5.2.2) + inquirer: 8.2.6 + transitivePeerDependencies: + - typescript + netmask@2.0.2: {} nice-try@1.0.5: {} @@ -11268,7 +11334,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.4.45)(ts-node@10.9.1(@types/node@20.9.2)(typescript@5.2.2)): dependencies: lilconfig: 3.1.2 - yaml: 2.4.1 + yaml: 2.5.1 optionalDependencies: postcss: 8.4.45 ts-node: 10.9.1(@types/node@20.9.2)(typescript@5.2.2) @@ -11303,6 +11369,8 @@ snapshots: prettier@3.1.0: {} + prettier@3.4.2: {} + pretty-bytes@6.1.1: {} pretty-format@29.7.0: @@ -12120,7 +12188,7 @@ snapshots: micromatch: 4.0.5 normalize-path: 3.0.0 object-hash: 3.0.0 - picocolors: 1.0.0 + picocolors: 1.1.0 postcss: 8.4.45 postcss-import: 15.1.0(postcss@8.4.45) postcss-js: 4.0.1(postcss@8.4.45) @@ -12659,8 +12727,6 @@ snapshots: yallist@4.0.0: {} - yaml@2.4.1: {} - yaml@2.5.1: {} yargs-parser@20.2.9: {} diff --git a/src/app.module.ts b/src/app.module.ts index da2d450..42ac533 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,8 @@ import { JwtRefreshStrategy } from './modules/auth/strategies/jwt-refresh.strate import { JwtStrategy } from './modules/auth/strategies/jwt.strategy' import { CardsModule } from './modules/cards/cards.module' import { DecksModule } from './modules/decks/decks.module' +import { DecksRepository } from './modules/decks/infrastructure/decks.repository' +import { TaskRunner } from './modules/users/commands/remove-empty-decks.command' import { UsersModule } from './modules/users/users.module' import { PrismaModule } from './prisma.module' import { ConfigModule } from './settings/config.module' @@ -48,7 +50,7 @@ import { ConfigModule } from './settings/config.module' }), ], controllers: [], - providers: [JwtStrategy, JwtRefreshStrategy, FileUploadService], + providers: [JwtStrategy, JwtRefreshStrategy, FileUploadService, DecksRepository, TaskRunner], exports: [CqrsModule, FileUploadService], }) export class AppModule implements NestModule { diff --git a/src/command-factory.ts b/src/command-factory.ts new file mode 100644 index 0000000..9ca479a --- /dev/null +++ b/src/command-factory.ts @@ -0,0 +1,10 @@ +import { Logger } from '@nestjs/common' +import { CommandFactory } from 'nest-commander' + +import { AppModule } from './app.module' + +const bootstrap = async () => { + await CommandFactory.run(AppModule, new Logger()) +} + +bootstrap() diff --git a/src/modules/users/commands/remove-empty-decks.command.ts b/src/modules/users/commands/remove-empty-decks.command.ts new file mode 100644 index 0000000..cac57a8 --- /dev/null +++ b/src/modules/users/commands/remove-empty-decks.command.ts @@ -0,0 +1,27 @@ +import { CommandRunner, DefaultCommand } from 'nest-commander' + +import { DecksRepository } from '../../decks/infrastructure/decks.repository' + +@DefaultCommand({ + name: 'remove-empty', +}) +export class TaskRunner extends CommandRunner { + constructor(private decksRepository: DecksRepository) { + super() + } + + async run(): Promise { + console.log('Running remove-empty command') + const decks = await this.decksRepository.findAllDecksForAdmin({}) + + console.log('Found', decks.items.length, 'decks') + const emptyDecks = decks.items.filter(deck => deck.cardsCount === 0) + const emptyDeckIds = emptyDecks.map(deck => deck.id) + + console.log('Found', emptyDeckIds.length, 'empty decks') + if (emptyDeckIds.length === 0) return + console.log(await this.decksRepository.deleteManyById(emptyDeckIds)) + + return + } +}