diff --git a/Dockerfile b/Dockerfile index 4b6df2a0c..2528d200b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,14 +7,25 @@ RUN npm run build FROM python:3.9-alpine -RUN apk add --no-cache libxml2-dev libxslt-dev libxml2 caddy libffi-dev -ENV ENV prod + +RUN apk add --no-cache libxml2-dev \ + libxslt-dev \ + libxml2 caddy \ + libffi-dev \ + python3 \ + python3-dev \ + jpeg-dev \ + lcms2-dev \ + openjpeg-dev \ + zlib-dev + + +ENV ENV True EXPOSE 80 WORKDIR /app/ COPY ./pyproject.toml /app/ - RUN apk add --update --no-cache --virtual .build-deps \ curl \ g++ \ diff --git a/Dockerfile.dev b/Dockerfile.dev index 8f3d38ffa..bce6c5ea0 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -12,10 +12,11 @@ RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get- poetry config virtualenvs.create false # Copy poetry.lock* in case it doesn't exist in the repo -COPY ./pyproject.toml ./poetry.lock* /app/ - -RUN poetry install +COPY ./pyproject.toml /app/ COPY ./mealie /app/mealie -CMD ["uvicorn", "mealie.app:app", "--host", "0.0.0.0", "--port", "9000", "--reload"] \ No newline at end of file +RUN poetry install + +RUN chmod +x /app/mealie/run.sh +CMD ["/app/mealie/run.sh", "reload"] diff --git a/dev/data/templates/recipes.md b/dev/data/templates/recipes.md index 84a404798..6ee2d4bb6 100644 --- a/dev/data/templates/recipes.md +++ b/dev/data/templates/recipes.md @@ -1,6 +1,6 @@ -![Recipe Image](../../images/{{ recipe.image }}) +![Recipe Image](../../images/{{ recipe.slug }}/original.jpg) # {{ recipe.name }} {{ recipe.description }} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index eb1b33251..6e1aac0a9 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -29,8 +29,8 @@ services: db_type: sqlite TZ: America/Anchorage # Specify Correct Timezone for Date/Time to line up correctly. volumes: - - ./app_data:/app_data - - ./mealie:/app + - ./dev/data:/app/dev/data + - ./mealie:/app/mealie # Mkdocs mealie-docs: diff --git a/docs/docs/changelog/v0.4.1.md b/docs/docs/changelog/v0.4.1.md new file mode 100644 index 000000000..9ab671f6a --- /dev/null +++ b/docs/docs/changelog/v0.4.1.md @@ -0,0 +1,35 @@ +# v0.4.1 + +**App Version: v0.4.1** + +**Database Version: v0.4.0** + +!!! error "Breaking Changes" + + #### Recipe Images + While it *shouldn't* be a breaking change, I feel it is important to note that you may experience issues with the new image migration. Recipe images are now minified, this is done on start-up, import, migration, and when a new recipe is created. The initial boot or load may be a bit slow if you have lots of recipes but you likely won't notice. What you may notice is that if your recipe slug and the image name do not match, you will encounter issues with your images showing up. This can be resolved by finding the image directory and rename it to the appropriate slug. I did fix multiple edge cases, but it is likely more exists. As always make a backup before you update! + + On the plus side, this comes with a huge performance increase! 🎉 + +- Add markdown support for ingredients - Resolves #32 +- Ingredients editor improvements +- Fix Tags/Categories render problems on recipes +- Tags redirect to new tag pages +- Categories redirect to category pages +- Fix Backup download blocked by authentication +- Random meal-planner will no longer duplicate recipes unless no other options +- New Quick Week button to generate next 5 day week of recipe slots. +- Minor UI tweaks +- Recipe Cards now display 2 recipe tags +- Recipe images are now minified. This comes with a serious performance improvement. On initial startup you may experience some delays. Images are migrated to the new structure on startup, depending on the size of your database this can take some time. + - Note that original images are still kept for large displays like on the individual recipe pages. + - A smaller image is used for recipe cards + - A 'tiny' image is used for search images. +- Advanced Search Page. You can now use the search page to filter recipes to include/exclude tags and categories as well as select And/Or matching criteria. +- Added link to advanced search on quick search +- Better support for NextCloud imports + - Translate keywords to tags + - Fix rollback on failure +- Recipe Tag/Category Input components have been unified and now share a single way to interact. To add a new category in the recipe editor you need to click to '+' icon next to the input and fill out the form. This is the same for adding a Tag. + + diff --git a/docs/docs/getting-started/api-usage.md b/docs/docs/getting-started/api-usage.md index 4964ae6ed..b58fc485f 100644 --- a/docs/docs/getting-started/api-usage.md +++ b/docs/docs/getting-started/api-usage.md @@ -10,5 +10,94 @@ For example you could add `{"message": "Remember to thaw the chicken"}` to a rec ## 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. -Have Ideas? Submit a PR! \ No newline at end of file +#### 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! diff --git a/docs/docs/getting-started/install.md b/docs/docs/getting-started/install.md index 3f2aaf113..737d20e48 100644 --- a/docs/docs/getting-started/install.md +++ b/docs/docs/getting-started/install.md @@ -23,6 +23,11 @@ docker run \ ``` +!!! tip "Default Credentials" + **Username:** changeme@email.com + + **Password:** MyPassword + ## Docker Compose with SQLite Deployment with docker-compose is the recommended method for deployment. The example below will create an instance of mealie available on port `9925` with the data volume mounted from the local directory. To use, create a docker-compose.yml file, paste the contents below and save. In the terminal run `docker-compose up -d` to start the container. diff --git a/docs/docs/overrides/api.html b/docs/docs/overrides/api.html index 5249b0d34..f679a924c 100644 --- a/docs/docs/overrides/api.html +++ b/docs/docs/overrides/api.html @@ -14,7 +14,7 @@
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 080c3ca3d..7d675172a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -74,6 +74,7 @@ nav: - Guidelines: "contributors/developers-guide/general-guidelines.md" - Development Road Map: "roadmap.md" - Change Log: + - v0.4.1 Frontend/UI: "changelog/v0.4.1.md" - v0.4.0 Authentication: "changelog/v0.4.0.md" - v0.3.0 Improvements: "changelog/v0.3.0.md" - v0.2.0 Now With Tests!: "changelog/v0.2.0.md" diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 69b2e3c0f..bb545577b 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,35 +1,6 @@ @@ -160,6 +113,7 @@ export default { .notify-base { color: white !important; + /* min-height: 50px; */ margin-right: 60px; margin-bottom: -5px; opacity: 0.9 !important; @@ -176,11 +130,4 @@ export default { *::-webkit-scrollbar-thumb { background: grey; } - -.notify-base { - color: white !important; - margin-right: 60px; - margin-bottom: -5px; - opacity: 0.9 !important; -} diff --git a/frontend/src/api/category.js b/frontend/src/api/category.js index ef582c580..f60739c8d 100644 --- a/frontend/src/api/category.js +++ b/frontend/src/api/category.js @@ -5,27 +5,27 @@ import { store } from "@/store"; const prefix = baseURL + "categories"; const categoryURLs = { - get_all: `${prefix}`, - get_category: category => `${prefix}/${category}`, - delete_category: category => `${prefix}/${category}`, + getAll: `${prefix}`, + getCategory: category => `${prefix}/${category}`, + deleteCategory: category => `${prefix}/${category}`, }; export const categoryAPI = { async getAll() { - let response = await apiReq.get(categoryURLs.get_all); + let response = await apiReq.get(categoryURLs.getAll); return response.data; }, async create(name) { - let response = await apiReq.post(categoryURLs.get_all, { name: name }); + let response = await apiReq.post(categoryURLs.getAll, { name: name }); store.dispatch("requestCategories"); return response.data; }, async getRecipesInCategory(category) { - let response = await apiReq.get(categoryURLs.get_category(category)); + let response = await apiReq.get(categoryURLs.getCategory(category)); return response.data; }, async delete(category) { - let response = await apiReq.delete(categoryURLs.delete_category(category)); + let response = await apiReq.delete(categoryURLs.deleteCategory(category)); store.dispatch("requestCategories"); return response.data; }, @@ -44,6 +44,11 @@ export const tagAPI = { let response = await apiReq.get(tagURLs.getAll); return response.data; }, + async create(name) { + let response = await apiReq.post(tagURLs.getAll, { name: name }); + store.dispatch("requestTags"); + return response.data; + }, async getRecipesInTag(tag) { let response = await apiReq.get(tagURLs.getTag(tag)); return response.data; diff --git a/frontend/src/api/meta.js b/frontend/src/api/meta.js index bcf6470fc..59183c0c5 100644 --- a/frontend/src/api/meta.js +++ b/frontend/src/api/meta.js @@ -5,15 +5,22 @@ const prefix = baseURL + "debug"; const debugURLs = { version: `${prefix}/version`, + debug: `${prefix}`, lastRecipe: `${prefix}/last-recipe-json`, demo: `${prefix}/is-demo`, }; export const metaAPI = { - async get_version() { + async getAppInfo() { let response = await apiReq.get(debugURLs.version); return response.data; }, + + async getDebugInfo() { + const response = await apiReq.get(debugURLs.debug); + return response.data; + }, + async getLastJson() { let response = await apiReq.get(debugURLs.lastRecipe); return response.data; @@ -21,7 +28,6 @@ export const metaAPI = { async getIsDemo() { let response = await apiReq.get(debugURLs.demo); - console.log(response); return response.data; }, }; diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index 97a4b08c4..50e45e593 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -8,6 +8,7 @@ const prefix = baseURL + "recipes/"; const recipeURLs = { allRecipes: baseURL + "recipes", + summary: baseURL + "recipes" + "/summary", allRecipesByCategory: prefix + "category", create: prefix + "create", createByURL: prefix + "create-url", @@ -56,9 +57,7 @@ export const recipeAPI = { const fd = new FormData(); fd.append("image", fileObject); fd.append("extension", fileObject.name.split(".").pop()); - let response = apiReq.put(recipeURLs.updateImage(recipeSlug), fd); - return response; }, @@ -87,4 +86,21 @@ export const recipeAPI = { return response.data; }, + + async allSummary() { + const response = await apiReq.get(recipeURLs.summary); + return response.data; + }, + + recipeImage(recipeSlug) { + return `/api/recipes/${recipeSlug}/image?image_type=original`; + }, + + recipeSmallImage(recipeSlug) { + return `/api/recipes/${recipeSlug}/image?image_type=small`; + }, + + recipeTinyImage(recipeSlug) { + return `/api/recipes/${recipeSlug}/image?image_type=tiny`; + }, }; diff --git a/frontend/src/components/Admin/AdminSidebar.vue b/frontend/src/components/Admin/AdminSidebar.vue index 5e719307b..f09070492 100644 --- a/frontend/src/components/Admin/AdminSidebar.vue +++ b/frontend/src/components/Admin/AdminSidebar.vue @@ -74,7 +74,7 @@ - + mdi-information @@ -83,10 +83,11 @@ {{ $t("settings.current") }} - {{ version }} + {{ appVersion }} - + {{ $t("general.download") }} @@ -61,6 +66,7 @@ diff --git a/frontend/src/components/Admin/General/CreatePageDialog.vue b/frontend/src/components/Admin/General/CreatePageDialog.vue index efedf8cf0..57f6830ed 100644 --- a/frontend/src/components/Admin/General/CreatePageDialog.vue +++ b/frontend/src/components/Admin/General/CreatePageDialog.vue @@ -19,10 +19,11 @@ v-model="page.name" label="Page Name" > - @@ -43,10 +44,10 @@ \ No newline at end of file diff --git a/frontend/src/components/Admin/Migration/MigrationCard.vue b/frontend/src/components/Admin/Migration/MigrationCard.vue index 7f3ef7ed4..8dad1af08 100644 --- a/frontend/src/components/Admin/Migration/MigrationCard.vue +++ b/frontend/src/components/Admin/Migration/MigrationCard.vue @@ -40,7 +40,13 @@ {{ $t("general.delete") }} - + {{ $t("general.import") }} @@ -82,10 +88,10 @@ export default { this.$emit("refresh"); }, async importMigration(file_name) { - this.loading == true; + this.loading = true; let response = await api.migrations.import(this.folder, file_name); this.$emit("imported", response.successful, response.failed); - this.loading == false; + this.loading = false; }, readableTime(timestamp) { let date = new Date(timestamp); diff --git a/frontend/src/components/FormHelpers/CategorySelector.vue b/frontend/src/components/FormHelpers/CategorySelector.vue deleted file mode 100644 index 390bd60d6..000000000 --- a/frontend/src/components/FormHelpers/CategorySelector.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/components/FormHelpers/CategoryTagSelector.vue b/frontend/src/components/FormHelpers/CategoryTagSelector.vue new file mode 100644 index 000000000..4abdffe1f --- /dev/null +++ b/frontend/src/components/FormHelpers/CategoryTagSelector.vue @@ -0,0 +1,129 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Login/LoginForm.vue b/frontend/src/components/Login/LoginForm.vue index b2cc18981..1cb48b729 100644 --- a/frontend/src/components/Login/LoginForm.vue +++ b/frontend/src/components/Login/LoginForm.vue @@ -22,13 +22,11 @@ diff --git a/frontend/src/components/Recipe/MobileRecipeCard.vue b/frontend/src/components/Recipe/MobileRecipeCard.vue index 9b436c076..c0df606bf 100644 --- a/frontend/src/components/Recipe/MobileRecipeCard.vue +++ b/frontend/src/components/Recipe/MobileRecipeCard.vue @@ -1,8 +1,13 @@ - \ No newline at end of file diff --git a/frontend/src/components/Recipe/RecipeViewer/RecipeChips.vue b/frontend/src/components/Recipe/RecipeViewer/RecipeChips.vue index 825f2e8d9..de8d9a4b4 100644 --- a/frontend/src/components/Recipe/RecipeViewer/RecipeChips.vue +++ b/frontend/src/components/Recipe/RecipeViewer/RecipeChips.vue @@ -1,13 +1,14 @@