Skip to content

Commit

Permalink
Merge pull request #252 from julianpoy/check-recipe-steps
Browse files Browse the repository at this point in the history
Check recipe steps
  • Loading branch information
julianpoy authored Apr 29, 2019
2 parents 17054d7 + d7b6c9e commit baeff5a
Show file tree
Hide file tree
Showing 23 changed files with 558 additions and 50 deletions.
26 changes: 21 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ jobs: # a collection of steps
- image: circleci/node:8.15.0 # ...with this image as the primary container; this is where all `steps` will run
steps: # a collection of executable commands
- checkout # special step to check out source code to working directory
- run:
name: install-graphicsmagick
command: 'sudo apt install graphicsmagick -y'
- run:
name: update-npm
command: 'sudo npm install -g npm@latest'
- run:
name: install-deps
command: 'sudo npm install -g sequelize'
# -------------- Setup Backend -----------------
- restore_cache: # special step to restore the dependency cache
# Read about caching dependencies: https://circleci.com/docs/2.0/caching/
key: backend-dependency-cache-{{ checksum "Backend/package.json" }}
Expand All @@ -31,9 +29,27 @@ jobs: # a collection of steps
- run:
name: extract-config
command: cd Backend/config && unzip -j -P $credentialskey config.json.zip
- run: # run tests
name: test
# ------------- Setup Frontend ----------------
- restore_cache: # special step to restore the dependency cache
# Read about caching dependencies: https://circleci.com/docs/2.0/caching/
key: frontend-dependency-cache-{{ checksum "Frontend/package.json" }}
- run:
name: install-npm-frontend
command: 'cd Frontend && npm install'
- save_cache: # special step to save the dependency cache
key: frontend-dependency-cache-{{ checksum "Frontend/package.json" }}
paths:
- ./Frontend/node_modules
# -------------- Tests -----------------
- run: # run backend tests
name: test-backend
command: cd Backend && npm run test:ci
- run: # lint frontend
name: lint-frontend
command: cd Frontend && npm run lint
- run: # build frontend
name: build-frontend
command: cd Frontend && npm run build
# - run: # run coverage report
# name: code-coverage
# command: './node_modules/.bin/nyc report --reporter=text-lcov'
Expand Down
5 changes: 5 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

cd Frontend;
npm run lint;
# npm run build;
2 changes: 1 addition & 1 deletion Backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var devMode = appConfig.environment === 'dev';

Raven.config(appConfig.sentry.dsn, {
environment: appConfig.environment,
release: '1.7.5'
release: '1.7.6'
}).install();

// Routes
Expand Down
156 changes: 156 additions & 0 deletions Backend/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ let mdb = require('mdb');
let extract = require('extract-zip');
let sqlite3 = require('sqlite3');
const performance = require('perf_hooks').performance;
let path = require('path');

// DB
var Op = require("sequelize").Op;
Expand Down Expand Up @@ -750,4 +751,159 @@ router.post(
}
)

router.post(
'/import/paprika',
cors(),
MiddlewareService.validateSession(['user']),
MiddlewareService.validateUser,
multer({
dest: '/tmp/paprika-import/',
}).single('paprikadb'),
async (req, res, next) => {
if (!req.file) {
res.status(400).send("Must include a file with the key lcbdb")
} else {
console.log(req.file.path)
}

Raven.captureMessage("Starting Paprika Import");

let metrics = {
t0: performance.now(),
tExtracted: null,
tRecipesProcessed: null,
tRecipesSaved: null,
tLabelsSaved: null
}

let zipPath = req.file.path;
let extractPath = zipPath + '-extract';

new Promise((resolve, reject) => {
extract(zipPath, { dir: extractPath }, err => {
if (err) {
if (err.message === 'end of central directory record signature not found') err.status = 406;
reject(err)
}
else resolve(extractPath);
})
}).then(extractPath => {
metrics.tExtracted = performance.now();

let labelMap = {};
let pendingRecipes = [];
return SQ.transaction(t => {
return fs.readdir(extractPath).then(fileNames => {
return fileNames.reduce((acc, fileName) => {
return acc.then(() => {
let filePath = path.join(extractPath, fileName);

return fs.readFile(filePath).then(fileBuf => {
return UtilService.gunzip(fileBuf).then(data => {
let recipeData = JSON.parse(data.toString());

let imageP = recipeData.photo_data ?
UtilService.sendFileToS3(new Buffer(recipeData.photo_data, "base64"), true) : Promise.resolve();

return imageP.then(image => {
let notes = [
recipeData.notes,
recipeData.nutritional_info ? `Nutritional Info: ${recipeData.difficulty}` : '',
recipeData.difficulty ? `Difficulty: ${recipeData.difficulty}` : '',
recipeData.rating ? `Rating: ${recipeData.rating}` : ''
].filter(e => e && e.length > 0).join('\r\n');

let totalTime = [
recipeData.total_time,
recipeData.cook_time ? `(${recipeData.cook_time} cooking time)` : ''
].filter(e => e).join(' ');

pendingRecipes.push({
model: {
userId: res.locals.session.userId,
title: recipeData.name,
image,
description: recipeData.description,
ingredients: recipeData.ingredients,
instructions: recipeData.directions,
yield: recipeData.servings,
totalTime,
activeTime: recipeData.prep_time,
notes,
source: recipeData.source,
folder: 'main',
fromUserId: null,
url: recipeData.source_url
},
labels: (recipeData.categories || []).map(e => e.trim().toLowerCase()).filter(e => e)
});
});
})
});
});
}, Promise.resolve()).then(() => {
metrics.tRecipesProcessed = performance.now();

return Recipe.bulkCreate(pendingRecipes.map(el => el.model), {
returning: true,
transaction: t
}).then(recipes => {
recipes.map((recipe, idx) => {
pendingRecipes[idx].labels.map(labelTitle => {
labelMap[labelTitle] = labelMap[labelTitle] || [];
labelMap[labelTitle].push(recipe.id);
})
})
})
}).then(() => {
metrics.tRecipesSaved = performance.now();

return Promise.all(Object.keys(labelMap).map(labelTitle => {
return Label.findOrCreate({
where: {
userId: res.locals.session.userId,
title: labelTitle
},
transaction: t
}).then(labels => {
return Recipe_Label.bulkCreate(labelMap[labelTitle].map(recipeId => {
return {
labelId: labels[0].id,
recipeId
}
}), {
transaction: t
})
});
}))
});
});
});
}).then(() => {
metrics.tLabelsSaved = performance.now();

metrics.performance = {
tExtract: Math.floor(metrics.tExtracted - metrics.t0),
tRecipesProcess: Math.floor(metrics.tRecipesProcessed - metrics.tExtracted),
tRecipesSave: Math.floor(metrics.tRecipesSaved - metrics.tRecipesProcessed),
tLabelsSave: Math.floor(metrics.tLabelsSaved - metrics.tRecipesSaved)
}

Raven.captureMessage('Paprika Metrics', {
extra: {
metrics
},
user: res.locals.session.toJSON(),
level: 'info'
});

res.status(201).json({});
}).catch(err => {
fs.removeSync(zipPath);
fs.removeSync(extractPath);
next(err);
});
}
)

module.exports = router;
18 changes: 15 additions & 3 deletions Backend/services/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var Raven = require('raven');
let fs = require('fs-extra');
let sharp = require('sharp');
let path = require('path');
let zlib = require('zlib');

// Service
var FirebaseService = require('./firebase');
Expand Down Expand Up @@ -137,13 +138,15 @@ exports.sendURLToS3 = url => {
})
}

exports.sendFileToS3 = path => {
return fs.readFile(path).then(buf => {
exports.sendFileToS3 = (file, isBuffer) => {
let p = isBuffer ? Promise.resolve(file) : fs.readFile(file);

return p.then(buf => {
return exports.convertImage(buf);
}).then(stream => {
return exports.sendBufferToS3(stream);
}).then(result => {
var stats = fs.statSync(path);
var stats = isBuffer ? { size: file.length } : fs.statSync(file);
return exports.formatS3ImageResponse(result.key, 'image/jpeg', stats["size"], result.s3Response.ETag)
})
}
Expand Down Expand Up @@ -290,3 +293,12 @@ let emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
exports.validateEmail = email => emailRegex.test(email);

exports.validatePassword = password => typeof password === 'string' && password.length >= 6;

exports.gunzip = buf => {
return new Promise((resolve, reject) => {
zlib.gunzip(buf, (err, result) => {
if (err) reject(err);
resolve(result);
})
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<ion-checkbox [(ngModel)]="allSelected" (ngModelChange)="resetAll()" checked="true"></ion-checkbox>
</ion-item>
<ion-item *ngFor="let ingredient of scaledIngredients; let i = index;">
<ion-label>{{ ingredient }}</ion-label>
<ion-label>{{ ingredient.content }}</ion-label>
<ion-checkbox [(ngModel)]="ingredientBinders[i]" (ngModelChange)="toggleIngredient(i)" checked="true"></ion-checkbox>
</ion-item>
<br /><br />
Expand Down
24 changes: 12 additions & 12 deletions Frontend/src/components/select-ingredients/select-ingredients.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { RecipeServiceProvider } from '../../providers/recipe-service/recipe-service';
import { RecipeServiceProvider, Ingredient } from '../../providers/recipe-service/recipe-service';

@Component({
selector: 'select-ingredients',
Expand All @@ -8,17 +8,17 @@ import { RecipeServiceProvider } from '../../providers/recipe-service/recipe-ser
export class SelectIngredientsComponent {

allSelected: boolean = true;
ingredientBinders: any = {};
scaledIngredients: any = [];
scale: any = 1;
ingredientBinders: { [index: number]: boolean } = {};
scaledIngredients: Ingredient[] = [];
scale: number = 1;

_ingredients: any;
_ingredients: string;
@Input()
get ingredients() {
return this._ingredients;
}

set ingredients(val) {
set ingredients(val: string) {
this._ingredients = val;

this.selectedIngredients = [];
Expand All @@ -27,12 +27,12 @@ export class SelectIngredientsComponent {
this.applyScale(true);
}

selectedIngredients: any;
selectedIngredients: Ingredient[];

@Output() selectedIngredientsChange = new EventEmitter();

@Input()
set initialScale(val) {
set initialScale(val: number) {
this.scale = val;
this.applyScale();
}
Expand All @@ -46,8 +46,8 @@ export class SelectIngredientsComponent {
});
}

applyScale(init?) {
this.scaledIngredients = this.recipeService.scaleIngredients(this._ingredients, this.scale);
applyScale(init?: boolean) {
this.scaledIngredients = this.recipeService.scaleIngredients(this._ingredients, this.scale).filter(e => !e.isHeader);

this.selectedIngredients = [];
for (var i = 0; i < (this.scaledIngredients || []).length; i++) {
Expand All @@ -58,7 +58,7 @@ export class SelectIngredientsComponent {
this.selectedIngredientsChange.emit(this.selectedIngredients);
}

toggleIngredient(i) {
toggleIngredient(i: number) {
if (this.ingredientBinders[i]) {
this.selectedIngredients.push(this.scaledIngredients[i]);
} else {
Expand All @@ -70,7 +70,7 @@ export class SelectIngredientsComponent {
for (let idx in Object.keys(this.ingredientBinders)) {
if (this.ingredientBinders.hasOwnProperty(idx)) {
this.ingredientBinders[idx] = this.allSelected
this.toggleIngredient(idx)
this.toggleIngredient(parseInt(idx))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Frontend/src/components/select-recipe/select-recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class SelectRecipeComponent {
public navCtrl: NavController
) {}

search(text) {
search(text: string) {
let loading = this.loadingService.start();

this.recipeService.search(text, {}).subscribe(response => {
Expand Down
2 changes: 1 addition & 1 deletion Frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
</script>

<script>
window.version = '1.7.5';
window.version = '1.7.6';
</script>

<link href="build/main.css" rel="stylesheet" async>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@

<br />

<b>v1.7.6</b><br />
- Add new Paprika importer (Settings -> Import -> Import from Paprika)<br />
- Add ability to temporarily check-off/mark ingredients or instructions<br />
- Updated welcome screen text<br />
- Fixed a bug where ingredient headers would appear in "add to shopping list"<br />

<br />

<b>v1.7.5</b><br />
- Add support for headers in ingredients and instructions<br />
- Add tutorial for adding headers<br />
Expand Down
Loading

0 comments on commit baeff5a

Please sign in to comment.