import { Schema, Error, Types } from 'mongoose'; import { UserInputError } from 'apollo-server-micro'; import { RecipeFilterAction, RecipeFilterAttribute, buildFilterQuery, validateFilterItem, } from './filters'; import { RecipeCollection, RecipeCollectionType, RecipeCollectionProps, } from './collection-model'; import { Recipe } from '../recipe/recipe-model'; // // Type Defs // export interface SmartRecipeCollectionProps extends RecipeCollectionProps { filters: RecipeCollectionFilterItemProps[]; } export interface RecipeCollectionFilterItemProps { attribute: RecipeFilterAttribute; action: RecipeFilterAction; value: string; } export interface RecipeCollectionFilterItem extends Types.Embedded, RecipeCollectionFilterItemProps {} export interface SmartRecipeCollection extends RecipeCollection { filters: Types.DocumentArray; // methods getRecipes: (this: SmartRecipeCollection) => Recipe[]; addFilters: ( filters: Partial[], options?: { order?: number } ) => RecipeCollection; updateFilter: ( this: RecipeCollection, props: Partial & { id: Types.ObjectId | string; }, options?: { order?: number } ) => RecipeCollection; removeFilter: (filter: Types.ObjectId | string) => RecipeCollection; } const recipeCollectionFilterItemSchema = new Schema({ attribute: { type: Schema.Types.String, enum: Object.values(RecipeFilterAttribute), required: true, }, action: { type: Schema.Types.String, enum: Object.values(RecipeFilterAction), required: true, }, value: { type: Schema.Types.String, required: true, }, }); const SmartCollectionSchema = new Schema( { filters: [recipeCollectionFilterItemSchema], }, { discriminatorKey: 'type' } ); // // Custom Methods // SmartCollectionSchema.methods = { async getRecipes() { let query = Recipe.find({ ownerId: this.ownerId }); query = buildFilterQuery(this.filters, query); const recipes = await query.exec(); return recipes; }, /** * Add an item to the filter list */ async addFilters( this: SmartRecipeCollection, filters: RecipeCollectionFilterItemProps[], options: { order?: number } = {} ) { const { order } = options; const filterDocs = filters.map(filter => { const filterDoc = this.filters.create(filter); try { validateFilterItem(filterDoc); } catch (e) { throw new UserInputError(e.message); } return filterDoc; }); if (order) { this.filters.splice(order, 0, filterDocs); } else { this.filters.addToSet(...filterDocs); } return this; }, /** * Update an item in the filter list */ async updateFilter( this: SmartRecipeCollection, props: Partial & { id: Types.ObjectId | string; }, options?: { order?: number } ) { const filter = this.filters.id(props.id); const { order } = options; if (filter === null) { throw new Error.ValidatorError({ message: 'Unable to Find Filter in Collection', }); } filter.set(props); // move order if (order !== undefined && order < this.filters.length) { filter.remove(); this.filters.splice(order, 0, filter); } return this; }, /** * Remove an item from the filter list */ async removeFilter(this: SmartRecipeCollection, id: Types.ObjectId | string) { const filter = this.filters.id(id); if (!filter) { throw new Error.ValidatorError({ message: 'No Matching Recipe was found in the collection', }); } else { filter.remove(); } return this; }, }; // // Model // export const SmartRecipeCollection = RecipeCollection.discriminator< SmartRecipeCollection >(RecipeCollectionType.SMART, SmartCollectionSchema);