add cli command to remove empty decks

This commit is contained in:
2024-12-22 13:55:06 +01:00
parent 3c21c83e2c
commit d48a113a4a
5 changed files with 116 additions and 10 deletions

View File

@@ -49,6 +49,7 @@
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"nest-commander": "^3.15.0",
"nodemailer": "^6.9.7", "nodemailer": "^6.9.7",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",

84
pnpm-lock.yaml generated
View File

@@ -83,6 +83,9 @@ importers:
jsonwebtoken: jsonwebtoken:
specifier: ^9.0.2 specifier: ^9.0.2
version: 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: nodemailer:
specifier: ^6.9.7 specifier: ^6.9.7
version: 6.9.7 version: 6.9.7
@@ -504,6 +507,11 @@ packages:
'@fastify/middie@8.3.0': '@fastify/middie@8.3.0':
resolution: {integrity: sha512-h+zBxCzMlkEkh4fM7pZaSGzqS7P9M0Z6rXnWPdUEPfe7x1BCj++wEk/pQ5jpyYY4pF8AknFqb77n7uwh8HdxEA==} 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': '@floating-ui/core@1.6.0':
resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==}
@@ -525,6 +533,12 @@ packages:
'@floating-ui/vue@1.1.4': '@floating-ui/vue@1.1.4':
resolution: {integrity: sha512-ammH7T3vyCx7pmm9OF19Wc42zrGnUw0QvLoidgypWsCLJMtGXEwY7paYIHO+K+oLC3mbWpzIHzeTVienYenlNg==} 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': '@headlessui/tailwindcss@0.2.1':
resolution: {integrity: sha512-2+5+NZ+RzMyrVeCZOxdbvkUSssSxGvcUxphkIfSVLpRiKsj+/63T2TOL9dBYMXVfj/CGr6hMxSRInzXv6YY7sA==} resolution: {integrity: sha512-2+5+NZ+RzMyrVeCZOxdbvkUSssSxGvcUxphkIfSVLpRiKsj+/63T2TOL9dBYMXVfj/CGr6hMxSRInzXv6YY7sA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1106,6 +1120,9 @@ packages:
'@types/http-errors@2.0.4': '@types/http-errors@2.0.4':
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} 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': '@types/istanbul-lib-coverage@2.0.6':
resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
@@ -1187,6 +1204,9 @@ packages:
'@types/supertest@2.0.16': '@types/supertest@2.0.16':
resolution: {integrity: sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==} 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': '@types/unist@3.0.2':
resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
@@ -1893,6 +1913,10 @@ packages:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'} engines: {node: '>=14'}
commander@11.1.0:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
commander@2.20.3: commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -4026,6 +4050,13 @@ packages:
neo-async@2.6.2: neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} 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: netmask@2.0.2:
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
@@ -4425,6 +4456,11 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
prettier@3.4.2:
resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
engines: {node: '>=14'}
hasBin: true
pretty-bytes@6.1.1: pretty-bytes@6.1.1:
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
engines: {node: ^14.13.1 || >=16.0.0} engines: {node: ^14.13.1 || >=16.0.0}
@@ -5571,11 +5607,6 @@ packages:
yallist@4.0.0: yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml@2.4.1:
resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}
engines: {node: '>= 14'}
hasBin: true
yaml@2.5.1: yaml@2.5.1:
resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
@@ -6039,6 +6070,11 @@ snapshots:
path-to-regexp: 6.2.1 path-to-regexp: 6.2.1
reusify: 1.0.4 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': '@floating-ui/core@1.6.0':
dependencies: dependencies:
'@floating-ui/utils': 0.2.1 '@floating-ui/utils': 0.2.1
@@ -6075,6 +6111,12 @@ snapshots:
- '@vue/composition-api' - '@vue/composition-api'
- vue - 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)))': '@headlessui/tailwindcss@0.2.1(tailwindcss@3.4.10(ts-node@10.9.1(@types/node@20.9.2)(typescript@5.2.2)))':
dependencies: dependencies:
tailwindcss: 3.4.10(ts-node@10.9.1(@types/node@20.9.2)(typescript@5.2.2)) 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/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-coverage@2.0.6': {}
'@types/istanbul-lib-report@3.0.3': '@types/istanbul-lib-report@3.0.3':
@@ -7058,6 +7105,10 @@ snapshots:
dependencies: dependencies:
'@types/superagent': 4.1.22 '@types/superagent': 4.1.22
'@types/through@0.0.33':
dependencies:
'@types/node': 20.9.2
'@types/unist@3.0.2': {} '@types/unist@3.0.2': {}
'@types/validator@13.11.6': {} '@types/validator@13.11.6': {}
@@ -7941,6 +7992,8 @@ snapshots:
commander@10.0.1: {} commander@10.0.1: {}
commander@11.1.0: {}
commander@2.20.3: {} commander@2.20.3: {}
commander@4.1.1: {} commander@4.1.1: {}
@@ -10911,6 +10964,19 @@ snapshots:
neo-async@2.6.2: {} 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: {} netmask@2.0.2: {}
nice-try@1.0.5: {} 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)): 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: dependencies:
lilconfig: 3.1.2 lilconfig: 3.1.2
yaml: 2.4.1 yaml: 2.5.1
optionalDependencies: optionalDependencies:
postcss: 8.4.45 postcss: 8.4.45
ts-node: 10.9.1(@types/node@20.9.2)(typescript@5.2.2) 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.1.0: {}
prettier@3.4.2: {}
pretty-bytes@6.1.1: {} pretty-bytes@6.1.1: {}
pretty-format@29.7.0: pretty-format@29.7.0:
@@ -12120,7 +12188,7 @@ snapshots:
micromatch: 4.0.5 micromatch: 4.0.5
normalize-path: 3.0.0 normalize-path: 3.0.0
object-hash: 3.0.0 object-hash: 3.0.0
picocolors: 1.0.0 picocolors: 1.1.0
postcss: 8.4.45 postcss: 8.4.45
postcss-import: 15.1.0(postcss@8.4.45) postcss-import: 15.1.0(postcss@8.4.45)
postcss-js: 4.0.1(postcss@8.4.45) postcss-js: 4.0.1(postcss@8.4.45)
@@ -12659,8 +12727,6 @@ snapshots:
yallist@4.0.0: {} yallist@4.0.0: {}
yaml@2.4.1: {}
yaml@2.5.1: {} yaml@2.5.1: {}
yargs-parser@20.2.9: {} yargs-parser@20.2.9: {}

View File

@@ -13,6 +13,8 @@ import { JwtRefreshStrategy } from './modules/auth/strategies/jwt-refresh.strate
import { JwtStrategy } from './modules/auth/strategies/jwt.strategy' import { JwtStrategy } from './modules/auth/strategies/jwt.strategy'
import { CardsModule } from './modules/cards/cards.module' import { CardsModule } from './modules/cards/cards.module'
import { DecksModule } from './modules/decks/decks.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 { UsersModule } from './modules/users/users.module'
import { PrismaModule } from './prisma.module' import { PrismaModule } from './prisma.module'
import { ConfigModule } from './settings/config.module' import { ConfigModule } from './settings/config.module'
@@ -48,7 +50,7 @@ import { ConfigModule } from './settings/config.module'
}), }),
], ],
controllers: [], controllers: [],
providers: [JwtStrategy, JwtRefreshStrategy, FileUploadService], providers: [JwtStrategy, JwtRefreshStrategy, FileUploadService, DecksRepository, TaskRunner],
exports: [CqrsModule, FileUploadService], exports: [CqrsModule, FileUploadService],
}) })
export class AppModule implements NestModule { export class AppModule implements NestModule {

10
src/command-factory.ts Normal file
View File

@@ -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()

View File

@@ -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<void> {
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
}
}