controller.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { Request, Response, NextFunction } from 'express'
  2. import { Recipe, IIngredient, IngredientSuggestion } from './model';
  3. import { IMongoService } from '../mongo';
  4. import { FilterQuery } from 'mongodb';
  5. import Scrape from './scrape/index';
  6. import { ScrapedRecipe } from './model';
  7. import Ingredient from '../ingredients/model';
  8. import { dissoc } from 'ramda';
  9. function validRecipe(data: any): Promise<Recipe> {
  10. return new Promise(function(resolve, reject) {
  11. return data.name !== undefined ? resolve(data) : reject('Missing data');
  12. })
  13. }
  14. type Controller = (db: IMongoService) => (req: Request, res: Response, next: NextFunction) => Promise<void|Response>;
  15. type ChainPController<T, U> = (req: Request, res: Response, next: NextFunction) => (result: T) => Promise<U>;
  16. const getSuggestions = (db: IMongoService) => async function(ingredient: IIngredient): Promise<IngredientSuggestion> {
  17. const suggestions = await db.find<Ingredient>({$text: {$search: ingredient.name}}, { score: { $meta: "textScore" } });
  18. return {
  19. ...ingredient,
  20. suggestions,
  21. }
  22. }
  23. const scrapeRecipe: (db: IMongoService) => ChainPController<void, ScrapedRecipe> = (db) => (req) => () => {
  24. return Scrape(req.body.url)
  25. .then(async scrapedRecipe => {
  26. const recipeIngredients = await Promise.all(scrapedRecipe.ingredients.map(async subGroup => {
  27. const subgroupIngredients = await Promise.all(subGroup.ingredients.map(getSuggestions(db)));
  28. return ({
  29. name: subGroup.name,
  30. ingredients: subgroupIngredients
  31. });
  32. }));
  33. return {
  34. ...scrapedRecipe,
  35. ingredients: recipeIngredients
  36. }
  37. })
  38. }
  39. const save: Controller = (db) => ({ body }, res) => {
  40. return validRecipe(body).then(
  41. recipe => db.insertOne(recipe).then(
  42. dbRecipe => res.json(dbRecipe)
  43. ));
  44. };
  45. const get: Controller = (db) => ({ query }, res) => {
  46. return db.findOne<Recipe>(query).then(result => res.json(result));
  47. }
  48. const getById: Controller = (db) => ({ params }, res) => {
  49. return db.findOneById<Recipe>(params.id).then(recipe => res.json(recipe));
  50. }
  51. const getAll: Controller = (db) => ({ query }, res) => {
  52. const _query = Object.keys(query).reduce((q, field) => {
  53. return {
  54. ...q,
  55. ...buildSearchQuery(field, query[field])
  56. }
  57. }, {});
  58. return db.find<Recipe>(_query).then(recipes => res.json(recipes));
  59. }
  60. type RegexQuery = {
  61. '$regex': string,
  62. '$options': 'i'
  63. };
  64. const buildSearchQuery = (fieldName: string, value: string): { [field: string] : RegexQuery} => ({
  65. [fieldName]: {
  66. '$regex': value,
  67. '$options': 'i'
  68. }
  69. });
  70. async function buildQuery(tryQuery:()=>FilterQuery<any>): Promise<FilterQuery<any>> {
  71. try {
  72. return Promise.resolve(tryQuery());
  73. } catch(error) {
  74. return Promise.reject(error);
  75. }
  76. }
  77. const getByIngredients: Controller = (db) => ({ query }, res) => {
  78. const ingredientsQuery = (query: FilterQuery<any>) => () => {
  79. if(query.ingredients) {
  80. const ingredients: string[] = query.ingredients.split(',');
  81. return {
  82. '$or': ingredients.map((ing) => buildSearchQuery('ingredients.ingredients.name', ing))
  83. };
  84. } else {
  85. throw new Error('Invalid query key');
  86. }
  87. }
  88. return buildQuery(ingredientsQuery(query)).then(
  89. builtQuery => db.find<Recipe>(builtQuery).then(
  90. recipes => res.json(recipes)
  91. )
  92. )
  93. }
  94. const update: Controller = (db) => ({params, body}, res) => {
  95. const newRecipe = dissoc('_id', body) as Recipe;
  96. return db.update<Recipe>(params.id, newRecipe).then(result => res.json(result))
  97. }
  98. export {
  99. save,
  100. scrapeRecipe,
  101. getById,
  102. get,
  103. getAll,
  104. getByIngredients,
  105. update,
  106. }