diff --git a/src/admin/controllers/shop_puzzle.controller.ts b/src/admin/controllers/shop_puzzle.controller.ts new file mode 100644 index 0000000..1140965 --- /dev/null +++ b/src/admin/controllers/shop_puzzle.controller.ts @@ -0,0 +1,91 @@ +import BaseController from '../../common/base.controller' +import { permission, router } from '../../decorators/router' +import { ShopPuzzle } from '../../models/shop/ShopPuzzle' +import { ZError } from '../../common/ZError' + +export default class ShopPuzzleController extends BaseController { + @permission('shoppuzzle:read') + @router('post /api/:shop/puzzles') + async list(req, res) { + let { start, limit, page, shop } = req.params + limit = +limit || 10 + start = +start || (+page - 1) * limit || 0 + let { opt, sort } = ShopPuzzle.parseQueryParam(req.params) + let puzzles = await ShopPuzzle.find(opt).sort(sort).skip(start).limit(limit) + let count = await ShopPuzzle.countDocuments(opt) + let records = [] + for (let record of puzzles) { + let data = record.toJson() + records.push(data) + } + return { + total: count, + start, + limit, + records, + } + } + + @permission('shoppuzzle:read') + @router('get /api/:shop/puzzle/:id') + async detail(req, res) { + let { id } = req.params + const record = await ShopPuzzle.findById(id) + if (!record) { + throw new ZError(11, 'record not found') + } + return record.toJson() + } + + @permission('shoppuzzle:edit') + @router('post /api/:shop/puzzle/save') + async save(req: any) { + let { _id, withNext, shop } = req.params + let user = req.user + let record + if (!_id) { + record = new ShopPuzzle(req.params) + record.createdBy = user.id + } else { + record = await ShopPuzzle.findById(_id) + record.updateFromReq(req.params) + } + await record.save() + if (withNext) { + let nextRecord = await ShopPuzzle.nextQuestion(record.id, shop) + return nextRecord.toJson() + } + return record.toJson() + } + @permission('shoppuzzle:delete') + @router('post /api/:shop/puzzle/:id/delete') + async delete(req: any) { + let { id } = req.params + if (!id) { + throw new ZError(11, 'params mismatch') + } + await ShopPuzzle.findByIdAndUpdate(id, { + $set: { + deleted: 1, + deleteTime: new Date(), + }, + }) + return {} + } + @permission('shoppuzzle:read') + @router('post /api/:shop/nextpuzzle') + async next(req: any) { + let { id, shop } = req.params + if (!id) { + throw new ZError(11, 'params mismatch') + } + let nextRecord = await ShopPuzzle.nextQuestion(id, shop) + return nextRecord.toJson() + } + + @router('get /api/:shop/categorys') + async categoryList(req, res) { + const { shop } = req.params + return await ShopPuzzle.allTags(shop) + } +} diff --git a/src/models/content/Puzzle.ts b/src/models/content/Puzzle.ts index 28da010..6436215 100644 --- a/src/models/content/Puzzle.ts +++ b/src/models/content/Puzzle.ts @@ -71,7 +71,7 @@ export class PuzzleClass extends BaseModule { opt.sub_tag = sub_tag } if (groups) { - Object.assign(opt, { groups }) + Object.assign(opt, { groups: { $in: groups } }) } if (quality != undefined) { opt.quality = quality diff --git a/src/models/shop/ShopCategory.ts b/src/models/shop/ShopCategory.ts new file mode 100644 index 0000000..c8ce88c --- /dev/null +++ b/src/models/shop/ShopCategory.ts @@ -0,0 +1,59 @@ +import { dbconn } from '../../decorators/dbconn' +import { getModelForClass, index, modelOptions, prop, ReturnModelType } from '@typegoose/typegoose' +import { BaseModule } from '../Base' +import { noJson } from '../../decorators/nojson' + +/** + * 店铺的分类 + */ + +class SubType { + @prop() + public _id: string + + @prop() + public name: string +} + +@dbconn('second') +@index({ shop: 1 }, { unique: false }) +@modelOptions({ schemaOptions: { collection: 'question_category' } }) +class ShopCategoryClass extends BaseModule { + @prop() + public _id: string + + @prop() + public shop: string + + @prop() + public name: string + + @prop({ type: () => [SubType] }) + public children: SubType[] + + /** + * 是否删除 + * @type {boolean} + */ + @noJson() + @prop({ default: false }) + public deleted: boolean + + @noJson() + @prop() + public deleteTime: Date + + public static async allCateMap(this: ReturnModelType, shop: string) { + let result: Map = new Map() + let records = await this.find({ shop, deleted: false }).exec() + for (let record of records) { + result.set(record._id, record.name) + for (let sub of record.children) { + result.set(sub._id, sub.name) + } + } + return result + } +} + +export const ShopCategory = getModelForClass(ShopCategoryClass, { existingConnection: ShopCategoryClass.db }) diff --git a/src/models/shop/ShopPuzzle.ts b/src/models/shop/ShopPuzzle.ts new file mode 100644 index 0000000..bbf5571 --- /dev/null +++ b/src/models/shop/ShopPuzzle.ts @@ -0,0 +1,147 @@ +import { dbconn } from '../../decorators/dbconn' +import { getModelForClass, index, modelOptions, prop, ReturnModelType } from '@typegoose/typegoose' +import { BaseModule } from '../Base' +import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses' +import { checkJson, noJson } from '../../decorators/nojson' +import { PuzzleClass } from '../content/Puzzle' + +const jsonExcludeKeys = ['updatedAt', '__v'] + +/** + * 店铺自定义题库, 单独拿出来放, 避免污染系统题库 + */ +// @ts-ignore +export interface ShopPuzzleClass extends Base, TimeStamps {} + +@dbconn() +@index({ shop: 1 }, { unique: false }) +@index({ groups: 1 }, { unique: false }) +@index({ shop: 1, tag: 1, sub_tag: 1 }, { unique: false }) +@modelOptions({ schemaOptions: { collection: 'shop_puzzle', timestamps: true } }) +export class ShopPuzzleClass extends BaseModule { + @prop() + public shop: string + @prop() + public question: string + @prop() + public a1: string + @prop() + public a2: string + @prop() + public a3: string + @prop() + public a4: string + /** + * 难度 + * @type {number} + */ + @prop({ default: 1 }) + public quality: number + + /** + * 大分类 + * @type {string} + */ + @prop() + public tag: string + /** + * 子分类 + * @type {string} + */ + @prop() + public sub_tag: string + + /** + * 这个是tag + * @type {string} + */ + + @prop({ type: () => [String] }) + public groups: string[] + /** + * 是否删除 + * @type {boolean} + */ + @noJson() + @prop({ default: 0 }) + public deleted: number + + @noJson() + @prop() + public deleteTime: Date + + @noJson() + @prop({ default: 0 }) + public is_hide: number + + public static parseQueryParam(params) { + let { key, timeBegin, timeEnd, tag, sub_tag, groups, dp, quality, sort, length, shop } = params + let opt: any = { deleted: 0, is_hide: 0 } + if (key) { + opt.question = { $regex: key, $options: 'i' } + } + if (tag) { + opt.tag = tag + } + if (sub_tag) { + opt.sub_tag = sub_tag + } + if (groups) { + Object.assign(opt, { groups: { $in: groups } }) + } + if (quality != undefined) { + opt.quality = quality + } + if (dp != undefined) { + if (dp == 'none') { + opt.dp = { $exists: false } + } else { + opt.dp = dp + } + } + if (shop) { + opt.shop = shop + } + if (timeBegin && !timeEnd) { + opt.createdAt = { $gte: timeBegin } + } else if (timeBegin && timeEnd) { + opt['$and'] = [{ createdAt: { $gte: timeBegin } }, { createdAt: { $lte: timeEnd } }] + } else if (!timeBegin && timeEnd) { + opt.createdAt = { $lte: timeEnd } + } + if (length) { + opt.length = { $gte: length } + } + let sortObj = { _id: 1 } + if (sort) { + sortObj = sort + } + + return { opt, sort: sortObj } + } + + public toJson() { + let result: any = {} + // @ts-ignore + for (let key in this._doc) { + if (checkJson(this, key + '') && jsonExcludeKeys.indexOf(key) == -1) { + if (key === 'createdAt') { + result.createtime = this['createdAt'] + } else { + result[key] = this[key] + } + } + } + return result + } + + public static async nextQuestion(this: ReturnModelType, id: string, shop: string) { + return this.findOne({ deleted: 0, shop, is_hide: 0, _id: { $gt: id } }).exec() + } + + public static async allTags(shop: string) { + return ShopPuzzle.distinct('groups', { shop, deleted: 0 }) + } +} + +export const ShopPuzzle = getModelForClass(ShopPuzzleClass, { existingConnection: ShopPuzzleClass.db })