From ab81cd0ca788aaba3777b4123211ffefe5c2fe77 Mon Sep 17 00:00:00 2001 From: Ivan Alglave <ivanalglave@outlook.fr> Date: Wed, 9 Nov 2022 11:39:07 +0100 Subject: [PATCH] CRUD for groups / GET as Observable --- package.json | 2 + src/app.module.ts | 5 +- src/groups/dao/groups.dao.ts | 38 ++++++++++ src/groups/dto/create-group.dto.ts | 14 ++++ src/groups/dto/update-group.dto.ts | 14 ++++ src/groups/entities/group.entity.ts | 15 ++++ src/groups/groups.controller.ts | 47 ++++++++++++ src/groups/groups.module.ts | 15 ++++ src/groups/groups.service.ts | 109 ++++++++++++++++++++++++++++ src/groups/groups.types.ts | 10 +++ src/groups/schemas/group.schema.ts | 67 +++++++++++++++++ 11 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 src/groups/dao/groups.dao.ts create mode 100644 src/groups/dto/create-group.dto.ts create mode 100644 src/groups/dto/update-group.dto.ts create mode 100644 src/groups/entities/group.entity.ts create mode 100644 src/groups/groups.controller.ts create mode 100644 src/groups/groups.module.ts create mode 100644 src/groups/groups.service.ts create mode 100644 src/groups/groups.types.ts create mode 100644 src/groups/schemas/group.schema.ts diff --git a/package.json b/package.json index 4ba4e23..75519d1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "@nestjs/core": "^9.0.0", "@nestjs/mongoose": "^9.2.1", "@nestjs/platform-express": "^9.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.13.2", "mongoose": "^6.7.2", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", diff --git a/src/app.module.ts b/src/app.module.ts index 92af588..2c1d559 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,10 +1,9 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { mongodb } from './config'; +import { GroupsModule } from './groups/groups.module'; @Module({ - imports: [MongooseModule.forRoot(mongodb.uri)], - controllers: [], - providers: [], + imports: [GroupsModule, MongooseModule.forRoot(mongodb.uri)], }) export class AppModule {} diff --git a/src/groups/dao/groups.dao.ts b/src/groups/dao/groups.dao.ts new file mode 100644 index 0000000..2e053bb --- /dev/null +++ b/src/groups/dao/groups.dao.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { from, map, Observable } from 'rxjs'; +import { CreateGroupDto } from '../dto/create-group.dto'; +import { UpdateGroupDto } from '../dto/update-group.dto'; +import { Group } from '../schemas/group.schema'; + +@Injectable() +export class GroupsDao { + constructor( + @InjectModel(Group.name) + private readonly _groupModel: Model<Group>, + ) {} + + find = (): Observable<Group[]> => + from(this._groupModel.find({})).pipe(map((groups) => [].concat(groups))); + + findById = (id: string): Observable<Group | void> => + from(this._groupModel.findById(id)); + + save = (group: CreateGroupDto): Observable<Group> => + from(new this._groupModel(group).save()); + + findByIdAndUpdate = ( + id: string, + group: UpdateGroupDto, + ): Observable<Group | void> => + from( + this._groupModel.findByIdAndUpdate(id, group, { + new: true, + runValidators: true, + }), + ); + + findByIdAndRemove = (id: string): Observable<Group | void> => + from(this._groupModel.findByIdAndRemove(id)); +} diff --git a/src/groups/dto/create-group.dto.ts b/src/groups/dto/create-group.dto.ts new file mode 100644 index 0000000..44a55e1 --- /dev/null +++ b/src/groups/dto/create-group.dto.ts @@ -0,0 +1,14 @@ +import { IsBoolean, IsString, IsNotEmpty, IsMongoId } from 'class-validator'; + +export class CreateGroupDto { + @IsString() + @IsNotEmpty() + id: string; + + @IsBoolean() + final: boolean; + + @IsMongoId() + @IsNotEmpty() + parent: any; +} diff --git a/src/groups/dto/update-group.dto.ts b/src/groups/dto/update-group.dto.ts new file mode 100644 index 0000000..954e852 --- /dev/null +++ b/src/groups/dto/update-group.dto.ts @@ -0,0 +1,14 @@ +import { IsBoolean, IsString, IsNotEmpty, IsMongoId } from 'class-validator'; + +export class UpdateGroupDto { + @IsString() + @IsNotEmpty() + id: string; + + @IsBoolean() + final: boolean; + + @IsMongoId() + @IsNotEmpty() + parent: string; +} diff --git a/src/groups/entities/group.entity.ts b/src/groups/entities/group.entity.ts new file mode 100644 index 0000000..b9182cd --- /dev/null +++ b/src/groups/entities/group.entity.ts @@ -0,0 +1,15 @@ +import { Group } from '../schemas/group.schema'; + +export class GroupEntity { + _id: string; + id: string; + final: boolean; + responsibles: string[]; + secretaries: string[]; + students: string[]; + parent: string; + + constructor(partial: Partial<Group>) { + Object.assign(this, partial); + } +} diff --git a/src/groups/groups.controller.ts b/src/groups/groups.controller.ts new file mode 100644 index 0000000..191fe59 --- /dev/null +++ b/src/groups/groups.controller.ts @@ -0,0 +1,47 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Param, + Body, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { CreateGroupDto } from './dto/create-group.dto'; +import { UpdateGroupDto } from './dto/update-group.dto'; +import { GroupEntity } from './entities/group.entity'; +import { GroupsService } from './groups.service'; + +@Controller('groups') +export class GroupsController { + constructor(private readonly _groupsService: GroupsService) {} + + @Get() + findAll(): Observable<GroupEntity[] | void> { + return this._groupsService.findAll(); + } + + @Get(':id') + findOne(@Param() params: { id: string }): Observable<GroupEntity> { + return this._groupsService.findOne(params.id); + } + + @Post() + create(@Body() createGroupDto: CreateGroupDto): Observable<GroupEntity> { + return this._groupsService.create(createGroupDto); + } + + @Put(':id') + update( + @Param() params: { id: string }, + @Body() updateGroupDto: UpdateGroupDto, + ): Observable<GroupEntity> { + return this._groupsService.update(params.id, updateGroupDto); + } + + @Delete(':id') + delete(@Param() params: { id: string }): Observable<void> { + return this._groupsService.delete(params.id); + } +} diff --git a/src/groups/groups.module.ts b/src/groups/groups.module.ts new file mode 100644 index 0000000..3320cf0 --- /dev/null +++ b/src/groups/groups.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { GroupsDao } from './dao/groups.dao'; +import { GroupsController } from './groups.controller'; +import { GroupsService } from './groups.service'; +import { Group, GroupSchema } from './schemas/group.schema'; + +@Module({ + imports: [ + MongooseModule.forFeature([{ name: Group.name, schema: GroupSchema }]), + ], + controllers: [GroupsController], + providers: [GroupsService, GroupsDao], +}) +export class GroupsModule {} diff --git a/src/groups/groups.service.ts b/src/groups/groups.service.ts new file mode 100644 index 0000000..798420c --- /dev/null +++ b/src/groups/groups.service.ts @@ -0,0 +1,109 @@ +import { + Injectable, + UnprocessableEntityException, + NotFoundException, + ConflictException, +} from '@nestjs/common'; +import { + Observable, + of, + filter, + map, + mergeMap, + defaultIfEmpty, + catchError, + throwError, +} from 'rxjs'; +import { GroupsDao } from './dao/groups.dao'; +import { CreateGroupDto } from './dto/create-group.dto'; +import { UpdateGroupDto } from './dto/update-group.dto'; +import { GroupEntity } from './entities/group.entity'; + +@Injectable() +export class GroupsService { + constructor(private readonly _groupsDao: GroupsDao) {} + + findAll = (): Observable<GroupEntity[] | void> => + this._groupsDao.find().pipe( + filter(Boolean), + map((groups) => (groups || []).map((group) => new GroupEntity(group))), + defaultIfEmpty(undefined), + ); + + findOne = (id: string): Observable<GroupEntity> => + this._groupsDao.findById(id).pipe( + catchError((e) => + throwError(() => new UnprocessableEntityException(e.message)), + ), + mergeMap((group) => + !!group + ? of(new GroupEntity(group)) + : throwError( + () => new NotFoundException(`Group with id ${id} not found`), + ), + ), + ); + + create = (group: CreateGroupDto): Observable<GroupEntity> => + this._prepareNewGroup(group).pipe( + mergeMap((newPreparedGroup: CreateGroupDto) => + this._groupsDao.save(newPreparedGroup), + ), + catchError((e) => + e.code === 11000 + ? throwError( + () => + new ConflictException( + `Group with id ${group.id} already exists`, + ), + ) + : throwError(() => new UnprocessableEntityException(e.message)), + ), + map((groupCreated) => new GroupEntity(groupCreated)), + ); + + update = (id: string, group: UpdateGroupDto): Observable<GroupEntity> => + this._groupsDao.findByIdAndUpdate(id, group).pipe( + catchError((e) => + e.code === 11000 + ? throwError( + () => + new ConflictException( + `Group with id ${group.id} already exists`, + ), + ) + : throwError(() => new UnprocessableEntityException(e.message)), + ), + mergeMap((groupUpdated) => + !!groupUpdated + ? of(new GroupEntity(groupUpdated)) + : throwError( + () => new NotFoundException(`Group with id '${id}' not found`), + ), + ), + ); + + delete = (id: string): Observable<void> => + this._groupsDao.findByIdAndRemove(id).pipe( + catchError((e) => + throwError(() => new UnprocessableEntityException(e.message)), + ), + mergeMap((groupDeleted) => + !!groupDeleted + ? of(undefined) + : throwError( + () => new NotFoundException(`Group with id '${id}' not found`), + ), + ), + ); + + private _prepareNewGroup = ( + group: CreateGroupDto, + ): Observable<CreateGroupDto> => + of({ + ...group, + responsibles: [], + secretaries: [], + students: [], + }); +} diff --git a/src/groups/groups.types.ts b/src/groups/groups.types.ts new file mode 100644 index 0000000..ddbd330 --- /dev/null +++ b/src/groups/groups.types.ts @@ -0,0 +1,10 @@ +export type Group = { + _id: any; + id: string; + final: boolean; + responsibles: string[]; + secretaries: string[]; + students: string[]; + subgroups: string[]; + parent: string; +}; diff --git a/src/groups/schemas/group.schema.ts b/src/groups/schemas/group.schema.ts new file mode 100644 index 0000000..c8ee51e --- /dev/null +++ b/src/groups/schemas/group.schema.ts @@ -0,0 +1,67 @@ +import * as mongoose from 'mongoose'; +import { Document } from 'mongoose'; +import { Prop, raw, Schema, SchemaFactory } from '@nestjs/mongoose'; + +export type GroupDocument = Group & Document; + +@Schema({ + toJSON: { + virtuals: true, + transform: (doc: any, ret: any) => { + delete ret._id; + }, + }, +}) +export class Group { + @Prop({ + type: mongoose.Schema.Types.ObjectId, + auto: true, + }) + _id: any; + + @Prop({ + type: String, + required: true, + trim: true, + }) + id: string; + + @Prop({ + type: Boolean, + required: true, + trim: true, + }) + final: boolean; + + @Prop({ + type: [mongoose.Schema.Types.ObjectId], + required: true, + trim: true, + }) + responsibles: string[]; + + @Prop({ + type: [mongoose.Schema.Types.ObjectId], + required: true, + trim: true, + }) + secretaries: string[]; + + @Prop({ + type: [mongoose.Schema.Types.ObjectId], + required: true, + trim: true, + }) + students: string[]; + + @Prop({ + type: mongoose.Schema.Types.ObjectId, + required: true, + trim: true, + }) + parent: string; +} + +export const GroupSchema = SchemaFactory.createForClass(Group); + +GroupSchema.index({ id: 1 }, { unique: true }); -- GitLab