some progress
This commit is contained in:
parent
c4d2cba4cb
commit
b8ff56847a
@ -3,7 +3,7 @@ services:
|
|||||||
mongodb:
|
mongodb:
|
||||||
image: mongo:6
|
image: mongo:6
|
||||||
container_name: mongo
|
container_name: mongo
|
||||||
restart: unless-stopped
|
restart: 'no'
|
||||||
ports:
|
ports:
|
||||||
- 27017:27017
|
- 27017:27017
|
||||||
environment:
|
environment:
|
||||||
|
8
src/decorators/token.decorator.ts
Normal file
8
src/decorators/token.decorator.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const Token = createParamDecorator(
|
||||||
|
(data: unknown, ctx: ExecutionContext) => {
|
||||||
|
const response = ctx.switchToHttp().getResponse();
|
||||||
|
return response.locals.token as string;
|
||||||
|
},
|
||||||
|
);
|
20
src/guards/auth.guard.ts
Normal file
20
src/guards/auth.guard.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import type { Response, Request } from 'express';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
canActivate(
|
||||||
|
context: ExecutionContext,
|
||||||
|
): boolean | Promise<boolean> | Observable<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
const response = context.switchToHttp().getResponse<Response>();
|
||||||
|
const authHeader = request.header('Authorization');
|
||||||
|
if (!authHeader) return true; // false;
|
||||||
|
const [, token] = authHeader.split(' ');
|
||||||
|
if (!token) return true; // false
|
||||||
|
// Validate `token` JWT here
|
||||||
|
response.locals.token = token;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
14
src/industry-change-application/dtos/list-query.dto.ts
Normal file
14
src/industry-change-application/dtos/list-query.dto.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
import { ApplicationStatus } from 'src/enums/status.enum';
|
||||||
|
|
||||||
|
export class ListQueryDto {
|
||||||
|
@IsOptional()
|
||||||
|
@Transform(({ value }) => value.split(','))
|
||||||
|
@IsEnum(ApplicationStatus, { each: true })
|
||||||
|
readonly statuses?: ApplicationStatus[];
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
readonly residentSub: string;
|
||||||
|
}
|
@ -1,43 +1,30 @@
|
|||||||
import { Exclude, Expose } from 'class-transformer';
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsEnum,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateIf,
|
||||||
|
} from 'class-validator';
|
||||||
import { Industry } from 'src/enums/industry.enum';
|
import { Industry } from 'src/enums/industry.enum';
|
||||||
import { RegulatoryElection } from 'src/enums/regulatory-election.enums';
|
import { RegulatoryElection } from 'src/enums/regulatory-election.enums';
|
||||||
import { ApplicationStatus, ObjectStatus } from 'src/enums/status.enum';
|
|
||||||
import { Resident } from 'src/resident/schemas/Resident.schema';
|
|
||||||
|
|
||||||
export class ApplicationInformationDto {
|
export class RegisterIndustryChangeApplicationDto {
|
||||||
@Exclude()
|
@IsString()
|
||||||
_id: string;
|
residentSub: string;
|
||||||
|
|
||||||
willWorkInPhysicalJuristiction: string;
|
@IsBoolean()
|
||||||
|
willWorkInPhysicalJurisdiction: boolean;
|
||||||
|
|
||||||
|
@IsEnum(Industry)
|
||||||
|
@ValidateIf((o) => o.willWorkInPhysicalJurisdiction === true)
|
||||||
industry: Industry;
|
industry: Industry;
|
||||||
|
|
||||||
|
@IsEnum(RegulatoryElection)
|
||||||
|
@ValidateIf((o) => o.willWorkInPhysicalJurisdiction === true)
|
||||||
regulatoryElection: RegulatoryElection;
|
regulatoryElection: RegulatoryElection;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@ValidateIf((o) => o.willWorkInPhysicalJurisdiction === true)
|
||||||
regulatoryElectionSub: string;
|
regulatoryElectionSub: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DecisionDto {
|
|
||||||
@Exclude()
|
|
||||||
_id: string;
|
|
||||||
|
|
||||||
decidedAt: Date;
|
|
||||||
rejctionReason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GetRegisterIndustryChangeApplicationDto {
|
|
||||||
@Expose({ name: 'id' })
|
|
||||||
_id: string;
|
|
||||||
|
|
||||||
@Exclude()
|
|
||||||
resident: Resident;
|
|
||||||
|
|
||||||
@Expose({ name: 'residentSub' })
|
|
||||||
getResidentSub() {
|
|
||||||
return this.resident.sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
current: ApplicationInformationDto;
|
|
||||||
requested: ApplicationInformationDto;
|
|
||||||
status: ApplicationStatus;
|
|
||||||
submittedAt: Date;
|
|
||||||
decision: DecisionDto;
|
|
||||||
objectStatus: ObjectStatus;
|
|
||||||
}
|
|
||||||
|
@ -1,24 +1,61 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import {
|
||||||
import { plainToInstance } from 'class-transformer';
|
Body,
|
||||||
import { GetRegisterIndustryChangeApplicationDto } from './dtos/register-industry-change-application.dto';
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
ValidationPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Token } from 'src/decorators/token.decorator';
|
||||||
|
import { AuthGuard } from 'src/guards/auth.guard';
|
||||||
|
import { ListQueryDto } from './dtos/list-query.dto';
|
||||||
|
import { RegisterIndustryChangeApplicationDto } from './dtos/register-industry-change-application.dto';
|
||||||
import { IndustryChangeApplicationService } from './industry-change-application.service';
|
import { IndustryChangeApplicationService } from './industry-change-application.service';
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
path: '/resident-register/industry-change-applications',
|
path: '/resident-register/industry-change-applications',
|
||||||
})
|
})
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
export class IndustryChangeApplicationController {
|
export class IndustryChangeApplicationController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly applicationsSerivce: IndustryChangeApplicationService,
|
private readonly applicationsSerivce: IndustryChangeApplicationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async getList() {
|
async getList(
|
||||||
const allApplications = await this.applicationsSerivce.getAll();
|
@Query(new ValidationPipe({ transform: true })) queryOpts: ListQueryDto,
|
||||||
const transformed = plainToInstance(
|
) {
|
||||||
GetRegisterIndustryChangeApplicationDto,
|
const allApplications = await this.applicationsSerivce.getAll(queryOpts);
|
||||||
allApplications,
|
return this.applicationsSerivce.makeReadable(allApplications);
|
||||||
{ excludeExtraneousValues: true },
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async getSingle(@Param('id') id: string) {
|
||||||
|
const findInstance = await this.applicationsSerivce.getById(id);
|
||||||
|
if (!findInstance) return null;
|
||||||
|
return this.applicationsSerivce.makeReadable(findInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async delete(@Param('id') id: string, @Token() authToken: string) {
|
||||||
|
return this.applicationsSerivce.makeReadable(
|
||||||
|
await this.applicationsSerivce.markDeleted(id, authToken),
|
||||||
);
|
);
|
||||||
return transformed;
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async create(
|
||||||
|
@Body() application: RegisterIndustryChangeApplicationDto,
|
||||||
|
@Token() authToken: string,
|
||||||
|
) {
|
||||||
|
const newApplication = await this.applicationsSerivce.create(
|
||||||
|
application,
|
||||||
|
authToken,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.applicationsSerivce.makeReadable(newApplication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { MongooseModule } from '@nestjs/mongoose';
|
import { MongooseModule } from '@nestjs/mongoose';
|
||||||
|
import { ResidentModule } from 'src/resident/resident.module';
|
||||||
import { IndustryChangeApplicationController } from './industry-change-application.controller';
|
import { IndustryChangeApplicationController } from './industry-change-application.controller';
|
||||||
import { IndustryChangeApplicationService } from './industry-change-application.service';
|
import { IndustryChangeApplicationService } from './industry-change-application.service';
|
||||||
import { Decision, DecisionSchema } from './schemas/Decision.schema';
|
import { Decision, DecisionSchema } from './schemas/Decision.schema';
|
||||||
@ -28,6 +29,7 @@ import {
|
|||||||
schema: IndustryChangeApplicationSchema,
|
schema: IndustryChangeApplicationSchema,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
ResidentModule,
|
||||||
],
|
],
|
||||||
controllers: [IndustryChangeApplicationController],
|
controllers: [IndustryChangeApplicationController],
|
||||||
providers: [IndustryChangeApplicationService],
|
providers: [IndustryChangeApplicationService],
|
||||||
|
@ -1,19 +1,156 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Model } from 'mongoose';
|
import { Model } from 'mongoose';
|
||||||
|
import {
|
||||||
|
ApplicationStatus,
|
||||||
|
ObjectStatus,
|
||||||
|
ResidentStatus,
|
||||||
|
} from 'src/enums/status.enum';
|
||||||
|
import { TypeOfRegistration } from 'src/enums/type-of-registration.enums';
|
||||||
|
import { ResidentService } from 'src/resident/resident.service';
|
||||||
|
import { takeMongoObject, equate, take } from 'src/utility';
|
||||||
|
import { ListQueryDto } from './dtos/list-query.dto';
|
||||||
|
import { RegisterIndustryChangeApplicationDto } from './dtos/register-industry-change-application.dto';
|
||||||
import {
|
import {
|
||||||
IndustryChangeApplication,
|
IndustryChangeApplication,
|
||||||
IndustryChangeApplicationDocument,
|
IndustryChangeApplicationDocument,
|
||||||
} from './schemas/IndustryChangeApplication.schema';
|
} from './schemas/IndustryChangeApplication.schema';
|
||||||
|
|
||||||
|
const requestedFields = [
|
||||||
|
'industry',
|
||||||
|
'willWorkInPhysicalJurisdiction',
|
||||||
|
'regulatoryElection',
|
||||||
|
'regulatoryElectionSub',
|
||||||
|
];
|
||||||
|
|
||||||
|
const fieldsToExpose = [
|
||||||
|
'id',
|
||||||
|
'residentSub',
|
||||||
|
'current',
|
||||||
|
'requested',
|
||||||
|
'status',
|
||||||
|
'submittedAt',
|
||||||
|
'decision',
|
||||||
|
'objectStatus',
|
||||||
|
];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IndustryChangeApplicationService {
|
export class IndustryChangeApplicationService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectModel(IndustryChangeApplication.name)
|
@InjectModel(IndustryChangeApplication.name)
|
||||||
private applicationModel: Model<IndustryChangeApplicationDocument>,
|
private applicationModel: Model<IndustryChangeApplicationDocument>,
|
||||||
|
private resident: ResidentService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getAll() {
|
async getAll(options: ListQueryDto) {
|
||||||
return this.applicationModel.find();
|
const getResident = await this.resident.getResidentBySub(
|
||||||
|
options.residentSub,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!getResident) {
|
||||||
|
throw new NotFoundException('Resident not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.applicationModel.find({
|
||||||
|
residentSub: options.residentSub,
|
||||||
|
status: {
|
||||||
|
$in: options.statuses || Object.values(ApplicationStatus),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string) {
|
||||||
|
return this.applicationModel.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: RegisterIndustryChangeApplicationDto, token: string) {
|
||||||
|
// This might be possible to turn into a custom validator for class-validator
|
||||||
|
if (
|
||||||
|
data.willWorkInPhysicalJurisdiction === false &&
|
||||||
|
(!!data.industry ||
|
||||||
|
!!data.regulatoryElection ||
|
||||||
|
!!data.regulatoryElectionSub)
|
||||||
|
) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'industry, regulatoryElection and regulatoryElectionSub are not allowed when willWorkInPhysicalJurisdiction is false',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getResident = await this.resident.getResidentBySub(data.residentSub);
|
||||||
|
if (!getResident) {
|
||||||
|
throw new BadRequestException('Resident not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getResident.status !== ResidentStatus.ACTIVE) {
|
||||||
|
throw new BadRequestException('Resident must be active!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
![TypeOfRegistration.E_RESIDENCY, TypeOfRegistration.RESIDENCY].includes(
|
||||||
|
getResident.typeOfRegistration,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Resident must be either an E-resident or a resident',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
equate(data, getResident, requestedFields).length ===
|
||||||
|
requestedFields.length
|
||||||
|
) {
|
||||||
|
throw new BadRequestException('Cannot request what is already the case.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const status =
|
||||||
|
data.willWorkInPhysicalJurisdiction === true
|
||||||
|
? ApplicationStatus.IN_REVIEW
|
||||||
|
: ApplicationStatus.APPROVED;
|
||||||
|
|
||||||
|
const newApplication = new this.applicationModel({
|
||||||
|
residentSub: getResident.sub,
|
||||||
|
current: take(takeMongoObject(getResident), requestedFields),
|
||||||
|
requested: take(data, requestedFields),
|
||||||
|
status,
|
||||||
|
submittedAt: new Date(),
|
||||||
|
createdBy: token ?? 'no token provided for testing',
|
||||||
|
});
|
||||||
|
|
||||||
|
return newApplication.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async markDeleted(id: string, token: string) {
|
||||||
|
const findApplication = await this.applicationModel.findById(id);
|
||||||
|
|
||||||
|
if (!findApplication) {
|
||||||
|
throw new NotFoundException('Application was not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (findApplication.status !== ApplicationStatus.IN_REVIEW) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Only applications which are currently in review can be deleted.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (findApplication.objectStatus === ObjectStatus.DELETED) {
|
||||||
|
throw new BadRequestException('The object has already been deleted.');
|
||||||
|
}
|
||||||
|
|
||||||
|
findApplication.objectStatus = ObjectStatus.DELETED;
|
||||||
|
findApplication.updatedBy = token || 'no token provided for testing';
|
||||||
|
return findApplication.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// I wrote this because I could not for the life of me get class-transformer to
|
||||||
|
// play along with mongo documents. I do not have experience with either, so this
|
||||||
|
// was a last ditch effort.
|
||||||
|
makeReadable(input: IndustryChangeApplication | IndustryChangeApplication[]) {
|
||||||
|
return Array.isArray(input)
|
||||||
|
? input.map((object) => take(takeMongoObject(object), fieldsToExpose))
|
||||||
|
: take(takeMongoObject(input), fieldsToExpose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||||
import mongoose, { HydratedDocument } from 'mongoose';
|
import { HydratedDocument } from 'mongoose';
|
||||||
import { Resident } from 'src/resident/schemas/Resident.schema';
|
|
||||||
import { Decision } from './Decision.schema';
|
import { Decision } from './Decision.schema';
|
||||||
import { ApplicationStatus, ObjectStatus } from 'src/enums/status.enum';
|
import { ApplicationStatus, ObjectStatus } from 'src/enums/status.enum';
|
||||||
import { ICAInformation } from './ICAInformation.schema';
|
import { ICAInformation } from './ICAInformation.schema';
|
||||||
@ -14,10 +13,8 @@ export type IndustryChangeApplicationDocument =
|
|||||||
export class IndustryChangeApplication {
|
export class IndustryChangeApplication {
|
||||||
@Prop({
|
@Prop({
|
||||||
required: true,
|
required: true,
|
||||||
ref: 'Resident',
|
|
||||||
type: mongoose.Schema.Types.ObjectId,
|
|
||||||
})
|
})
|
||||||
resident: Resident;
|
residentSub: string;
|
||||||
|
|
||||||
@Prop({
|
@Prop({
|
||||||
required: true,
|
required: true,
|
||||||
@ -58,6 +55,10 @@ export class IndustryChangeApplication {
|
|||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
objectStatus: ObjectStatus;
|
objectStatus: ObjectStatus;
|
||||||
|
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IndustryChangeApplicationSchema = SchemaFactory.createForClass(
|
export const IndustryChangeApplicationSchema = SchemaFactory.createForClass(
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
app.useGlobalPipes(new ValidationPipe());
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Model } from 'mongoose';
|
import { Model } from 'mongoose';
|
||||||
import { Resident, ResidentDocument } from './schemas/Resident.schema';
|
import { Resident, ResidentDocument } from './schemas/Resident.schema';
|
||||||
import { ResidentAddress } from './schemas/ResidentAddress.schema';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResidentService {
|
export class ResidentService {
|
||||||
@ -14,4 +13,8 @@ export class ResidentService {
|
|||||||
const createdResident = new this.residentModel(resident);
|
const createdResident = new this.residentModel(resident);
|
||||||
return createdResident.save();
|
return createdResident.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getResidentBySub(sub: string): Promise<Resident> {
|
||||||
|
return this.residentModel.findOne({ sub });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
src/utility/equate.ts
Normal file
17
src/utility/equate.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Equate keys of two objects
|
||||||
|
* @param object1 First object
|
||||||
|
* @param object2 Second object
|
||||||
|
* @param keys Keys to equate
|
||||||
|
* @returns Keys which are equal in both objects
|
||||||
|
*/
|
||||||
|
export default function equate<T, K>(
|
||||||
|
object1: T,
|
||||||
|
object2: K,
|
||||||
|
keys: string[],
|
||||||
|
): string[] {
|
||||||
|
return keys.reduce<string[]>((list, current) => {
|
||||||
|
if (object1[current] === object2[current]) return [...list, current];
|
||||||
|
return list;
|
||||||
|
}, []);
|
||||||
|
}
|
3
src/utility/index.ts
Normal file
3
src/utility/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { default as take } from './take';
|
||||||
|
export { default as equate } from './equate';
|
||||||
|
export { default as takeMongoObject } from './take-mongo-object';
|
17
src/utility/take-mongo-object.ts
Normal file
17
src/utility/take-mongo-object.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Take the plain javascript object from a mongo document.
|
||||||
|
* I wrote this because I could not for the life of me get class-transformer to
|
||||||
|
* play along with mongo documents.
|
||||||
|
* @param mongoObj Mongo database response
|
||||||
|
* @returns Plain javascript object
|
||||||
|
*/
|
||||||
|
export default function takeMongoObject<T>(mongoObj: T): T {
|
||||||
|
const dirty = (mongoObj as Record<string, unknown>)._doc as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
|
dirty.id = dirty._id.toString();
|
||||||
|
delete dirty._id;
|
||||||
|
delete dirty.__v;
|
||||||
|
return dirty as T;
|
||||||
|
}
|
9
src/utility/take.ts
Normal file
9
src/utility/take.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default function take<T>(object: T, keys: string[]): Partial<T> {
|
||||||
|
if (!object) return null;
|
||||||
|
return Object.keys(object).reduce<Partial<T>>((obj, field) => {
|
||||||
|
if (keys.includes(field)) {
|
||||||
|
obj[field] = object[field];
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
0
src/validators/registration.validator.ts
Normal file
0
src/validators/registration.validator.ts
Normal file
Loading…
Reference in New Issue
Block a user