Merge branch 'dev' of https://github.com/hay-kot/mealie into feature/toolbox-page

This commit is contained in:
hay-kot 2021-04-09 09:03:34 -08:00
commit eae4a134ea
14 changed files with 355 additions and 103 deletions

9
.vscode/tasks.json vendored
View file

@ -52,6 +52,15 @@
"group": "groupA"
},
"problemMatcher": []
},
{
"label": "Run python tests",
"command": "make test",
"type": "shell",
"presentation": {
"reveal": "always"
},
"problemMatcher": []
}
]
}

View file

@ -40,9 +40,9 @@ RUN apk add --update --no-cache --virtual .build-deps \
cd /app/ && poetry install --no-root --no-dev && \
apk --purge del .build-deps
COPY ./mealie /app/mealie
RUN poetry install --no-dev
COPY ./Caddyfile /app
COPY ./dev/data/templates /app/data/templates
COPY --from=build-stage /app/dist /app/dist

View file

@ -0,0 +1,93 @@
!!! info
This example was submitted by a user. Have an Example? Submit a PR!
Recipes can be imported in bulk from a file containing a list of URLs. This can be done using the following bash or python scripts with the `list` file containing one URL per line.
#### Bash
```bash
#!/bin/bash
function authentification () {
auth=$(curl -X 'POST' \
"$3/api/auth/token" \
-H 'accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=&username='$1'&password='$2'&scope=&client_id=&client_secret=')
echo $auth | sed -e 's/.*token":"\(.*\)",.*/\1/'
}
function import_from_file () {
while IFS= read -r line
do
echo $line
curl -X 'POST' \
"$3/api/recipes/create-url" \
-H "Authorization: Bearer $2" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"url": "'$line'" }'
echo
done < "$1"
}
input="list"
mail="changeme@email.com"
password="MyPassword"
mealie_url=http://localhost:9000
token=$(authentification $mail $password $mealie_url)
import_from_file $input $token $mealie_url
```
#### Python
```python
import requests
import re
def authentification(mail, password, mealie_url):
headers = {
'accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
}
data = {
'grant_type': '',
'username': mail,
'password': password,
'scope': '',
'client_id': '',
'client_secret': ''
}
auth = requests.post(mealie_url + "/api/auth/token", headers=headers, data=data)
token = re.sub(r'.*token":"(.*)",.*', r'\1', auth.text)
return token
def import_from_file(input_file, token, mealie_url):
with open(input_file) as fp:
for l in fp:
line = re.sub(r'(.*)\n', r'\1', l)
print(line)
headers = {
'Authorization': "Bearer " + token,
'accept': 'application/json',
'Content-Type': 'application/json'
}
data = {
'url': line
}
response = requests.post(mealie_url + "/api/recipes/create-url", headers=headers, json=data)
print(response.text)
input_file="list"
mail="changeme@email.com"
password="MyPassword"
mealie_url="http://localhost:9000"
token = authentification(mail, password, mealie_url)
import_from_file(input_file, token, mealie_url)
```

View file

@ -0,0 +1,39 @@
# Usage
## Getting a Token
Currently Mealie doesn't support creating a long-live token. You can however get a token from the API. This example was pulled from the automatic API documentation provided by Mealie.
### Curl
```bash
curl -X 'POST' \
'https://mealie-demo.hay-kot.dev/api/auth/token' \
-H 'accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=&username=changeme%40email.com&password=demo&scope=&client_id=&client_secret='
```
#### Response
```json
{
"snackbar": {
"text": "User Successfully Logged In",
"type": "success"
},
"access_token": "your-long-token-string",
"token_type": "bearer"
}
```
## Key Components
### Exploring Your Local API
On your local installation you can access interactive API documentation that provides `curl` examples and expected results. This allows you to easily test and interact with your API to identify places to include your own functionality. You can visit the documentation at `http://mealie.yourdomain.com/docs or see the example at the [Demo Site](https://mealie-demo.hay-kot.dev/docs)
### Recipe Extras
Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs within a recipe to reference from 3rd part applications. You can use these keys to contain information to trigger automation or custom messages to relay to your desired device.
For example you could add `{"message": "Remember to thaw the chicken"}` to a recipe and use the webhooks built into mealie to send that message payload to a destination to be processed.
![api-extras-gif](../assets/gifs/api-extras.gif)

View file

@ -0,0 +1,30 @@
In a lot of ways, Home Assistant is why this project exists! Since it Mealie has a robust API it makes it a great fit for interacting with Home Assistant and pulling information into your dashboard.
### Get Todays Meal in Lovelace
Starting in v0.4.1 you are now able to use the uri `/api/meal-plans/today/image?group_name=Home` to directly access the image to todays meal. This makes it incredible easy to include the image into your Home Assistant Dashboard using the picture entity.
Here's an example where `sensor.mealie_todays_meal` is pulling in the meal-plan name and I'm using the url to get the image.
![api-extras-gif](../assets/img/home-assistant-card.png)
```yaml
type: picture-entity
entity: sensor.mealie_todays_meal
name: Dinner Tonight
show_state: true
show_name: true
image: 'http://localhost:9000/api/meal-plans/today/image?group_name=Home'
style:
.: |
ha-card {
max-height: 300px !important;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
```
!!! tip
Due to how Home Assistant works with images, I had to include the additional styling to get the images to not appear distorted. This includes and [additional installation](https://github.com/thomasloven/lovelace-card-mod) from HACS.

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

View file

@ -1,5 +1,8 @@
# Usage
## Getting a Token
Bla Bla
## Key Components
### Recipe Extras
Recipes extras are a key feature of the Mealie API. They allow you to create custom json key/value pairs within a recipe to reference from 3rd part applications. You can use these keys to contain information to trigger automation or custom messages to relay to your desired device.
@ -8,96 +11,4 @@ For example you could add `{"message": "Remember to thaw the chicken"}` to a rec
![api-extras-gif](../assets/gifs/api-extras.gif)
## Examples
### Bulk import
Recipes can be imported in bulk from a file containing a list of URLs. This can be done using the following bash or python scripts with the `list` file containing one URL per line.
#### Bash
```bash
#!/bin/bash
function authentification () {
auth=$(curl -X 'POST' \
"$3/api/auth/token" \
-H 'accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=&username='$1'&password='$2'&scope=&client_id=&client_secret=')
echo $auth | sed -e 's/.*token":"\(.*\)",.*/\1/'
}
function import_from_file () {
while IFS= read -r line
do
echo $line
curl -X 'POST' \
"$3/api/recipes/create-url" \
-H "Authorization: Bearer $2" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"url": "'$line'" }'
echo
done < "$1"
}
input="list"
mail="changeme@email.com"
password="MyPassword"
mealie_url=http://localhost:9000
token=$(authentification $mail $password $mealie_url)
import_from_file $input $token $mealie_url
```
#### Python
```python
import requests
import re
def authentification(mail, password, mealie_url):
headers = {
'accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
}
data = {
'grant_type': '',
'username': mail,
'password': password,
'scope': '',
'client_id': '',
'client_secret': ''
}
auth = requests.post(mealie_url + "/api/auth/token", headers=headers, data=data)
token = re.sub(r'.*token":"(.*)",.*', r'\1', auth.text)
return token
def import_from_file(input_file, token, mealie_url):
with open(input_file) as fp:
for l in fp:
line = re.sub(r'(.*)\n', r'\1', l)
print(line)
headers = {
'Authorization': "Bearer " + token,
'accept': 'application/json',
'Content-Type': 'application/json'
}
data = {
'url': line
}
response = requests.post(mealie_url + "/api/recipes/create-url", headers=headers, json=data)
print(response.text)
input_file="list"
mail="changeme@email.com"
password="MyPassword"
mealie_url="http://localhost:9000"
token = authentification(mail, password, mealie_url)
import_from_file(input_file, token, mealie_url)
```
Have Ideas? Submit a PR!

View file

@ -56,7 +56,6 @@ nav:
- Organizing Recipes: "getting-started/organizing-recipes.md"
- Planning Meals: "getting-started/meal-planner.md"
- iOS Shortcuts: "getting-started/ios.md"
- API Usage: "getting-started/api-usage.md"
- Site Administration:
- User Settings: "site-administration/user-settings.md"
- Site Settings: "site-administration/site-settings.md"
@ -64,6 +63,10 @@ nav:
- User Management: "site-administration/user-management.md"
- Backups and Restore: "site-administration/backups-and-exports.md"
- Recipe Migration: "site-administration/migration-imports.md"
- API Usage:
- Getting Started: "api-usage/getting-started.md"
- Home Assistant: "api-usage/home-assistant.md"
- Bulk Url Import: "api-usage/bulk-url-import.md"
- API Reference: "api/redoc.md"
- Contributors Guide:
- Non-Code: "contributors/non-coders.md"

View file

@ -0,0 +1,160 @@
{
"404": {
"page-not-found": "404 Página não encontrada",
"take-me-home": "Voltar ao início"
},
"new-recipe": {
"from-url": "Do URL",
"recipe-url": "URL da Receita",
"error-message": "Ocorreu um erro ao ler o URL. Verifica os registos e o debug/last_recipe.json para perceber o que correu mal." ,
"bulk-add": "Adicionar Vários",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Insira os dados da sua receita. Cada linha será tratada como um item numa lista."
},
"general": {
"upload": "Enviar",
"submit": "Submeter",
"name": "Nome",
"settings": "Definições",
"close": "Fechar",
"save": "Guardar",
"image-file": "Ficheiro de Imagem",
"update": "Atualizar",
"edit": "Editar",
"delete": "Eliminar",
"select": "Seleccionar",
"random": "Aleatório",
"new": "Novo",
"create": "Criar",
"cancel": "Cancelar",
"ok": "OK",
"enabled": "Ativado",
"download": "Transferir",
"import": "Importar",
"options": "Opções",
"templates": "Templates",
"recipes": "Receitas",
"themes": "Temas",
"confirm": "Confirmar"
},
"login": {
"stay-logged-in": "Manter a sessão iniciada?",
"email": "Email",
"password": "Password",
"sign-in": "Iniciar Sessão",
"sign-up": "Criar Conta"
},
"meal-plan": {
"shopping-list": "Lista de Compras",
"dinner-this-week": "Jantar esta semana",
"meal-planner": "Planeador de Refeições",
"dinner-today": "Jantar Hoje",
"planner": "Planeador",
"edit-meal-plan": "Editar Plano de Refeições",
"meal-plans": "Planos de Refeições",
"create-a-new-meal-plan": "Criar novo Plano de Refeições",
"start-date": "Data de Inicio",
"end-date": "Data de Fim"
},
"recipe": {
"description": "Descrição",
"ingredients": "Ingredientes",
"categories": "Categorias",
"tags": "Etiquetas",
"instructions": "Instruções",
"step-index": "Passo: {step}",
"recipe-name": "Nome da Receita",
"servings": "Porções",
"ingredient": "Ingrediente",
"notes": "Notas",
"note": "Nota",
"original-url": "URL Original",
"view-recipe": "Ver Receita",
"title": "Título",
"total-time": "Tempo Total",
"prep-time": "Tempo de Preparação",
"perform-time": "Tempo de Cozedura",
"api-extras": "Extras API",
"object-key": "Chave do Objeto",
"object-value": "Valor do Objeto",
"new-key-name": "Novo nome da Chave",
"add-key": "Adicionar Chave",
"key-name-required": "Nome da Chave é Obrigatório",
"no-white-space-allowed": "Espaço em Branco não Permitido",
"delete-recipe": "Eliminar Receita",
"delete-confirmation": "Tem a certeza que deseja eliminar esta receita?"
},
"search": {
"search-mealie": "Pesquisar Mealie"
},
"settings": {
"general-settings": "Definições Gerais",
"local-api": "API Local",
"language": "Língua",
"add-a-new-theme": "Adicionar novo tema",
"set-new-time": "Definir hora",
"current": "Versão:",
"latest": "Mais Recente",
"explore-the-docs": "Explorar Documentação",
"contribute": "Contribuir",
"backup-and-exports": "Backups",
"backup-info": "Backups are exported in standard JSON format along with all the images stored on the file system. In your backup folder you'll find a .zip file that contains all of the recipe JSON and images from the database. Additionally, if you selected a markdown file, those will also be stored in the .zip file. To import a backup, it must be located in your backups folder. Automated backups are done each day at 3:00 AM.",
"available-backups": "Backups Disponíveis",
"theme": {
"theme-name": "Nome do Tema",
"theme-settings": "Definições do Tema",
"select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Selecione um tema da lista ou crie um novo tema. Note que o tema por defeito será utilizado por todos os utilizadores que não selecionaram um tema preferido.",
"dark-mode": "Modo Escuro",
"theme-is-required": "Tema é Obrigatório",
"primary": "Primário",
"secondary": "Secondário",
"accent": "Accent",
"success": "Successo",
"info": "Info",
"warning": "Aviso",
"error": "Erro",
"default-to-system": "Mesmo do Sistema",
"light": "Claro",
"dark": "Escuro",
"theme": "Tema",
"saved-color-theme": "Cor de Tema Guardado",
"delete-theme": "Eliminar Tema",
"are-you-sure-you-want-to-delete-this-theme": "Tem a certeza que deseja eliminar este tema?",
"choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Escolha como o Mealie estará visivel. Escolha o Mesmo do sistema para seguir o tema do seu dispositivo, ou selecione claro ou escuro.",
"theme-name-is-required": "Nome do Tema é Obrigatório."
},
"webhooks": {
"meal-planner-webhooks": "Webhooks do Organizador de Refeições",
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Os URLs apresentados abaixo receberão webhooks que contêm os dados da receita para o plano de refeições no dia marcado. Atualmente, os webhooks serão executados a ",
"test-webhooks": "Webhooks de Teste",
"webhook-url": "Webhook URL"
},
"new-version-available": "Uma nova versão do Mealie está disponível, <a {aContents}> Visite o Repo </a>",
"backup": {
"import-recipes": "Importar Receitas",
"import-themes": "Importar Temas",
"import-settings": "Importa Definições",
"create-heading": "Criar um Backup",
"backup-tag": "Etiqueta do Backup",
"full-backup": "Backup Completo",
"partial-backup": "Backup Parcial",
"backup-restore-report": "Análise do Resultado do Backup",
"successfully-imported": "Importado com Sucesso",
"failed-imports": "Importações falhadas"
}
},
"migration": {
"recipe-migration": "Migração da Receita",
"failed-imports": "Importações Falhadas",
"migration-report": "Análise das Migrações",
"successful-imports": "Importações Bem sucedidas",
"no-migration-data-available": "Não há dados de migração disponíveis",
"nextcloud": {
"title": "Nextcloud Cookbook",
"description": "Migraar dados de uma instância do Nextcloud CookBook"
},
"chowdown": {
"title": "Chowdown",
"description": "Migrar dados do Chowdown"
}
}
}

View file

@ -35,6 +35,10 @@ const state = {
name: "German",
value: "de",
},
{
name: "Português",
value: "pt-PT",
},
],
};
@ -53,8 +57,8 @@ const actions = {
};
const getters = {
getActiveLang: (state) => state.lang,
getAllLangs: (state) => state.allLangs,
getActiveLang: state => state.lang,
getAllLangs: state => state.allLangs,
};
export default {

View file

@ -79,7 +79,7 @@ def get_today(session: Session = Depends(generate_session), current_user: UserIn
@router.get("/today/image", tags=["Meal Plan"])
def get_today(session: Session = Depends(generate_session), group_name: str = "Home"):
def get_todays_image(session: Session = Depends(generate_session), group_name: str = "Home"):
"""
Returns the image for todays meal-plan.
"""

View file

@ -4,11 +4,11 @@
ARG1=${1:-production}
# Set Script Directory - Used for running the script from a different directory.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# # Initialize Database Prerun
poetry run python $DIR/db/init_db.py
poetry run python $DIR/services/image/minify.py
poetry run python /app/mealie/db/init_db.py
poetry run python /app/mealie/services/image/minify.py
# Migrations
# TODO

View file

@ -60,7 +60,10 @@ def move_all_images():
continue
new_folder = app_dirs.IMG_DIR.joinpath(image_file.stem)
new_folder.mkdir(parents=True, exist_ok=True)
image_file.rename(new_folder.joinpath(f"original{image_file.suffix}"))
new_file = new_folder.joinpath(f"original{image_file.suffix}")
if new_file.is_file():
new_file.unlink()
image_file.rename(new_file)
def validate_slugs_in_database(session: Session = None):

View file

@ -80,7 +80,7 @@ def test_cleaner_instructions(instructions):
def test_html_with_recipe_data():
path = TEST_RAW_HTML.joinpath("healthy_pasta_bake_60759.html")
url = "https://www.bbc.co.uk/food/recipes/healthy_pasta_bake_60759"
recipe_data = extract_recipe_from_html(open(path).read(), url)
recipe_data = extract_recipe_from_html(open(path,encoding="utf8").read(), url)
assert len(recipe_data["name"]) > 10
assert len(recipe_data["slug"]) > 10