From 414409533e9bc3ce73e42455763f1902a776f24b Mon Sep 17 00:00:00 2001 From: Arylo Date: Wed, 28 Feb 2018 15:05:24 +0800 Subject: [PATCH 1/5] Add Usergroup Service --- TODOLIST.md | 7 +- src/models/User-Usergroup.ts | 70 ++++++++++++ src/models/Usergroup.ts | 36 ++++++ .../common/services/usergroups.service.ts | 105 ++++++++++++++++++ src/modules/database/database.providers.ts | 29 ++++- 5 files changed, 238 insertions(+), 9 deletions(-) create mode 100644 src/models/User-Usergroup.ts create mode 100644 src/models/Usergroup.ts create mode 100644 src/modules/common/services/usergroups.service.ts diff --git a/TODOLIST.md b/TODOLIST.md index ca2065e..a5fff82 100644 --- a/TODOLIST.md +++ b/TODOLIST.md @@ -1,6 +1,7 @@ # TODO LIST -- [ ] 用户组权限 +- [ ] 用户组 + - [ ] 权限 - [ ] 默认用户组 - [ ] 上传到指定Categroy - [ ] 整顿collectin info的goods 列表 @@ -9,6 +10,6 @@ - [ ] 接入统计 - [ ] 配置文件写入初始化用户账号密码 - [ ] 接入AuthBox -- [ ] 缓存整理 - [ ] Redis 接入 - - [x] 支持Session \ No newline at end of file + - [x] 支持Session + - [ ] 缓存整理 \ No newline at end of file diff --git a/src/models/User-Usergroup.ts b/src/models/User-Usergroup.ts new file mode 100644 index 0000000..8d364b9 --- /dev/null +++ b/src/models/User-Usergroup.ts @@ -0,0 +1,70 @@ +import { model, SchemaDefinition, Model as M, SchemaTypes } from "mongoose"; +import { FLAG as UF, IUser } from "@models/User"; +import { FLAG as GF, IUsergroups } from "@models/Usergroup"; +import { ObjectId } from "@models/common"; +import { Base, IDoc, IDocRaw } from "./common"; + +const Definition: SchemaDefinition = { + user: { + type: SchemaTypes.ObjectId, + ref: UF, + index: true, + required: true + }, + usergroup: { + type: SchemaTypes.ObjectId, + ref: GF, + index: true, + required: true + } +}; + +/** + * User-Usergroup Model FLAG + */ +export const FLAG = "user-usergroups"; + +/** + * User-Usergroup Doc Interface + */ +export interface IUserUsergroups extends IDocRaw { + user: ObjectId | IUser; + usergroup: ObjectId | IUsergroups; +} + +/** + * User-Usergroup Raw Doc Interface + */ +export interface IUserUsergroupsRaw extends IUserUsergroups { + user: IUser; + usergroup: IUsergroups; +} + +export type UserUsergroupDoc = IDoc; + +const UserUsergroupsSchema = new Base(Definition).createSchema(); + +// region validators + +UserUsergroupsSchema.path("user").validate({ + isAsync: true, + validator: async function userIdModifyValidator(val, respond) { + if (!this.isNew) { + const id = this.getQuery()._id; + const cur = await Model.findById(id).exec(); + if (cur.toObject().user === val) { + return respond(true); + } + } + const result = await Model.findOne({ user: val }).exec(); + return respond(!result); + }, + message: "The User ID is existed" +}); + +// endregion validators + +/** + * User-Usergroup Model + */ +export const Model: M = model(FLAG, UserUsergroupsSchema); diff --git a/src/models/Usergroup.ts b/src/models/Usergroup.ts new file mode 100644 index 0000000..fe1a7c6 --- /dev/null +++ b/src/models/Usergroup.ts @@ -0,0 +1,36 @@ +import { model, SchemaDefinition, Model as M, SchemaTypes } from "mongoose"; +import { FLAG as UF, IUser } from "@models/User"; +import { ObjectId } from "@models/common"; +import { Base, IDoc, IDocRaw } from "./common"; + +const Definition: SchemaDefinition = { + name: { type: String, required: true } +}; + +export const FLAG = "usergroups"; + +export interface IUsergroups extends IDocRaw { + name: string; +} + +export type UsergroupDoc = IDoc; + +const UsergroupsSchema = new Base(Definition).createSchema(); + +UsergroupsSchema.path("name").validate({ + isAsync: true, + validator: async function nameExistValidator(val, respond) { + if (!this.isNew) { + const id = this.getQuery()._id; + const ug = await Model.findById(id).exec(); + if (ug.toObject().user === val) { + return respond(true); + } + } + const ug = await Model.findOne({ name: val }).exec(); + return respond(!ug); + }, + message: "The Name is exist" +}); + +export const Model: M = model(FLAG, UsergroupsSchema); diff --git a/src/modules/common/services/usergroups.service.ts b/src/modules/common/services/usergroups.service.ts new file mode 100644 index 0000000..16d2839 --- /dev/null +++ b/src/modules/common/services/usergroups.service.ts @@ -0,0 +1,105 @@ +import { Component, BadRequestException } from "@nestjs/common"; +import { Model as UsergroupsModel } from "@models/Usergroup"; +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; +import { ObjectId } from "@models/common"; +import { DEF_PER_COUNT, IPerPage } from "@dtos/page"; + +@Component() +export class UsergroupsService { + + private DEF_PER_OBJ: IPerPage = { + perNum: DEF_PER_COUNT, + page: 1 + }; + + public async add(obj: object) { + try { + return await UsergroupsModel.create(obj); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + public async edit(id: ObjectId, obj: object) { + try { + return await UsergroupsModel.update( + { _id: id }, obj, { runValidators: true, context: "query" } + ).exec(); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + public usersCount(gid: ObjectId) { + return UserUsergroupsModel.count({ usergroup: gid }).exec(); + } + + public async usersCountPage(id: ObjectId, perNum = DEF_PER_COUNT) { + const total = await this.usersCount(id); + return Math.ceil(total / perNum); + } + + public getGroup(gid: ObjectId) { + return UsergroupsModel.findById(gid).exec(); + } + + public async getGroupUsers( + gid: ObjectId, pageObj: IPerPage = this.DEF_PER_OBJ + ) { + const perNum = pageObj.perNum; + const page = pageObj.page; + return (await UserUsergroupsModel.find({ usergroup: gid }) + .skip((page - 1) * perNum).limit(perNum) + .populate("user").exec() + ).map((item) => { + return item.toObject().user; + }); + } + + public count() { + return UsergroupsModel.count({ }).exec(); + } + + public async countPage(perNum = DEF_PER_COUNT) { + const total = await this.count(); + return Math.ceil(total / perNum); + } + + public list(pageObj: IPerPage = this.DEF_PER_OBJ) { + const perNum = pageObj.perNum; + const page = pageObj.page; + return UsergroupsModel.find({ }) + .skip((page - 1) * perNum).limit(perNum) + .sort({ createdAt: -1 }) + .exec(); + } + + public async remove(id: ObjectId) { + try { + return await UsergroupsModel.findByIdAndRemove(id).exec(); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + public async addUserToGroup(id: ObjectId, uid: ObjectId) { + try { + await UserUsergroupsModel.create({ + user: uid, usergroup: id + }); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + public async removeUserToGroup(id: ObjectId, uid: ObjectId) { + try { + await UserUsergroupsModel.findOneAndRemove({ + user: uid, usergroup: id + }).exec(); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + +} diff --git a/src/modules/database/database.providers.ts b/src/modules/database/database.providers.ts index d495479..55cb117 100644 --- a/src/modules/database/database.providers.ts +++ b/src/modules/database/database.providers.ts @@ -2,6 +2,8 @@ import * as mongoose from "mongoose"; import { isArray } from "util"; import { config } from "@utils/config"; import { Model as UsersModel } from "@models/User"; +import { Model as UsergroupsModel } from "@models/Usergroup"; +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { systemLogger } from "../common/helper/log"; const getDatabaseUrl = () => { @@ -21,21 +23,36 @@ export const connectDatabase = () => { return new Promise((resolve, reject) => { const connection = mongoose.connect(getDatabaseUrl(), { useMongoClient: true, - }, (err) => { + }, async (err) => { if (err) { return reject(err); } systemLogger.info("Connected Database."); - UsersModel.count({ }).exec().then((num) => { - if (num === 0) { - return UsersModel.addUser("root", "admin"); - } - }); + await injectData(); return resolve(connection); }); }); }; +export const injectData = async () => { + let num = await UsersModel.count({ }).exec(); + if (num === 0) { + return UsersModel.addUser("root", "admin"); + } + num = await UsergroupsModel.count({ }).exec(); + if (num === 0) { + const group = await UsergroupsModel.create({ name: "admin" }); + const conditions = (await UsersModel.find({ }).exec()) + .map((item) => { + return { + user: item._id, + usergroup: group._id + }; + }); + await UserUsergroupsModel.create(conditions); + } +}; + export const databaseProviders = [ { provide: "DbConnectionToken", From e74ea8b6fc2b1db0b71feaaeae6a225ab5ec877c Mon Sep 17 00:00:00 2001 From: Arylo Date: Thu, 1 Mar 2018 14:58:34 +0800 Subject: [PATCH 2/5] Add Usergroup apis --- src/modules/common/dtos/ids.dto.ts | 6 + .../common/services/usergroups.service.ts | 19 ++- src/modules/common/services/users.service.ts | 12 ++ src/modules/controllers.module.ts | 12 +- .../usergroups/usergroups.controller.ts | 130 ++++++++++++++++++ src/modules/usergroups/usergroups.dto.ts | 15 ++ src/modules/users/users.controller.ts | 38 ++++- src/modules/users/users.dto.ts | 10 ++ 8 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 src/modules/usergroups/usergroups.controller.ts create mode 100644 src/modules/usergroups/usergroups.dto.ts diff --git a/src/modules/common/dtos/ids.dto.ts b/src/modules/common/dtos/ids.dto.ts index ce69680..02ee5ff 100644 --- a/src/modules/common/dtos/ids.dto.ts +++ b/src/modules/common/dtos/ids.dto.ts @@ -79,6 +79,12 @@ export class GidDto implements IGidDto { public readonly gid: ObjectId; } +export class UGidDto implements IGidDto { + @ApiModelProperty({ type: String, description: "Usergroup ID" }) + @IsMongoId() + public readonly gid: ObjectId; +} + export interface IUidDto { /** * User MongoID diff --git a/src/modules/common/services/usergroups.service.ts b/src/modules/common/services/usergroups.service.ts index 16d2839..78c20e5 100644 --- a/src/modules/common/services/usergroups.service.ts +++ b/src/modules/common/services/usergroups.service.ts @@ -74,28 +74,35 @@ export class UsergroupsService { .exec(); } - public async remove(id: ObjectId) { + public async remove(gid: ObjectId) { + if ((await this.count()) === 1) { + throw new BadRequestException("Nnn delete unique group"); + } try { - return await UsergroupsModel.findByIdAndRemove(id).exec(); + return await UsergroupsModel.findByIdAndRemove(gid).exec(); } catch (error) { throw new BadRequestException(error.toString()); } } - public async addUserToGroup(id: ObjectId, uid: ObjectId) { + public async addUserToGroup(gid: ObjectId, uid: ObjectId) { + // TODO User Check + // TODO Usergroup Check try { await UserUsergroupsModel.create({ - user: uid, usergroup: id + user: uid, usergroup: gid }); } catch (error) { throw new BadRequestException(error.toString()); } } - public async removeUserToGroup(id: ObjectId, uid: ObjectId) { + public async removeUserFromGroup(gid: ObjectId, uid: ObjectId) { + // TODO User Check + // TODO Usergroup Check try { await UserUsergroupsModel.findOneAndRemove({ - user: uid, usergroup: id + user: uid, usergroup: gid }).exec(); } catch (error) { throw new BadRequestException(error.toString()); diff --git a/src/modules/common/services/users.service.ts b/src/modules/common/services/users.service.ts index d9d1c1d..7b247b5 100644 --- a/src/modules/common/services/users.service.ts +++ b/src/modules/common/services/users.service.ts @@ -1,10 +1,22 @@ import { Component, BadRequestException } from "@nestjs/common"; import { ObjectId } from "@models/common"; import { Model as UsersModel, UserDoc } from "@models/User"; +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; +import { IUsergroups } from "@models/Usergroup"; @Component() export class UsersService { + public async getUsergroup(uid: ObjectId) { + const group = await UserUsergroupsModel + .findOne({ user: uid }).populate("usergroup") + .exec(); + if (!group) { + return group as undefined; + } + return group.toObject().usergroup as IUsergroups; + } + /** * 修改`User`属性, 除了`username` * @param id User ID diff --git a/src/modules/controllers.module.ts b/src/modules/controllers.module.ts index fd4cf44..b20dcd0 100644 --- a/src/modules/controllers.module.ts +++ b/src/modules/controllers.module.ts @@ -14,6 +14,7 @@ import { CollectionsAdminController } from "./collections/collections.admin.controller"; import { TokensAdminController } from "./tokens/tokens.controller"; +import { UsergroupsAdminController } from "./usergroups/usergroups.controller"; // endregion Controllers // region Middlewares @@ -33,19 +34,26 @@ import { import { CollectionsService } from "@services/collections"; import { UsersService } from "@services/users"; import { TokensService } from "@services/tokens"; +import { UsergroupsService } from "@services/usergroups"; // endregion Services export const controllers = [ FilesController, GoodsController, - UsersAdminController, AuthAdminController, RegexpsAdminController, + UsersAdminController, AuthAdminController, + UsergroupsAdminController, + RegexpsAdminController, CategoriesAdminController, GoodsAdminController, TokensAdminController, CollectionsController, CollectionsAdminController ]; +export const services = [ + CollectionsService, TokensService, UsersService, UsergroupsService +]; + @Module({ controllers, - components: [ CollectionsService, TokensService, UsersService ] + components: [ ...services ] }) export class ControllersModule { private uploadFileMethod = { diff --git a/src/modules/usergroups/usergroups.controller.ts b/src/modules/usergroups/usergroups.controller.ts new file mode 100644 index 0000000..6ff40d2 --- /dev/null +++ b/src/modules/usergroups/usergroups.controller.ts @@ -0,0 +1,130 @@ +import { UseGuards, Controller, Get, Query, HttpStatus, HttpCode, Post, Body, Param, Delete } from "@nestjs/common"; +import { ApiUseTags, ApiOperation, ApiResponse } from "@nestjs/swagger"; +import { RolesGuard } from "@guards/roles"; +import { UsergroupsService } from "@services/usergroups"; +import { Roles } from "@decorators/roles"; +import { ParseIntPipe } from "@pipes/parse-int"; +import { PerPageDto, ListResponse, DEF_PER_COUNT } from "@dtos/page"; +import { UGidDto } from "@dtos/ids"; + +import { AddUsergroupDto, EditUsergroupDto } from "./usergroups.dto"; + +@UseGuards(RolesGuard) +@ApiUseTags("User Groups") +@Controller("api/v1/usergroups") +export class UsergroupsAdminController { + + constructor(private readonly ugSvr: UsergroupsService) { } + + @Roles("admin") + @Get() + // region Swagger Docs + @HttpCode(HttpStatus.OK) + @ApiOperation({ title: "Get Usergroup List" }) + @ApiResponse({ + status: HttpStatus.OK, description: "Usergroup List", + type: ListResponse + }) + // endregion Swagger Docs + public async getList(@Query(new ParseIntPipe()) query: PerPageDto) { + const curPage = query.page || 1; + const perNum = query.perNum || DEF_PER_COUNT; + const resData = new ListResponse(); + resData.current = curPage; + resData.totalPages = await this.ugSvr.countPage(perNum); + resData.total = await this.ugSvr.count(); + if (resData.totalPages >= resData.current) { + resData.data = await this.ugSvr.list({ + page: curPage, perNum + }); + } + return resData; + } + + @Roles("admin") + @Post() + // region Swagger Docs + @HttpCode(HttpStatus.CREATED) + @ApiOperation({ title: "New Usergroup" }) + @ApiResponse({ + status: HttpStatus.CREATED, description: "Success" + }) + // endregion Swagger Docs + public addUsergroup(@Body() body: AddUsergroupDto) { + return this.ugSvr.add(body); + } + + @Roles("admin") + @Get("/:gid/delete") + // region Swagger Docs + @HttpCode(HttpStatus.OK) + @ApiOperation({ title: "Delete Usergroup" }) + @ApiResponse({ + status: HttpStatus.OK, description: "Delete Success" + }) + // endregion Swagger Docs + public async removeUsergroupByGet(@Param() param: UGidDto) { + return this.ugSvr.remove(param.gid); + } + + @Roles("admin") + @Delete("/:gid") + // region Swagger Docs + @HttpCode(HttpStatus.OK) + @ApiOperation({ title: "Delete Usergroup" }) + @ApiResponse({ + status: HttpStatus.OK, description: "Delete Success" + }) + // endregion Swagger Docs + public async removeUsergroupByDelete(@Param() param: UGidDto) { + return this.ugSvr.remove(param.gid); + } + + @Roles("admin") + @Post("/:gid") + // region Swagger Docs + @HttpCode(HttpStatus.OK) + @ApiOperation({ title: "Modify Usergroup Info" }) + @ApiResponse({ + status: HttpStatus.OK, description: "Modify Success" + }) + // endregion Swagger Docs + public async editUsergroup( + @Param() param: UGidDto, @Body() body: EditUsergroupDto + ) { + return this.ugSvr.edit(param.gid, body); + } + + @Roles("admin") + @Get("/:gid") + // region Swagger Docs + @HttpCode(HttpStatus.OK) + @ApiOperation({ title: "Get Usergroup Info" }) + @ApiResponse({ + status: HttpStatus.OK, description: "Usergroup Info" + }) + // endregion Swagger Docs + public async getUsergroup( + @Param() param: UGidDto, @Query(new ParseIntPipe()) query: PerPageDto + ) { + let group: any = await this.ugSvr.getGroup(param.gid); + if (!group) { + return group; + } + group = group.toObject(); + const users = new ListResponse(); + const curPage = query.page || 1; + const perNum = query.perNum || DEF_PER_COUNT; + users.current = curPage; + users.totalPages = await this.ugSvr.usersCountPage(param.gid, perNum); + users.total = await this.ugSvr.usersCount(param.gid); + if (users.totalPages >= users.current) { + users.data = await this.ugSvr.getGroupUsers(param.gid, { + page: curPage, perNum + }); + } + group.users = users; + return group; + } + +} diff --git a/src/modules/usergroups/usergroups.dto.ts b/src/modules/usergroups/usergroups.dto.ts new file mode 100644 index 0000000..d87b6e6 --- /dev/null +++ b/src/modules/usergroups/usergroups.dto.ts @@ -0,0 +1,15 @@ +import { ApiModelProperty } from "@nestjs/swagger"; +import { IsString, IsMongoId } from "class-validator"; +import { ObjectId } from "@models/common"; + +export class AddUsergroupDto { + @ApiModelProperty({ type: String, description: "Usergroup Name" }) + @IsString() + public readonly name: string; +} + +export class EditUsergroupDto { + @ApiModelProperty({ type: String, description: "Usergroup Name" }) + @IsString() + public readonly name: string; +} diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index ecbd06a..796c8fc 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -14,6 +14,7 @@ import { Roles } from "@decorators/roles"; import { RolesGuard } from "@guards/roles"; import { ParseIntPipe } from "@pipes/parse-int"; import { TokensService } from "@services/tokens"; +import { UsergroupsService } from "@services/usergroups"; import { CollectionsService } from "@services/collections"; import { UsersService } from "@services/users"; import { PerPageDto, ListResponse, DEF_PER_COUNT } from "@dtos/page"; @@ -21,7 +22,7 @@ import { UidDto } from "@dtos/ids"; import { DefResDto } from "@dtos/res"; import { - CreateUserDto, ModifyPasswordDto, EditUserDto + CreateUserDto, ModifyPasswordDto, EditUserDto, UserUsergroupParamDto } from "./users.dto"; @UseGuards(RolesGuard) @@ -35,7 +36,8 @@ export class UsersAdminController { constructor( private readonly tokensSvr: TokensService, private readonly collectionsSvr: CollectionsService, - private readonly usersSvr: UsersService + private readonly usersSvr: UsersService, + private readonly ugSvr: UsergroupsService ) { } @Roles("admin") @@ -362,6 +364,38 @@ export class UsersAdminController { // endregion Collection Methods //////////////////////////////////////// + //////////////////////////////////////// + // region Usergroup Methods + //////////////////////////////////////// + + @Roles("admin") + @Get("/:uid/usergroup/:gid") + // region Swagger Docs + @HttpCode(HttpStatus.OK) + @ApiOperation({ title: "Move Usergroup" }) + @ApiResponse({ + status: HttpStatus.OK, description: "Move Success", + }) + // endregion Swagger Docs + public async moveUsergroup(@Param() param: UserUsergroupParamDto) { + const gid = (await this.usersSvr.getUsergroup(param.gid))._id; + if (gid.toString() === param.gid) { + throw new BadRequestException("This is old Usergroup ID"); + } + await this.ugSvr.removeUserFromGroup(gid, param.uid); + try { + await this.ugSvr.addUserToGroup(param.gid, param.uid); + } catch (error) { + await this.ugSvr.addUserToGroup(gid, param.uid); + throw error; + } + return new DefResDto(); + } + + //////////////////////////////////////// + // endregion Usergroup Methods + //////////////////////////////////////// + @Roles("admin") @Get("/:uid") // region Swagger Docs diff --git a/src/modules/users/users.dto.ts b/src/modules/users/users.dto.ts index df44a52..e2f2f25 100644 --- a/src/modules/users/users.dto.ts +++ b/src/modules/users/users.dto.ts @@ -1,6 +1,7 @@ import { IsString, IsMongoId } from "class-validator"; import { ObjectId } from "@models/common"; import { ApiUseTags, ApiModelProperty } from "@nestjs/swagger"; +import { IUidDto, IGidDto } from "@dtos/ids"; export class CreateUserDto { @ApiModelProperty({ type: String, description: "Username" }) @@ -25,3 +26,12 @@ export class ModifyPasswordDto { @IsString() public readonly newPassword: string; } + +export class UserUsergroupParamDto implements IUidDto, IGidDto { + @ApiModelProperty({ type: String, description: "User ID" }) + @IsMongoId() + public readonly uid: ObjectId; + @ApiModelProperty({ type: String, description: "Usergroup ID" }) + @IsMongoId() + public readonly gid: ObjectId; +} From ed1ab5e2168b14170503eb2e9fb3f244c860cf1e Mon Sep 17 00:00:00 2001 From: Arylo Date: Thu, 1 Mar 2018 18:22:11 +0800 Subject: [PATCH 3/5] Add Test Unit --- .../common/services/usergroups.service.ts | 16 +++ src/modules/common/services/users.service.ts | 2 +- src/modules/users/users.controller.ts | 17 ++- test/api/user_usergroup.e2e.ts | 67 +++++++++++ test/api/usergroups.e2e.ts | 106 ++++++++++++++++++ test/helpers/database.ts | 46 ++++---- test/helpers/database/user.ts | 10 ++ test/helpers/database/usergroups.ts | 29 +++++ 8 files changed, 259 insertions(+), 34 deletions(-) create mode 100644 test/api/user_usergroup.e2e.ts create mode 100644 test/api/usergroups.e2e.ts create mode 100644 test/helpers/database/user.ts create mode 100644 test/helpers/database/usergroups.ts diff --git a/src/modules/common/services/usergroups.service.ts b/src/modules/common/services/usergroups.service.ts index 78c20e5..49a03fc 100644 --- a/src/modules/common/services/usergroups.service.ts +++ b/src/modules/common/services/usergroups.service.ts @@ -109,4 +109,20 @@ export class UsergroupsService { } } + public async moveUser(gid: ObjectId, uid: ObjectId) { + let doc = null; + try { + doc = await UserUsergroupsModel.update( + { user: uid }, { usergroup: gid }, + { runValidators: true, context: "query" } + ).exec(); + } catch (error) { + throw new BadRequestException(error.toString()); + } + if (!doc) { + throw new BadRequestException("User hasnt usergroup"); + } + return doc; + } + } diff --git a/src/modules/common/services/users.service.ts b/src/modules/common/services/users.service.ts index 7b247b5..dd2cbee 100644 --- a/src/modules/common/services/users.service.ts +++ b/src/modules/common/services/users.service.ts @@ -12,7 +12,7 @@ export class UsersService { .findOne({ user: uid }).populate("usergroup") .exec(); if (!group) { - return group as undefined; + return null; } return group.toObject().usergroup as IUsergroups; } diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 796c8fc..8edc326 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -378,16 +378,15 @@ export class UsersAdminController { }) // endregion Swagger Docs public async moveUsergroup(@Param() param: UserUsergroupParamDto) { - const gid = (await this.usersSvr.getUsergroup(param.gid))._id; - if (gid.toString() === param.gid) { - throw new BadRequestException("This is old Usergroup ID"); - } - await this.ugSvr.removeUserFromGroup(gid, param.uid); - try { + const group = await this.usersSvr.getUsergroup(param.uid); + if (group) { + const gid = group._id; + if (gid.toString() === param.gid) { + throw new BadRequestException("This is old Usergroup ID"); + } + await this.ugSvr.moveUser(param.gid, param.uid); + } else { await this.ugSvr.addUserToGroup(param.gid, param.uid); - } catch (error) { - await this.ugSvr.addUserToGroup(gid, param.uid); - throw error; } return new DefResDto(); } diff --git a/test/api/user_usergroup.e2e.ts b/test/api/user_usergroup.e2e.ts new file mode 100644 index 0000000..61128e3 --- /dev/null +++ b/test/api/user_usergroup.e2e.ts @@ -0,0 +1,67 @@ +import supertest = require("supertest"); +import faker = require("faker"); + +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; +import { UsersService } from "@services/users"; + +import { + connect, drop, addCategoryAndRegexp +} from "../helpers/database"; +import { init } from "../helpers/server"; +import { + newUsergroup, getLinkIdByUserId +} from "../helpers/database/usergroups"; +import { newUser } from "../helpers/database/user"; + +describe("User's Usergroup E2E Api", () => { + + let request: supertest.SuperTest; + + before(() => { + return connect(); + }); + + const ids = { + users: [ ], + usergroups: [ ], + userusergroups: [ ] + }; + + after(() => { + return drop(ids); + }); + + before(async () => { + request = await init(); + }); + + let userSvr: UsersService; + before(() => { + userSvr = new UsersService(); + }); + + const user = { + username: `${faker.name.firstName()}${Math.random()}`, + password: faker.random.words() + }; + step("Login", async () => { + const userDoc = await newUser(user.username, user.password); + ids.users.push(userDoc._id); + const groupDoc = await newUsergroup(undefined, userDoc._id); + ids.usergroups.push(groupDoc._id); + ids.userusergroups.push(await getLinkIdByUserId(userDoc._id)); + await request.post("/api/v1/auth/login").send(user).then(); + }); + + step("Move Usergroup", async () => { + const groupDoc = await newUsergroup(); + ids.usergroups.push(groupDoc._id); + const url = `/api/v1/users/${ids.users[0]}/usergroup/${groupDoc._id}`; + const { status, body: result } = await request.get(url).then(); + status.should.be.eql(200); + const group = await userSvr.getUsergroup(ids.users[0]); + group._id.toString().should.be.eql(groupDoc._id.toString()); + group._id.toString().should.be.not.eql(ids.usergroups[0].toString()); + }); + +}); diff --git a/test/api/usergroups.e2e.ts b/test/api/usergroups.e2e.ts new file mode 100644 index 0000000..ba57a35 --- /dev/null +++ b/test/api/usergroups.e2e.ts @@ -0,0 +1,106 @@ +import supertest = require("supertest"); +import faker = require("faker"); + +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; +import { UsersService } from "@services/users"; + +import { + connect, drop, addCategoryAndRegexp +} from "../helpers/database"; +import { init } from "../helpers/server"; +import { + newUsergroup, getLinkIdByUserId +} from "../helpers/database/usergroups"; +import { newUser } from "../helpers/database/user"; + +describe("Usergroup E2E Api", () => { + + let request: supertest.SuperTest; + + before(() => { + return connect(); + }); + + const ids = { + users: [ ], + usergroups: [ ], + userusergroups: [ ] + }; + + after(() => { + return drop(ids); + }); + + before(async () => { + request = await init(); + }); + + let userSvr: UsersService; + before(() => { + userSvr = new UsersService(); + }); + + const user = { + username: `${faker.name.firstName()}${Math.random()}`, + password: faker.random.words() + }; + step("Login", async () => { + const userDoc = await newUser(user.username, user.password); + ids.users.push(userDoc._id); + const groupDoc = await newUsergroup(undefined, userDoc._id); + ids.usergroups.push(groupDoc._id); + ids.userusergroups.push(await getLinkIdByUserId(userDoc._id)); + await request.post("/api/v1/auth/login").send(user).then(); + }); + + step("New Usergroup * 2", async () => { + for (let i = 0; i < 2; i++) { + const url = `/api/v1/usergroups`; + const name = `${faker.random.word()}${Math.random()}`; + const { status, body: result } = + await request.post(url).send({ name }).then(); + status.should.be.eql(201); + ids.usergroups.push(result._id); + } + }); + + step("Usergroup List", async () => { + const url = `/api/v1/usergroups`; + const { status, body: result } = await request.get(url).then(); + status.should.be.eql(200); + result.total.should.aboveOrEqual(2); + result.data.should.be.an.Array(); + }); + + step("Get Usergroup Info", async () => { + const id = ids.usergroups[ids.usergroups.length - 1]; + const url = `/api/v1/usergroups/${id}`; + const { status, body: result } = await request.get(url).then(); + status.should.be.eql(200); + result.users.data.should.be.an.Array(); + result.users.total.should.be.eql(0); + }); + + step("Modify Usergroup's name", async () => { + const id = ids.usergroups[ids.usergroups.length - 1]; + const url = `/api/v1/usergroups/${id}`; + const name = `${faker.random.word()}${Math.random()}`; + const { status } = await request.post(url).send({ name }).then(); + status.should.be.eql(200); + const { body: result } = await request.get(url).then(); + result.should.have.property("name", name); + }); + + step("Delete By GET", async () => { + const id = ids.usergroups[ids.usergroups.length - 1]; + const url = `/api/v1/usergroups/${id}/delete`; + const { status } = await request.get(url).then(); + }); + + step("Delete By DELETE", async () => { + const id = ids.usergroups[ids.usergroups.length - 2]; + const url = `/api/v1/usergroups/${id}`; + const { status } = await request.delete(url).then(); + }); + +}); diff --git a/test/helpers/database.ts b/test/helpers/database.ts index 393c1a8..f771f77 100644 --- a/test/helpers/database.ts +++ b/test/helpers/database.ts @@ -1,7 +1,6 @@ import faker = require("faker"); import { config } from "@utils/config"; import { ObjectId } from "@models/common"; -import { connectDatabase } from "../../src/modules/database/database.providers"; import { Model as ValuesModel } from "@models/Value"; import { Model as GoodsModels } from "@models/Good"; @@ -10,6 +9,11 @@ import { Model as UsersModel } from "@models/User"; import { Model as TokensModel } from "@models/Token"; import { Model as CollectionsModel } from "@models/Collection"; import { Model as CategoriesModel, CategoryDoc } from "@models/Categroy"; +import { Model as UsergroupsModel } from "@models/Usergroup"; +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; + +import { connectDatabase } from "../../src/modules/database/database.providers"; +import { newUser as newUserFn } from "./database/user"; config.db.database = "storebox-test"; @@ -37,26 +41,22 @@ export const drop = async (ids?: IIds) => { await CategoriesModel.remove({ }).exec(); return; } - for (const id of (ids.values || [ ])) { - await ValuesModel.findByIdAndRemove(id).exec(); - } - for (const id of (ids.goods || [ ])) { - await GoodsModels.findByIdAndRemove(id).exec(); - } - for (const id of (ids.regexps || [ ])) { - await RegexpsModel.findByIdAndRemove(id).exec(); - } - for (const id of (ids.users || [ ])) { - await UsersModel.findByIdAndRemove(id).exec(); - } - for (const id of (ids.tokens || [ ])) { - await TokensModel.findByIdAndRemove(id).exec(); - } - for (const id of (ids.categories || [ ])) { - await CategoriesModel.findByIdAndRemove(id).exec(); - } - for (const id of (ids.collections || [ ])) { - await CollectionsModel.findByIdAndRemove(id).exec(); + const MODEL_IDMETHOD_MAP = { + "values": ValuesModel, + "goods": GoodsModels, + "regexps": RegexpsModel, + "users": UsersModel, + "tokens": TokensModel, + "categories": CategoriesModel, + "collections": CollectionsModel, + "usergroups": UsergroupsModel, + "userusergroups": UserUsergroupsModel + }; + for (const method of Object.keys(MODEL_IDMETHOD_MAP)) { + const model = MODEL_IDMETHOD_MAP[method]; + for (const id of (ids[method] || [ ])) { + await model.findByIdAndRemove(id).exec(); + } } }; @@ -68,9 +68,7 @@ export const addCategoryAndRegexp = async (regexp: RegExp) => { return [category, reg]; }; -export const newUser = (username: string, password: string) => { - return UsersModel.addUser(username, password); -}; +export const newUser = newUserFn; export const newRegexp = (name: string, value: RegExp, link?) => { return RegexpsModel.addRegexp(name, value.source, link); diff --git a/test/helpers/database/user.ts b/test/helpers/database/user.ts new file mode 100644 index 0000000..a2d6217 --- /dev/null +++ b/test/helpers/database/user.ts @@ -0,0 +1,10 @@ +import { Model as UsersModel } from "@models/User"; +import { ObjectId } from "@models/common"; +import faker = require("faker"); + +export const newUser = ( + username = `${faker.name.firstName()}${Math.random()}`, + password = `${faker.random.words()}${Math.random()}` +) => { + return UsersModel.addUser(username, password); +}; diff --git a/test/helpers/database/usergroups.ts b/test/helpers/database/usergroups.ts new file mode 100644 index 0000000..a61d377 --- /dev/null +++ b/test/helpers/database/usergroups.ts @@ -0,0 +1,29 @@ +import { ObjectId } from "@models/common"; +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; +import { Model as UsergroupsModel } from "@models/Usergroup"; +import faker = require("faker"); + +/// + +export const getLinkIdByUserId = async (uid: ObjectId) => { + return (await UserUsergroupsModel.findOne({ user: uid }).exec())._id; +}; + +export const getLinkIdsByUsergroupId = async (gid: ObjectId) => { + return (await UserUsergroupsModel.find({ usergroup: gid }).exec()) + .map((item) => { + return item._id; + }); +}; + +export const newUsergroup = async ( + name = `${faker.random.word}${Math.random()}`, uid?: ObjectId +) => { + const group = await UsergroupsModel.create({ name }); + if (uid) { + const link = await UserUsergroupsModel.create({ + user: uid, usergroup: group._id + }); + } + return group; +}; From 41572162a04277732d9baf786b13d6454d6b73dc Mon Sep 17 00:00:00 2001 From: Arylo Date: Mon, 5 Mar 2018 15:52:23 +0800 Subject: [PATCH 4/5] Add the feature of Default Usergroup ID --- TODOLIST.md | 4 +- src/models/System.ts | 20 ++++++ src/models/User-Usergroup.ts | 20 ------ src/modules/common/services/system.service.ts | 38 +++++++++++ .../common/services/usergroups.service.ts | 16 ----- src/modules/common/services/users.service.ts | 60 +++++++++++++++-- src/modules/controllers.module.ts | 4 +- src/modules/database/database.providers.ts | 6 ++ .../usergroups/usergroups.controller.ts | 28 +++++++- src/modules/usergroups/usergroups.dto.ts | 10 +++ src/modules/users/users.controller.ts | 60 ++++++++--------- src/modules/users/users.dto.ts | 12 ++-- test/api/user_usergroup.e2e.ts | 65 ++++++++++++------- test/api/usergroups.e2e.ts | 11 +--- test/helpers/database.ts | 5 ++ test/helpers/database/user.ts | 12 ++++ test/helpers/database/usergroups.ts | 9 ++- test/issues/ban_user_n_its_token.e2e.ts | 3 +- test/services/users.spec.ts | 3 +- 19 files changed, 265 insertions(+), 121 deletions(-) create mode 100644 src/models/System.ts create mode 100644 src/modules/common/services/system.service.ts diff --git a/TODOLIST.md b/TODOLIST.md index a5fff82..36ce465 100644 --- a/TODOLIST.md +++ b/TODOLIST.md @@ -1,8 +1,8 @@ # TODO LIST -- [ ] 用户组 +- [x] 用户组 - [ ] 权限 - - [ ] 默认用户组 + - [x] 默认用户组 - [ ] 上传到指定Categroy - [ ] 整顿collectin info的goods 列表 - [ ] Token 使用日志显示 diff --git a/src/models/System.ts b/src/models/System.ts new file mode 100644 index 0000000..375b703 --- /dev/null +++ b/src/models/System.ts @@ -0,0 +1,20 @@ +import { model, SchemaDefinition, Model as M } from "mongoose"; +import { Base, IDoc, IDocRaw } from "./common"; + +const Definition: SchemaDefinition = { + key: { type: String, required: true }, + value: { type: String, required: true } +}; + +export interface ISystem extends IDocRaw { + key: string; + value: string; +} + +const SystemSchema = new Base(Definition).createSchema(); + +export const Flag = "sys"; + +export type SystemDoc = IDoc; + +export const Model: M = model(Flag, SystemSchema); diff --git a/src/models/User-Usergroup.ts b/src/models/User-Usergroup.ts index 8d364b9..ae38d5d 100644 --- a/src/models/User-Usergroup.ts +++ b/src/models/User-Usergroup.ts @@ -44,26 +44,6 @@ export type UserUsergroupDoc = IDoc; const UserUsergroupsSchema = new Base(Definition).createSchema(); -// region validators - -UserUsergroupsSchema.path("user").validate({ - isAsync: true, - validator: async function userIdModifyValidator(val, respond) { - if (!this.isNew) { - const id = this.getQuery()._id; - const cur = await Model.findById(id).exec(); - if (cur.toObject().user === val) { - return respond(true); - } - } - const result = await Model.findOne({ user: val }).exec(); - return respond(!result); - }, - message: "The User ID is existed" -}); - -// endregion validators - /** * User-Usergroup Model */ diff --git a/src/modules/common/services/system.service.ts b/src/modules/common/services/system.service.ts new file mode 100644 index 0000000..c131a45 --- /dev/null +++ b/src/modules/common/services/system.service.ts @@ -0,0 +1,38 @@ +import { Component } from "@nestjs/common"; +import { ObjectId } from "@models/common"; +import { Model as SystemModel } from "@models/System"; +import { Model as UsergroupsModel } from "@models/Usergroup"; + +@Component() +export class SystemService { + + public static DEFAULT_USERGROUP_FLAG = "DEFAULT_USERGROUP"; + + /** + * Get Default Usergroup ID + * @returns Usergroup ID + */ + public async getDefaultUsergroup(): Promise { + let gid: any = await SystemModel.findOne({ + key: SystemService.DEFAULT_USERGROUP_FLAG + }).exec(); + if (!gid) { + gid = (await UsergroupsModel.findOne().exec())._id; + this.setDefaultUsergroup(gid); + } + return gid; + } + + /** + * Set Default Usergroup ID + * @param gid Usergroup ID + */ + public setDefaultUsergroup(gid: ObjectId) { + return SystemModel.findOneAndUpdate( + { key: SystemService.DEFAULT_USERGROUP_FLAG }, { value: gid }, + { upsert: true } + ) + .exec(); + } + +} diff --git a/src/modules/common/services/usergroups.service.ts b/src/modules/common/services/usergroups.service.ts index 49a03fc..78c20e5 100644 --- a/src/modules/common/services/usergroups.service.ts +++ b/src/modules/common/services/usergroups.service.ts @@ -109,20 +109,4 @@ export class UsergroupsService { } } - public async moveUser(gid: ObjectId, uid: ObjectId) { - let doc = null; - try { - doc = await UserUsergroupsModel.update( - { user: uid }, { usergroup: gid }, - { runValidators: true, context: "query" } - ).exec(); - } catch (error) { - throw new BadRequestException(error.toString()); - } - if (!doc) { - throw new BadRequestException("User hasnt usergroup"); - } - return doc; - } - } diff --git a/src/modules/common/services/users.service.ts b/src/modules/common/services/users.service.ts index dd2cbee..27bd62a 100644 --- a/src/modules/common/services/users.service.ts +++ b/src/modules/common/services/users.service.ts @@ -3,18 +3,64 @@ import { ObjectId } from "@models/common"; import { Model as UsersModel, UserDoc } from "@models/User"; import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { IUsergroups } from "@models/Usergroup"; +import { SystemService } from "@services/system"; +import { IPerPage, DEF_PER_COUNT } from "@dtos/page"; @Component() export class UsersService { - public async getUsergroup(uid: ObjectId) { - const group = await UserUsergroupsModel - .findOne({ user: uid }).populate("usergroup") - .exec(); - if (!group) { - return null; + private DEF_PER_OBJ: IPerPage = { + perNum: DEF_PER_COUNT, + page: 1 + }; + + constructor(private readonly sysSvr: SystemService) { } + + public async addUser(obj, gid?: ObjectId) { + try { + const user = await UsersModel.addUser(obj.username, obj.password); + if (!gid) { + gid = await this.sysSvr.getDefaultUsergroup(); + } + await UserUsergroupsModel.create({ + user: user._id, usergroup: gid + }); + return user; + } catch (error) { + throw new BadRequestException(error.toString()); } - return group.toObject().usergroup as IUsergroups; + } + + public async removeUser(uid: ObjectId) { + try { + await UsersModel.removeUser(uid); + await UserUsergroupsModel.remove({ user: uid }).exec(); + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + public countUsergroups(uid: ObjectId) { + return UserUsergroupsModel.count({ user: uid }).exec(); + } + + public async countPageUsergroups(uid: ObjectId, perNum = DEF_PER_COUNT) { + const total = await this.countUsergroups(uid); + return Math.ceil(total / perNum); + } + + public async getUsergroups( + uid: ObjectId, pageObj: IPerPage = this.DEF_PER_OBJ + ) { + const perNum = pageObj.perNum; + const page = pageObj.page; + const groups = await UserUsergroupsModel + .find({ user: uid }).populate("usergroup") + .skip((page - 1) * perNum).limit(perNum) + .exec(); + return groups.map((item) => { + return item.toObject().usergroup as IUsergroups; + }); } /** diff --git a/src/modules/controllers.module.ts b/src/modules/controllers.module.ts index b20dcd0..7770fe7 100644 --- a/src/modules/controllers.module.ts +++ b/src/modules/controllers.module.ts @@ -35,6 +35,7 @@ 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"; // endregion Services export const controllers = [ @@ -48,7 +49,8 @@ export const controllers = [ ]; export const services = [ - CollectionsService, TokensService, UsersService, UsergroupsService + CollectionsService, TokensService, UsersService, UsergroupsService, + SystemService ]; @Module({ diff --git a/src/modules/database/database.providers.ts b/src/modules/database/database.providers.ts index 55cb117..eff2f04 100644 --- a/src/modules/database/database.providers.ts +++ b/src/modules/database/database.providers.ts @@ -4,6 +4,8 @@ import { config } from "@utils/config"; import { Model as UsersModel } from "@models/User"; import { Model as UsergroupsModel } from "@models/Usergroup"; import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; +import { Model as SystemModel } from "@models/System"; +import { SystemService } from "@services/system"; import { systemLogger } from "../common/helper/log"; const getDatabaseUrl = () => { @@ -49,6 +51,10 @@ export const injectData = async () => { usergroup: group._id }; }); + await SystemModel.findOneAndUpdate( + { key: SystemService.DEFAULT_USERGROUP_FLAG }, + { value: group._id.toString() }, { upsert: true } + ).exec(); await UserUsergroupsModel.create(conditions); } }; diff --git a/src/modules/usergroups/usergroups.controller.ts b/src/modules/usergroups/usergroups.controller.ts index 6ff40d2..187cfac 100644 --- a/src/modules/usergroups/usergroups.controller.ts +++ b/src/modules/usergroups/usergroups.controller.ts @@ -7,7 +7,7 @@ import { ParseIntPipe } from "@pipes/parse-int"; import { PerPageDto, ListResponse, DEF_PER_COUNT } from "@dtos/page"; import { UGidDto } from "@dtos/ids"; -import { AddUsergroupDto, EditUsergroupDto } from "./usergroups.dto"; +import { AddUsergroupDto, EditUsergroupDto, UserUsergroupDto } from "./usergroups.dto"; @UseGuards(RolesGuard) @ApiUseTags("User Groups") @@ -127,4 +127,30 @@ export class UsergroupsAdminController { return group; } + @Roles("admin") + @Get("/:gid/add/:uid") + // region Swagger Docs + @HttpCode(HttpStatus.CREATED) + @ApiOperation({ title: "Add User to Usergroup" }) + @ApiResponse({ + status: HttpStatus.CREATED, description: "Add Success" + }) + // endregion Swagger Docs + public addUser(@Param() param: UserUsergroupDto) { + return this.ugSvr.addUserToGroup(param.gid, param.uid); + } + + @Roles("admin") + @Get("/:gid/remove/:uid") + // region Swagger Docs + @HttpCode(HttpStatus.OK) + @ApiOperation({ title: "Remove User from Usergroup" }) + @ApiResponse({ + status: HttpStatus.OK, description: "Remove Success" + }) + // endregion Swagger Docs + public removeUser(@Param() param: UserUsergroupDto) { + return this.ugSvr.removeUserFromGroup(param.gid, param.uid); + } + } diff --git a/src/modules/usergroups/usergroups.dto.ts b/src/modules/usergroups/usergroups.dto.ts index d87b6e6..fcacae5 100644 --- a/src/modules/usergroups/usergroups.dto.ts +++ b/src/modules/usergroups/usergroups.dto.ts @@ -1,6 +1,7 @@ import { ApiModelProperty } from "@nestjs/swagger"; import { IsString, IsMongoId } from "class-validator"; import { ObjectId } from "@models/common"; +import { IGidDto, IUidDto } from "@dtos/ids"; export class AddUsergroupDto { @ApiModelProperty({ type: String, description: "Usergroup Name" }) @@ -13,3 +14,12 @@ export class EditUsergroupDto { @IsString() public readonly name: string; } + +export class UserUsergroupDto implements IGidDto, IUidDto { + @ApiModelProperty({ type: String, description: "User ID" }) + @IsMongoId() + public readonly uid: ObjectId; + @ApiModelProperty({ type: String, description: "Usergroup ID" }) + @IsMongoId() + public readonly gid: ObjectId; +} diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index 8edc326..f1eeabb 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -6,6 +6,7 @@ import { ApiBearerAuth, ApiUseTags, ApiResponse, ApiOperation, ApiImplicitParam } from "@nestjs/swagger"; import { Model as UserModel, IUser, UserDoc } from "@models/User"; +import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { Model as TokensModel } from "@models/Token"; import { Model as GoodsModels } from "@models/Good"; import { CollectionDoc } from "@models/Collection"; @@ -17,12 +18,13 @@ import { TokensService } from "@services/tokens"; import { UsergroupsService } from "@services/usergroups"; import { CollectionsService } from "@services/collections"; import { UsersService } from "@services/users"; +import { SystemService } from "@services/system"; import { PerPageDto, ListResponse, DEF_PER_COUNT } from "@dtos/page"; import { UidDto } from "@dtos/ids"; import { DefResDto } from "@dtos/res"; import { - CreateUserDto, ModifyPasswordDto, EditUserDto, UserUsergroupParamDto + CreateUserDto, ModifyPasswordDto, EditUserDto, UsergroupBodyDto } from "./users.dto"; @UseGuards(RolesGuard) @@ -37,7 +39,8 @@ export class UsersAdminController { private readonly tokensSvr: TokensService, private readonly collectionsSvr: CollectionsService, private readonly usersSvr: UsersService, - private readonly ugSvr: UsergroupsService + private readonly ugSvr: UsergroupsService, + private readonly sysSvr: SystemService ) { } @Roles("admin") @@ -77,14 +80,8 @@ export class UsersAdminController { status: HttpStatus.BAD_REQUEST, description: "Add User Fail" }) // endregion Swagger Docs - public async addUser(@Body() user: CreateUserDto) { - let obj; - try { - obj = await UserModel.addUser(user.username, user.password); - } catch (error) { - throw new BadRequestException(error.toString()); - } - return obj; + public addUser(@Body() user: CreateUserDto) { + return this.usersSvr.addUser(user); } @Roles("admin") @@ -155,12 +152,8 @@ export class UsersAdminController { status: HttpStatus.BAD_REQUEST, description: "Delete User Fail" }) // endregion Swagger Docs - public async delete(@Param() user: UidDto) { - try { - await UserModel.removeUser(user.uid); - } catch (error) { - throw new BadRequestException(error.toString()); - } + public delete(@Param() user: UidDto) { + this.usersSvr.removeUser(user.uid); return new DefResDto(); } @@ -369,26 +362,33 @@ export class UsersAdminController { //////////////////////////////////////// @Roles("admin") - @Get("/:uid/usergroup/:gid") + @Get("/:uid/usergroups") // region Swagger Docs @HttpCode(HttpStatus.OK) - @ApiOperation({ title: "Move Usergroup" }) + @ApiOperation({ title: "Get Usergroup" }) @ApiResponse({ - status: HttpStatus.OK, description: "Move Success", + status: HttpStatus.OK, type: ListResponse }) // endregion Swagger Docs - public async moveUsergroup(@Param() param: UserUsergroupParamDto) { - const group = await this.usersSvr.getUsergroup(param.uid); - if (group) { - const gid = group._id; - if (gid.toString() === param.gid) { - throw new BadRequestException("This is old Usergroup ID"); - } - await this.ugSvr.moveUser(param.gid, param.uid); - } else { - await this.ugSvr.addUserToGroup(param.gid, param.uid); + public async getUsergroups( + @Param() param: UidDto, @Query(new ParseIntPipe()) query: PerPageDto + ) { + const curPage = query.page || 1; + const perNum = query.perNum || DEF_PER_COUNT; + const totalPages = + await this.usersSvr.countPageUsergroups(param.uid, query.perNum); + const totalCount = await this.usersSvr.countUsergroups(param.uid); + + const resData = new ListResponse(); + resData.current = curPage; + resData.totalPages = totalPages; + resData.total = totalCount; + if (totalPages >= curPage) { + resData.data = await this.usersSvr.getUsergroups(param.uid, { + page: curPage, perNum + }); } - return new DefResDto(); + return resData; } //////////////////////////////////////// diff --git a/src/modules/users/users.dto.ts b/src/modules/users/users.dto.ts index e2f2f25..49b6c2d 100644 --- a/src/modules/users/users.dto.ts +++ b/src/modules/users/users.dto.ts @@ -27,11 +27,11 @@ export class ModifyPasswordDto { public readonly newPassword: string; } -export class UserUsergroupParamDto implements IUidDto, IGidDto { - @ApiModelProperty({ type: String, description: "User ID" }) - @IsMongoId() - public readonly uid: ObjectId; +export class UsergroupBodyDto { + @ApiModelProperty({ type: String, description: "Action" }) + @IsString() + public readonly type: "add" | "remove"; @ApiModelProperty({ type: String, description: "Usergroup ID" }) - @IsMongoId() - public readonly gid: ObjectId; + @IsMongoId({ each: true }) + public readonly gids: ObjectId; } diff --git a/test/api/user_usergroup.e2e.ts b/test/api/user_usergroup.e2e.ts index 61128e3..1d611a2 100644 --- a/test/api/user_usergroup.e2e.ts +++ b/test/api/user_usergroup.e2e.ts @@ -3,15 +3,13 @@ import faker = require("faker"); import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { UsersService } from "@services/users"; +import { SystemService } from "@services/system"; +import { UsergroupsService } from "@services/usergroups"; -import { - connect, drop, addCategoryAndRegexp -} from "../helpers/database"; +import { connect, drop, addCategoryAndRegexp } from "../helpers/database"; import { init } from "../helpers/server"; -import { - newUsergroup, getLinkIdByUserId -} from "../helpers/database/usergroups"; -import { newUser } from "../helpers/database/user"; +import { newUsergroup, getLinkIdsByUserId, getLinkIdsByUsergroupId } from "../helpers/database/usergroups"; +import { newUser, newUserWithUsergroup } from "../helpers/database/user"; describe("User's Usergroup E2E Api", () => { @@ -23,8 +21,7 @@ describe("User's Usergroup E2E Api", () => { const ids = { users: [ ], - usergroups: [ ], - userusergroups: [ ] + usergroups: [ ] }; after(() => { @@ -35,33 +32,51 @@ describe("User's Usergroup E2E Api", () => { request = await init(); }); - let userSvr: UsersService; - before(() => { - userSvr = new UsersService(); - }); - const user = { username: `${faker.name.firstName()}${Math.random()}`, password: faker.random.words() }; step("Login", async () => { - const userDoc = await newUser(user.username, user.password); + const userDoc = await newUserWithUsergroup( + user.username, user.password + ); ids.users.push(userDoc._id); - const groupDoc = await newUsergroup(undefined, userDoc._id); - ids.usergroups.push(groupDoc._id); - ids.userusergroups.push(await getLinkIdByUserId(userDoc._id)); await request.post("/api/v1/auth/login").send(user).then(); }); - step("Move Usergroup", async () => { - const groupDoc = await newUsergroup(); - ids.usergroups.push(groupDoc._id); - const url = `/api/v1/users/${ids.users[0]}/usergroup/${groupDoc._id}`; + step("Get Usergroup", async () => { + const url = `/api/v1/users/${ids.users[0]}/usergroups`; + const { status, body: result } = await request.get(url).then(); + status.should.be.eql(200); + result.should.have.property("total", 1); + }); + + step("User Add 4 Usergroups", async () => { + const uid = ids.users[0]; + for (let i = 0; i < 4; i++) { + const group = await newUsergroup(undefined, uid); + ids.usergroups.push(group._id); + } + }); + + step("Have 5 Usergroups ", async () => { + const url = `/api/v1/users/${ids.users[0]}/usergroups`; + const { status, body: result } = await request.get(url).then(); + status.should.be.eql(200); + result.should.have.property("total", 1 + ids.usergroups.length); + }); + + step("Remove one usergroup", () => { + const uid = ids.users[0]; + const url = `/api/v1/usergroups/${ids.usergroups[0]}/remove/${uid}`; + return request.get(url).then(); + }); + + step("Have 4 Usergroups ", async () => { + const url = `/api/v1/users/${ids.users[0]}/usergroups`; const { status, body: result } = await request.get(url).then(); status.should.be.eql(200); - const group = await userSvr.getUsergroup(ids.users[0]); - group._id.toString().should.be.eql(groupDoc._id.toString()); - group._id.toString().should.be.not.eql(ids.usergroups[0].toString()); + result.should.have.property("total", ids.usergroups.length); }); }); diff --git a/test/api/usergroups.e2e.ts b/test/api/usergroups.e2e.ts index ba57a35..156dc42 100644 --- a/test/api/usergroups.e2e.ts +++ b/test/api/usergroups.e2e.ts @@ -1,15 +1,15 @@ import supertest = require("supertest"); import faker = require("faker"); -import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { UsersService } from "@services/users"; +import { SystemService } from "@services/system"; import { connect, drop, addCategoryAndRegexp } from "../helpers/database"; import { init } from "../helpers/server"; import { - newUsergroup, getLinkIdByUserId + newUsergroup, getLinkIdsByUserId } from "../helpers/database/usergroups"; import { newUser } from "../helpers/database/user"; @@ -35,11 +35,6 @@ describe("Usergroup E2E Api", () => { request = await init(); }); - let userSvr: UsersService; - before(() => { - userSvr = new UsersService(); - }); - const user = { username: `${faker.name.firstName()}${Math.random()}`, password: faker.random.words() @@ -49,7 +44,7 @@ describe("Usergroup E2E Api", () => { ids.users.push(userDoc._id); const groupDoc = await newUsergroup(undefined, userDoc._id); ids.usergroups.push(groupDoc._id); - ids.userusergroups.push(await getLinkIdByUserId(userDoc._id)); + ids.userusergroups.push(await getLinkIdsByUserId(userDoc._id)); await request.post("/api/v1/auth/login").send(user).then(); }); diff --git a/test/helpers/database.ts b/test/helpers/database.ts index f771f77..3a1cfdf 100644 --- a/test/helpers/database.ts +++ b/test/helpers/database.ts @@ -56,6 +56,11 @@ export const drop = async (ids?: IIds) => { const model = MODEL_IDMETHOD_MAP[method]; for (const id of (ids[method] || [ ])) { await model.findByIdAndRemove(id).exec(); + if (method === "users") { + await UserUsergroupsModel.remove({ user: id }).exec(); + } else if (method === "usergroups") { + await UserUsergroupsModel.remove({ usergroup: id }).exec(); + } } } }; diff --git a/test/helpers/database/user.ts b/test/helpers/database/user.ts index a2d6217..b7fb827 100644 --- a/test/helpers/database/user.ts +++ b/test/helpers/database/user.ts @@ -1,5 +1,7 @@ import { Model as UsersModel } from "@models/User"; import { ObjectId } from "@models/common"; +import { UsersService } from "@services/users"; +import { SystemService } from "@services/system"; import faker = require("faker"); export const newUser = ( @@ -8,3 +10,13 @@ export const newUser = ( ) => { return UsersModel.addUser(username, password); }; + +export const newUserWithUsergroup = ( + username = `${faker.name.firstName()}${Math.random()}`, + password = `${faker.random.words()}${Math.random()}`, + gid?: ObjectId +) => { + return new UsersService(new SystemService()).addUser({ + username, password + }, gid); +}; diff --git a/test/helpers/database/usergroups.ts b/test/helpers/database/usergroups.ts index a61d377..e2401e7 100644 --- a/test/helpers/database/usergroups.ts +++ b/test/helpers/database/usergroups.ts @@ -5,14 +5,17 @@ import faker = require("faker"); /// -export const getLinkIdByUserId = async (uid: ObjectId) => { - return (await UserUsergroupsModel.findOne({ user: uid }).exec())._id; +export const getLinkIdsByUserId = async (uid: ObjectId) => { + return (await UserUsergroupsModel.find({ user: uid }).exec()) + .map((item) => { + return item._id as ObjectId; + }); }; export const getLinkIdsByUsergroupId = async (gid: ObjectId) => { return (await UserUsergroupsModel.find({ usergroup: gid }).exec()) .map((item) => { - return item._id; + return item._id as ObjectId; }); }; diff --git a/test/issues/ban_user_n_its_token.e2e.ts b/test/issues/ban_user_n_its_token.e2e.ts index 98ea6ee..5d3e5e9 100644 --- a/test/issues/ban_user_n_its_token.e2e.ts +++ b/test/issues/ban_user_n_its_token.e2e.ts @@ -5,12 +5,13 @@ import { connect, drop, newUser } from "../helpers/database"; import { init } from "../helpers/server"; import { UsersService } from "@services/users"; import { TokensService } from "@services/tokens"; +import { SystemService } from "@services/system"; describe("Fix Issues", () => { let request: supertest.SuperTest; const tokensSvr = new TokensService(); - const usersSvr = new UsersService(); + const usersSvr = new UsersService(new SystemService()); before(() => { return connect(); diff --git a/test/services/users.spec.ts b/test/services/users.spec.ts index bc0fcaa..56df9ab 100644 --- a/test/services/users.spec.ts +++ b/test/services/users.spec.ts @@ -1,4 +1,5 @@ import { UsersService } from "@services/users"; +import { SystemService } from "@services/system"; import { Model as UsersModel } from "@models/User"; import db = require("../helpers/database"); import faker = require("faker"); @@ -20,7 +21,7 @@ describe("Users Service Test Unit", () => { }); beforeEach(() => { - usersSvr = new UsersService(); + usersSvr = new UsersService(new SystemService()); }); const user = { From 2de7000f87b37008e1ecc81ee02260b5f84b6a05 Mon Sep 17 00:00:00 2001 From: Arylo Date: Mon, 5 Mar 2018 18:14:08 +0800 Subject: [PATCH 5/5] Add user into usergroup validator --- .../common/services/usergroups.service.ts | 18 +++++++--- test/api/usergroups.e2e.ts | 35 ++++++++++++++----- test/helpers/database/usergroups.ts | 11 +++--- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/modules/common/services/usergroups.service.ts b/src/modules/common/services/usergroups.service.ts index 78c20e5..98f5cdb 100644 --- a/src/modules/common/services/usergroups.service.ts +++ b/src/modules/common/services/usergroups.service.ts @@ -1,4 +1,5 @@ import { Component, BadRequestException } from "@nestjs/common"; +import { Model as UsersModel } from "@models/User"; import { Model as UsergroupsModel } from "@models/Usergroup"; import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; import { ObjectId } from "@models/common"; @@ -86,10 +87,19 @@ export class UsergroupsService { } public async addUserToGroup(gid: ObjectId, uid: ObjectId) { - // TODO User Check - // TODO Usergroup Check + if (!(await UsersModel.findById(uid).exec())) { + throw new BadRequestException("The User ID is not exist"); + } + if (!(await UsergroupsModel.findById(gid).exec())) { + throw new BadRequestException("The Usergroup ID is not exist"); + } + if (await UserUsergroupsModel.findOne({ + user: uid, usergroup: gid + }).exec()) { + throw new BadRequestException("User has been in the usergroup"); + } try { - await UserUsergroupsModel.create({ + return await UserUsergroupsModel.create({ user: uid, usergroup: gid }); } catch (error) { @@ -98,8 +108,6 @@ export class UsergroupsService { } public async removeUserFromGroup(gid: ObjectId, uid: ObjectId) { - // TODO User Check - // TODO Usergroup Check try { await UserUsergroupsModel.findOneAndRemove({ user: uid, usergroup: gid diff --git a/test/api/usergroups.e2e.ts b/test/api/usergroups.e2e.ts index 156dc42..057b57d 100644 --- a/test/api/usergroups.e2e.ts +++ b/test/api/usergroups.e2e.ts @@ -1,9 +1,6 @@ import supertest = require("supertest"); import faker = require("faker"); -import { UsersService } from "@services/users"; -import { SystemService } from "@services/system"; - import { connect, drop, addCategoryAndRegexp } from "../helpers/database"; @@ -11,7 +8,7 @@ import { init } from "../helpers/server"; import { newUsergroup, getLinkIdsByUserId } from "../helpers/database/usergroups"; -import { newUser } from "../helpers/database/user"; +import { newUser, newUserWithUsergroup } from "../helpers/database/user"; describe("Usergroup E2E Api", () => { @@ -40,11 +37,9 @@ describe("Usergroup E2E Api", () => { password: faker.random.words() }; step("Login", async () => { - const userDoc = await newUser(user.username, user.password); + const userDoc = + await newUserWithUsergroup(user.username, user.password); ids.users.push(userDoc._id); - const groupDoc = await newUsergroup(undefined, userDoc._id); - ids.usergroups.push(groupDoc._id); - ids.userusergroups.push(await getLinkIdsByUserId(userDoc._id)); await request.post("/api/v1/auth/login").send(user).then(); }); @@ -76,6 +71,30 @@ describe("Usergroup E2E Api", () => { result.users.total.should.be.eql(0); }); + step("Add User into Usergroup", async () => { + const gid = ids.usergroups[ids.usergroups.length - 1]; + const uid = ids.users[0]; + const url = `/api/v1/usergroups/${gid}/add/${uid}`; + const { status } = await request.get(url).then(); + status.should.be.eql(201); + }); + + step("Have 1 User", async () => { + const id = ids.usergroups[ids.usergroups.length - 1]; + const url = `/api/v1/usergroups/${id}`; + const { status, body: result } = await request.get(url).then(); + status.should.be.eql(200); + result.users.total.should.be.eql(1); + }); + + step("Fail to Add User into Same Usergroup", async () => { + const gid = ids.usergroups[ids.usergroups.length - 1]; + const uid = ids.users[0]; + const url = `/api/v1/usergroups/${gid}/add/${uid}`; + const { status } = await request.get(url).then(); + status.should.be.eql(400); + }); + step("Modify Usergroup's name", async () => { const id = ids.usergroups[ids.usergroups.length - 1]; const url = `/api/v1/usergroups/${id}`; diff --git a/test/helpers/database/usergroups.ts b/test/helpers/database/usergroups.ts index e2401e7..2e5a39f 100644 --- a/test/helpers/database/usergroups.ts +++ b/test/helpers/database/usergroups.ts @@ -1,7 +1,7 @@ +import faker = require("faker"); import { ObjectId } from "@models/common"; import { Model as UserUsergroupsModel } from "@models/User-Usergroup"; -import { Model as UsergroupsModel } from "@models/Usergroup"; -import faker = require("faker"); +import { UsergroupsService } from "@services/usergroups"; /// @@ -22,11 +22,10 @@ export const getLinkIdsByUsergroupId = async (gid: ObjectId) => { export const newUsergroup = async ( name = `${faker.random.word}${Math.random()}`, uid?: ObjectId ) => { - const group = await UsergroupsModel.create({ name }); + const svr = new UsergroupsService(); + const group = await svr.add({ name }); if (uid) { - const link = await UserUsergroupsModel.create({ - user: uid, usergroup: group._id - }); + await svr.addUserToGroup(group._id, uid); } return group; };