feat: init category module

This commit is contained in:
Innei
2021-09-06 14:22:51 +08:00
parent ce71fb66f6
commit 07f4f19a86
18 changed files with 329 additions and 808 deletions

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@ lerna-debug.log*
!.vscode/launch.json
!.vscode/extensions.json
patch/dist

36
bin/patch.js Normal file
View File

@@ -0,0 +1,36 @@
// @ts-check
const inquirer = require('inquirer')
const chalk = require('chalk')
const prompt = inquirer.createPromptModule()
const package = require('../package.json')
const { execSync } = require('child_process')
const { resolve } = require('path')
const { readdirSync } = require('fs')
const PATCH_DIR = resolve(process.cwd(), './patch')
async function bootstarp() {
console.log(chalk.yellowBright('mx-space server patch center'))
console.log(chalk.yellow(`current version: ${package.version}`))
const patchFiles = readdirSync(PATCH_DIR).filter(
(file) => file.startsWith('v') && file.endsWith('.ts'),
)
prompt({
type: 'list',
name: 'version',
message: 'Select version you want to patch.',
choices: patchFiles.map((f) => f.replace(/\.ts$/, '')),
}).then(({ version }) => {
execSync('yarn run build', {
encoding: 'utf-8',
})
const patchPath = resolve('dist/patch/', version + '.js')
console.log(chalk.green('starting patch... ' + patchPath))
execSync(`node ${patchPath}`, { encoding: 'utf8' })
})
}
bootstarp()

View File

@@ -35,7 +35,8 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"patch": "node bin/patch.js"
},
"dependencies": {
"@nestjs/common": "^8.0.6",
@@ -54,15 +55,16 @@
"bcrypt": "^5.0.1",
"cache-manager": "3.4.4",
"cache-manager-ioredis": "^2.1.0",
"chalk": "^4.1.2",
"chalk": "*",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"dayjs": "^1.10.6",
"ejs": "^3.1.6",
"fastify-swagger": "^4.9.0",
"image-size": "^1.0.0",
"lodash": "^4.17.21",
"mongoose": "^5.13.7",
"lodash": "*",
"mongoose": "*",
"dotenv": "*",
"mongoose-lean-virtuals": "^0.8.0",
"mongoose-paginate-v2": "^1.4.2",
"nanoid": "^3.1.25",
@@ -74,7 +76,8 @@
"redis": "3.1.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.3.0",
"snakecase-keys": "^4.0.2"
"snakecase-keys": "^4.0.2",
"inquirer": "*"
},
"devDependencies": {
"@innei-util/eslint-config-ts": "^0.2.3",

39
patch/bootstrap.ts Normal file
View File

@@ -0,0 +1,39 @@
import { getModelForClass, mongoose } from '@typegoose/typegoose'
import { config } from 'dotenv'
import { ConnectionBase } from 'mongoose'
import * as APP from '../src/app.config'
import { CategoryModel } from '../src/modules/category/category.model'
import { NoteModel } from '../src/modules/note/note.model'
import { PostModel } from '../src/modules/post/post.model'
const env = config().parsed || {}
const url = APP.MONGO_DB.uri
const opt = {
useCreateIndex: true,
useFindAndModify: false,
useNewUrlParser: true,
useUnifiedTopology: true,
autoIndex: true,
}
mongoose.connect(url, opt)
const post = getModelForClass(PostModel)
const note = getModelForClass(NoteModel)
const category = getModelForClass(CategoryModel)
const Config = {
env,
db: (mongoose.connection as any).client.db('mx-space-next') as ConnectionBase,
models: {
post,
note,
category,
},
}
async function bootstrap(cb: (config: typeof Config) => any) {
await cb.call(this, Config)
mongoose.disconnect()
process.exit()
}
export { bootstrap as patch }

11
patch/global.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
import { ModelType } from '@typegoose/typegoose/lib/types'
import { Document, PaginateModel } from 'mongoose'
/// <reference types="../global" />
declare global {
export type KV<T = any> = Record<string, T>
// @ts-ignore
export type MongooseModel<T> = ModelType<T> & PaginateModel<T & Document>
}
export {}

1
patch/index.ts Normal file
View File

@@ -0,0 +1 @@
import './bootstrap'

34
patch/tsconfig.json Normal file
View File

@@ -0,0 +1,34 @@
{
"compilerOptions": {
"module": "CommonJS",
"declaration": false,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": false,
"outDir": "./dist",
"baseUrl": ".",
"noImplicitAny": false,
"incremental": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"paths": {
"~": [
"../src"
],
"~/*": [
"../src/*"
]
},
},
"include": [
"*.ts",
"../src"
],
"exclude": [
"dist"
]
}

17
patch/v2.0.0-alpha.1.ts Normal file
View File

@@ -0,0 +1,17 @@
// patch for version lower than v2.0.0-alpha.1
import { patch } from './bootstrap'
patch(async ({ models: { note, post, category } }) => {
await Promise.all([
[note, post].map((model) => {
return model.updateMany(
{},
{
$unset: ['options'],
},
)
}),
category.aggregate([{ $unset: 'count' }]),
])
})

44
pnpm-lock.yaml generated
View File

@@ -34,22 +34,24 @@ specifiers:
bcrypt: ^5.0.1
cache-manager: 3.4.4
cache-manager-ioredis: ^2.1.0
chalk: ^4.1.2
chalk: '*'
class-transformer: ^0.4.0
class-validator: ^0.13.1
cross-env: ^7.0.3
dayjs: ^1.10.6
dotenv: '*'
ejs: ^3.1.6
eslint: ^7.32.0
fastify: '*'
fastify-swagger: ^4.9.0
husky: ^7.0.1
image-size: ^1.0.0
inquirer: '*'
ioredis: '*'
jest: 27.1.0
lint-staged: ^11.1.2
lodash: ^4.17.21
mongoose: ^5.13.7
lodash: '*'
mongoose: '*'
mongoose-lean-virtuals: ^0.8.0
mongoose-paginate-v2: ^1.4.2
nanoid: ^3.1.25
@@ -96,9 +98,11 @@ dependencies:
class-transformer: 0.4.0
class-validator: 0.13.1
dayjs: 1.10.6
dotenv: 10.0.0
ejs: 3.1.6
fastify-swagger: 4.9.1
image-size: 1.0.0
inquirer: 8.1.1
lodash: 4.17.21
mongoose: 5.13.8
mongoose-lean-virtuals: 0.8.0_mongoose@5.13.8
@@ -2068,7 +2072,6 @@ packages:
engines: {node: '>=8'}
dependencies:
type-fest: 0.21.3
dev: true
/ansi-regex/2.1.1:
resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=}
@@ -2083,7 +2086,6 @@ packages:
/ansi-regex/5.0.0:
resolution: {integrity: sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==}
engines: {node: '>=8'}
dev: true
/ansi-styles/3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
@@ -2325,7 +2327,6 @@ packages:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.0
dev: true
/bluebird/3.5.1:
resolution: {integrity: sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==}
@@ -2473,7 +2474,6 @@ packages:
/chardet/0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
dev: true
/chokidar/3.5.2:
resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==}
@@ -2530,12 +2530,10 @@ packages:
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
dev: true
/cli-spinners/2.6.0:
resolution: {integrity: sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==}
engines: {node: '>=6'}
dev: true
/cli-table3/0.5.1:
resolution: {integrity: sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==}
@@ -2558,7 +2556,6 @@ packages:
/cli-width/3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
dev: true
/cliui/7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
@@ -2571,7 +2568,6 @@ packages:
/clone/1.0.4:
resolution: {integrity: sha1-2jCcwmPfFZlMaIypAheco8fNfH4=}
engines: {node: '>=0.8'}
dev: true
/cluster-key-slot/1.1.0:
resolution: {integrity: sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==}
@@ -2799,7 +2795,6 @@ packages:
resolution: {integrity: sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=}
dependencies:
clone: 1.0.4
dev: true
/define-properties/1.1.3:
resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==}
@@ -2921,7 +2916,6 @@ packages:
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
/encodeurl/1.0.2:
resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=}
@@ -3259,7 +3253,6 @@ packages:
chardet: 0.7.0
iconv-lite: 0.4.24
tmp: 0.0.33
dev: true
/fast-decode-uri-component/1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -3385,7 +3378,6 @@ packages:
engines: {node: '>=8'}
dependencies:
escape-string-regexp: 1.0.5
dev: true
/file-entry-cache/6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
@@ -3752,7 +3744,6 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: true
/ieee754/1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -3853,7 +3844,6 @@ packages:
string-width: 4.2.2
strip-ansi: 6.0.0
through: 2.3.8
dev: true
/internal-slot/1.0.3:
resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
@@ -3965,7 +3955,6 @@ packages:
/is-fullwidth-code-point/3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: true
/is-function/1.0.2:
resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==}
@@ -3986,7 +3975,6 @@ packages:
/is-interactive/1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
dev: true
/is-negative-zero/2.0.1:
resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==}
@@ -4053,7 +4041,6 @@ packages:
/is-unicode-supported/0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
dev: true
/isarray/1.0.0:
resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=}
@@ -4939,7 +4926,6 @@ packages:
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
dev: true
/log-update/4.0.0:
resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
@@ -5068,7 +5054,6 @@ packages:
/mimic-fn/2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
dev: true
/min-document/2.19.0:
resolution: {integrity: sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=}
@@ -5232,7 +5217,6 @@ packages:
/mute-stream/0.0.8:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
dev: true
/nanoid/3.1.25:
resolution: {integrity: sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==}
@@ -5407,7 +5391,6 @@ packages:
engines: {node: '>=6'}
dependencies:
mimic-fn: 2.1.0
dev: true
/openapi-types/9.3.0:
resolution: {integrity: sha512-sR23YjmuwDSMsQVZDHbV9mPgi0RyniQlqR0AQxTC2/F3cpSjRFMH3CFPjoWvNqhC4OxPkDYNb2l8Mc1Me6D/KQ==}
@@ -5474,7 +5457,6 @@ packages:
log-symbols: 4.1.0
strip-ansi: 6.0.0
wcwidth: 1.0.1
dev: true
/os-name/4.0.1:
resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==}
@@ -5487,7 +5469,6 @@ packages:
/os-tmpdir/1.0.2:
resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=}
engines: {node: '>=0.10.0'}
dev: true
/p-each-series/2.2.0:
resolution: {integrity: sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==}
@@ -5950,7 +5931,6 @@ packages:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.3
dev: true
/ret/0.2.2:
resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==}
@@ -5972,7 +5952,6 @@ packages:
/run-async/2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
dev: true
/run-parallel/1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -5990,7 +5969,6 @@ packages:
engines: {npm: '>=2.0.0'}
dependencies:
tslib: 1.14.1
dev: true
/rxjs/7.3.0:
resolution: {integrity: sha512-p2yuGIg9S1epc3vrjKf6iVb3RCaAYjYskkO+jHIaV0IjOPlJop4UnodOoFb2xeNwlguqLYvGw1b1McillYb5Gw==}
@@ -6011,7 +5989,6 @@ packages:
/safer-buffer/2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: true
/saslprep/1.0.3:
resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
@@ -6335,7 +6312,6 @@ packages:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.0
dev: true
/string.prototype.trimend/1.0.4:
resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==}
@@ -6390,7 +6366,6 @@ packages:
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.0
dev: true
/strip-bom/3.0.0:
resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=}
@@ -6578,7 +6553,6 @@ packages:
/through/2.3.8:
resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=}
dev: true
/timm/1.7.1:
resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==}
@@ -6597,7 +6571,6 @@ packages:
engines: {node: '>=0.6.0'}
dependencies:
os-tmpdir: 1.0.2
dev: true
/tmpl/1.0.4:
resolution: {integrity: sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=}
@@ -6745,7 +6718,6 @@ packages:
/tslib/1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
/tslib/2.1.0:
resolution: {integrity: sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==}
@@ -6795,7 +6767,6 @@ packages:
/type-fest/0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
dev: true
/typedarray-to-buffer/3.1.5:
resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
@@ -6913,7 +6884,6 @@ packages:
resolution: {integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=}
dependencies:
defaults: 1.0.3
dev: true
/webidl-conversions/5.0.0:
resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==}

View File

@@ -0,0 +1,54 @@
import { Controller, forwardRef, Get, Inject, Query } from '@nestjs/common'
import { ApiName } from '~/common/decorator/openapi.decorator'
import { PostService } from '../post/post.service'
import { CategoryType, MultiCategoriesQueryDto } from './category.dto'
import { CategoryService } from './category.service'
@Controller({ path: 'categories' })
@ApiName
export class CategoryController {
constructor(
private readonly categoryService: CategoryService,
@Inject(forwardRef(() => PostService))
private readonly postService: PostService,
) {}
@Get('/')
async getCategories(@Query() query: MultiCategoriesQueryDto) {
const { ids, joint, type = CategoryType.Category } = query // categories is category's mongo id
if (ids) {
return joint
? await Promise.all(
ids.map(async (id) => {
return await this.postService.model.find(
{ categoryId: id },
{
select: 'title slug _id categoryId created modified',
sort: { created: -1 },
},
)
}),
)
: await Promise.all(
ids.map(async (id) => {
const posts = await this.postService.model.find(
{ categoryId: id },
{
select: 'title slug _id created modified',
sort: { created: -1 },
},
)
const category = await this.categoryService.model
.findById(id)
.lean()
return {
category: { ...category, children: posts },
}
}),
)
}
return type === CategoryType.Category
? await this.categoryService.model.find({ type }).lean()
: await this.categoryService.getPostTagsSum()
}
}

View File

@@ -0,0 +1,86 @@
import { UnprocessableEntityException } from '@nestjs/common'
import { ApiProperty } from '@nestjs/swagger'
import { Transform } from 'class-transformer'
import {
IsBoolean,
IsEnum,
IsMongoId,
IsNotEmpty,
IsOptional,
IsString,
} from 'class-validator'
import { uniq } from 'lodash'
import { IsBooleanOrString } from '~/utils/validator/isBooleanOrString'
export enum CategoryType {
Category,
Tag,
}
export class CategoryDto {
@IsString()
@IsNotEmpty()
@ApiProperty()
name: string
@IsEnum(CategoryType)
@IsOptional()
@ApiProperty({ enum: [0, 1] })
type?: CategoryType
@IsString()
@IsNotEmpty()
@IsOptional()
slug?: string
}
export class SlugOrIdDto {
@IsString()
@IsNotEmpty()
@ApiProperty()
query?: string
}
export class MultiQueryTagAndCategoryDto {
@IsOptional()
@Transform(({ value: val }) => {
if (val === '1' || val === 'true') {
return true
} else {
return val
}
})
@IsBooleanOrString()
tag?: boolean | string
}
export class MultiCategoriesQueryDto {
@IsOptional()
@IsMongoId({
each: true,
message: '多分类查询使用逗号分隔, 应为 mongoID',
})
@Transform(({ value: v }) => uniq(v.split(',')))
ids?: Array<string>
@IsOptional()
@IsBoolean()
@Transform((b) => Boolean(b))
@ApiProperty({ enum: [1, 0] })
joint?: boolean
@IsOptional()
@Transform(({ value: v }: { value: string }) => {
if (typeof v !== 'string') {
throw new UnprocessableEntityException('type must be a string')
}
switch (v.toLowerCase()) {
case 'category':
return CategoryType.Category
case 'tag':
return CategoryType.Tag
default:
return CategoryType.Category
}
})
type: CategoryType
}

View File

@@ -1,8 +1,12 @@
import { Module } from '@nestjs/common'
import { forwardRef, Module } from '@nestjs/common'
import { PostModule } from '../post/post.module'
import { CategoryController } from './category.controller'
import { CategoryService } from './category.service'
@Module({
providers: [CategoryService],
exports: [CategoryService],
controllers: [CategoryController],
imports: [forwardRef(() => PostModule)],
})
export class CategoryModule {}

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common'
import { forwardRef, Inject, Injectable } from '@nestjs/common'
import { ReturnModelType } from '@typegoose/typegoose'
import { InjectModel } from 'nestjs-typegoose'
import { PostService } from '../post/post.service'
import { CategoryModel } from './category.model'
@Injectable()
@@ -8,6 +9,8 @@ export class CategoryService {
constructor(
@InjectModel(CategoryModel)
private readonly categoryModel: ReturnModelType<typeof CategoryModel>,
@Inject(forwardRef(() => PostService))
private readonly postService: PostService,
) {}
findCategoryById(categoryId: string) {
@@ -17,4 +20,22 @@ export class CategoryService {
get model() {
return this.categoryModel
}
async getPostTagsSum() {
const data = await this.postService.model.aggregate([
{ $project: { tags: 1 } },
{
$unwind: '$tags',
},
{ $group: { _id: '$tags', count: { $sum: 1 } } },
{
$project: {
_id: 0,
name: '$_id',
count: 1,
},
},
])
return data
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common'
import chalk from 'chalk'
import { mkdirSync } from 'fs'
import { DATA_DIR, LOGGER_DIR, TEMP_DIR } from 'src/constants/path.constant'
import { DATA_DIR, LOGGER_DIR, TEMP_DIR } from '~/constants/path.constant'
@Injectable()
export class InitService {

View File

@@ -1,10 +1,10 @@
import { Module } from '@nestjs/common'
import { forwardRef, Module } from '@nestjs/common'
import { CategoryModule } from '../category/category.module'
import { PostController } from './post.controller'
import { PostService } from './post.service'
@Module({
imports: [CategoryModule],
imports: [forwardRef(() => CategoryModule)],
controllers: [PostController],
providers: [PostService],
exports: [PostService],

View File

@@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing'
import { getFakeCategoryModel } from 'test/db-model.mock'
import { CategoryService } from '../category/category.service'
import { getFakeCategoryModel } from '~/../test/db-model.mock'
import { DbModule } from '../../processors/database/database.module'
import { CategoryService } from '../category/category.service'
import { PostService } from './post.service'
describe('PostService', () => {

View File

@@ -1,6 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication } from '@nestjs/common'
import * as request from 'supertest'
import { Test, TestingModule } from '@nestjs/testing'
import request from 'supertest'
import { AppModule } from './../src/app.module'
describe('AppController (e2e)', () => {