diff --git a/package.json b/package.json index 0bd71e0..87bb229 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "apollo-tracing": "^0.4.0", "bcrypt-nodejs": "^0.0.3", "body-parser": "^1.15.0", + "chai-as-promised": "^7.1.1", "chai-datetime": "^1.5.0", "compression": "^1.7.3", "cors": "^2.8.1", diff --git a/src/apollo/resolvers/mealPlan.ts b/src/apollo/resolvers/mealPlan.ts index ebb2a98..df12c95 100644 --- a/src/apollo/resolvers/mealPlan.ts +++ b/src/apollo/resolvers/mealPlan.ts @@ -5,9 +5,12 @@ import { IContext, IObjectId } from 'types/global'; import { IMealEvent, IMealEventAddInput, + IMealEventEditInput, + IMealMacro, IMealPlanRangeInput, IWorkoutEvent, IWorkoutEventAddInput, + IWorkoutEventEditInput, } from 'types/mealPlan'; import { IRecipe } from 'types/recipe'; @@ -16,6 +19,8 @@ import { IAccount } from 'types/account'; export default { MealEvent: { + macros: async (mealEvent: { _id: IObjectId }): Promise => + MealPlan.getMealMacros(mealEvent._id), recipes: async (mealEvent: { recipes: IObjectId[] }): Promise => Recipe.findByIds(mealEvent.recipes), }, @@ -45,6 +50,21 @@ export default { data: IWorkoutEventAddInput, ctx: IContext ): Promise => MealPlan.addWorkoutEvent(data, ctx), + deleteEvent: async ( + _: object, + { id }: { id: IObjectId }, + ctx: IContext + ): Promise => MealPlan.deleteEvent(id, ctx), + editMealEvent: async ( + _: object, + data: IMealEventEditInput, + ctx: IContext + ): Promise => MealPlan.editMealEvent(data, ctx), + editWorkoutEvent: async ( + _: object, + data: IWorkoutEventEditInput, + ctx: IContext + ): Promise => MealPlan.editWorkoutEvent(data, ctx), }, Query: { mealPlanEvents: async ( diff --git a/src/apollo/types/mealPlan.gql b/src/apollo/types/mealPlan.gql index 6e45afc..e185351 100644 --- a/src/apollo/types/mealPlan.gql +++ b/src/apollo/types/mealPlan.gql @@ -5,6 +5,13 @@ interface MealPlanEvent { owner: Account } +type MealMacros { + carbs: Float + protein: Float + fat: Float + calories: Float +} + type MealEvent implements MealPlanEvent { _id: ID startTime: Date @@ -12,6 +19,7 @@ type MealEvent implements MealPlanEvent { owner: Account mealType: String recipes: [Recipe] + macros: MealMacros } type WorkoutEvent implements MealPlanEvent { @@ -30,11 +38,25 @@ input MealEventAddInput { mealType: String! } +input MealEventEditInput { + _id: ID! + startTime: Date! + endTime: Date! + recipes: [ID]! + mealType: String! +} + input WorkoutEventAddInput { startTime: Date! endTime: Date! } +input WorkoutEventEditInput { + _id: ID! + startTime: Date! + endTime: Date! +} + input MealPlanRangeInput { startDay: Date! endDay: Date! @@ -49,5 +71,10 @@ type Mutation { @auth(role: USER) addWorkoutEvent(input: WorkoutEventAddInput!): WorkoutEvent @auth(role: USER) - # deleteRecipe(id: ID!): Boolean @auth(role: USER) -} \ No newline at end of file + deleteEvent(id: ID!): Boolean + @auth(role: USER) + editMealEvent(input: MealEventEditInput!): MealEvent + @auth(role: USER) + editWorkoutEvent(input: WorkoutEventEditInput!): WorkoutEvent + @auth(role: USER) +} diff --git a/src/apollo/types/recipe.gql b/src/apollo/types/recipe.gql index 0b01ad4..6aa76fc 100644 --- a/src/apollo/types/recipe.gql +++ b/src/apollo/types/recipe.gql @@ -28,7 +28,7 @@ type RecipeRating { } type Recipe { - id: ID + _id: ID slug: String title: String description: String diff --git a/src/context/auth/index.ts b/src/context/auth/index.ts index 60c25b4..1a20494 100644 --- a/src/context/auth/index.ts +++ b/src/context/auth/index.ts @@ -22,7 +22,6 @@ export default class Auth { } public async signup(data: ISignupInput): Promise { - console.log('Here', data); const doesAccountExist = await AccountContext.emailExists(data.input.email); if (doesAccountExist) { diff --git a/src/context/mealplan/__test__/mealplan.spec.ts b/src/context/mealplan/__test__/mealplan.spec.ts index fa45e6b..f6e67a9 100644 --- a/src/context/mealplan/__test__/mealplan.spec.ts +++ b/src/context/mealplan/__test__/mealplan.spec.ts @@ -1,13 +1,14 @@ import chai, { expect } from 'chai'; import chaiDatetime from 'chai-datetime'; import 'mocha'; -import moment from 'moment'; -import mongoose from 'mongoose'; -import { IContext } from 'types/global'; + +import { IContext, IObjectId } from 'types/global'; import { IMealEventAddInput, + IMealEventEditInput, IMealPlanRangeInput, IWorkoutEventAddInput, + IWorkoutEventEditInput, } from 'types/mealPlan'; import '../../../../tests'; import { fakeAccount } from '../../../../tests/stub/account'; @@ -21,9 +22,8 @@ import MealPlanContext from '../index'; chai.use(chaiDatetime); describe('Meal Plan Context', () => { - const user1 = fakeAccount({ - // _id: new mongoose.mongo.ObjectID('5d1c935b0a13de0022030159'), - }); + const user1 = fakeAccount({}); + const user2 = fakeAccount({}); const recipe1 = fakeRecipe({ createdBy: user1._id }); const recipe2 = fakeRecipe({ createdBy: user1._id }); @@ -49,6 +49,12 @@ describe('Meal Plan Context', () => { startTime: new Date('2019-08-28T13:30:00'), }); + const mealEvent5 = fakeMealEvent({ + endTime: new Date('2019-09-28T15:30:00'), + owner: user2._id, + startTime: new Date('2019-08-28T13:30:00'), + }); + const workoutEvent1 = fakeWorkoutEvent({ endTime: new Date('2019-08-28T15:30:00'), owner: user1._id, @@ -64,12 +70,14 @@ describe('Meal Plan Context', () => { before(async () => { try { await user1.save(); + await user2.save(); await recipe1.save(); await recipe2.save(); await mealEvent1.save(); await mealEvent2.save(); await mealEvent3.save(); await mealEvent4.save(); + await mealEvent5.save(); await workoutEvent1.save(); await workoutEvent2.save(); } catch (e) { @@ -110,16 +118,206 @@ describe('Meal Plan Context', () => { expect(eventIds).to.not.include(workoutEvent2._id.toString()); }); - it('should create a new MealEvent', async () => { - const data: IMealEventAddInput = { - input: { - endTime: new Date('2019-08-28T13:30:00'), - mealType: 'mt-1', - recipes: [recipe1._id], - startTime: new Date('2019-08-28T12:30:00'), - }, - }; + describe('Meal Event', () => { + it('should create a new MealEvent', async () => { + const data: IMealEventAddInput = { + input: { + endTime: new Date('2019-08-28T13:30:00'), + mealType: 'mt-1', + recipes: [recipe1._id], + startTime: new Date('2019-08-28T12:30:00'), + }, + }; + + const ctx: IContext = { + user: { + _id: user1._id, + email: user1.email, + firstName: user1.firstName, + lastName: user1.lastName, + }, + }; + + const result = await MealPlanContext.addMealEvent(data, ctx); + expect(result).to.have.property('_id'); + expect(result).to.have.property('owner'); + expect(result) + .to.have.property('startTime') + .to.equalDate(new Date('2019-08-28T12:30:00')); + expect(result) + .to.have.property('endTime') + .to.equalDate(new Date('2019-08-28T13:30:00')); + expect(result).to.have.property('mealType', 'mt-1'); + expect(result).to.have.property('type', 'MealEvent'); + expect(result.recipes) + .to.be.an('array') + .that.include(recipe1._id); + }); + + it('should edit a MealEvent', async () => { + const data: IMealEventEditInput = { + input: { + _id: mealEvent4._id, + endTime: new Date('2019-11-28T15:30:00'), + mealType: 'mt-2', + recipes: [recipe2._id], + startTime: new Date('2019-11-28T14:30:00'), + }, + }; + + const ctx: IContext = { + user: { + _id: user1._id, + email: user1.email, + firstName: user1.firstName, + lastName: user1.lastName, + }, + }; + + const result = await MealPlanContext.editMealEvent(data, ctx); + + expect(result).to.have.property('_id'); + expect(result).to.have.property('owner'); + expect(result) + .to.have.property('startTime') + .to.equalDate(new Date('2019-11-28T14:30:00')); + expect(result) + .to.have.property('endTime') + .to.equalDate(new Date('2019-11-28T15:30:00')); + expect(result).to.have.property('mealType', 'mt-2'); + expect(result).to.have.property('type', 'MealEvent'); + + expect(result.recipes).to.be.an('array'); + + const recipes = result.recipes.map((id: IObjectId) => id.toString()); + expect(recipes) + .to.be.an('array') + .that.include(recipe2._id.toString()); + + // Ensure recipe 1 is removed + expect(recipes) + .to.be.an('array') + .that.not.include(recipe1._id.toString()); + }); + + it('should reject editing a MealEvent if the owner is not correct', async () => { + const data: IMealEventEditInput = { + input: { + _id: mealEvent2._id, + endTime: new Date('2019-11-28T15:30:00'), + mealType: 'mt-2', + recipes: [recipe2._id], + startTime: new Date('2019-11-28T14:30:00'), + }, + }; + + const ctx: IContext = { + user: { + _id: user2._id, + email: user2.email, + firstName: user2.firstName, + lastName: user2.lastName, + }, + }; + + try { + await MealPlanContext.editMealEvent(data, ctx); + } catch (error) { + expect(error).to.be.an('error'); + } + }); + }); + + describe('Workout Event', () => { + it('should create a new WorkoutEvent', async () => { + const data: IWorkoutEventAddInput = { + input: { + endTime: new Date('2019-08-28T13:30:00'), + startTime: new Date('2019-08-28T12:30:00'), + }, + }; + + const ctx: IContext = { + user: { + _id: user1._id, + email: user1.email, + firstName: user1.firstName, + lastName: user1.lastName, + }, + }; + + const result = await MealPlanContext.addWorkoutEvent(data, ctx); + + expect(result).to.have.property('_id'); + expect(result).to.have.property('owner'); + expect(result) + .to.have.property('startTime') + .to.equalDate(new Date('2019-08-28T12:30:00')); + expect(result) + .to.have.property('endTime') + .to.equalDate(new Date('2019-08-28T13:30:00')); + expect(result).to.have.property('type', 'WorkoutEvent'); + }); + + it('should edit a WorkoutEvent', async () => { + const data: IWorkoutEventEditInput = { + input: { + _id: workoutEvent1._id, + endTime: new Date('2019-11-28T15:30:00'), + startTime: new Date('2019-11-28T14:30:00'), + }, + }; + + const ctx: IContext = { + user: { + _id: user1._id, + email: user1.email, + firstName: user1.firstName, + lastName: user1.lastName, + }, + }; + + const result = await MealPlanContext.editWorkoutEvent(data, ctx); + + expect(result).to.have.property('_id'); + expect(result).to.have.property('owner'); + expect(result) + .to.have.property('startTime') + .to.equalDate(new Date('2019-11-28T14:30:00')); + expect(result) + .to.have.property('endTime') + .to.equalDate(new Date('2019-11-28T15:30:00')); + expect(result).to.have.property('type', 'WorkoutEvent'); + }); + + it('should reject editing a WorkoutEvent if the owner is not correct', async () => { + const data: IWorkoutEventEditInput = { + input: { + _id: workoutEvent2._id, + endTime: new Date('2019-08-28T13:30:00'), + startTime: new Date('2019-08-28T12:30:00'), + }, + }; + + const ctx: IContext = { + user: { + _id: user2._id, + email: user2.email, + firstName: user2.firstName, + lastName: user2.lastName, + }, + }; + + try { + await MealPlanContext.editWorkoutEvent(data, ctx); + } catch (error) { + expect(error).to.be.an('error'); + } + }); + }); + + it('should delete an event', async () => { const ctx: IContext = { user: { _id: user1._id, @@ -129,31 +327,17 @@ describe('Meal Plan Context', () => { }, }; - const result = await MealPlanContext.addMealEvent(data, ctx); - - expect(result).to.have.property('_id'); - expect(result).to.have.property('owner'); - expect(result) - .to.have.property('startTime') - .to.equalDate(new Date('2019-08-28T12:30:00')); - expect(result) - .to.have.property('endTime') - .to.equalDate(new Date('2019-08-28T13:30:00')); - expect(result).to.have.property('mealType', 'mt-1'); - expect(result).to.have.property('type', 'MealEvent'); - expect(result.recipes) - .to.be.an('array') - .that.include(recipe1._id); - }); + await MealPlanContext.deleteEvent(mealEvent1._id, ctx); - it('should create a new WorkoutEvent', async () => { - const data: IWorkoutEventAddInput = { - input: { - endTime: new Date('2019-08-28T13:30:00'), - startTime: new Date('2019-08-28T12:30:00'), - }, - }; + const expected = await MealPlanContext.findBy( + mealEvent1._id.toString(), + '_id' + ); + + expect(expected).to.be.a('null'); + }); + it('should throw an error when deleting, if the user is not the owner', async () => { const ctx: IContext = { user: { _id: user1._id, @@ -163,16 +347,10 @@ describe('Meal Plan Context', () => { }, }; - const result = await MealPlanContext.addWorkoutEvent(data, ctx); - - expect(result).to.have.property('_id'); - expect(result).to.have.property('owner'); - expect(result) - .to.have.property('startTime') - .to.equalDate(new Date('2019-08-28T12:30:00')); - expect(result) - .to.have.property('endTime') - .to.equalDate(new Date('2019-08-28T13:30:00')); - expect(result).to.have.property('type', 'WorkoutEvent'); + try { + await MealPlanContext.deleteEvent(mealEvent5._id, ctx); + } catch (error) { + expect(error).to.be.an('error'); + } }); }); diff --git a/src/context/mealplan/index.ts b/src/context/mealplan/index.ts index 04eb372..f9de011 100644 --- a/src/context/mealplan/index.ts +++ b/src/context/mealplan/index.ts @@ -2,10 +2,13 @@ import { IContext, ILimitSkipInput, IObjectId } from 'types/global'; import { IMealEvent, IMealEventAddInput, + IMealEventEditInput, + IMealMacro, IMealPlanEvent, IMealPlanRangeInput, IWorkoutEvent, IWorkoutEventAddInput, + IWorkoutEventEditInput, } from 'types/mealPlan'; import MealPlanService from './services'; @@ -20,10 +23,23 @@ export default { data: IWorkoutEventAddInput, ctx: IContext ): Promise => mealPlanService.addWorkoutEvent(data, ctx), + deleteEvent: (id: IObjectId, ctx: IContext): Promise => + mealPlanService.deleteEvent(id, ctx), + editMealEvent: ( + data: IMealEventEditInput, + ctx: IContext + ): Promise => mealPlanService.editMealEvent(data, ctx), + editWorkoutEvent: ( + data: IWorkoutEventEditInput, + ctx: IContext + ): Promise => mealPlanService.editWorkoutEvent(data, ctx), + findBy: (field: string, fieldName: string) => + mealPlanService.findBy(field, fieldName), findWithinRange: ( data: IMealPlanRangeInput, ctx: IContext ): Promise> => mealPlanService.findWithinRange(data, ctx), - // list: (): Promise => mealPlanService.list(), + getMealMacros: (mealId: IObjectId): Promise => + mealPlanService.getMealMacros(mealId), }; diff --git a/src/context/mealplan/repo/MealEventRepo.ts b/src/context/mealplan/repo/MealEventRepo.ts new file mode 100644 index 0000000..0ac44b3 --- /dev/null +++ b/src/context/mealplan/repo/MealEventRepo.ts @@ -0,0 +1,45 @@ +import Repository from '@lib/Repository'; +import { IAccount } from 'types/account'; + +import { + IMealEvent, + IMealEventAddInput, + IMealEventEditInput, +} from 'types/mealPlan'; +import { MealEvent } from '../schema/MealEvent'; + +export default class IMealEventRepo extends Repository { + constructor() { + super(MealEvent); + } + + public async create( + data: IMealEventAddInput, + user: IAccount + ): Promise { + const mealEventData = { + endTime: data.input.endTime, + mealType: data.input.mealType, + owner: user._id, + recipes: data.input.recipes, + startTime: data.input.startTime, + timezoneOffset: data.input.startTime.getTimezoneOffset(), + }; + + const mealEvent = new MealEvent(mealEventData); + return mealEvent.save(); + } + + public async edit(data: IMealEventEditInput): Promise { + const query = { _id: data.input._id }; + const set = { + endTime: data.input.endTime, + mealType: data.input.mealType, + recipes: data.input.recipes, + startTime: data.input.startTime, + timezoneOffset: data.input.startTime.getTimezoneOffset(), + }; + + return this.findOneAndUpdate(query, set); + } +} diff --git a/src/context/mealplan/repo/WorkoutEventRepo.ts b/src/context/mealplan/repo/WorkoutEventRepo.ts new file mode 100644 index 0000000..fea0579 --- /dev/null +++ b/src/context/mealplan/repo/WorkoutEventRepo.ts @@ -0,0 +1,42 @@ +import Repository from '@lib/Repository'; +import { IAccount } from 'types/account'; + +import { + IWorkoutEvent, + IWorkoutEventAddInput, + IWorkoutEventEditInput, +} from 'types/mealPlan'; +import { MealPlanEvent } from '../schema'; +import { WorkoutEvent } from '../schema/WorkoutEvent'; + +export default class IWorkoutPlanEventRepo extends Repository { + constructor() { + super(MealPlanEvent); + } + + public async create( + data: IWorkoutEventAddInput, + user: IAccount + ): Promise { + const workoutEventData = { + endTime: data.input.endTime, + owner: user._id, + startTime: data.input.startTime, + timezoneOffset: data.input.startTime.getTimezoneOffset(), + }; + + const workoutEvent = new WorkoutEvent(workoutEventData); + return workoutEvent.save(); + } + + public async edit(data: IWorkoutEventEditInput): Promise { + const query = { _id: data.input._id }; + const set = { + endTime: data.input.endTime, + startTime: data.input.startTime, + timezoneOffset: data.input.startTime.getTimezoneOffset(), + }; + + return this.findOneAndUpdate(query, set); + } +} diff --git a/src/context/mealplan/repo/index.ts b/src/context/mealplan/repo/index.ts index 805596f..7d6f72d 100644 --- a/src/context/mealplan/repo/index.ts +++ b/src/context/mealplan/repo/index.ts @@ -1,14 +1,13 @@ import Repository from '@lib/Repository'; -import moment from 'moment'; import { IAccount } from 'types/account'; -import { ITimezonedDate } from 'types/global'; import { IMealEvent, IMealEventAddInput, - IMealPlanEvent, + IMealEventEditInput, IWorkoutEvent, IWorkoutEventAddInput, + IWorkoutEventEditInput, } from 'types/mealPlan'; import { MealPlanEvent } from '../schema'; import { MealEvent } from '../schema/MealEvent'; @@ -18,36 +17,4 @@ export default class IMealPlanEventRepo extends Repository { constructor() { super(MealPlanEvent); } - - public async createMealEvent( - data: IMealEventAddInput, - user: IAccount - ): Promise { - const mealEventData = { - endTime: data.input.endTime, - mealType: data.input.mealType, - owner: user._id, - recipes: data.input.recipes, - startTime: data.input.startTime, - timezoneOffset: data.input.startTime.getTimezoneOffset(), - }; - - const recipe = new MealEvent(mealEventData); - return recipe.save(); - } - - public async createWorkoutEvent( - data: IWorkoutEventAddInput, - user: IAccount - ): Promise { - const workoutEventData = { - endTime: data.input.endTime, - owner: user._id, - startTime: data.input.startTime, - timezoneOffset: data.input.startTime.getTimezoneOffset(), - }; - - const recipe = new WorkoutEvent(workoutEventData); - return recipe.save(); - } } diff --git a/src/context/mealplan/schema/MealEvent.ts b/src/context/mealplan/schema/MealEvent.ts index b627183..475813b 100644 --- a/src/context/mealplan/schema/MealEvent.ts +++ b/src/context/mealplan/schema/MealEvent.ts @@ -5,7 +5,7 @@ import { MealPlanEvent } from './index'; const MealEventSchema = new mongoose.Schema({ mealType: String, // ('Breakfast', 'Snack', 'Dinner'...) --- Use codes so it cna potentially be translated - recipes: [], // @TODO This needs to contain, name, id, picture and slug of recipe + recipes: [], }); export const MealEvent: Model = MealPlanEvent.discriminator( diff --git a/src/context/mealplan/schema/index.ts b/src/context/mealplan/schema/index.ts index ac893cc..d8be557 100644 --- a/src/context/mealplan/schema/index.ts +++ b/src/context/mealplan/schema/index.ts @@ -1,5 +1,4 @@ import _ from 'lodash'; -import moment from 'moment'; import mongoose, { Model } from 'mongoose'; import { IMealPlanEvent } from 'types/mealPlan'; diff --git a/src/context/mealplan/services/index.ts b/src/context/mealplan/services/index.ts index 809b0b7..fe00ee4 100644 --- a/src/context/mealplan/services/index.ts +++ b/src/context/mealplan/services/index.ts @@ -1,24 +1,35 @@ import AccountContext, { IAccountContext } from '@context/account'; +import RecipeContext, { IRecipeContext } from '@context/recipe'; import { AuthenticationError } from 'apollo-server'; -import moment from 'moment'; import { IContext, IObjectId } from 'types/global'; import { IMealEvent, IMealEventAddInput, + IMealEventEditInput, + IMealMacro, IMealPlanEvent, IMealPlanRangeInput, IWorkoutEvent, IWorkoutEventAddInput, + IWorkoutEventEditInput, } from 'types/mealPlan'; import MealPlanRepo from '../repo'; +import MealEventRepo from '../repo/MealEventRepo'; +import WorkoutEventRepo from '../repo/WorkoutEventRepo'; export default class MealPlanService { public mealPlanEventRepo: MealPlanRepo; + public mealEventRepo: MealEventRepo; + public workoutEventRepo: WorkoutEventRepo; public accountContext: IAccountContext; + public recipeContext: IRecipeContext; constructor() { this.mealPlanEventRepo = new MealPlanRepo(); + this.mealEventRepo = new MealEventRepo(); + this.workoutEventRepo = new WorkoutEventRepo(); this.accountContext = AccountContext; + this.recipeContext = RecipeContext; } public async findWithinRange( @@ -65,10 +76,26 @@ export default class MealPlanService { const creator = await this.accountContext.findBy(ctx.user._id, '_id'); if (!creator) { - throw new AuthenticationError('Provided user does not exist '); + throw new AuthenticationError('Provided user does not exist'); } - return this.mealPlanEventRepo.createMealEvent(data, creator); + return this.mealEventRepo.create(data, creator); + } + + public async editMealEvent( + data: IMealEventEditInput, + ctx: IContext + ): Promise { + const event = await this.mealEventRepo.findOneBy({ + _id: data.input._id, + owner: ctx.user._id, + }); + + if (!event) { + throw new Error('Provided event does not exist'); + } + + return this.mealEventRepo.edit(data); } public async addWorkoutEvent( @@ -78,14 +105,63 @@ export default class MealPlanService { const creator = await this.accountContext.findBy(ctx.user._id, '_id'); if (!creator) { - throw new AuthenticationError('Provided user does not exist '); + throw new AuthenticationError('Provided user does not exist'); } - const created = await this.mealPlanEventRepo.createWorkoutEvent( - data, - creator - ); + const created = await this.workoutEventRepo.create(data, creator); return created; } + + public async editWorkoutEvent( + data: IWorkoutEventEditInput, + ctx: IContext + ): Promise { + const event = await this.workoutEventRepo.findOneBy({ + _id: data.input._id, + owner: ctx.user._id, + }); + + if (!event) { + throw new Error('Provided event does not exist'); + } + + return this.workoutEventRepo.edit(data); + } + + public async deleteEvent(id: IObjectId, ctx: IContext): Promise { + const event = await this.mealPlanEventRepo.findOneBy({ + _id: id, + owner: ctx.user._id, + }); + + if (!event) { + throw new Error('Provided event does not exist'); + } + + await this.mealPlanEventRepo.hardDelete({ _id: id }); + + return true; + } + + public async findBy(field: string, fieldName: string) { + return this.mealPlanEventRepo.findOneBy({ [fieldName]: field }); + } + + public async getMealMacros(mealId: IObjectId): Promise { + const event = await this.mealPlanEventRepo.findById(mealId); + const recipes = await this.recipeContext.findByIds(event.recipes); + + const macros: IMealMacro = recipes.reduce( + (acc, recipe) => ({ + calories: acc.calories + recipe.calories, + carbs: acc.carbs + recipe.carbohydrates, + fat: acc.fat + recipe.fat, + protein: acc.protein + recipe.protein, + }), + { calories: 0, carbs: 0, fat: 0, protein: 0 } + ); + + return macros; + } } diff --git a/src/context/recipe/index.ts b/src/context/recipe/index.ts index 516f1ed..8cf2a56 100644 --- a/src/context/recipe/index.ts +++ b/src/context/recipe/index.ts @@ -32,3 +32,17 @@ export default { recipeService.searchByTitle(name), show: (slug: string): Promise => recipeService.show(slug), }; + +export interface IRecipeContext { + create: (data: IRecipeCreateInput, ctx: IContext) => Promise; + delete: (id: IObjectId) => Promise; + edit: (data: IRecipeEditInput, ctx: IContext) => Promise; + findByIds: (ids: IObjectId[]) => Promise; + likeOrDislike: (data: IRecipeLikeInput, ctx: IContext) => Promise; + list: (data: ILimitSkipInput) => Promise; + rate: (data: IRecipeRateInput, ctx: IContext) => Promise; + rating: (recipeId: IObjectId) => Promise; + ratings: (recipeId: IObjectId) => Promise; + searchByTitle: (name: string) => Promise; + show: (slug: string) => Promise; +} diff --git a/src/lib/mailer/index.ts b/src/lib/mailer/index.ts index 6cfbe02..fa15b4e 100644 --- a/src/lib/mailer/index.ts +++ b/src/lib/mailer/index.ts @@ -18,6 +18,7 @@ interface IOptions { export default class Mailer { public options: IOptions; public sender: string; + public name: string; constructor() { this.options = { @@ -28,6 +29,7 @@ export default class Mailer { method: 'POST', url: 'https://api.sendinblue.com/v3/smtp/email', }; + this.name = 'Healfit Team'; this.sender = config('sendInBlue.sender'); } @@ -54,7 +56,7 @@ export default class Mailer { NAME: params.name, RESET_PASSWORD_LINK: params.resetPasswordLink, }, - templateId: 5, + templateId: 6, }; default: throw new Error('Email type does not exists!'); @@ -81,7 +83,6 @@ export default class Mailer { if (error) { throw new Error(error); } - console.log('EMAIL'); }); } } diff --git a/src/migrations/versions/20190606142523.ts b/src/migrations/versions/20190606142523.ts index 1769c58..e8893d2 100644 --- a/src/migrations/versions/20190606142523.ts +++ b/src/migrations/versions/20190606142523.ts @@ -1,11 +1,11 @@ -import { Recipe as RecipeModel } from '../../context/recipe/schema'; +import { RecipeSchema } from '../../context/recipe/schema'; export default function Version20190606142523() { return { execute: async () => { // migration - await RecipeModel.collection.dropIndexes(); + await RecipeSchema.collection.dropIndexes(); return true; }, }; diff --git a/src/migrations/versions/20190802103600.ts b/src/migrations/versions/20190802103600.ts new file mode 100644 index 0000000..8b52e13 --- /dev/null +++ b/src/migrations/versions/20190802103600.ts @@ -0,0 +1,21 @@ +import { RecipeSchema } from '../../context/recipe/schema'; + +export default function Version20190802103600() { + return { + execute: async () => { + const recipes = await RecipeSchema.collection.find(); + recipes.forEach(recipe => { + const { createdBy } = recipe; + if (createdBy.hasOwnProperty('name')) { + RecipeSchema.collection.update( + { _id: recipe._id }, + { + $set: { createdBy: createdBy.id }, + } + ); + } + }); + return true; + }, + }; +} diff --git a/src/types/mealPlan.d.ts b/src/types/mealPlan.d.ts index 913ed61..1103e78 100644 --- a/src/types/mealPlan.d.ts +++ b/src/types/mealPlan.d.ts @@ -3,7 +3,7 @@ import { IObjectId } from './global'; export interface IMealPlanEvent extends Document { _id: IObjectId; - type: number; + type: string; start: Date; end: Date; // owner: { _id: false; type: 'ObjectId'; ref: 'account' }; @@ -15,6 +15,13 @@ export interface IWorkoutEvent extends IMealPlanEvent { workoutType: number; } +export interface IMealMacro { + carbs: number; + protein: number; + fat: number; + calories: number; +} + export interface IMealEvent extends IMealPlanEvent { _id: IObjectId; recipes: []; @@ -36,6 +43,15 @@ export interface IMealEventAddInput { mealType: string; }; } +export interface IMealEventEditInput { + input: { + _id: IObjectId; + startTime: Date; + endTime: Date; + recipes: [IObjectId]; + mealType: string; + }; +} export interface IWorkoutEventAddInput { input: { @@ -43,3 +59,12 @@ export interface IWorkoutEventAddInput { endTime: Date; }; } + + +export interface IWorkoutEventEditInput { + input: { + _id: IObjectId; + startTime: Date; + endTime: Date; + }; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2d7227c..fad16cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -984,6 +984,13 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +chai-as-promised@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + chai-datetime@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/chai-datetime/-/chai-datetime-1.5.0.tgz#3742f18b024c75b76a2b7eee291662324467596c"