Merge pull request #24 from hay-kot/0.1.0

This commit is contained in:
hay-kot 2021-01-03 20:09:25 -09:00 committed by GitHub
commit f5ab2dcde8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 579 additions and 211 deletions

12
.gitignore vendored
View File

@ -6,6 +6,15 @@ __pycache__/
frontend/.env.development
docs/site/
mealie/data/backups/*
mealie/data/debug/*
mealie/data/img/*
#Exception to keep folders
!mealie/data/backups/.gitkeep
!mealie/data/backups/dev_sample_data*
!mealie/data/debug/.gitkeep
!mealie/data/img/.gitkeep
.DS_Store
node_modules
@ -24,7 +33,6 @@ pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
@ -136,4 +144,4 @@ ENV/
# Node Modules
node_modules/
/*.env.development
/*.env.development*

16
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"python.formatting.provider": "black",
"python.pythonPath": "venv/bin/python",
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.autoComplete.extraPaths": ["mealie", "mealie/mealie"],
"python.analysis.extraPaths": ["mealie", "mealie/mealie"],
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": false,
"python.testing.promptToConfigure": false,
"cSpell.enableFiletypes": [
"!python"
]
}

27
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,27 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "DEV: Build and Start Docker Compose",
"command": "./dev/scripts/docker-compose.dev.sh",
"type": "shell",
"args": [],
"problemMatcher": ["$tsc"],
"presentation": {
"reveal": "always"
},
"group": "test"
},
{
"label": "Production: Build and Start Docker Compose",
"command": "./dev/scripts/docker-compose.sh",
"type": "shell",
"args": [],
"problemMatcher": ["$tsc"],
"presentation": {
"reveal": "always"
},
"group": "test"
}
]
}

View File

@ -18,7 +18,7 @@ WORKDIR /app
RUN pip install -r requirements.txt
COPY ./mealie /app
COPY ./mealie/data/templates/recipes.md /app/data/templates/
COPY --from=build-stage /app/dist /app/dist
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "9000"]

View File

@ -10,7 +10,9 @@
<br />
<p align="center">
<a href="https://github.com/hay-kot/mealie">
<img src="images/logo.png" alt="Logo" width="80" height="80">
<svg style="width:100px;height:100px" viewBox="0 0 24 24">
<path fill="currentColor" d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z" />
</svg>
</a>
<h3 align="center">Mealie</h3>

55
dev/dev-notes.md Normal file
View File

@ -0,0 +1,55 @@
# Getting A Developer Instance Started
For the best experience developing I recommend using docker. I've used both WSL2 and Ubuntu to develop mealie and have had no issues with cross compatibility with docker. 2 Scripts are available along ith docker-compose files to make development instances easier. After cloning the repo you can set the scripts in /dev/scripts/ as executable and then use VSCode tasks to execute the scripts or execute them from the CLI.
`docker-compose.dev.sh` Will spin up a development stack with hot-reloading enabled.
`docker-compose.sh` Will spin up a production version of the stack.
After the stack is running navigate to the [admin page localhost:9090/settings/site](http://localhost:9090/settings/site). On the Backups and Exports section import the backup labeled dev_sample_data_{DATE}.zip. This will give you some recipe data to work with.
Once you're up and running you should be able to make changes and see them reflected on the frontend/backend. If you're not sure what to work on you can check:
- The Todo's below.
- The [Development Road Map](https://hay-kot.github.io/mealie/2.0%20-%20roadmap/)
- The [Current Open Issues](https://github.com/hay-kot/mealie/issues)
Don't forget to [join the Discord](https://discord.gg/R6QDyJgbD2)!
# Todo's
Frontend
- [ ] .Vue file reorganized into something that makes sense
- [ ] Recipe Print Page
- [x] Catch 400 / bad response on create from URL
- [ ] Recipe Editor Data Validation Client Side
- [x] Favicon
- [x] Rename Window
- [ ] Add version indicator and notification for new version available
- [ ] Enhanced Search Functionality
- [ ] Organize Home Page my Category, ideally user selectable.
Backend
- [x] Add Debug folder for writing the last pulled recipe data to.
- [x] Recipe Editor Data Validation Server Side
- [ ] Normalize Recipe data on scrape
- [ ] Support how to Sections and how to steps
- [ ] Export Markdown on Auto backups
- [ ] Recipe request by category/tags
- [ ] Add Additional Migrations, See mealie/services/migrations/chowdown.py for examples of how to do this.
- [ ] Open Eats [See Issue #4](https://github.com/hay-kot/mealie/issues/4)
- [ ] NextCloud [See Issue #14](https://github.com/hay-kot/mealie/issues/14)
# Draft Changelog
## v0.0.2
General
- Fixed opacity issues with marked steps - [mtoohey31](https://github.com/mtoohey31)
- Updated Favicon
- Renamed Frontend Window
- Added Debug folder to dump scraper data prior to processing.
- Improved documentation
- Added version tag / relevant links, and new version notifier
Recipes
- Added user feedback on bad URL.
- Better backend data validation for updating recipes, avoid small syntax errors corrupting database entry. [Issue #8](https://github.com/hay-kot/mealie/issues/8)
- Fixed spacing issue while editing new recipes in JSON

0
dev/scratch/scratch.json Normal file
View File

View File

@ -1,6 +1,5 @@
# Getting Started
To deploy docker on your local network it is highly recommended to use docker to deploy the image straight from dockerhub. Using the docker-compose below you should be able to get a stack up and running easily by changing a few default values and deploying. Currently the only supported database is mongo.
To deploy docker on your local network it is highly recommended to use docker to deploy the image straight from dockerhub. Using the docker-compose below you should be able to get a stack up and running easily by changing a few default values and deploying. Currently the only supported database is Mongo. Mealie is looking for contributors to support additional databases.
[Get Docker](https://docs.docker.com/get-docker/)
@ -9,13 +8,15 @@ To deploy docker on your local network it is highly recommended to use docker to
## Env Variables
| Variables | default | description |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| db_username | root | The Mongodb username you specified in your mongo container |
| db_password | example | The Mongodb password you specified in your mongo container |
| db_host | mongo | The host address of MongoDB if you're in docker and using the same network you can use mongo as the host name |
| db_port | 27017 | the port to access MongoDB 27017 is the default for mongo |
| TZ | | You should set your time zone accordingly so the date/time features work correctly |
| Variables | default | description |
| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mealie_db_name | mealie | The name of the database to be created in Mongodb |
| mealie_port | 9000 | The port exposed by mealie. **do not change this if you're running in docker** If you'd like to use another port, map 9000 to another port of the host. |
| db_username | root | The Mongodb username you specified in your mongo container |
| db_password | example | The Mongodb password you specified in your mongo container |
| db_host | mongo | The host address of MongoDB if you're in docker and using the same network you can use mongo as the host name |
| db_port | 27017 | the port to access MongoDB 27017 is the default for mongo |
| TZ | | You should set your time zone accordingly so the date/time features work correctly |
## Docker Compose

View File

@ -13,13 +13,85 @@ Color themes can be created and set from the UI in the settings page. You can se
## Backup and Export
![](img/admin-backup.png)
All recipe data can be imported and exported as necessary from the UI. Under the admin page you'll find the section for using Backups and Exports.
To create an export simple add the tag and the markdown template and click Backup Recipes and your backup will be created on the server. The backup is a standard zipfile containing all the images, json files, and rendered markdown files for each recipe. Markdown files are rendered from jinja2 templates. Adding your own markdown file into the templates folder will automatically show up as an option to select when creating a backup. To view the availible variables, open a recipe in the json editor.
To import a backup it must be in your backups folder. If it is in the backup folder it will automatically show up as an source to restore from. Selected the desired backup and import the backup file.
![](img/admin-backup.png)
### Custom Templating
On export you can select a template to use to render files using the jinja2 syntax. This can be done to export recipes in other formats besides regular .json.Look at this example for rendering a markdown recipe using the jinja2 syntax.
#### Input
```jinja2
![Recipe Image](../images/{{ recipe.image }})
# {{ recipe.name }}
{{ recipe.description }}
## Ingredients
{% for ingredient in recipe.recipeIngredient %}
- [ ] {{ ingredient }}
{% endfor %}
## Instructions
{% for step in recipe.recipeInstructions %}
- [ ] {{ step.text }}
{% endfor %}
{% for note in recipe.notes %}
**{{ note.title }}:** {{ note.text }}
{% endfor %}
---
Tags: {{ recipe.tags }}
Categories: {{ recipe.categories }}
Original URL: {{ recipe.orgURL }}
```
#### Output
```markdown
![Recipe Image](../images/five-spice-popcorn-chicken.jpg)
# Five Spice Popcorn Chicken
Its easy to rely on take-out for some of our favorite Chinese dishes. However, with the right pantry staples, dishes like this Five Spice Popcorn Chicken can become part of your go-to arsenal of recipes. This crispy chicken is coated in a creamy, tangy sauce, made zesty with The Spice Hunter Chinese Five Spice, a blend of star anise, cloves, cinnamon, fennel, and black pepper.
## Ingredients
- [ ] 1 tablespoon soy sauce
- [ ] 1 tablespoon sugar
- [ ] 1 teaspoon The Spice Hunter® Chinese Five Spice Blend, plus more for serving
- [ ] 1 clove garlic, finely grated
- [ ] 1 1/2 pounds boneless skinless chicken thighs, cut into roughly 1-inch chunks
- [ ] ⅓ cup cornstarch
- [ ] 1 large egg, beaten
- [ ] ¾ cup all-purpose flour
- [ ] Canola or vegetable oil, for frying
- [ ] Flaky sea salt
- [ ] Scallion, thinly sliced, for serving
- [ ] Sriracha mayonnaise, for serving, optional
## Instructions
- [ ] In a medium bowl, whisk the soy sauce with the sugar, Chinese Five Spice, and garlic. Add the chicken and toss to coat. Let marinate 15 minutes.
- [ ] Drain any excess marinade off of the chicken and toss the chicken with the cornstarch to coat. Once fully coated, add the beaten egg and toss to coat.
- [ ] In a large heavy bottomed pot, heat 1-inch of oil to 350.
- [ ] Place the flour in a large ziploc bag. Working in batches, transfer a few chicken pieces into the bag with the flour and toss to coat, then remove, leaving excess flour in the bag.
- [ ] Carefully place the breaded chicken in the hot oil and fry, turning occasionally, until golden and cooked through about 3 to 4 minutes.
- [ ] Using a slotted spoon or spider, transfer the cooked chicken to a paper towel lined plate. Season with salt and additional Chinese Five Spice seasoning. Repeat the flouring and frying with remaining chicken.
- [ ] Serve with scallions, more Chinese Five Spice Blend, and optional sriracha mayonnaise.
---
Tags: []
Categories: []
Original URL: https://www.bonappetit.com/recipe/five-spice-popcorn-chicken#intcid=_bon-appetit-recipe-bottom-recirc_3cad5ce9-734a-46f8-b503-78c33d2e7279_similar2-3
```
If you decide you don't like mealie. This is a good way to export into a format that can be imported into another.
## Meal Planner Webhooks
Meal planner webhooks are post requests sent from Mealie to an external endpoint. The body of the message is the Recipe JSON of the scheduled meal. If no meal is schedule, no request is sent. The webhook functionality can be enabled or disabled as well as scheduled. Note that you must "Save Webhooks" prior to any changes taking affect server side.

View File

@ -1,5 +1,16 @@
# Release Notes
## v0.0.1 - Pre-release Patch
General
- Updated Favicon
- Renamed Frontend Window
- Added Debug folder to dump scraper data prior to processing.
Recipes
- Added user feedback on bad URL
- Better backend data validation for updating recipes, avoid small syntax errors corrupting database entry. [Issue #8](https://github.com/hay-kot/mealie/issues/8)
- Fixed spacing issue while editing new recipes in JSON
## v0.0.0 - Initial Pre-release
The initial pre-release. It should be semi-functional but does not include a lot of user feedback You may notice errors that have no user feedback and have no idea what went wrong.

View File

@ -0,0 +1,49 @@
# Contributing to Mealie
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
- Reporting a bug
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer
[Remember to join the Discord and stay in touch with other developers working on the project](https://discord.gg/R6QDyJgbD2)!
## We Develop with Github
We use github to host code, to track issues and feature requests, as well as accept pull requests.
## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests
Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests:
1. Fork the repo and create your branch from `master`.
2. Read the page in in [dev/dev-notes.md](https://github.com/hay-kot/mealie/blob/0.1.0/dev/dev-notes.md) to get an idea on where the project is at.
3. If you've changed APIs, update the documentation.
4. Make sure your code lints.
5. Issue that pull request!
6. If you make changes to the dev/0.1.0 branch reflect those changes in the dev/dev-notes.md to keep track of changes.
## Any contributions you make will be under the MIT Software License
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
## Report bugs using Github's [issues](https://github.com/hay-kot/mealie/issues)
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/hay-kot/mealie/issues/new); it's that easy!
## Write bug reports with detail, background, and sample code
**Great Bug Reports** tend to have:
- A quick summary and/or background
- Steps to reproduce
- Be specific!
- Give sample code if you can. [This stackoverflow question](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing
- What you expected would happen
- What actually happens
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
People *love* thorough bug reports. I'm not even kidding.
## License
By contributing, you agree that your contributions will be licensed under its MIT License.
## References
This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md)

BIN
docs/docs/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -18,6 +18,8 @@
Mealie also provides an API for interactions from 3rd party applications. **Why does my recipe manager need an API?** An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based of Meal Plan data to remind you to defrost the chicken, marinade the steak, or start the CrockPot. Additionally, you can access any available API from the backend server. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation.
[Remember to join the Discord](https://discord.gg/R6QDyJgbD2)!
!!! note
In some of the demo gifs the styling may be different than the finale application. demos were done during development prior to finale styling.
@ -93,17 +95,4 @@ Project Link: [https://github.com/hay-kot/mealie](https://github.com/hay-kot/mea
<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
[contributors-shield]: https://img.shields.io/github/contributors/hay-kot/mealie.svg?style=flat-square
[contributors-url]: https://github.com/hay-kot/mealie/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/hay-kot/mealie.svg?style=flat-square
[forks-url]: https://github.com/hay-kot/mealie/network/members
[stars-shield]: https://img.shields.io/github/stars/hay-kot/mealie.svg?style=flat-square
[stars-url]: https://github.com/hay-kot/mealie/stargazers
[issues-shield]: https://img.shields.io/github/issues/hay-kot/mealie.svg?style=flat-square
[issues-url]: https://github.com/hay-kot/mealie/issues
[license-shield]: https://img.shields.io/github/license/hay-kot/mealie.svg?style=flat-square
[license-url]: https://github.com/hay-kot/mealie/blob/master/LICENSE.txt
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
[linkedin-url]: https://linkedin.com/in/hay-kot
[product-screenshot]: img/home_screenshot.png

View File

@ -1,5 +1,6 @@
site_name: Mealie
site_name: Mealie Docs
theme:
favicon: img/favicon.png
name: material
icon:
logo: material/silverware-variant

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<title> Mealie </title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</head>

View File

@ -1,13 +1,13 @@
<template>
<v-app>
<v-app-bar dense app color="primary" dark class="d-print-none">
<div class="d-flex align-center">
<v-icon size="40" @click="$router.push('/')">
<v-btn @click="$router.push('/')" icon class="d-flex align-center">
<v-icon size="40" >
mdi-silverware-variant
</v-icon>
</div>
<div btn class="pl-2" @click="$router.push('/')">
<v-toolbar-title>Mealie</v-toolbar-title>
</v-btn>
<div btn class="pl-2">
<v-toolbar-title @click="$router.push('/')">Mealie</v-toolbar-title>
</div>
<v-spacer></v-spacer>

View File

@ -19,9 +19,8 @@ const apiReq = {
post: async function(url, data) {
let response = await axios.post(url, data).catch(function(error) {
if (error.response) {
console.log("Error");
processResponse(error.response);
return;
return error.response;
}
});
processResponse(response);
@ -32,7 +31,7 @@ const apiReq = {
let response = await axios.get(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return;
return response;
} else return;
});
// processResponse(response);
@ -43,7 +42,7 @@ const apiReq = {
let response = await axios.delete(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return;
return response;
}
});
processResponse(response);

View File

@ -23,10 +23,9 @@ export default {
let response = await apiReq.post(recipeURLs.createByURL, {
url: recipeURL,
});
console.log(response);
let recipeSlug = response.data;
store.dispatch("requestRecentRecipes");
router.push(`/recipe/${recipeSlug}`);
return response;
},
async create(recipeData) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

Before

Width:  |  Height:  |  Size: 539 B

View File

@ -8,6 +8,11 @@
<v-form>
<v-text-field v-model="recipeURL" label="Recipe URL"></v-text-field>
</v-form>
<v-alert v-if="error" color="red" outlined type="success">
Looks like there was an error parsing the URL. Check the log and
debug/last_recipe.json to see what went wrong.
</v-alert>
</v-card-text>
<v-divider></v-divider>
@ -37,6 +42,7 @@ import api from "../api";
export default {
data() {
return {
error: false,
fab: false,
addRecipe: false,
recipeURL: "",
@ -47,9 +53,16 @@ export default {
methods: {
async createRecipe() {
this.processing = true;
await api.recipes.createByURL(this.recipeURL);
let response = await api.recipes.createByURL(this.recipeURL);
if (response.status !== 201) {
this.error = true;
this.processing = false;
return;
}
this.addRecipe = false;
this.processing = false;
this.$router.push(`/recipe/${response.data}`);
},
navCreate() {

View File

@ -1,9 +1,28 @@
<template>
<v-container>
<v-alert v-if="newVersion" color="green" type="success" outlined>
A New Version of Mealie is Avaiable,
<a href="https://github.com/hay-kot/mealie" class="green--text">
Visit the Repo
</a>
</v-alert>
<Theme />
<Backup />
<Webhooks />
<Migration />
<p class="text-center my-2">
Version: {{ version }} | Latest: {{ latestVersion }} ·
<a href="https://hay-kot.github.io/mealie/" target="_blank">
Explore the Docs
</a>
·
<a
href="https://hay-kot.github.io/mealie/2.1%20-%20Contributions/"
target="_blank"
>
Contribute
</a>
</p>
</v-container>
</template>
@ -12,6 +31,8 @@ import Backup from "./Backup";
import Webhooks from "./Webhooks";
import Theme from "./Theme";
import Migration from "./Migration";
import axios from "axios";
export default {
components: {
Backup,
@ -19,6 +40,34 @@ export default {
Theme,
Migration,
},
data() {
return {
latestVersion: null,
version: "v0.0.1",
};
},
mounted() {
this.getVersion();
},
computed: {
newVersion() {
if ((this.latestVersion != null) & (this.latestVersion != this.version)) {
console.log("New Version Avaiable");
return true;
} else {
return false;
}
},
},
methods: {
async getVersion() {
let response = await axios.get(
"https://api.github.com/repos/hay-kot/mealie/releases/latest"
);
console.log(response);
this.latestVersion = response.data.tag_name;
},
},
};
</script>

View File

@ -1,8 +1,9 @@
<template>
<v-card :loading="backupLoading" class="mt-3">
<v-card :loading="backupLoading" class="mt-3" min-height="410px">
<v-card-title class="secondary white--text">
Backup and Exports
</v-card-title>
<v-card-text>
<p>
Backups are exported in standard JSON format along with all the images

View File

@ -18,12 +18,16 @@
@save="createRecipe"
/>
<VJsoneditor
v-if="jsonEditor"
v-model="recipeDetails"
height="1500px"
:options="jsonEditorOptions"
/>
<div v-if="jsonEditor">
<!-- Probably not the best way, but it works! -->
<br />
<br />
<VJsoneditor
v-model="recipeDetails"
height="1500px"
:options="jsonEditorOptions"
/>
</div>
<EditRecipe v-else v-model="recipeDetails" @upload="getImage" />
</v-card>
@ -73,7 +77,6 @@ export default {
methods: {
getImage(fileObject) {
console.log(fileObject);
this.fileObject = fileObject;
this.onFileChange();
},
@ -83,11 +86,9 @@ export default {
async createRecipe() {
this.isLoading = true;
this.recipeDetails.image = this.fileObject.name;
console.log(this.recipeDetails);
let slug = await api.recipes.create(this.recipeDetails);
let response = await api.recipes.updateImage(slug, this.fileObject);
console.log(response);
await api.recipes.updateImage(slug, this.fileObject);
this.isLoading = false;
this.$router.push(`/recipe/${slug}`);

View File

@ -32,6 +32,7 @@
:orgURL="recipeDetails.orgURL"
/>
<VJsoneditor
@error="logError()"
class="mt-10"
v-else-if="showJsonEditor"
v-model="recipeDetails"
@ -152,6 +153,6 @@ export default {
margin-top: -10px;
}
.disabled-card {
opacity: 50%;
opacity: 0.5;
}
</style>

View File

@ -123,6 +123,6 @@ export default {
<style>
.disabled-card {
opacity: 50%;
opacity: 0.5;
}
</style>

View File

@ -20,10 +20,7 @@
<div class="my-2"></div>
<v-row dense disabled>
<v-col sm="5">
<v-text-field
label="Servings"
v-model="value.recipeYield"
>
<v-text-field label="Servings" v-model="value.recipeYield">
</v-text-field>
</v-col>
<v-col></v-col>
@ -74,7 +71,13 @@
v-model="value.categories"
>
<template v-slot:selection="data">
<v-chip :input-value="data.selected" close color="secondary" dark>
<v-chip
:input-value="data.selected"
close
@click:close="removeCategory(data.index)"
color="secondary"
dark
>
{{ data.item }}
</v-chip>
</template>
@ -83,7 +86,13 @@
<h2 class="mt-4">Tags</h2>
<v-combobox dense multiple chips deletable-chips v-model="value.tags">
<template v-slot:selection="data">
<v-chip :input-value="data.selected" close color="secondary" dark>
<v-chip
:input-value="data.selected"
close
@click:close="removeTags(data.index)"
color="secondary"
dark
>
{{ data.item }}
</v-chip>
</template>
@ -255,13 +264,19 @@ export default {
removeNote(index) {
this.value.notes.splice(index, 1);
},
removeCategory(index) {
this.value.categories.splice(index, 1);
},
removeTags(index) {
this.value.tags.splice(index, 1);
},
},
};
</script>
<style>
.disabled-card {
opacity: 50%;
opacity: 0.5;
}
.my-divider {
margin: 0 -1px;

View File

Binary file not shown.

View File

View File

@ -0,0 +1,128 @@
{
"@context": "http://schema.org",
"@type": "Recipe",
"articleBody": "Leftover rice is ideal for this dish (and a great way to use up any takeout that\u2019s hanging around), since fully chilled rice tends to be drier and will become crispier and browner in the skillet. To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it out like a pancake. Don\u2019t touch until you hear it crackle! Finish with a sunny-side-up egg\u2014or poach it if you don't mind the stovetop fuss. This recipe is part of the 2021\u00a0Feel Good Food Plan, our eight-day dinner plan for starting the year off right.",
"alternativeHeadline": "To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it. Don\u2019t touch until you hear it crackle!",
"dateModified": "2021-01-03 03:40:32.190000",
"datePublished": "2021-01-01 06:00:00",
"keywords": [
"recipes",
"healthyish",
"salad",
"ginger",
"garlic",
"orange",
"oil",
"soy sauce",
"lemon juice",
"sesame oil",
"kosher salt",
"broccoli",
"brown rice",
"egg",
"celery",
"cilantro",
"mint",
"feel good food plan 2021",
"feel good food plan",
"web"
],
"thumbnailUrl": "https://assets.bonappetit.com/photos/5fdbe70a84d333dd1dcc7900/1:1/w_1698,h_1698,c_limit/BA1220feelgoodalt.jpg",
"publisher": {
"@context": "https://schema.org",
"@type": "Organization",
"name": "Bon App\u00e9tit",
"logo": {
"@type": "ImageObject",
"url": "https://www.bonappetit.com/verso/static/bon-appetit/assets/logo-seo.328de564b950e3d5d1fbe3e42f065290ca1d3844.png",
"width": "479px",
"height": "100px"
},
"url": "https://www.bonappetit.com"
},
"isPartOf": {
"@type": [
"CreativeWork",
"Product"
],
"name": "Bon App\u00e9tit"
},
"isAccessibleForFree": true,
"author": [
{
"@type": "Person",
"name": "Devonn Francis",
"sameAs": "https://bon-appetit.com/contributor/devonn-francis/"
}
],
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": 4,
"ratingCount": 2
},
"description": "To get the best texture, evenly distribute the rice in your pan and gently press down to flatten it. Don\u2019t touch until you hear it crackle! ",
"image": "crispy-rice-with-ginger-citrus-celery-salad.jpg",
"name": "Crispy Rice With Ginger-Citrus Celery Salad",
"recipeIngredient": [
"1 2\" piece ginger, peeled, finely grated",
"1 small garlic clove, finely grated",
"Juice of 1 orange",
"2 tbsp. vegetable oil",
"1Tbsp. coconut aminos or low-sodium soy sauce",
"1 Tbsp. fresh lemon juice",
"\u00bc tsp. toasted sesame oil",
"Kosher salt",
"1 medium head of broccoli",
"6 Tbsp. (or more) vegetable oil, divided",
"Kosher salt",
"2 cups chilled cooked brown rice",
"4 large eggs",
"3 celery stalks, thinly sliced on a steep diagonal",
"\u00bd cup cilantro leaves with tender stems",
"\u00bd cup mint leaves",
"Crushed red pepper flakes (for serving)"
],
"recipeInstructions": [
{
"@type": "HowToStep",
"text": "Whisk ginger, garlic, orange juice, vegetable oil, coconut aminos, lemon juice, and sesame oil in a small bowl; season with salt and set aside."
},
{
"@type": "HowToStep",
"text": "Trim about \u00bd\" from woody end of broccoli stem. Peel tough outer layer from stem. Cut florets from stems and thinly slice stems about \u00bd\" thick. Break florets apart with your hands into 1\"\u20131\u00bd\" pieces."
},
{
"@type": "HowToStep",
"text": "Heat 2 Tbsp. oil in a large nonstick skillet over medium. Working in 2 batches if needed, arrange broccoli in a single layer and cook, tossing occasionally, until broccoli is bright green and lightly charred around the edges, about\u00a03 minutes. Transfer to a large plate."
},
{
"@type": "HowToStep",
"text": "Pour 2 Tbsp. oil into same pan and heat over medium-high. Once you see the first wisp of smoke, add rice and season lightly with salt. Using a spatula or spoon, press rice evenly into pan like a pancake. Rice will begin to crackle, but don\u2019t fuss with it. When the crackling has died down almost completely, about\u00a03 minutes, break rice into large pieces and turn over."
},
{
"@type": "HowToStep",
"text": "Add broccoli back to pan and give everything a toss to combine. Cook, tossing occasionally and adding another\u00a01 Tbsp. oil if pan looks dry, until broccoli is tender and rice is warmed through and very crisp, about 5 minutes. Transfer mixture to a platter or divide among plates and set aside."
},
{
"@type": "HowToStep",
"text": "Wipe out skillet; heat remaining\u00a02 Tbsp. oil over medium-high. Crack eggs into skillet; season with salt. Oil should bubble around eggs right away. Cook, rotating skillet occasionally, until whites are golden brown and crisp at the edges and set around the yolk (which should be runny), about 2 minutes."
},
{
"@type": "HowToStep",
"text": "Toss celery, cilantro, and mint with\u00a03 Tbsp. reserved dressing and a pinch of salt in a medium bowl to combine."
},
{
"@type": "HowToStep",
"text": "Scatter celery salad over fried rice; top with fried eggs and sprinkle with red pepper flakes. Serve extra dressing alongside."
}
],
"recipeYield": "4 servings",
"url": "https://www.bonappetit.com/recipe/crispy-rice-with-ginger-citrus-celery-salad",
"slug": "crispy-rice-with-ginger-citrus-celery-salad",
"orgURL": "https://www.bonappetit.com/recipe/crispy-rice-with-ginger-citrus-celery-salad",
"categories": [],
"tags": [],
"dateAdded": null,
"notes": [],
"extras": []
}

0
mealie/data/img/.gitkeep Normal file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 997 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 812 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -3,6 +3,7 @@
![Recipe Image](../images/{{ recipe.image }})
# {{ recipe.name }}
{{ recipe.description }}
## Ingredients
{% for ingredient in recipe.recipeIngredient %}

View File

@ -1,15 +1,14 @@
import mongoengine
from settings import DB_HOST, DB_PASSWORD, DB_PORT, DB_USERNAME
from settings import DB_HOST, DB_PASSWORD, DB_PORT, DB_USERNAME, MEALIE_DB_NAME
def global_init():
mongoengine.register_connection(
alias="core",
name="mealie",
name=MEALIE_DB_NAME,
host=DB_HOST,
port=int(DB_PORT),
username=DB_USERNAME,
password=DB_PASSWORD,
authentication_source="admin",
)

View File

@ -24,7 +24,7 @@ async def get_all_recipes(
async def get_recipe(recipe_slug: str):
""" Takes in a recipe slug, returns all data for a recipe """
recipe = Recipe.get_by_slug(recipe_slug)
return recipe
@ -37,18 +37,19 @@ async def get_recipe_img(recipe_slug: str):
# Recipe Creations
@router.post("/api/recipe/create-url/", tags=["Recipes"])
@router.post("/api/recipe/create-url/", tags=["Recipes"], status_code=201)
async def get_recipe_url(url: dict):
""" Takes in a URL and Attempts to scrape data and load it into the database """
url = url.get("url")
slug = create_from_url(url)
try:
slug = create_from_url(url)
except:
raise HTTPException(
status_code=400, detail=SnackResponse.error("Unable to Parse URL")
)
# try:
# slug = create_from_url(url)
# except:
# raise HTTPException(
# status_code=400, detail=SnackResponse.error("Unable to Parse URL")
# )
return slug
@ -72,9 +73,11 @@ def update_image(
@router.post("/api/recipe/{recipe_slug}/update/", tags=["Recipes"])
async def update(recipe_slug: str, data: dict):
async def update(recipe_slug: str, data: Recipe):
""" Updates a recipe by existing slug and data. Data should containt """
Recipe.update(recipe_slug, data)
data.update(recipe_slug)
return {"message": "PLACEHOLDER"}

View File

@ -1,106 +0,0 @@
import shutil
from os.path import join
from pathlib import Path
from pprint import pprint
import git
import yaml
from git.util import join_path
from services.image_services import IMG_DIR
from services.recipe_services import Recipe
try:
from yaml import CDumper as Dumper
from yaml import CLoader as Loader
except ImportError:
from yaml import Dumper, Loader
CWD = Path(__file__).parent
file = f"/home/hayden/Projects/mealie-fastAPI/mealie/chowdown.md"
repo = "https://github.com/clarklab/chowdown"
def pull_repo(repo):
dest_dir = CWD.joinpath("data/temp/migration/git_pull")
if dest_dir.exists():
shutil.rmtree(dest_dir)
dest_dir.mkdir(parents=True, exist_ok=True)
git.Git(dest_dir).clone(repo)
repo_name = repo.split("/")[-1]
recipe_dir = dest_dir.joinpath(repo_name, "_recipes")
image_dir = dest_dir.joinpath(repo_name, "images")
return recipe_dir, image_dir
def read_chowdown_file(recipe_file: Path) -> Recipe:
with open(recipe_file, "r") as stream:
recipe_description: str = str
recipe_data: dict = {}
try:
for x, item in enumerate(yaml.load_all(stream, Loader=Loader)):
print(item)
if x == 0:
recipe_data = item
elif x == 1:
recipe_description = str(item)
except yaml.YAMLError as exc:
print(exc)
return
reformat_data = {
"name": recipe_data.get("title"),
"description": recipe_description,
"image": recipe_data.get("image", ""),
"recipeIngredient": recipe_data.get("ingredients"),
"recipeInstructions": recipe_data.get("directions"),
"tags": recipe_data.get("tags").split(","),
}
pprint(reformat_data)
new_recipe = Recipe(**reformat_data)
reformated_list = []
for instruction in new_recipe.recipeInstructions:
reformated_list.append({"text": instruction})
new_recipe.recipeInstructions = reformated_list
return new_recipe
def main():
from db.mongo_setup import global_init
global_init()
recipe_dir, image_dir = pull_repo(repo)
failed_images = []
for image in image_dir.iterdir():
try:
image.rename(IMG_DIR.joinpath(image.name))
except:
failed_images.append(image.name)
failed_recipes = []
for recipe in recipe_dir.glob("*.md"):
print(recipe.name)
try:
new_recipe = read_chowdown_file(recipe)
new_recipe.save_to_db()
except:
failed_recipes.append(recipe.name)
report = {"failedImages": failed_images, "failedRecipes": failed_recipes}
print(report)
main()

View File

@ -20,7 +20,11 @@ def auto_backup_job():
for backup in BACKUP_DIR.glob("Auto*.zip"):
backup.unlink()
export_db(tag="Auto", template=None)
templates = []
for template in TEMPLATE_DIR.iterdir():
templates.append(template)
export_db(tag="Auto", templates=templates)
logger.info("Auto Backup Called")
@ -56,7 +60,7 @@ def import_from_archive(file_name: str) -> list:
return successful_imports
def export_db(tag=None, template=None) -> str:
def export_db(tag=None, templates=None):
if tag:
export_tag = tag + "_" + datetime.now().strftime("%Y-%b-%d")
else:
@ -72,7 +76,12 @@ def export_db(tag=None, template=None) -> str:
recipe_folder.mkdir(parents=True, exist_ok=True)
export_images(img_folder)
export_recipes(recipe_folder, template)
if type(templates) == list:
for template in templates:
export_recipes(recipe_folder, template)
elif type(templates) == str:
export_recipes(recipe_folder, templates)
zip_path = BACKUP_DIR.joinpath(f"{export_tag}")
shutil.make_archive(zip_path, "zip", backup_folder)
@ -95,7 +104,7 @@ def export_recipes(dest_dir: Path, template=None) -> Path:
json_recipe = recipe.to_json(indent=4)
if template:
md_dest = dest_dir.parent.joinpath("markdown")
md_dest = dest_dir.parent.joinpath("templates")
md_dest.mkdir(parents=True, exist_ok=True)
template = TEMPLATE_DIR.joinpath(template)
export_markdown(md_dest, json_recipe, template)

View File

@ -29,6 +29,18 @@ def pull_repo(repo):
def read_chowdown_file(recipe_file: Path) -> Recipe:
"""Parse through the yaml file to try and pull out the relavent information.
Some issues occur when ":" are used in the text. I have no put a lot of effort
into this so there may be better ways of going about it. Currently, I get about 80-90%
of recipes from repos I've tried.
Args:
recipe_file (Path): Path to the .yml file
Returns:
Recipe: Recipe class object
"""
with open(recipe_file, "r") as stream:
recipe_description: str = str
recipe_data: dict = {}
@ -85,7 +97,6 @@ def chowdown_migrate(repo):
except:
failed_recipes.append(recipe.name)
report = {"failedImages": failed_images, "failedRecipes": failed_recipes}
return report

View File

@ -125,26 +125,25 @@ class Recipe(BaseModel):
document.delete()
return "Document Deleted"
@staticmethod
def update(recipe_slug: str, data: dict) -> dict:
""" Updates the recipe from the database by slug """
def update(self, recipe_slug: str):
""" Updates the recipe from the database by slug"""
document = RecipeDocument.objects.get(slug=recipe_slug)
if document:
document.update(set__name=data.get("name"))
document.update(set__description=data.get("description"))
document.update(set__image=data.get("image"))
document.update(set__recipeYield=data.get("recipeYield"))
document.update(set__recipeIngredient=data.get("recipeIngredient"))
document.update(set__recipeInstructions=data.get("recipeInstructions"))
document.update(set__totalTime=data.get("totalTime"))
document.update(set__name=self.name)
document.update(set__description=self.description)
document.update(set__image=self.image)
document.update(set__recipeYield=self.recipeYield)
document.update(set__recipeIngredient=self.recipeIngredient)
document.update(set__recipeInstructions=self.recipeInstructions)
document.update(set__totalTime=self.totalTime)
document.update(set__categories=data.get("categories"))
document.update(set__tags=data.get("tags"))
document.update(set__notes=data.get("notes"))
document.update(set__orgURL=data.get("orgURL"))
document.update(set__rating=data.get("rating"))
document.update(set__extras=data.get("extras"))
document.update(set__categories=self.categories)
document.update(set__tags=self.tags)
document.update(set__notes=self.notes)
document.update(set__orgURL=self.orgURL)
document.update(set__rating=self.rating)
document.update(set__extras=self.extras)
document.save()
@ -174,5 +173,3 @@ def read_requested_values(keys: list, max_results: int = 0) -> List[dict]:
recipe_list.append(recipe_details)
return recipe_list

View File

@ -1,3 +1,6 @@
import json
from pathlib import Path
from scrape_schema_recipe import scrape_url
from slugify import slugify
from utils.logger import logger
@ -5,9 +8,24 @@ from utils.logger import logger
from services.image_services import scrape_image
from services.recipe_services import Recipe
CWD = Path(__file__).parent
TEMP_FILE = CWD.parent.joinpath("data", "debug", "last_recipe.json")
def normalize_data(recipe_data: dict) -> dict:
if type(recipe_data["recipeYield"]) == list:
recipe_data["recipeYield"] = recipe_data["recipeYield"][0]
return recipe_data
def create_from_url(url: str) -> dict:
recipe_data = process_recipe_url(url)
with open(TEMP_FILE, "w") as f:
f.write(json.dumps(recipe_data, indent=4, default=str))
recipe_data = normalize_data(recipe_data)
recipe = Recipe(**recipe_data)
return recipe.save_to_db()

View File

@ -6,10 +6,12 @@ import dotenv
CWD = Path(__file__).parent
ENV = CWD.joinpath(".env")
dotenv.load_dotenv(ENV)
PORT = 9000
# General
PORT = int(os.getenv("mealie_port", 9000))
# Mongo Database
MEALIE_DB_NAME = os.getenv("mealie_db_name", "mealie")
DB_USERNAME = os.getenv("db_username", "root")
DB_PASSWORD = os.getenv("db_password", "example")
DB_HOST = os.getenv("db_host", "mongo")

View File

@ -13,6 +13,7 @@ def ensure_dirs():
DATA_DIR.joinpath("img").mkdir(parents=True, exist_ok=True)
DATA_DIR.joinpath("backups").mkdir(parents=True, exist_ok=True)
DATA_DIR.joinpath("templates").mkdir(parents=True, exist_ok=True)
DATA_DIR.joinpath("debug").mkdir(parents=True, exist_ok=True)
def generate_default_theme():
@ -21,7 +22,7 @@ def generate_default_theme():
"accent": "#00457A",
"secondary": "#973542",
"success": "#5AB1BB",
"info": "#FFFD99",
"info": "#4990BA",
"warning": "#FF4081",
"error": "#EF5350",
}

View File

@ -1,4 +1 @@
{'name': 'Broccoli Beer Cheese Soup', 'description': "This recipe is inspired by one of my favorites, Gourmand's Beer Cheese Soup, which uses Shiner Bock. Feel free to use whatever you want, then go to [Gourmand's](http://lovethysandwich.com) to have the real thing.", 'image': 'broccoli-beer-cheese-soup.jpg', 'recipeYield': None, 'recipeIngredient': ['4 tablespoons butter', '1 cup diced onion', '1/2 cup shredded carrot', '1/2 cup diced celery', '1 tablespoon garlic', '1/4 cup flour', '1 quart chicken broth', '1 cup heavy cream', '10 ounces muenster cheese', '1 cup white white wine', '1 cup pale beer', '1 teaspoon Worcestershire sauce', '1/2 teaspoon hot sauce'
], 'recipeInstructions': ['Start with butter, onions, carrots, celery, garlic until cooked down', 'Add flour, stir well, cook for 4-5 mins', 'Add chicken broth, bring to a boil', 'Add wine and reduce to a simmer', 'Add cream, cheese, Worcestershire, and hot sauce', 'Serve with croutons'
], 'totalTime': None, 'slug': 'broccoli-beer-cheese-soup', 'categories': None, 'tags': None, 'dateAdded': None, 'notes': None, 'rating': None, 'orgURL': None, 'extras': None
}
// Test Notify