From 35afec909437170a32b45e2d8367190fe0f98be5 Mon Sep 17 00:00:00 2001 From: Gaston Morixe Date: Wed, 31 Mar 2021 22:37:42 -0300 Subject: [PATCH] Finishes OneToMany Decorator for Moongose --- package.json | 2 + src/entities/calendar/calendar.model.ts | 1 - .../calendarEvent/calendarEvent.model.ts | 1 - src/entities/event/event.model.ts | 1 - src/entities/school/school.model.ts | 13 +-- src/entities/school/school.resolver.ts | 2 +- src/entities/subject/subject.model.ts | 1 - src/entities/user/user.model.ts | 2 - src/schema.gql | 2 +- src/utils/Model.ts | 89 +++++++++---------- yarn.lock | 7 +- 11 files changed, 54 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 6cfc699..b2c566e 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,12 @@ "mongoose": "^5.11.19", "passport": "^0.4.1", "passport-jwt": "^4.0.0", + "pluralize": "^8.0.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rrule": "^2.6.8", "rxjs": "^6.6.6", + "titleize": "^2.1.0", "type-fest": "^0.21.3" }, "devDependencies": { diff --git a/src/entities/calendar/calendar.model.ts b/src/entities/calendar/calendar.model.ts index da71be2..195ace2 100644 --- a/src/entities/calendar/calendar.model.ts +++ b/src/entities/calendar/calendar.model.ts @@ -12,7 +12,6 @@ import { CalendarEvent } from '../calendarEvent/calendarEvent.model' timestamps: true, // autoIndex: true }) -@Utils.ValidateSchema() export class Calendar extends Utils.Model { @GQL.Field(() => GQL.ID) _id: mongoose.Types.ObjectId diff --git a/src/entities/calendarEvent/calendarEvent.model.ts b/src/entities/calendarEvent/calendarEvent.model.ts index c285aaa..2937363 100644 --- a/src/entities/calendarEvent/calendarEvent.model.ts +++ b/src/entities/calendarEvent/calendarEvent.model.ts @@ -11,7 +11,6 @@ import { Calendar } from '../calendar/calendar.model' timestamps: true, // autoIndex: true }) -@Utils.ValidateSchema() export class CalendarEvent extends Utils.Model { @GQL.Field(() => GQL.ID) _id: mongoose.Types.ObjectId diff --git a/src/entities/event/event.model.ts b/src/entities/event/event.model.ts index a7c1f38..2b9e67b 100644 --- a/src/entities/event/event.model.ts +++ b/src/entities/event/event.model.ts @@ -11,7 +11,6 @@ import { CalendarEvent } from '../calendarEvent/calendarEvent.model' @DB.Schema({ autoIndex: true, }) -@Utils.ValidateSchema() export class Event extends Utils.Model { @GQL.Field(() => GQL.ID) _id: mongoose.Types.ObjectId diff --git a/src/entities/school/school.model.ts b/src/entities/school/school.model.ts index 88e3c0d..ebccbb5 100644 --- a/src/entities/school/school.model.ts +++ b/src/entities/school/school.model.ts @@ -12,7 +12,6 @@ import { Subject } from '../subject/subject.model' timestamps: true, // autoIndex: true }) -@Utils.ValidateSchema() export class School extends Utils.Model { @GQL.Field(() => GQL.ID) _id: mongoose.Types.ObjectId @@ -21,20 +20,10 @@ export class School extends Utils.Model { @DB.Prop() name: string - //@DB.Prop({ type: [mongoose.Schema.Types.ObjectId], ref: 'Subject' }) - @GQL.Field(() => [Subject]) + @GQL.Field(() => [Subject], { nullable: true }) @Utils.OneToMany() subjects: mongoose.Types.ObjectId[] | Subject[] } export type SchoolDocument = School & mongoose.Document export const SchoolSchema = School.schema -// SchoolSchema.virtual('subjects', { -// ref: 'Subject', -// localField: '_id', -// foreignField: 'school' -// }) - -// SchoolSchema.set('toObject', { virtuals: true }) -// SchoolSchema.set('toJSON', { virtuals: true }) - diff --git a/src/entities/school/school.resolver.ts b/src/entities/school/school.resolver.ts index b2d264e..7670019 100644 --- a/src/entities/school/school.resolver.ts +++ b/src/entities/school/school.resolver.ts @@ -23,7 +23,7 @@ import { Subject } from '../subject/subject.model' @Resolver(() => School) export class SchoolResolver { - constructor(private service: SchoolService) { } + constructor(private service: SchoolService) {} @Query(() => School) async school(@Args('_id', { type: () => ID }) _id: Types.ObjectId) { diff --git a/src/entities/subject/subject.model.ts b/src/entities/subject/subject.model.ts index b952d86..0105d26 100644 --- a/src/entities/subject/subject.model.ts +++ b/src/entities/subject/subject.model.ts @@ -12,7 +12,6 @@ import { School } from '../school/school.model' timestamps: true, // autoIndex: true }) -@Utils.ValidateSchema() export class Subject extends Utils.Model { @GQL.Field(() => GQL.ID) _id: mongoose.Types.ObjectId diff --git a/src/entities/user/user.model.ts b/src/entities/user/user.model.ts index f33215d..4c8f280 100644 --- a/src/entities/user/user.model.ts +++ b/src/entities/user/user.model.ts @@ -10,7 +10,6 @@ import * as Utils from '../../utils/Model' timestamps: true, // autoIndex: true }) -@Utils.ValidateSchema() export class User extends Utils.Model { @GQL.Field(() => GQL.ID) _id: mongoose.Types.ObjectId @@ -18,7 +17,6 @@ export class User extends Utils.Model { @GQL.Field(() => String, { nullable: true }) @DB.Prop({ required: false, min: 3, max: 100 }) fullName: string - // @V.Contains('franco') @GQL.Field(() => String, { nullable: false }) @DB.Prop({ diff --git a/src/schema.gql b/src/schema.gql index 56465fb..8e30696 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -159,7 +159,7 @@ type Query { type School { _id: ID! name: String! - subjects(populate: Boolean!): [Subject!]! + subjects(populate: Boolean!): [Subject!] } type Subject { diff --git a/src/utils/Model.ts b/src/utils/Model.ts index 411aac6..05a4fb9 100644 --- a/src/utils/Model.ts +++ b/src/utils/Model.ts @@ -1,8 +1,9 @@ import * as DB from '@nestjs/mongoose' // { Prop, Schema, SchemaFactory } import * as V from 'class-validator' -import * as Reflect from 'reflect-metadata' import { ApolloError } from 'apollo-server-errors' import { plainToClass } from 'class-transformer' +import pluralize from 'pluralize' +import titleize from 'titleize' export class ModelValidationError extends ApolloError { public path: any[] @@ -17,8 +18,6 @@ export class ModelValidationError extends ApolloError { ) { super(message, 'MODEL_VALIDATION_FAILED', extensions) this.path = path - // this.name = 'ModelValidationError' - // Object.defineProperty(this, 'name', { value: 'ValidationError' }) } } @@ -32,55 +31,35 @@ export function ValidateSchema() { } } -function WithRelations() { - return function (target: T) { - target.__withRelations__ = true - return target - } +interface IOneToManyOptions { + ref?: string + foreignField?: string + localField?: string } - -/*export function HasMany(options: { - field: string, - ref: string, - //foreignField: string -}) { - const { field, ref } = options - return function (target: T) { - target.schema.virtual(field, { +export function OneToMany(options: IOneToManyOptions = {}) { + const { ref } = options + return (target: any, propertyKey: string) => { + const model = target.constructor as typeof Model + model.__assoc__ = model.__assoc__ || {} + model.__assoc__['OneToMany'] = model.__assoc__['OneToMany'] || [] + model.__assoc__['OneToMany'].push({ + target, + propertyKey, ref, - localField: '_id', - foreignField: target.name.toLowerCase() + foreignField, + localField, }) - //console.log(target.name.toLowerCase()) - target.schema.set('toObject', { virtuals: true }) - target.schema.set('toJSON', { virtuals: true }) - - return target - } -}*/ - -type ObjectType = { new(): T } | Function; -// string | ((type?: any) => ObjectType) -export function OneToMany(typeFunctionOrTarget?: string | ((type?: any) => ObjectType)): PropertyDecorator { - return function (object: Object, propertyName: string) { - console.log(arguments) - console.log(object) } } -// function format(formatString: string) { -// return Reflect.metadata(formatMetadataKey, formatString); -// } - - /** * Base Model */ -@WithRelations() +@ValidateSchema() export class Model { static __validate__: boolean | undefined - static __withRelations__: boolean | undefined + static __assoc__: Record | undefined static schema?: ReturnType } @@ -96,18 +75,36 @@ Object.defineProperty(Model, 'schema', { // eslint-disable-next-line const klass = this - if (this['__withRelations__']) { - schema.pre('findOneAndDelete', async function (next) { - console.log('findOneAndDelete:', this) - next(undefined) - }) + const assocs = this?.__assoc__?.['OneToMany'] + if (assocs) { + for (const assoc of assocs) { + // TODO Cleanup to a function + const { + target, + propertyKey, + foreignField: _foreignField, + ref: _ref, + localField: _localField, + } = assoc + const model = target.constructor as typeof Model + const foreignField = _foreignField ?? model.name.toLowerCase() + const localField = _localField ?? '_id' + const ref = _ref ?? titleize(pluralize.singular(`${propertyKey}`)) + model?.schema.virtual(propertyKey, { + ref, + localField, + foreignField, + }) + model?.schema.set('toObject', { virtuals: true }) + model?.schema.set('toJSON', { virtuals: true }) + } } if (this['__validate__']) { + // TODO Cleanup to a function schema.pre('validate', async function (next) { const modelData = this.toObject() const model = plainToClass(klass, modelData) - try { console.log('model', model) await V.validateOrReject(model) diff --git a/yarn.lock b/yarn.lock index 8a2a0cb..d3a3d81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7167,7 +7167,7 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" -pluralize@8.0.0: +pluralize@8.0.0, pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== @@ -8410,6 +8410,11 @@ through@^2.3.6: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +titleize@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-2.1.0.tgz#5530de07c22147a0488887172b5bd94f5b30a48f" + integrity sha512-m+apkYlfiQTKLW+sI4vqUkwMEzfgEUEYSqljx1voUE3Wz/z1ZsxyzSxvH2X8uKVrOp7QkByWt0rA6+gvhCKy6g== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"