diff --git a/TODOLIST.md b/TODOLIST.md index 36ce465..433517d 100644 --- a/TODOLIST.md +++ b/TODOLIST.md @@ -3,7 +3,8 @@ - [x] 用户组 - [ ] 权限 - [x] 默认用户组 -- [ ] 上传到指定Categroy +- [x] 上传到指定Categroy + - [x] 上传时追加Catogroy - [ ] 整顿collectin info的goods 列表 - [ ] Token 使用日志显示 - [ ] Good 下载次数统计 diff --git a/package.json b/package.json index 70e0fb3..7a5edd7 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "author": "AryloYeung ", "license": "MIT", "devDependencies": { - "@nestjs/testing": "^4.5.1", + "@nestjs/testing": "4.5.2", "@types/basic-auth": "^1.1.2", "@types/bunyan": "^1.8.4", "@types/connect-redis": "0.0.7", @@ -67,8 +67,8 @@ "typescript": "^2.6.1" }, "dependencies": { - "@nestjs/common": "^4.5.1", - "@nestjs/core": "^4.5.1", + "@nestjs/common": "4.5.2", + "@nestjs/core": "4.5.2", "@nestjs/swagger": "^1.1.3", "basic-auth": "^2.0.0", "body-parser": "^1.18.2", diff --git a/src/index.ts b/src/index.ts index 8b085f1..54dac33 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ import { isDevelopment } from "./modules/common/helper/env"; const bootstrap = async () => { const server = initExpress(); - const app = await NestFactory.create(ApplicationModule, server, { }); + const app = await NestFactory.create(ApplicationModule, server); app.useGlobalPipes(new ValidationPipe()); if (isDevelopment) { diff --git a/src/models/Categroy.ts b/src/models/Categroy.ts index aeef6b4..bf46d10 100644 --- a/src/models/Categroy.ts +++ b/src/models/Categroy.ts @@ -7,7 +7,7 @@ import { reduce, includes, difference } from "lodash"; import { MongoError } from "mongodb"; import Cache = require("schedule-cache"); -const cache = Cache.create(`${Date.now()}${Math.random()}`); +export const cache = Cache.create(`${Date.now()}${Math.random()}`); export const FLAG = "categories"; export type CategoryDoc = IDoc; diff --git a/src/models/Regexp.ts b/src/models/Regexp.ts index 43eb997..f8314ad 100644 --- a/src/models/Regexp.ts +++ b/src/models/Regexp.ts @@ -11,37 +11,37 @@ export const cache = Cache.create(`${Date.now()}${Math.random()}`); const Definition: SchemaDefinition = { name: { type: String, required: true, unique: true }, - value: { type: String, required: true, unique: true }, + value: { type: String, required: true }, link: { type: SchemaTypes.ObjectId, ref: CF - } + }, + hidden: { type: Boolean, default: false } }; export interface IRegexp extends IDocRaw { name: string; value: string; link: ObjectId | ICategory; + hidden: boolean; } export interface IRegexpsRaw extends IRegexp { link: ICategory; } +export interface IRegexpDoc { + name: string; + value: string; + link?: ObjectId; + hidden?: boolean; +} + export type RegexpDoc = IDoc; const RegexpSchema = new Base(Definition).createSchema(); // region static methods -RegexpSchema.static("countRegexps", async (perNum = 1) => { - const FLAG = `page_count_${perNum}`; - if (cache.get(FLAG)) { - return cache.get(FLAG); - } - cache.put(FLAG, Math.ceil((await Model.count({ }).exec()) / perNum)); - return cache.get(FLAG); -}); - RegexpSchema.static( "addRegexp", (name: string, value: string, link?: ObjectId) => { @@ -52,10 +52,7 @@ RegexpSchema.static( if (link) { obj.link = link; } - return Model.create(obj).then((result) => { - cache.clear(); - return result; - }); + return Model.create(obj); } ); @@ -63,55 +60,6 @@ RegexpSchema.static("removeRegexp", (id: ObjectId) => { return Model.findByIdAndRemove(id).exec(); }); -RegexpSchema.static("link", (id: ObjectId, linkId: ObjectId | false) => { - if (!linkId) { - return Model.findByIdAndUpdate(id, { - "$unset": { link: 0 } - }).exec(); - } else { - return Model.findByIdAndUpdate( - id, { link: linkId }, { runValidators: true } - ).exec(); - } -}); - -RegexpSchema.static("list", (perNum = DEF_PER_COUNT, page = 1) => { - const FLAG_LIST = `list_${perNum}_${page}`; - if (cache.get(FLAG_LIST)) { - return cache.get(FLAG_LIST); - } - cache.put( - FLAG_LIST, - Model.find({ }) - .skip((page - 1) * perNum).limit(perNum) - .populate("link").exec() - ); - return cache.get(FLAG_LIST); -}); - -RegexpSchema.static("discern", (name: string) => { - const FLAG_DISCER_LIST = "discern"; - let p: Promise; - if (cache.get(FLAG_DISCER_LIST)) { - p = cache.get(FLAG_DISCER_LIST); - } else { - p = Model.find({ link: { $exists: true } }) - .populate("link") - .exec(); - cache.put(FLAG_DISCER_LIST, p); - } - return p.then((result) => { - const list = [ ]; - result.forEach((item) => { - const obj = item.toObject(); - const reg = new RegExp(obj.value); - if (reg.test(name)) { - list.push(obj.link); - } - }); - return list; - }); -}); // endregion static methods export const FLAG = "regexps"; @@ -127,27 +75,6 @@ interface IRegexpModel extends M { * @return {Promise} */ removeRegexp(id: ObjectId): Promise; - /** - * 规则关联 - * @return {Promise} - */ - link(id: ObjectId, linkId: ObjectId | false): Promise; - /** - * 规则列表 - * @param perNum {number} 每页数量 - * @param page {number} 页数 - * @return {Promise} - */ - list(perNum?: number, page?: number): Promise; - /** - * 根据规则进行识别 - * @return {Promise} - */ - discern(filename: string): Promise; - /** - * 返回总页数 - */ - countRegexps(perNum?: number): Promise; } // region Validators @@ -155,7 +82,7 @@ RegexpSchema.path("name").validate({ isAsync: true, validator: async (value, respond) => { const result = await Model.findOne({ name: value }).exec(); - return !result; + return respond(!result); }, message: "The name is exist" }); @@ -163,16 +90,20 @@ RegexpSchema.path("name").validate({ RegexpSchema.path("value").validate({ isAsync: true, validator: (value, respond) => { - return isRegExp(value); + return respond(isRegExp(value)); }, message: "The value isnt Regexp" }); RegexpSchema.path("value").validate({ isAsync: true, - validator: async (value, respond) => { - const result = await Model.findOne({ value: value }).exec(); - return !result; + validator: async function ValueExistValidator(value, respond) { + if (this && this.hidden) { + return respond(true); + } + const result = + await Model.findOne({ value: value, hidden: false }).exec(); + return respond(!result); }, message: "The value is exist" }); @@ -181,10 +112,30 @@ RegexpSchema.path("link").validate({ isAsync: true, validator: async (value, respond) => { const result = await CM.findById(value).exec(); - return !!result; + return respond(!!result); }, message: "The Category ID is not exist" }); + +RegexpSchema.path("hidden").validate({ + isAsync: true, + validator: async function hiddenExistValidator(value, respond) { + if (!value) { // hidden === false + return respond(true); + } + if (!this.isNew) { // hidden === Old Value + const id = this.getQuery()._id; + const col = await Model.findById(id).exec(); + if (col.toObject().hidden === value) { + return respond(true); + } + } + const result = + await Model.findOne({ value: this.value, hidden: value }).exec(); + respond(result ? false : true); + }, + message: "Only one active item with every value" +}); // endregion Validators for (const method of MODIFY_MOTHODS) { diff --git a/src/modules/common/pipes/regexp-count-check.pipe.ts b/src/modules/common/pipes/regexp-count-check.pipe.ts index dfe2a03..785e002 100644 --- a/src/modules/common/pipes/regexp-count-check.pipe.ts +++ b/src/modules/common/pipes/regexp-count-check.pipe.ts @@ -1,16 +1,19 @@ import { Pipe, PipeTransform, ArgumentMetadata, BadRequestException } from "@nestjs/common"; -import { Model as RegexpModel } from "@models/Regexp"; import { isArray } from "util"; import fs = require("fs-extra"); +import { RegexpsService } from "@services/regexps"; type File = Express.Multer.File; @Pipe() export class RegexpCountCheckPipe implements PipeTransform { + + private readonly regexpSvr = new RegexpsService(); + public async transform(value: File | File[], metadata: ArgumentMetadata) { - const regexpCount = (await RegexpModel.list()).length; + const regexpCount = await this.regexpSvr.count(); if (regexpCount !== 0) { return value; } diff --git a/src/modules/common/pipes/to-array.pipe.ts b/src/modules/common/pipes/to-array.pipe.ts new file mode 100644 index 0000000..776ccbd --- /dev/null +++ b/src/modules/common/pipes/to-array.pipe.ts @@ -0,0 +1,34 @@ +import { Pipe, PipeTransform, ArgumentMetadata } from "@nestjs/common"; +import { isObject, isArray } from "util"; + +@Pipe() +export class ToArrayPipe implements PipeTransform { + + private readonly properties: string[]; + constructor(...properties: string[]) { + this.properties = properties; + } + + public async transform(value: any, metadata: ArgumentMetadata) { + if (!value || isArray(value)) { + return value; + } + if (this.properties.length === 0) { + if (isObject(value)) { + for (const key of Object.keys(value)) { + value[key] = [ value[key] ]; + } + } else { + return [ value ]; + } + } else { + for (const property of this.properties) { + if (!value[property] || isArray(value[property])) { + continue; + } + value[property] = [ value[property] ]; + } + } + return value; + } +} diff --git a/src/modules/common/services/categories.service.ts b/src/modules/common/services/categories.service.ts new file mode 100644 index 0000000..e071783 --- /dev/null +++ b/src/modules/common/services/categories.service.ts @@ -0,0 +1,58 @@ +import { Component } from "@nestjs/common"; +import { ObjectId } from "@models/common"; +import { Model as CategoriesModel, cache } from "@models/Categroy"; +import { isFunction } from "util"; + +interface IIdMap { + [parentId: string]: ObjectId[]; +} + +@Component() +export class CategoriesService { + + private loadAndCache(FLAG: string, value: () => any, time?: number) { + const c = cache.get(FLAG); + if (c) { + return c; + } + const val = value(); + cache.put(FLAG, val, time); + return val; + } + + private async getIdMap() { + // { parentId: childrenIds } + const map: IIdMap = { }; + const docs = await CategoriesModel.find().select("_id pid").exec(); + docs.forEach((doc) => { + const category = doc.toObject(); + let index; + if (!category.pid) { + index = "*"; + } else { + index = category.pid.toString(); + } + if (!map[index]) { + map[index] = [ ]; + } + map[index].push(category._id.toString()); + }); + return map; + } + + public async getChildrenIds(pid: ObjectId) { + const map: IIdMap = await this.loadAndCache("IdMap", () => { + return this.getIdMap(); + }); + const ids: ObjectId[] = [ ]; + const childrenIds = map[pid.toString()]; + if (childrenIds) { + ids.push(...childrenIds); + for (const id of childrenIds) { + ids.push(...(await this.getChildrenIds(id))); + } + } + return ids; + } + +} diff --git a/src/modules/common/services/regexps.service.ts b/src/modules/common/services/regexps.service.ts new file mode 100644 index 0000000..b742f28 --- /dev/null +++ b/src/modules/common/services/regexps.service.ts @@ -0,0 +1,201 @@ +import { Component, BadRequestException } from "@nestjs/common"; +import { ObjectId } from "@models/common"; +import { + Model as RegexpsModel, cache, RegexpDoc, IRegexpDoc +} from "@models/Regexp"; +import { Model as CategroiesModel, ICategory } from "@models/Categroy"; +import { DEF_PER_COUNT } from "@dtos/page"; +import { isUndefined } from "util"; + +export interface IGetRegexpsOptions { + categroies?: ObjectId[]; + appends?: ObjectId[]; +} + +@Component() +export class RegexpsService { + + constructor() { + // Update + setTimeout(() => { + // Add Hidden Label + RegexpsModel.update( + { hidden: { $exists: false } }, { hidden: false }, + { multi: true } + ).exec(); + }, 3000); + } + + private loadAndCache(FLAG: string, value: any, time?: number | string) { + if (cache.get(FLAG) === null) { + cache.put(FLAG, value, time); + } + return cache.get(FLAG); + } + + /** + * 新增规则 + */ + public async create(obj: IRegexpDoc) { + try { + return await RegexpsModel.create(obj); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + /** + * 修改规则 + */ + public async editById(id: ObjectId, obj) { + try { + return await RegexpsModel + .update( + { _id: id }, obj, { runValidators: true, context: "query" } + ) + .exec(); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + /** + * 删除规则 + */ + public async remove(id: ObjectId) { + try { + return await RegexpsModel.findByIdAndRemove(id).exec(); + } catch (error) { + throw new BadRequestException(error.toSrting()); + } + } + + /** + * 规则关联 + * @return {Promise} + */ + public async link(id: ObjectId, linkId?: ObjectId) { + if (!linkId) { + try { + return await RegexpsModel.findByIdAndUpdate(id, { + "$unset": { link: 0 } + }).exec(); + } catch (error) { + throw new BadRequestException(error.toSrting()); + } + } + if (!(await CategroiesModel.findById(linkId).exec())) { + throw new BadRequestException("Nonexist Categroy ID"); + } + try { + return await RegexpsModel.findByIdAndUpdate( + id, { link: linkId }, { runValidators: true } + ).exec(); + } catch (error) { + throw new BadRequestException(error.toSrting()); + } + } + + public async pageCount(perNum = 1): Promise { + const FLAG = `pageCount_${perNum}`; + return this.loadAndCache( + FLAG, + Math.ceil((await this.count()) / perNum), + 3000 + ); + } + + public count(): Promise { + const FLAG = "totalCount"; + return this.loadAndCache( + FLAG, + RegexpsModel.count({ }).exec(), + 3000 + ); + } + + public getRegexp(id: ObjectId) { + return RegexpsModel.findById(id) + .populate({ path: "link", populate: { path: "pid" } }) + .exec(); + } + + /** + * 规则列表 + * @param perNum {number} 每页数量 + * @param page {number} 页数 + * @return {Promise} + */ + public async list(perNum = DEF_PER_COUNT, page = 1): Promise { + const FLAG = `list_${perNum}_${page}`; + return this.loadAndCache( + FLAG, + RegexpsModel.find({ }) + .skip((page - 1) * perNum).limit(perNum) + .populate("link").exec(), + 3000 + ); + } + + private async getRegexps(opts: IGetRegexpsOptions = { }) + : Promise { + const DEF_CONDITIONS = { + link: { $exists: true }, hidden: false + }; + + if (opts.categroies && opts.categroies.length > 0) { + // 指定Categroy + const FLAG = `categroies_scan_regexps_${opts.categroies.join("_")}`; + const conditions = { + $or: opts.categroies.reduce((arr, item) => { + arr.push({ link: item }); + return arr; + }, [ ]) + }; + return this.loadAndCache( + FLAG, + RegexpsModel.find(conditions).populate("link").exec(), + 3000 + ); + } else if (opts.appends && opts.appends.length > 0) { + // 追加Categroy + const FLAG = `appends_scan_regexps_${opts.appends.join("_")}`; + const conditions = { + $or: opts.appends.reduce((arr: any, item) => { + arr.push({ link: item }); + return arr; + }, [ DEF_CONDITIONS ]) + }; + return this.loadAndCache( + FLAG, + RegexpsModel.find(conditions).populate("link").exec(), + 3000 + ); + } else { + const FLAG = "default_scan_regexps"; + return this.loadAndCache( + FLAG, + RegexpsModel.find(DEF_CONDITIONS).populate("link").exec(), + 3000 + ); + } + } + + /** + * 根据规则进行识别 + * @return {Promise} + */ + public async discern(name: string, opts?: IGetRegexpsOptions) { + const result = await this.getRegexps(opts); + const list = [ ]; + result.forEach((item) => { + const obj = item.toObject(); + const reg = new RegExp(obj.value); + if (reg.test(name)) { + list.push(obj.link); + } + }); + return list as ICategory[]; + } + +} diff --git a/src/modules/controllers.module.ts b/src/modules/controllers.module.ts index 7770fe7..5914375 100644 --- a/src/modules/controllers.module.ts +++ b/src/modules/controllers.module.ts @@ -31,11 +31,13 @@ import { // endregion Middlewares // region Services +import { RegexpsService } from "@services/regexps"; import { CollectionsService } from "@services/collections"; import { UsersService } from "@services/users"; import { TokensService } from "@services/tokens"; import { UsergroupsService } from "@services/usergroups"; import { SystemService } from "@services/system"; +import { CategoriesService } from "@services/categories"; // endregion Services export const controllers = [ @@ -49,6 +51,7 @@ export const controllers = [ ]; export const services = [ + RegexpsService, CategoriesService, CollectionsService, TokensService, UsersService, UsergroupsService, SystemService ]; diff --git a/src/modules/goods/goods.controller.ts b/src/modules/goods/goods.controller.ts index f72dc0b..fc6779f 100644 --- a/src/modules/goods/goods.controller.ts +++ b/src/modules/goods/goods.controller.ts @@ -11,6 +11,7 @@ import { Model as GoodsModels, IGoods } from "@models/Good"; import { Model as RegexpModel } from "@models/Regexp"; import { Model as TokensModel } from "@models/Token"; import { Model as CollectionsModel } from "@models/Collection"; +import { Model as CategroiesModel } from "@models/Categroy"; import { ObjectId } from "@models/common"; import { config } from "@utils/config"; import { RolesGuard } from "@guards/roles"; @@ -21,15 +22,18 @@ import { IReqUser } from "@dtos/req"; import { PerPageDto, ListResponse } from "@dtos/page"; import { RegexpCountCheckPipe } from "@pipes/regexp-count-check"; import { ParseIntPipe } from "@pipes/parse-int"; +import { ToArrayPipe } from "@pipes/to-array"; import { TokensService } from "@services/tokens"; import { CollectionsService } from "@services/collections"; +import { IGetRegexpsOptions, RegexpsService } from "@services/regexps"; +import { CategoriesService } from "@services/categories"; import * as hasha from "hasha"; import fs = require("fs-extra"); import multer = require("multer"); import { isArray } from "util"; import { CreateValueDto, EditValueDto } from "../values/values.dto"; -import { GoodAttributeParamDto } from "./goods.dto"; +import { GoodAttributeParamDto, UploadQueryDto } from "./goods.dto"; @UseGuards(RolesGuard) @Controller("api/v1/goods") @@ -40,7 +44,9 @@ export class GoodsAdminController { constructor( private readonly tokensSvr: TokensService, - private readonly collectionsSvr: CollectionsService + private readonly collectionsSvr: CollectionsService, + private readonly regexpSvr: RegexpsService, + private readonly categoriesSvr: CategoriesService ) { } private toMd5sum(filepath: string) { @@ -79,13 +85,44 @@ export class GoodsAdminController { return resData; } + private async getCategoriesIds(names: string[]) { + if (names.length === 0) { + return [ ]; + } + const conditions = { + $or: names.reduce((arr: any[], item) => { + arr.push({ name: item }); + return arr; + }, [ ]) + }; + const categories = await CategroiesModel.find(conditions).exec(); + const idSet = new Set(); + for (const category of categories) { + const id = category._id.toString(); + idSet.add(id); + const ids = await this.categoriesSvr.getChildrenIds(id); + for (const id of ids) { + idSet.add(id.toString()); + } + } + return Array.from(idSet); + } + + /** + * 文件处理 + */ private async fileProcess( - file: Express.Multer.File, uploader: string, + obj: { + file: Express.Multer.File, uploader: string, + opt?: IGetRegexpsOptions + }, cb?: (type: "Categories" | "Good", error) => void ) { - const categories = await RegexpModel.discern(file.originalname); + const categories = await this.regexpSvr.discern( + obj.file.originalname, obj.opt + ); if (categories.length !== 1) { - fs.remove(file.path); + fs.remove(obj.file.path); if (cb) { cb("Categories", categories.length); } @@ -93,13 +130,13 @@ export class GoodsAdminController { } let goodObj: IGoods; try { - const md5sum = this.toMd5sum(file.path); - const sha256sum = this.toSha256sum(file.path); + const md5sum = this.toMd5sum(obj.file.path); + const sha256sum = this.toSha256sum(obj.file.path); goodObj = (await GoodsModels.create({ - filename: file.filename, - originname: file.originalname, + filename: obj.file.filename, + originname: obj.file.originalname, category: categories[0]._id, - uploader, md5sum, sha256sum, + uploader: obj.uploader, md5sum, sha256sum, active: true })).toObject(); } catch (error) { @@ -109,8 +146,8 @@ export class GoodsAdminController { return; } const newFilePath = - `${config.paths.upload}/${categories[0]._id}/${file.filename}`; - fs.move(file.path, newFilePath); + `${config.paths.upload}/${categories[0]._id}/${obj.file.filename}`; + fs.move(obj.file.path, newFilePath); return goodObj; } @@ -126,23 +163,32 @@ export class GoodsAdminController { // endregion Swagger Docs public async addGood( @File(new RegexpCountCheckPipe()) file: Express.Multer.File, - @User() user: IReqUser, @Session() session + @User() user: IReqUser, @Session() session, + @Query(new ToArrayPipe()) query: UploadQueryDto ) { const uploaderId = session.loginUserId || await this.tokensSvr.getIdByToken(user.token); + const fileProcessOpts = { + categroies: + await this.getCategoriesIds(query.category || []), + appends: await this.getCategoriesIds(query.append || []) + }; - return await this.fileProcess(file, uploaderId, (type, error) => { - if (type === "Categories") { - if (error === 0) { - throw new BadRequestException("Lost Role for the file"); - } else { - throw new BadRequestException("Much Role for the file"); + return await this.fileProcess( + { file, uploader: uploaderId, opt: fileProcessOpts }, + (type, error) => { + if (type === "Categories") { + if (error === 0) { + throw new BadRequestException("Lost Role for the file"); + } else { + throw new BadRequestException("Much Role for the file"); + } + } + if (type === "Good") { + throw new BadRequestException(error.toString()); } } - if (type === "Good") { - throw new BadRequestException(error.toString()); - } - }); + ); } @Roles("admin", "token") @@ -157,14 +203,22 @@ export class GoodsAdminController { // endregion Swagger Docs public async addGoods( @Files(new RegexpCountCheckPipe()) files: Express.Multer.File[], - @User() user: IReqUser, @Session() session + @User() user: IReqUser, @Session() session, + @Query(new ToArrayPipe()) query: UploadQueryDto ) { const uploaderId = session.loginUserId || await this.tokensSvr.getIdByToken(user.token); + const fileProcessOpts = { + categroies: + await this.getCategoriesIds(query.category || []), + appends: await this.getCategoriesIds(query.append || []) + }; const goods: IGoods[] = [ ]; for (const file of files) { - const goodObj = await this.fileProcess(file, uploaderId); + const goodObj = await this.fileProcess({ + file, uploader: uploaderId, opt: fileProcessOpts + }); if (!goodObj) { fs.remove(file.path); continue; diff --git a/src/modules/goods/goods.dto.ts b/src/modules/goods/goods.dto.ts index a6ea7b1..84b9897 100644 --- a/src/modules/goods/goods.dto.ts +++ b/src/modules/goods/goods.dto.ts @@ -1,6 +1,6 @@ import { IGidDto, IAidDto } from "@dtos/ids"; import { ApiModelProperty } from "@nestjs/swagger"; -import { IsMongoId } from "class-validator"; +import { IsMongoId, IsString, IsOptional } from "class-validator"; import { ObjectId } from "@models/common"; import { IGoods } from "@models/Good"; @@ -19,3 +19,18 @@ export class GoodsDto { @ApiModelProperty({ type: Object, description: "Goods", isArray: true }) public readonly goods: IGoods[]; } + +export class UploadQueryDto { + @ApiModelProperty({ + type: String, description: "Category Name", isArray: true + }) + @IsString({ each: true }) + @IsOptional() + public readonly category?: string[]; + @ApiModelProperty({ + type: String, description: "Append Category Name", isArray: true + }) + @IsString({ each: true }) + @IsOptional() + public readonly append?: string[]; +} diff --git a/src/modules/regexps/regexps.controller.ts b/src/modules/regexps/regexps.controller.ts index 7307892..4eda6ef 100644 --- a/src/modules/regexps/regexps.controller.ts +++ b/src/modules/regexps/regexps.controller.ts @@ -5,7 +5,7 @@ import { import { ApiBearerAuth, ApiUseTags, ApiResponse, ApiOperation, ApiImplicitParam } from "@nestjs/swagger"; -import { Model as RegexpsModel, IRegexp, RegexpDoc } from "@models/Regexp"; +import { RegexpDoc } from "@models/Regexp"; import { NewRegexp, EditRegexpDot, EditRegexpRawDot } from "./regexps.dto"; @@ -14,6 +14,7 @@ import { RolesGuard } from "@guards/roles"; import { PerPageDto, ListResponse } from "@dtos/page"; import { RidDto } from "@dtos/ids"; import { ParseIntPipe } from "@pipes/parse-int"; +import { RegexpsService } from "@services/regexps"; @UseGuards(RolesGuard) @Controller("api/v1/regexps") @@ -23,6 +24,10 @@ import { ParseIntPipe } from "@pipes/parse-int"; // endregion Swagger Docs export class RegexpsAdminController { + constructor( + private readonly regexpsSvr: RegexpsService + ) { } + @Roles("admin") @Get() // region Swagger Docs @@ -35,15 +40,18 @@ export class RegexpsAdminController { // endregion Swagger Docs public async list(@Query(new ParseIntPipe()) query: PerPageDto) { const curPage = query.page || 1; - const totalPages = await RegexpsModel.countRegexps(query.perNum); - const totalCount = await RegexpsModel.countRegexps(); + const totalPages = await this.regexpsSvr.pageCount(query.perNum); + const totalCount = await this.regexpsSvr.count(); - const data = new ListResponse(); + const data = new ListResponse(); data.current = curPage; data.totalPages = totalPages; data.total = totalCount; + if (data.current > data.totalPages) { + data.current = data.totalPages; + } if (totalPages >= curPage) { - data.data = await RegexpsModel.list(query.perNum, query.page); + data.data = await this.regexpsSvr.list(query.perNum, data.current); } return data; } @@ -53,14 +61,7 @@ export class RegexpsAdminController { @HttpCode(HttpStatus.CREATED) @ApiOperation({ title: "Add RegExp" }) public async add(@Body() ctx: NewRegexp) { - let regexp; - try { - regexp = - await RegexpsModel.addRegexp(ctx.name, ctx.value, ctx.link); - } catch (error) { - throw new BadRequestException(error.toString()); - } - return regexp; + return await this.regexpsSvr.create(ctx); } @Roles("admin") @@ -70,9 +71,7 @@ export class RegexpsAdminController { @ApiOperation({ title: "Get RegExp Info" }) // endregion Swagger Docs public getRegexp(@Param() param: RidDto) { - return RegexpsModel.findById(param.rid) - .populate({ path: "link", populate: { path: "pid" } }) - .exec(); + return this.regexpsSvr.getRegexp(param.rid); } @Roles("admin") @@ -86,18 +85,15 @@ export class RegexpsAdminController { if (ctx.name) { data.name = ctx.name; } if (ctx.value) { data.value = ctx.value; } if (ctx.link) { data.link = ctx.link; } + if (ctx.hidden !== null && ctx.hidden !== undefined) { + data.hidden = ctx.hidden; + } if (Object.keys(data).length === 0) { throw new BadRequestException("No Params"); } - try { - const regexp = await RegexpsModel.findByIdAndUpdate( - param.rid, data, { runValidators: true } - ).exec(); - if (!regexp) { - throw new BadRequestException("NonExist RegExp"); - } - } catch (error) { - throw new BadRequestException(error.toString()); + const regexp = await this.regexpsSvr.editById(param.rid, data); + if (!regexp) { + throw new BadRequestException("NonExist RegExp"); } return { statusCode: HttpStatus.OK }; } @@ -125,11 +121,7 @@ export class RegexpsAdminController { }) // endregion Swagger Docs public async deleteByGet(@Param() param: RidDto) { - try { - await RegexpsModel.removeRegexp(param.rid); - } catch (error) { - throw new BadRequestException(error.toString()); - } + await this.regexpsSvr.remove(param.rid); return { statusCode: HttpStatus.OK }; } diff --git a/src/modules/regexps/regexps.dto.ts b/src/modules/regexps/regexps.dto.ts index 6078aaa..43fb6bf 100644 --- a/src/modules/regexps/regexps.dto.ts +++ b/src/modules/regexps/regexps.dto.ts @@ -17,6 +17,7 @@ export class EditRegexpRawDot implements IRegexp { public name?: string; public value?: string; public link?: ObjectId; + public hidden?: boolean; } export class NewRegexp implements INewRegexp { @@ -45,4 +46,8 @@ export class EditRegexpDot implements IRegexp { @IsOptional() @IsMongoId() public readonly link: ObjectId; + @ApiModelPropertyOptional({ type: Boolean }) + @IsOptional() + @IsMongoId() + public readonly hidden: boolean; } diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index f1eeabb..2f61db4 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -367,7 +367,7 @@ export class UsersAdminController { @HttpCode(HttpStatus.OK) @ApiOperation({ title: "Get Usergroup" }) @ApiResponse({ - status: HttpStatus.OK, type: ListResponse + status: HttpStatus.OK, description: "Usergroup List", type: ListResponse }) // endregion Swagger Docs public async getUsergroups( diff --git a/test/api/categroies.e2e.ts b/test/api/categroies.e2e.ts index c1d0f21..54b24cf 100644 --- a/test/api/categroies.e2e.ts +++ b/test/api/categroies.e2e.ts @@ -3,6 +3,7 @@ import faker = require("faker"); import { connect, drop, newUser } from "../helpers/database"; import { init } from "../helpers/server"; +import { login } from "../helpers/database/auth"; describe("Categories E2E Api", () => { @@ -26,17 +27,8 @@ describe("Categories E2E Api", () => { request = await init(); }); - const user = { - name: faker.name.firstName(), - pass: faker.random.words() - }; - step("Login", async () => { - const doc = await newUser(user.name, user.pass); - ids.users.push(doc._id); - await request.post("/api/v1/auth/login") - .send({ - username: user.name, password: user.pass - }).then(); + step("login", async () => { + ids.users.push((await login(request))[0]); }); step("Add Category", async () => { diff --git a/test/api/collections_token.e2e.ts b/test/api/collections_token.e2e.ts index c3ac298..14a3f73 100644 --- a/test/api/collections_token.e2e.ts +++ b/test/api/collections_token.e2e.ts @@ -10,7 +10,7 @@ import { connect, drop, newUser, addCategoryAndRegexp } from "../helpers/database"; import { init } from "../helpers/server"; -import { uploadFiles } from "../helpers/files"; +import { uploadFiles, newFile } from "../helpers/files"; describe("Token to Upload Files Api", () => { @@ -40,17 +40,9 @@ describe("Token to Upload Files Api", () => { const filepaths = [ ]; const prefix = `${faker.random.word()}_`; before(() => { - const folderpath = `${config.paths.tmp}/test`; - if (!fs.existsSync(folderpath)) { - fs.mkdirpSync(folderpath); - } // Generator Files for (let i = 0; i < FILE_COUNST; i++) { - const filepath = `${folderpath}/${prefix}${faker.random.uuid()}`; - filepaths.push(filepath); - fs.writeFileSync(filepath, JSON.stringify({ - data: Math.random() - }), { encoding: "utf-8" }); + filepaths.push(newFile(`${prefix}${faker.random.uuid()}`)); } }); diff --git a/test/api/goods.e2e.ts b/test/api/goods.e2e.ts index d6dc327..9932fdc 100644 --- a/test/api/goods.e2e.ts +++ b/test/api/goods.e2e.ts @@ -2,17 +2,18 @@ import supertest = require("supertest"); import path = require("path"); import faker = require("faker"); -import { connect, drop, addCategoryAndRegexp, newUser } from "../helpers/database"; +import { connect, drop, addCategoryAndRegexp } from "../helpers/database"; import { uploadFile } from "../helpers/files"; import { init } from "../helpers/server"; +import auth = require("../helpers/database/auth"); +import { newName } from "../helpers/utils"; describe("Goods E2E Api", () => { let request: supertest.SuperTest; const user = { - name: faker.name.firstName(), - pass: faker.random.words() + name: newName() }; before(async () => { @@ -35,13 +36,8 @@ describe("Goods E2E Api", () => { request = await init(); }); - step("Login", async () => { - const doc = await newUser(user.name, user.pass); - ids.users.push(doc._id); - const { body: result } = await request.post("/api/v1/auth/login") - .send({ - username: user.name, password: user.pass - }).then(); + before("login", async () => { + ids.users.push((await auth.login(request, user.name))[0]); }); step("Add Category", async () => { diff --git a/test/api/goods_append.e2e.ts b/test/api/goods_append.e2e.ts new file mode 100644 index 0000000..20267ff --- /dev/null +++ b/test/api/goods_append.e2e.ts @@ -0,0 +1,96 @@ +import * as supertest from "supertest"; +import path = require("path"); +import { init } from "../helpers/server"; +import db = require("../helpers/database"); +import auth = require("../helpers/database/auth"); +import goods = require("../helpers/database/goods"); +import regexps = require("../helpers/database/regexps"); +import files = require("../helpers/files"); +import categories = require("../helpers/database/categories"); +import { newName } from "../helpers/utils"; + +describe("Upload Good with Append categories", () => { + + let request: supertest.SuperTest; + + before(async () => { + request = await init(); + }); + + before(() => { + return db.connect(); + }); + + const ids = { + categories: [ ], + regexps: [ ], + users: [ ], + goods: [ ] + }; + after(() => { + return db.drop(ids); + }); + + const filepaths = [ ]; + after(() => { + return files.remove(filepaths); + }); + + let cids = [ ]; + before(async () => { + cids = await categories.addCategories(); + ids.categories.push(...cids); + }); + + before("login", async () => { + ids.users.push((await auth.login(request))[0]); + }); + + step("Add File and its Regexps", async () => { + for (let i = 0; i < 2; i++) { + const filepath = files.newFile(); + filepaths.push(filepath); + const filename = path.basename(filepath); + + ids.regexps.push((await regexps.newRegexp({ + name: newName(), + value: new RegExp(filename).source, + link: ids.categories[6], + hidden: true + }))._id); + ids.regexps.push((await regexps.newRegexp({ + name: newName(), + value: new RegExp(filename).source, + link: ids.categories[7], + hidden: false + }))._id); + } + }); + + step("Upload File Fail", async () => { + const targetIndex = 6; + const targetId = ids.categories[targetIndex]; + const filepath = filepaths[0]; + const filename = path.basename(filepath); + + const { status } = await files.uploadFile( + request, filepath, { query: { + "append": await categories.getNameById(targetId) + }} + ); + status.should.be.eql(400); + // ids.goods.push(await goods.getIdByOriginname(filename)); + }); + + step("Upload File Success", async () => { + const targetIndex = 6; + const targetId = ids.categories[targetIndex]; + const filepath = filepaths[0]; + const filename = path.basename(filepath); + + const { status } = await files.uploadFile(request, filepath); + status.should.be.eql(201); + ids.goods.push(await goods.getIdByOriginname(filename)); + }); + +}); diff --git a/test/api/goods_specified_category.e2e.ts b/test/api/goods_specified_category.e2e.ts new file mode 100644 index 0000000..6272369 --- /dev/null +++ b/test/api/goods_specified_category.e2e.ts @@ -0,0 +1,144 @@ +import * as supertest from "supertest"; +import db = require("../helpers/database"); +import path = require("path"); +import files = require("../helpers/files"); +import auth = require("../helpers/database/auth"); +import categories = require("../helpers/database/categories"); +import { init } from "../helpers/server"; +import { newName } from "../helpers/utils"; +import * as regexps from "../helpers/database/regexps"; +import * as goods from "../helpers/database/goods"; + +describe("Upload Good with specified categories", () => { + + let request: supertest.SuperTest; + + before(async () => { + request = await init(); + }); + + before(() => { + return db.connect(); + }); + + const ids = { + categories: [ ], + regexps: [ ], + users: [ ], + goods: [ ] + }; + after(() => { + return db.drop(ids); + }); + + const filepaths = [ ]; + after(() => { + return files.remove(filepaths); + }); + + let cids = [ ]; + before(async () => { + cids = await categories.addCategories(); + ids.categories.push(...cids); + }); + + before("login", async () => { + ids.users.push((await auth.login(request))[0]); + }); + + const targetIndex = 6; + + step("Set Category and Regexp", async () => { + const targetId = ids.categories[targetIndex]; + + for (let i = 0; i < 3; i++) { + const filepath = files.newFile(); + filepaths.push(filepath); + const filename = path.basename(filepath); + + const regDoc = await regexps.newRegexp({ + name: newName(), + value: new RegExp(filename).source, + link: targetId, + hidden: true + }); + ids.regexps.push(regDoc._id); + } + }); + + step("Default Upload Way Fail", async () => { + const targetId = ids.categories[targetIndex]; + const filepath = filepaths[0]; + + const { status } = await files.uploadFile(request, filepath); + status.should.be.eql(400); + }); + + step("Upload File Success with Specified Category", async () => { + const targetId = ids.categories[targetIndex]; + const filepath = filepaths[0]; + const filename = path.basename(filepath); + + const { status } = await files.uploadFile( + request, filepath, { query: { + "category": await categories.getNameById(targetId) + }} + ); + status.should.be.eql(201); + ids.goods.push(await goods.getIdByOriginname(filename)); + }); + + step("Upload File Success with Specified Parent Category", async () => { + const targetId = ids.categories[targetIndex - 1]; + const filepath = filepaths[1]; + const filename = path.basename(filepath); + + const { status } = await files.uploadFile( + request, filepath, { query: { + "category": await categories.getNameById(targetId) + }} + ); + status.should.be.eql(201); + ids.goods.push(await goods.getIdByOriginname(filename)); + }); + + step("Upload File Fail with Specified Child Category", async () => { + const targetId = ids.categories[8]; + const filepath = filepaths[1]; + const filename = path.basename(filepath); + + const { status } = await files.uploadFile( + request, filepath, { query: { + "category": await categories.getNameById(targetId) + }} + ); + status.should.be.eql(400); + }); + + step("Upload File Fail with Specified Child Category", async () => { + const targetId = ids.categories[8]; + const filepath = filepaths[2]; + const filename = path.basename(filepath); + + const { status } = await files.uploadFile( + request, filepath, { query: { + "category": await categories.getNameById(targetId) + }} + ); + status.should.be.eql(400); + }); + + step("Upload File Fail with Specified Brother Category", async () => { + const targetId = ids.categories[7]; + const filepath = filepaths[2]; + const filename = path.basename(filepath); + + const { status } = await files.uploadFile( + request, filepath, { query: { + "category": await categories.getNameById(targetId) + }} + ); + status.should.be.eql(400); + }); + +}); diff --git a/test/api/regexps.e2e.ts b/test/api/regexps.e2e.ts index 1d854ce..f2ab191 100644 --- a/test/api/regexps.e2e.ts +++ b/test/api/regexps.e2e.ts @@ -2,14 +2,18 @@ import supertest = require("supertest"); import faker = require("faker"); import { Model as RegexpsModel } from "@models/Regexp"; +import { RegexpsService } from "@services/regexps"; import { connect, drop, newUser } from "../helpers/database"; import { init } from "../helpers/server"; import { sleep } from "../helpers/utils"; +import { newName } from "./../helpers/utils"; +import auth = require("../helpers/database/auth"); describe("Regexp E2E Api", () => { const URL = "/api/v1/regexps"; let request: supertest.SuperTest; + let regexpsSvr: RegexpsService; before(() => { return connect(); @@ -28,34 +32,30 @@ describe("Regexp E2E Api", () => { request = await init(); }); - const user = { - name: faker.name.firstName(), - pass: faker.random.words() - }; - step("Login", async () => { - const doc = await newUser(user.name, user.pass); - ids.users.push(doc._id); - await request.post("/api/v1/auth/login") - .send({ - username: user.name, password: user.pass - }).then(); + before(() => { + regexpsSvr = new RegexpsService(); + }); + + before("login", async () => { + ids.users.push((await auth.login(request))[0]); }); step("Add 3 Regexps", async () => { const items = [{ - name: faker.random.word(), + name: newName(), value: "^list.0" }, { - name: faker.random.word(), + name: newName(), value: "^list.1" }, { - name: faker.random.word(), + name: newName(), value: "^list.2" }]; for (const item of items) { - const doc = await RegexpsModel.addRegexp( - item.name + Date.now(), item.value + Date.now() - ); + const doc = await regexpsSvr.create({ + name: item.name, + value: item.value + Date.now() + }); ids.regexps.push(doc._id); await sleep(100); } @@ -87,7 +87,7 @@ describe("Regexp E2E Api", () => { }); const data = { - name: faker.random.word(), + name: newName(), value: "^abc.ccd" }; step("Add Regexp", async () => { @@ -130,7 +130,7 @@ describe("Regexp E2E Api", () => { step("Modify Name", async () => { const raw = await RegexpsModel.addRegexp( - faker.random.word(), "^modify" + newName(), new RegExp(newName()).source ); ids.regexps.push(raw._id); const { body: result, status: status } = @@ -142,21 +142,22 @@ describe("Regexp E2E Api", () => { }); step("Modify Value", async () => { + const oldRegexp = new RegExp(newName()); + const newRegexp = new RegExp(newName()); const raw = await RegexpsModel.addRegexp( - faker.random.word(), "modify value" + newName(), oldRegexp.source ); ids.regexps.push(raw._id); - const { body: result, status: status } = - await request.post(`${URL}/${raw._id}`) - .send({ value: "^adb.ccd$" }).then(); + const { body: result, status } = await request.post(`${URL}/${raw._id}`) + .send({ value: newRegexp.source }).then(); status.should.be.eql(200); - const regexp = await RegexpsModel.findById(raw._id).exec(); - regexp.toObject().should.have.property("value", "^adb.ccd$"); + const regexpDoc = await RegexpsModel.findById(raw._id).exec(); + regexpDoc.toObject().should.have.property("value", newRegexp.source); }); step("Modify Exist Name", async () => { const raw = - await RegexpsModel.addRegexp(faker.random.word(), "^abc.ccd$"); + await RegexpsModel.addRegexp(newName(), "^abc.ccd$"); ids.regexps.push(raw._id); const { body: result, status: status } = await request.post(`${URL}/${raw._id}`) @@ -179,7 +180,7 @@ describe("Regexp E2E Api", () => { step("Modify with Empty Param", async () => { const raw = await RegexpsModel.addRegexp( - faker.random.word(), "^empty.param" + newName(), "^empty.param" ); ids.regexps.push(raw._id); const { body: result, status: status } = @@ -189,7 +190,7 @@ describe("Regexp E2E Api", () => { step("Delete Regexp By GET", async () => { const raw = await RegexpsModel.addRegexp( - faker.random.word(), "^get.delete" + newName(), "^get.delete" ); ids.regexps.push(raw._id); // Delete @@ -202,7 +203,7 @@ describe("Regexp E2E Api", () => { step("Delete Regexp By DELETE", async () => { const raw = await RegexpsModel.addRegexp( - faker.random.word(), "^delete.delete" + newName(), "^delete.delete" ); ids.regexps.push(raw._id); // Delete diff --git a/test/helpers/database.ts b/test/helpers/database.ts index 3a1cfdf..6292e1e 100644 --- a/test/helpers/database.ts +++ b/test/helpers/database.ts @@ -1,4 +1,3 @@ -import faker = require("faker"); import { config } from "@utils/config"; import { ObjectId } from "@models/common"; @@ -14,6 +13,9 @@ import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { connectDatabase } from "../../src/modules/database/database.providers"; import { newUser as newUserFn } from "./database/user"; +import * as regexps from "./database/regexps"; +import * as categories from "./database/categories"; +import { newName } from "./utils"; config.db.database = "storebox-test"; @@ -65,20 +67,21 @@ export const drop = async (ids?: IIds) => { } }; -export const addCategoryAndRegexp = async (regexp: RegExp) => { - const category = await CategoriesModel.create({ - name: faker.name.findName() +export const addCategoryAndRegexp = async (regexp: RegExp, pid?: ObjectId) => { + const category = await newCategory({ + name: newName(), + pid: pid }); - const reg = await newRegexp(faker.random.word(), regexp, category._id); - return [category, reg]; + const reg = await newRegexp(newName(), regexp, category._id); + return [ category, reg ]; }; export const newUser = newUserFn; -export const newRegexp = (name: string, value: RegExp, link?) => { - return RegexpsModel.addRegexp(name, value.source, link); +export const newRegexp = (name: string, value: RegExp, link?: ObjectId) => { + return regexps.newRegexp({ + name, value: value.source, link, hidden: false + }); }; -export const newCategory = (obj: object) => { - return CategoriesModel.create(obj) as Promise; -}; +export const newCategory = categories.newCategory; diff --git a/test/helpers/database/auth.ts b/test/helpers/database/auth.ts new file mode 100644 index 0000000..05a987e --- /dev/null +++ b/test/helpers/database/auth.ts @@ -0,0 +1,16 @@ +import supertest = require("supertest"); +import faker = require("faker"); +import { newUser } from "./user"; + +export const login = async ( + request: supertest.SuperTest, + username = `${faker.name.firstName()}${Math.random()}`, + password = `${faker.random.words()}${Math.random()}`, +) => { + const doc = await newUser(username, password); + await request.post("/api/v1/auth/login") + .send({ + username, password + }).then(); + return [ doc._id ]; +}; diff --git a/test/helpers/database/categories.ts b/test/helpers/database/categories.ts new file mode 100644 index 0000000..32f67aa --- /dev/null +++ b/test/helpers/database/categories.ts @@ -0,0 +1,52 @@ +import { ObjectId } from "@models/common"; +import { Model as CategoriesModel, CategoryDoc } from "@models/Categroy"; +import faker = require("faker"); + +export const newCategory = (obj: object) => { + return CategoriesModel.create(obj) as Promise; +}; + +/** + * Add 11 Categories + * ``` + * - 1 - 4 + * | + * - 0 - 2 - 5 - 6 - 8 + * | | | + * pid -| - 3 - 7 + * | + * - 9 - 10 + * ``` + * @param pid Parent Category ID + * @returns Categories' ID + */ +export const addCategories = async (pid?: ObjectId) => { + const cids: ObjectId[ ] = []; + // Create 11 Categories + for (let i = 0; i < 11; i++) { + const result = await CategoriesModel.create({ + name: faker.name.firstName() + i + }); + cids.push(result._id); + } + // [parent, child] + const initGroups = [ + [0, 1], [0, 2], [0, 3], + [1, 4], [2, 5], + [5, 6], [5, 7], [6, 8], + [9, 10] + ]; + for (const set of initGroups) { + await CategoriesModel.moveCategory(cids[set[1]], cids[set[0]]); + } + if (pid) { + await CategoriesModel.moveCategory(cids[9], pid); + await CategoriesModel.moveCategory(cids[0], pid); + } + return cids; +}; + +export const getNameById = async (id: ObjectId) => { + const doc = await CategoriesModel.findById(id).exec(); + return doc.toObject().name || undefined; +}; diff --git a/test/helpers/database/goods.ts b/test/helpers/database/goods.ts new file mode 100644 index 0000000..292b776 --- /dev/null +++ b/test/helpers/database/goods.ts @@ -0,0 +1,5 @@ +import { Model as GoodsModels } from "@models/Good"; + +export const getIdByOriginname = async (name: string) => { + return (await GoodsModels.findOne({ originname: name }).exec())._id; +}; diff --git a/test/helpers/database/regexps.ts b/test/helpers/database/regexps.ts new file mode 100644 index 0000000..1174b2f --- /dev/null +++ b/test/helpers/database/regexps.ts @@ -0,0 +1,16 @@ +import { IRegexpDoc } from "@models/Regexp"; +import { RegexpsService } from "@services/regexps"; + +let regexpSvr: RegexpsService; + +const init = () => { + if (!regexpSvr) { + regexpSvr = new RegexpsService(); + } + return regexpSvr; +}; + +export const newRegexp = (obj: IRegexpDoc) => { + init(); + return regexpSvr.create(obj); +}; diff --git a/test/helpers/database/usergroups.ts b/test/helpers/database/usergroups.ts index 2e5a39f..e779e6e 100644 --- a/test/helpers/database/usergroups.ts +++ b/test/helpers/database/usergroups.ts @@ -2,8 +2,7 @@ import faker = require("faker"); import { ObjectId } from "@models/common"; import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { UsergroupsService } from "@services/usergroups"; - -/// +import { newName } from "../utils"; export const getLinkIdsByUserId = async (uid: ObjectId) => { return (await UserUsergroupsModel.find({ user: uid }).exec()) @@ -20,7 +19,7 @@ export const getLinkIdsByUsergroupId = async (gid: ObjectId) => { }; export const newUsergroup = async ( - name = `${faker.random.word}${Math.random()}`, uid?: ObjectId + name = newName(), uid?: ObjectId ) => { const svr = new UsergroupsService(); const group = await svr.add({ name }); diff --git a/test/helpers/files.ts b/test/helpers/files.ts index f3bac33..c6123e1 100644 --- a/test/helpers/files.ts +++ b/test/helpers/files.ts @@ -1,19 +1,86 @@ import supertest = require("supertest"); +import { config } from "@utils/config"; +import { isArray } from "util"; +import faker = require("faker"); +import fs = require("fs-extra"); + +interface IUploadFileOptions { + query?: { + [key: string]: string + }; +} + +/* tslint:disable:no-empty-interface */ +interface IUploadFilesOptions extends IUploadFileOptions { } + +const addQuery = (url: string, opts: IUploadFileOptions) => { + if (opts.query && Object.keys(opts.query).length > 0) { + const query = [ ]; + for (const key of Object.keys(opts.query)) { + const q = opts.query[key].split(/\s+/).map((val) => { + return `${key}=${val}`; + }).join("&"); + query.push(q); + } + url += `?${query.join("&")}`; + } + return url; +}; export const uploadFile = ( - request: supertest.SuperTest, filepath: string + request: supertest.SuperTest, filepath: string, + opts: IUploadFileOptions = { } ) => { - return request.post("/api/v1/goods") - .attach("file", filepath) + let url = "/api/v1/goods"; + url = addQuery(url, opts); + + return request.post(url).attach("file", filepath) .then(); }; export const uploadFiles = ( - request: supertest.SuperTest, filepaths: string[] + request: supertest.SuperTest, filepaths: string[], + opts: IUploadFilesOptions = { } ) => { - let req = request.post("/api/v1/goods/collections"); + let url = "/api/v1/goods/collections"; + url = addQuery(url, opts); + + let req = request.post(url); filepaths.forEach((filepath) => { req = req.attach("files", filepath); }); return req.then(); }; + +const newFilename = () => { + const name = `${faker.random.word()}_${faker.random.uuid()}`; + const random = Math.random(); + const randomStr = `${random}`.replace(/(^0|\D)/g, ""); + return `${randomStr}-${name}`; +}; + +/** + * Generate File + * @param filename + * @returns filepath + */ +export const newFile = (filename = newFilename()) => { + const folderpath = `${config.paths.tmp}/test`; + if (!fs.existsSync(folderpath)) { + fs.mkdirpSync(folderpath); + } + const filepath = `${folderpath}/${filename}`; + fs.writeFileSync(filepath, JSON.stringify({ + data: Math.random() + }), { encoding: "utf-8" }); + return filepath; +}; + +export const remove = (filepaths: string[] | string) => { + if (!isArray(filepaths)) { + filepaths = [ filepaths ]; + } + return Promise.all(filepaths.map((filepath) => { + return fs.unlink(filepath); + })); +}; diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts index 5ccfb06..3871ddd 100644 --- a/test/helpers/utils.ts +++ b/test/helpers/utils.ts @@ -1,5 +1,13 @@ +import faker = require("faker"); + export const sleep = (ms: number) => { return new Promise((reslove) => { setTimeout(reslove, ms); }); }; + +export const newName = (prefix = "") => { + const randomStr = `${Math.random()}`.replace(/(^0|\D)/g, ""); + const name = `${faker.random.word()}${randomStr}${Date.now()}`; + return `${prefix}${name}`; +}; diff --git a/test/models/Categroies.spec.ts b/test/models/Categroies.spec.ts index 001b116..52e9599 100644 --- a/test/models/Categroies.spec.ts +++ b/test/models/Categroies.spec.ts @@ -2,6 +2,7 @@ import { Model as CategoriesModel } from "@models/Categroy"; import { Model as ValuesModel } from "@models/Value"; import db = require("../helpers/database"); import faker = require("faker"); +import { addCategories } from "../helpers/database/categories"; describe("Category Model", () => { @@ -33,24 +34,8 @@ describe("Category Model", () => { let cids = [ ]; before(async () => { - cids = []; - for (let i = 0; i < 10; i++) { - const result = await CategoriesModel.create({ - name: faker.name.firstName() + i - }); - cids.push(result._id); - ids.categories.push(result._id); - } - // [parent, child] - const initGroups = [ - [0, 1], [0, 2], [0, 3], - [1, 4], [2, 5], - [5, 6], [6, 7], - [8, 9] - ]; - for (const set of initGroups) { - await CategoriesModel.moveCategory(cids[set[1]], cids[set[0]]); - } + cids = await addCategories(); + ids.categories.push(...cids); }); it("# 0", async () => { diff --git a/test/models/regexp.spec.ts b/test/models/regexp.spec.ts index 9445703..365680e 100644 --- a/test/models/regexp.spec.ts +++ b/test/models/regexp.spec.ts @@ -47,46 +47,48 @@ describe("RegExp Model", () => { should(reg).be.a.null(); }); - it("Link One Category And Undo", async () => { - const md5sum = md5(Date.now() + ""); - let reg: RegexpDoc; + it.skip("Link One Category And Undo", async () => { + // const md5sum = md5(Date.now() + ""); + // let reg: RegexpDoc; - reg = await RegexpsModel.addRegexp(md5sum, /[\da-fA-F]/.source); - ids.regexps.push(reg._id); - reg = await RegexpsModel.link(reg._id, Category._id); - reg = await RegexpsModel.link(reg._id, false); - reg = await RegexpsModel.findById(reg._id).exec(); + // reg = await RegexpsModel.addRegexp(md5sum, /[\da-fA-F]/.source); + // ids.regexps.push(reg._id); + // reg = await RegexpsModel.link(reg._id, Category._id); + // reg = await RegexpsModel.link(reg._id, false); + // reg = await RegexpsModel.findById(reg._id).exec(); - should(reg.toObject().link).be.a.undefined(); + // should(reg.toObject().link).be.a.undefined(); }); - it("Discern from No link Regexp", async () => { - const md5sum = md5(Date.now() + ""); - const regs = [ - await RegexpsModel.addRegexp(`${md5sum}1`, /[\da-fA-F]/.source), - await RegexpsModel.addRegexp(`${md5sum}2`, /[\da-fA-F]{16}/.source), - await RegexpsModel.addRegexp(`${md5sum}3`, /[\da-fA-F]{8}/.source) - ]; - for (const reg of regs) { - ids.regexps.push(reg._id); - } - const list = await RegexpsModel.discern(md5sum); - list.should.be.length(0); + // Discern Method move to Service + it.skip("Discern from No link Regexp", async () => { + // const md5sum = md5(Date.now() + ""); + // const regs = [ + // await RegexpsModel.addRegexp(`${md5sum}1`, /[\da-fA-F]/.source), + // await RegexpsModel.addRegexp(`${md5sum}2`, /[\da-fA-F]{16}/.source), + // await RegexpsModel.addRegexp(`${md5sum}3`, /[\da-fA-F]{8}/.source) + // ]; + // for (const reg of regs) { + // ids.regexps.push(reg._id); + // } + // const list = await RegexpsModel.discern(md5sum); + // list.should.be.length(0); }); - it("Discern from linked Regexps", async () => { - const md5sum = md5(Date.now() + ""); - const regs = [ - await RegexpsModel.addRegexp(`${md5sum}1`, /[\da-fA-F]/.source), - await RegexpsModel.addRegexp(`${md5sum}2`, /[\da-fA-F]{16}/.source), - await RegexpsModel.addRegexp(`${md5sum}3`, /[\da-fA-F]{8}/.source) - ]; - for (const reg of regs) { - ids.regexps.push(reg._id); - await RegexpsModel.link(reg._id, Category._id); - } - const list = await RegexpsModel.discern(md5sum); - list.should.be.length(3); + // Discern Method move to Service + it.skip("Discern from linked Regexps", async () => { + // const md5sum = md5(Date.now() + ""); + // const regs = [ + // await RegexpsModel.addRegexp(`${md5sum}1`, /[\da-fA-F]/.source), + // await RegexpsModel.addRegexp(`${md5sum}2`, /[\da-fA-F]{16}/.source), + // await RegexpsModel.addRegexp(`${md5sum}3`, /[\da-fA-F]{8}/.source) + // ]; + // for (const reg of regs) { + // ids.regexps.push(reg._id); + // await RegexpsModel.link(reg._id, Category._id); + // } + // const list = await RegexpsModel.discern(md5sum); + // list.should.be.length(3); }); });