diff --git a/Dockerfile b/Dockerfile index bf8761a4b..6d833ce5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN npm run build FROM python:3.8-alpine -RUN apk add --no-cache git curl libxml2-dev libxslt-dev libxml2 +RUN apk add --no-cache libxml2-dev libxslt-dev libxml2 ENV ENV prod EXPOSE 80 WORKDIR /app @@ -15,6 +15,7 @@ WORKDIR /app COPY ./pyproject.toml /app/ RUN apk add --update --no-cache --virtual .build-deps \ + curl \ g++ \ py-lxml \ python3-dev \ @@ -34,35 +35,5 @@ COPY --from=build-stage /app/dist /app/dist RUN rm -rf /app/test /app/.temp - -CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"] - -# ---------------------------------- # -# Old Docker File -# ---------------------------------- # - -# FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8-slim -# FROM mrnr91/uvicorn-gunicorn-fastapi:python3.8 - - -# WORKDIR /app - -# RUN apt-get update -y && \ -# apt-get install -y python-pip python-dev git curl python3-dev libxml2-dev libxslt1-dev zlib1g-dev --no-install-recommends && \ -# rm -rf /var/lib/apt/lists/* && \ -# curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \ -# cd /usr/local/bin && \ -# ln -s /opt/poetry/bin/poetry && \ -# poetry config virtualenvs.create false - -# COPY ./pyproject.toml /app/ - -# COPY ./mealie /app -# RUN poetry install --no-root --no-dev -# COPY --from=build-stage /app/dist /app/dist -# RUN rm -rf /app/test /app/.temp - -# ENV ENV prod -# ENV APP_MODULE "app:app" - -# VOLUME [ "/app/data" ] +VOLUME [ "/app_data/" ] +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"] \ No newline at end of file diff --git a/dev/dev-notes.md b/dev/dev-notes.md index 2062d6be5..81433795e 100644 --- a/dev/dev-notes.md +++ b/dev/dev-notes.md @@ -16,6 +16,11 @@ Don't forget to [join the Discord](https://discord.gg/R6QDyJgbD2)! # Todo's +Test +- [ ] Image Upload Test +- [ ] Rename and Upload Image Test +- [x] Chowdown Migration End Point Test + Frontend - [ ] No Meal Today Page instead of Null - [ ] Recipe Print Page diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7061fce74..7f7e1ce4e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1966,6 +1966,16 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "cacache": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", @@ -1992,6 +2002,53 @@ "unique-filename": "^1.1.1" } }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2008,6 +2065,16 @@ "minipass": "^3.1.1" } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "terser-webpack-plugin": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", @@ -2024,6 +2091,18 @@ "terser": "^4.6.12", "webpack-sources": "^1.4.3" } + }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.1.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", + "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + } } } }, @@ -10175,6 +10254,11 @@ "is-plain-obj": "^1.0.0" } }, + "sortablejs": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz", + "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==" + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -11521,87 +11605,6 @@ } } }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.1.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", - "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "vue-router": { "version": "3.4.9", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz", @@ -11641,6 +11644,14 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vuedraggable": { + "version": "2.24.3", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz", + "integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==", + "requires": { + "sortablejs": "1.10.2" + } + }, "vuetify": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.4.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4deceb724..9d5aa8837 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "vue": "^2.6.11", "vue-i18n": "^8.22.4", "vue-router": "^3.4.9", + "vuedraggable": "^2.24.3", "vuetify": "^2.4.2", "vuex": "^3.6.0", "vuex-persistedstate": "^4.0.0-beta.3" @@ -61,4 +62,4 @@ "semi": true, "singleQuote": false } -} \ No newline at end of file +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 630fa1cce..8c043cbb6 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -17,7 +17,7 @@ @selected="navigateFromSearch" /> - + mdi-magnify @@ -34,11 +34,11 @@ \ No newline at end of file diff --git a/frontend/src/components/Settings/Backup/NewBackupCard.vue b/frontend/src/components/Settings/Backup/NewBackupCard.vue index 1f04ebd19..31962c5b5 100644 --- a/frontend/src/components/Settings/Backup/NewBackupCard.vue +++ b/frontend/src/components/Settings/Backup/NewBackupCard.vue @@ -77,8 +77,8 @@ export default { computed: { switchLabel() { if (this.fullBackup) { - return "Full Backup"; - } else return "Partial Backup"; + return this.$t("settings.backup.full-backup"); + } else return this.$t("settings.backup.partial-backup"); }, }, methods: { diff --git a/frontend/src/components/Settings/Backup/index.vue b/frontend/src/components/Settings/Backup/index.vue index d16e65164..2ae4c86d8 100644 --- a/frontend/src/components/Settings/Backup/index.vue +++ b/frontend/src/components/Settings/Backup/index.vue @@ -18,7 +18,7 @@ - Available Backups + {{ $t("settings.available-backups") }} diff --git a/frontend/src/components/Settings/General/HomePageSettings.vue b/frontend/src/components/Settings/General/HomePageSettings.vue new file mode 100644 index 000000000..8eb4af3bf --- /dev/null +++ b/frontend/src/components/Settings/General/HomePageSettings.vue @@ -0,0 +1,158 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Settings/General/index.vue b/frontend/src/components/Settings/General/index.vue index 3e3ac3cef..9f2c9cd0e 100644 --- a/frontend/src/components/Settings/General/index.vue +++ b/frontend/src/components/Settings/General/index.vue @@ -1,8 +1,20 @@ - - \ No newline at end of file diff --git a/frontend/src/components/Settings/Migration/MigrationCard.vue b/frontend/src/components/Settings/Migration/MigrationCard.vue index 139ff2dc1..8b1b53027 100644 --- a/frontend/src/components/Settings/Migration/MigrationCard.vue +++ b/frontend/src/components/Settings/Migration/MigrationCard.vue @@ -35,10 +35,10 @@ - Delete + {{ $t("general.delete") }} - Import + {{ $t("general.import") }} @@ -46,7 +46,7 @@
- No Migration Data Avaiable + {{ $t("migration.no-migration-data-available") }}
diff --git a/frontend/src/components/Settings/Migration/NextcloudCard.vue b/frontend/src/components/Settings/Migration/NextcloudCard.vue deleted file mode 100644 index 83230d97a..000000000 --- a/frontend/src/components/Settings/Migration/NextcloudCard.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/components/Settings/Migration/UploadMigrationButton.vue b/frontend/src/components/Settings/Migration/UploadMigrationButton.vue deleted file mode 100644 index b34ff6b27..000000000 --- a/frontend/src/components/Settings/Migration/UploadMigrationButton.vue +++ /dev/null @@ -1,49 +0,0 @@ -c - - - - \ No newline at end of file diff --git a/frontend/src/components/Settings/Migration/index.vue b/frontend/src/components/Settings/Migration/index.vue index 976756298..26694ea43 100644 --- a/frontend/src/components/Settings/Migration/index.vue +++ b/frontend/src/components/Settings/Migration/index.vue @@ -1,11 +1,11 @@ + + + + \ No newline at end of file diff --git a/frontend/src/components/UI/Confirmation.vue b/frontend/src/components/UI/Confirmation.vue index e7720b50d..e271f4303 100644 --- a/frontend/src/components/UI/Confirmation.vue +++ b/frontend/src/components/UI/Confirmation.vue @@ -21,8 +21,8 @@ - Cancel - Confirm + {{ $t("general.cancel") }} + {{ $t("general.confirm") }} diff --git a/frontend/src/components/UI/RecentRecipes.vue b/frontend/src/components/UI/RecentRecipes.vue deleted file mode 100644 index 684d482bd..000000000 --- a/frontend/src/components/UI/RecentRecipes.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/frontend/src/components/UI/Search.vue b/frontend/src/components/UI/Search.vue deleted file mode 100644 index ed2cd218b..000000000 --- a/frontend/src/components/UI/Search.vue +++ /dev/null @@ -1,63 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/components/UI/SearchBar.vue b/frontend/src/components/UI/SearchBar.vue index 795e7a553..84776fe37 100644 --- a/frontend/src/components/UI/SearchBar.vue +++ b/frontend/src/components/UI/SearchBar.vue @@ -6,7 +6,7 @@ item-text="item.name" dense light - label="Search Mealie" + :label="$t('search.search-mealie')" :search-input.sync="search" hide-no-data cache-items diff --git a/frontend/src/components/UI/UploadBtn.vue b/frontend/src/components/UI/UploadBtn.vue index cc7e92dd9..b13442b0f 100644 --- a/frontend/src/components/UI/UploadBtn.vue +++ b/frontend/src/components/UI/UploadBtn.vue @@ -3,7 +3,7 @@ mdi-cloud-upload - Upload + {{ $t('general.upload') }} @@ -15,7 +15,7 @@ export default { url: String, }, data: () => ({ - defaultButtonText: "Upload", + defaultButtonText: this.$t("general.upload"), file: null, isSelecting: false, }), diff --git a/frontend/src/locales/da.json b/frontend/src/locales/da.json index 73be28648..161cda64e 100644 --- a/frontend/src/locales/da.json +++ b/frontend/src/locales/da.json @@ -27,7 +27,6 @@ "save": "Gem", "select": "Vælg", "update": "Opdater", - "delete-data": "Slet data", "download": "Hent", "import": "Importere" }, @@ -42,7 +41,6 @@ "dinner-this-week": "Madplan denne uge", "dinner-today": "Madplan i dag", "planner": "Planlægger", - "choose-a-recipe": "Vælg en opskrift", "create-a-new-meal-plan": "Opret en ny måltidsplan", "edit-meal-plan": "Rediger måltidsplan", "end-date": "Slutdato", @@ -57,7 +55,7 @@ "instructions": "Instruktioner", "note": "Bemærk", "notes": "Bemærkninger", - "original-recipe": "Oprindelig opskrift", + "original-url": "Oprindelig opskrift", "recipe-name": "Opskriftens navn", "servings": "Portioner", "step-index": "Trin: {step}", @@ -65,36 +63,20 @@ "view-recipe": "Se opskrift" }, "search": { - "search-for-a-recipe": "Søg efter en opskrift", - "search-for-your-favorite-recipe": "Søg efter din foretrukne opskrift" + "search-mealie": "Search Mealie" }, "migration": { - "chowdown-repo-url": "Chowdown Repo URL", - "currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration": "I øjeblikket er Chowdown via offentlig Repo URL den eneste understøttede migreringstype", - "failed-images": "Mislykkede billeder", - "failed-recipes": "Mislykkede opskrifter", - "migrate": "Migrere", "recipe-migration": "Migrering af opskrifter", - "delete-confirmation": "Er du sikker på, at du vil slette disse migrationsdata?", - "failed-imports": "Mislykket import", - "nextcloud-data": "Nextcloud data", - "successfully-imported-from-nextcloud": "Importeret fra Nextcloud", - "upload-an-archive": "Upload et arkiv", - "you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected": "Du kan importere opskrifter fra enten en zip-fil eller et bibliotek i /app/data/migraiton/ folderen. \nGennemse dokumentationen for at sikre, at din bibliotekstruktur svarer til det, der forventes" + "failed-imports": "Mislykket import" }, "settings": { "add-a-new-theme": "Tilføj et nyt tema", "backup-and-exports": "Backup og eksport", "backup-info": "Sikkerhedskopier eksporteres i standard JSON-format sammen med alle de billeder, der er gemt på filsystemet. \nI din sikkerhedskopimappe finder du en .zip-fil, der indeholder alle opskrifterne JSON og billeder fra databasen. \nDerudover, hvis du valgte en markdown-fil, gemmes disse også i .zip-filen. \nFor at importere en sikkerhedskopi skal den være placeret i din sikkerhedskopimappe. \nAutomatiske sikkerhedskopier udføres hver dag kl. 3:00.", - "backup-recipes": "Sikkerhedskopier opksrifter", - "backup-tag": "Sikkerhedskopier tags", - "color": "Farve", "contribute": "Bidrag", "explore-the-docs": "Udforsk dokumentation", - "markdown-template": "Markdown skabelon", "new-version-available": "En ny version af Mealie er tilgængelig. Besøg repoen ", "set-new-time": "Indstil ny tid", - "swatches": "Prøver", "current": "Version:", "latest": "Seneste:", "theme": { @@ -114,13 +96,11 @@ "dark": "Mørkt", "delete-theme": "Slet tema", "light": "Lyst", - "save-colors-and-apply-theme": "Gem farver og anvend tema", "saved-color-theme": "Gemt farvetema", "theme": "Tema" }, "webhooks": { "meal-planner-webhooks": "Måltidsplanlægning Webhooks", - "save-webhooks": "Gem Webhooks", "test-webhooks": "Test Webhooks", "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Webadresserne, der er anført nedenfor, modtager webhooks, der indeholder opskriftsdataene for måltidsplanen på den planlagte dag. \nWebhooks udføres i øjeblikket på {time} ", "webhook-url": "Webhook adresse" @@ -131,4 +111,4 @@ "import-themes": "Importer temaer" } } -} \ No newline at end of file +} diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 5b48e2044..15e4152be 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -30,11 +30,11 @@ "enabled": "Enabled", "download": "Download", "import": "Import", - "delete-data": "Delete Data", "options": "Options", "templates": "Templates", "recipes": "Recipes", - "themes": "Themes" + "themes": "Themes", + "confirm": "Confirm" }, "login": { "stay-logged-in": "Stay logged in?", @@ -49,7 +49,6 @@ "planner": "Planner", "edit-meal-plan": "Edit Meal Plan", "meal-plans": "Meal Plans", - "choose-a-recipe": "Choose a Recipe", "create-a-new-meal-plan": "Create a New Meal Plan", "start-date": "Start Date", "end-date": "End Date" @@ -66,16 +65,29 @@ "ingredient": "Ingredient", "notes": "Notes", "note": "Note", - "original-recipe": "Original Recipe", - "view-recipe": "View Recipe" + "original-url": "Original URL", + "view-recipe": "View Recipe", + "title": "Title", + "total-time": "Total Time", + "prep-time": "Prep Time", + "perform-time": "Cook Time / Perform Time", + "api-extras": "API Extras", + "object-key": "Object Key", + "object-value": "Object Value", + "new-key-name": "New Key Name", + "add-key": "Add Key", + "key-name-required": "Key Name Required", + "no-white-space-allowed": "No White Space Allowed", + "delete-recipe": "Delete Recipe", + "delete-confirmation": "Are you sure you want to delete this recipe?" }, "search": { - "search-for-a-recipe": "Search for a Recipe", - "search-for-your-favorite-recipe": "Search for your Favorite Recipe" + "search-mealie": "Search Mealie" }, "settings": { - "color": "Color", - "swatches": "Swatches", + "general-settings": "General Settings", + "local-api": "Local API", + "language": "Language", "add-a-new-theme": "Add a New Theme", "set-new-time": "Set New Time", "current": "Version:", @@ -84,10 +96,9 @@ "contribute": "Contribute", "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.", - "backup-tag": "Backup Tag", - "markdown-template": "Markdown Template", - "backup-recipes": "Backup Recipes", + "available-backups": "Available Backups", "theme": { + "theme-name": "Theme Name", "theme-settings": "Theme Settings", "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": "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.", "dark-mode": "Dark Mode", @@ -99,21 +110,21 @@ "info": "Info", "warning": "Warning", "error": "Error", + "default-to-system": "Default to system", "light": "Light", "dark": "Dark", "theme": "Theme", "saved-color-theme": "Saved Color Theme", "delete-theme": "Delete Theme", "are-you-sure-you-want-to-delete-this-theme": "Are you sure you want to delete this theme?", - "save-colors-and-apply-theme": "Save Colors and Apply Theme", - "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Choose how Mealie looks to you. Set your theme preference to follow your system settings, or choose to use the light or dark theme." + "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Choose how Mealie looks to you. Set your theme preference to follow your system settings, or choose to use the light or dark theme.", + "theme-name-is-required": "Theme Name is required." }, "webhooks": { "meal-planner-webhooks": "Meal Planner Webhooks", "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on its scheduled day. Currently Webhooks will execute at { time }", "test-webhooks": "Test Webhooks", - "webhook-url": "Webhook URL", - "save-webhooks": "Save Webhooks" + "webhook-url": "Webhook URL" }, "new-version-available": "A New Version of Mealie is Available, Visit the Repo ", "backup": { @@ -121,21 +132,27 @@ "import-themes": "Import Themes", "import-settings": "Import Settings", "create-heading": "Create a Backup", - "backup-tag": "Backup Tag" + "backup-tag": "Backup Tag", + "full-backup": "Full Backup", + "partial-backup": "Partial Backup", + "backup-restore-report": "Backup Restore Report", + "successfully-imported": "Successfully Imported", + "failed-imports": "Failed Imports" } }, "migration": { "recipe-migration": "Recipe Migration", - "currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration": "Currently Chowdown via public Repo URL is the only supported type of migration", - "chowdown-repo-url": "Chowdown Repo URL", - "migrate": "Migrate", - "failed-recipes": "Failed Recipes", - "failed-images": "Failed Images", - "you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected": "You can import recipes from either a zip file or a directory located in the /app/data/migration/ folder. Please review the documentation to ensure your directory structure matches what is expected", - "nextcloud-data": "Nextcloud Data", - "delete-confirmation": "Are you sure you want to delete this migration data?", - "successfully-imported-from-nextcloud": "Successfully Imported from Nextcloud", "failed-imports": "Failed Imports", - "upload-an-archive": "Upload an Archive" + "migration-report": "Migration Report", + "successful-imports": "Successful Imports", + "no-migration-data-available": "No Migration Data Avaiable", + "nextcloud": { + "title": "Nextcloud Cookbook", + "description": "Migrate data from a Nextcloud Cookbook intance" + }, + "chowdown": { + "title": "Chowdown", + "description": "Migrate data from Chowdown" + } } -} \ No newline at end of file +} diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index d03135bf8..49ea9ff78 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -28,8 +28,7 @@ "ok": "OK", "enabled": "Activé", "download": "Télécharger", - "import": "Importer", - "delete-data": "Supprimer les données" + "import": "Importer" }, "login": { "stay-logged-in": "Rester connecté(e) ?", @@ -44,7 +43,6 @@ "planner": "Planificateur", "edit-meal-plan": "Éditer le plan de menu", "meal-plans": "Plans de menu", - "choose-a-recipe": "Choisir une recette", "create-a-new-meal-plan": "Créer un nouveau plan de menu", "start-date": "Date de début", "end-date": "Date de fin" @@ -61,16 +59,13 @@ "ingredient": "Ingrédient", "notes": "Notes", "note": "Note", - "original-recipe": "Recette originale", + "original-url": "Recette originale", "view-recipe": "Voir la recette" }, "search": { - "search-for-a-recipe": "Chercher une recette", - "search-for-your-favorite-recipe": "Cherchez votre recette préférée" + "search-mealie": "Search Mealie" }, "settings": { - "color": "Couleur", - "swatches": "Echantillons", "add-a-new-theme": "Ajouter un nouveau thème", "set-new-time": "Définir une nouvelle heure d'exécution", "current": "Version :", @@ -79,9 +74,6 @@ "contribute": "Contribuer", "backup-and-exports": "Sauver et exporter", "backup-info": "Les sauvegardes sont exportées en format JSON standard, ainsi que toutes les images stockées sur le système. Dans votre dossier de sauvegarde, vous trouverez un dossier .zip qui contient toutes les recettes en JSON et les images de la base de données. De plus, si vous avez sélectionné le format de fichier markdown, il sera sauvegardé dans le même dossier .zip. Pour importer une sauvegarde, celle-ci doit être enregistrée dans votre dossier de sauvegardes. Une sauvegarde automatique est effectuée quotidiennement à 03h00.", - "backup-tag": "Tags de la sauvegarde", - "markdown-template": "Modèle markdown", - "backup-recipes": "Sauvegarder les recettes", "theme": { "theme-settings": "Paramètres du thème", "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": "Sélectionnez un thème depuis la liste ou créez-en un nouveau. Le thème par défaut sera utilisé pour tous les utilisateurs qui n'ont pas choisi de thème personnalisé.", @@ -100,15 +92,13 @@ "saved-color-theme": "Thèmes sauvegardés", "delete-theme": "Supprimer le thème", "are-you-sure-you-want-to-delete-this-theme": "Etes-vous sûr(e) de vouloir supprimer ce thème ?", - "save-colors-and-apply-theme": "Sauvegarder les couleurs et appliquer le thème", "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Personnalisez l'apparence de Mealie. Utilisez le thème par défaut de votre système ou choisissez manuellement entre le thème clair ou sombre." }, "webhooks": { "meal-planner-webhooks": "Webhooks du planificateur de repas", "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Les liens dans cette liste recevront les webhooks contenant les recettes pour le plan de menu du jour défini. Actuellement, les webhooks s'executeront à { time }", "test-webhooks": "Tester les webhooks", - "webhook-url": "Lien du webhook", - "save-webhooks": "Enregistrer les webhooks" + "webhook-url": "Lien du webhook" }, "new-version-available": "Une nouvelle version de Mealie est disponible, vérifiez la source ! ", "backup": { @@ -119,16 +109,6 @@ }, "migration": { "recipe-migration": "Migrer les recettes", - "currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration": "Pour le moment, le seul type de migration supporté est Chowdown via un dépôt public.", - "chowdown-repo-url": "Lien du dépôt Chowdown", - "migrate": "Migrer", - "failed-recipes": "Recettes échouées", - "failed-images": "Images échouées", - "you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected": "Vous pouvez importer des recettes, soit depuis un dossier zip ou depuis un dossier enregistré directement dans le dossier /app/data/migraiton/. Veuillez vérifier dans la documentation que la structure de votre dossier corresponde à celle qui est attendue.", - "nextcloud-data": "Données Nextcloud", - "delete-confirmation": "Etes-vous sûr(e) de vouloir supprimer ces données de migration ?", - "successfully-imported-from-nextcloud": "Importation de Nexcloud réussie", - "failed-imports": "Importations échouées", - "upload-an-archive": "Téléverser une archive" + "failed-imports": "Importations échouées" } } diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json new file mode 100644 index 000000000..261df7c50 --- /dev/null +++ b/frontend/src/locales/sv.json @@ -0,0 +1,116 @@ +{ + "404": { + "page-not-found": "404 sidan kan inte hittas", + "take-me-home": "Ta mig hem" + }, + "new-recipe": { + "from-url": "Från länk", + "recipe-url": "Recept URL", + "error-message": "Ett fel uppstod när receptet skulle läsas in. Undersök loggen och debug/last_recipe.json för att felsöka problemet.", + "bulk-add": "Lägg till flera", + "paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Klistra in din receptdata, varje rad kommer att hanteras som ett listelement" + }, + "general": { + "submit": "Skicka", + "name": "Namn", + "settings": "Inställningar", + "cancel": "Avbryt", + "close": "Stäng", + "create": "Skapa", + "delete": "Ta bort", + "edit": "Redigera", + "enabled": "Aktiverad", + "image-file": "Bildfil", + "new": "Ny", + "ok": "Ok", + "random": "Slumpa", + "save": "Spara", + "select": "Välj", + "update": "Uppdatera", + "download": "Ladda ner", + "import": "Importera" + }, + "login": { + "email": "E-mail", + "password": "Lösenord", + "sign-in": "Logga in", + "sign-up": "Logga ut", + "stay-logged-in": "Kom ihåg mig" + }, + "meal-plan": { + "dinner-this-week": "Veckans middagar", + "dinner-today": "Middag idag", + "planner": "Planeringkalender", + "create-a-new-meal-plan": "Skapa en ny måltidsplan", + "edit-meal-plan": "Redigera måltidsplan", + "end-date": "Slutdatum", + "meal-plans": "Måltidsplaner", + "start-date": "Startdatum" + }, + "recipe": { + "description": "Beskrivning", + "categories": "Kategorier", + "ingredient": "Ingrediens", + "ingredients": "Ingredienser", + "instructions": "Instruktioner", + "note": "Anteckning", + "notes": "Anteckningar", + "original-url": "Originalrecept", + "recipe-name": "Receptets namn", + "servings": "Portioner", + "step-index": "Steg: {step}", + "tags": "Taggar", + "view-recipe": "Visa recept" + }, + "search": { + "search-mealie": "Search Mealie" + }, + "settings": { + "add-a-new-theme": "Lägg till ett nytt tema", + "set-new-time": "Välj ny tid", + "current": "Version:", + "latest": "Senaste", + "explore-the-docs": "Utforska dokumentationen", + "contribute": "Bidra", + "backup-and-exports": "Backups", + "backup-info": "Säkerhetskopior exporteras i JSON-format tillsammans med de bilder som finns i systemet. I din mapp för säkerhetskopior finner du en zip-fil som innehåller alla recept i JSON samt bilder från databasen. Om du dessutom valde att exportera till markdown så hittas också de i samma zip-fil. För att importera en säkerhetskopia så måste den ligga i din backup-mapp. Automatisk säkerhetskopiering genomförs varje dag kl. 03:00.", + "theme": { + "theme-settings": "Temainställningar", + "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": "Välj ett tema från menyn eller skapa ett nytt. Standardtemat kommer att användas för alla användare som inte gjort något val.", + "dark-mode": "Mörkt läge", + "theme-is-required": "Tema krävs", + "primary": "Primär", + "secondary": "Sekundär", + "accent": "Accent", + "success": "Success", + "info": "Info", + "warning": "Varning", + "error": "Error", + "light": "Ljust", + "dark": "Mörkt", + "theme": "Tema", + "saved-color-theme": "Sparat färgschema", + "delete-theme": "Radera tema", + "are-you-sure-you-want-to-delete-this-theme": "Är du säker på att du vill radera temat?", + "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Välj hur Mealie ska se ut för dig. Låt Mealie följa dina systeminställningar, eller välj mörkt eller ljust tema." + }, + "webhooks": { + "meal-planner-webhooks": "Webhooks för denna måltidsplan", + "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Följande URLer kommer att mottaga webhooks med receptdata för dagens planerade måltid. Datan kommer att skickas klockan { time }", + "test-webhooks": "Testa Webhooks", + "webhook-url": "Webhook URL" + }, + "new-version-available": "En ny version av Mealie finns tillgänglig, Besök repot ", + "backup": { + "import-recipes": "Importera recept", + "import-themes": "Importera färgscheman", + "import-settings": "Importera recept", + "create-heading": "Skapa en säkerhetskopia", + "backup-tag": "Backup tagg" + } + }, + "migration": { + "recipe-migration": "Migrera recept", + "failed-imports": "Misslyckade importer" + } +} diff --git a/frontend/src/locales/zh-CN.json b/frontend/src/locales/zh-CN.json new file mode 100644 index 000000000..898a4fcb2 --- /dev/null +++ b/frontend/src/locales/zh-CN.json @@ -0,0 +1,158 @@ +{ + "404": { + "page-not-found": "404页面不存在", + "take-me-home": "返回主页" + }, + "new-recipe": { + "from-url": "输入网址", + "recipe-url": "食谱网址", + "error-message": "貌似在解析网址时出错。请检查log和debug/last_recipe.json文件并找寻更多有关资讯。", + "bulk-add": "批量添加", + "paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "请粘贴您的食谱资料。每行将被视为列表中的一项。" + }, + "general": { + "upload": "上传", + "submit": "提交", + "name": "名称", + "settings": "设定", + "close": "关闭", + "save": "保存", + "image-file": "图像文件", + "update": "更新", + "edit": "编辑", + "delete": "删除", + "select": "选择", + "random": "随机", + "new": "新建", + "create": "创建", + "cancel": "取消", + "ok": "好的", + "enabled": "启用", + "download": "下载", + "import": "导入", + "options": "选项", + "templates": "模板", + "recipes": "食谱", + "themes": "布景主题", + "confirm": "确定" + }, + "login": { + "stay-logged-in": "保持登录状态?", + "email": "电子邮件", + "password": "密码", + "sign-in": "登入", + "sign-up": "注册" + }, + "meal-plan": { + "dinner-this-week": "本周晚餐", + "dinner-today": "今日晚餐", + "planner": "策划人", + "edit-meal-plan": "编辑用餐计划", + "meal-plans": "用餐计划", + "create-a-new-meal-plan": "创建一个新的用餐计划", + "start-date": "开始日期", + "end-date": "结束日期" + }, + "recipe": { + "description": "描述", + "ingredients": "材料", + "categories": "分类目录", + "tags": "标签", + "instructions": "做法", + "step-index": "步骤:{step}", + "recipe-name": "食谱名称", + "servings": "份量", + "ingredient": "材料", + "notes": "贴士", + "note": "贴士", + "original-url": "原食谱链接", + "view-recipe": "查看食谱", + "add-key": "Add Key", + "api-extras": "API Extras", + "delete-confirmation": "您确定要删除此食谱吗?", + "delete-recipe": "删除食谱", + "key-name-required": "Key Name Required", + "new-key-name": "New Key Name", + "no-white-space-allowed": "No White Space Allowed", + "object-key": "Object Key", + "object-value": "Object Value", + "perform-time": "烹饪时间 / 执行时间", + "prep-time": "准备时间", + "title": "标题", + "total-time": "总时间" + }, + "search": { + "search-mealie": "搜索Mealie" + }, + "settings": { + "add-a-new-theme": "新增布景主题", + "set-new-time": "设定新的时间", + "current": "版本号:", + "latest": "最新版本:", + "explore-the-docs": "浏览文档", + "contribute": "参与贡献", + "backup-and-exports": "备份", + "backup-info": "备份以标准JSON格式导出,并连同储存在系统文件中的所有图像。在备份文件夹中,您将找到一个.zip文件,其中包含数据库中的所有食谱JSON和图像。此外,如果您选择了Markdown文件,这些文件也将一并储存在.zip文件中。当需要要导入备份,它必须位于您的备份文件夹中。每天3:00 AM将进行自动备份。", + "theme": { + "theme-settings": "布景主题设置", + "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": "从以下列表中选择一个主题或创建一个新主题。请注意,默认主题将提供给尚未设置主题首选的所有用户。", + "dark-mode": "暗黑模式", + "theme-is-required": "必须选择主题", + "primary": "主要(Primary)", + "secondary": "次要(Secondary)", + "accent": "强调(Accent)", + "success": "成功(Success)", + "info": "信息(Info)", + "warning": "警告(Warning)", + "error": "错误(Error)", + "light": "浅色", + "dark": "深色", + "theme": "布景主题", + "saved-color-theme": "已保存主题色调", + "delete-theme": "删除主题", + "are-you-sure-you-want-to-delete-this-theme": "您确定要删除此主题吗?", + "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "选择Mealie的外观模式。设置布景主题首选并依据您的主机系统设置,或者选择使用浅色或深色主题。", + "default-to-system": "默认为系统", + "theme-name": "主题名称", + "theme-name-is-required": "主题名称是必填项。" + }, + "webhooks": { + "meal-planner-webhooks": "用餐计划器Webhooks", + "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "下方列出的网址将在预定日期接收到有关用餐计划的食谱资料。Webhooks将在{ time }执行", + "test-webhooks": "测试Webhooks", + "webhook-url": "Webhook网址" + }, + "new-version-available": "检测到Mealie最新版本出现,浏览仓库", + "backup": { + "import-recipes": "导入食谱", + "import-themes": "导入主题", + "import-settings": "导入设置", + "create-heading": "创建备份", + "backup-tag": "标签备份", + "backup-restore-report": "备份还原报告", + "failed-imports": "导入失败", + "full-backup": "完整备份", + "partial-backup": "部分备份", + "successfully-imported": "成功导入" + }, + "available-backups": "可用备份", + "general-settings": "基本设置", + "language": "语言", + "local-api": "Local API" + }, + "migration": { + "recipe-migration": "食谱迁移", + "failed-imports": "导入失败", + "chowdown": { + "description": "从Chowdown迁移数据", + "title": "Chowdown" + }, + "migration-report": "迁移报告", + "nextcloud": { + "description": "从Nextcloud Cookbook迁移数据", + "title": "Nextcloud Cookbook" + }, + "no-migration-data-available": "没有迁移数据可用", + "successful-imports": "成功导入" + } +} diff --git a/frontend/src/locales/zh-TW.json b/frontend/src/locales/zh-TW.json new file mode 100644 index 000000000..0232a1a8d --- /dev/null +++ b/frontend/src/locales/zh-TW.json @@ -0,0 +1,158 @@ +{ + "404": { + "page-not-found": "404頁面不存在", + "take-me-home": "返回主頁" + }, + "new-recipe": { + "from-url": "輸入網址", + "recipe-url": "食譜網址", + "error-message": "貌似在解析網址時出錯。請檢查log和debug/last_recipe.json文件並找尋更多有關資訊。", + "bulk-add": "批量添加", + "paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "請粘貼您的食譜資料。每行將被視為列表中的一項。" + }, + "general": { + "upload": "上傳", + "submit": "提交", + "name": "名稱", + "settings": "設定", + "close": "關閉", + "save": "保存", + "image-file": "圖像文件", + "update": "更新", + "edit": "编辑", + "delete": "删除", + "select": "選擇", + "random": "隨機", + "new": "新建", + "create": "創建", + "cancel": "取消", + "ok": "好的", + "enabled": "启用", + "download": "下载", + "import": "導入", + "options": "選項", + "templates": "模板", + "recipes": "食譜", + "themes": "佈景主題", + "confirm": "確定" + }, + "login": { + "stay-logged-in": "保持登錄狀態?", + "email": "電子郵件", + "password": "密碼", + "sign-in": "登入", + "sign-up": "註冊" + }, + "meal-plan": { + "dinner-this-week": "本週晚餐", + "dinner-today": "今日晚餐", + "planner": "策劃人", + "edit-meal-plan": "編輯用餐計劃", + "meal-plans": "用餐計劃", + "create-a-new-meal-plan": "創建一個新的用餐計劃", + "start-date": "開始日期", + "end-date": "結束日期" + }, + "recipe": { + "description": "描述", + "ingredients": "材料", + "categories": "分類目錄", + "tags": "標籤", + "instructions": "做法", + "step-index": "步驟:{step}", + "recipe-name": "食譜名稱", + "servings": "份量", + "ingredient": "材料", + "notes": "貼士", + "note": "貼士", + "original-url": "原食譜鏈接", + "view-recipe": "查看食譜", + "add-key": "Add Key", + "api-extras": "API Extras", + "delete-confirmation": "您確定要刪除此食譜嗎?", + "delete-recipe": "刪除食譜", + "key-name-required": "Key Name Required", + "new-key-name": "New Key Name", + "no-white-space-allowed": "No White Space Allowed", + "object-key": "Object Key", + "object-value": "Object Value", + "perform-time": "烹飪時間 / 執行時間", + "prep-time": "準備時間", + "title": "標題", + "total-time": "總時間" + }, + "search": { + "search-mealie": "搜索Mealie" + }, + "settings": { + "add-a-new-theme": "新增佈景主題", + "set-new-time": "設定新的時間", + "current": "版本號:", + "latest": "最新版本:", + "explore-the-docs": "瀏覽文檔", + "contribute": "參與貢獻", + "backup-and-exports": "備份", + "backup-info": "備份以標準JSON格式導出,並連同儲存在系統文件中的所有圖像。在備份文件夾中,您將找到一個.zip文件,其中包含數據庫中的所有食譜JSON和圖像。此外,如果您選擇了Markdown文件,這些文件也將一併儲存在.zip文件中。當需要要導入備份,它必須位於您的備份文件夾中。每天3:00 AM將進行自動備份。", + "theme": { + "theme-settings": "佈景主題設置", + "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": "從以下列表中選擇一個主題或創建一個新主題。請注意,默認主題將提供給尚未設置主題首選的所有用戶。", + "dark-mode": "暗黑模式", + "theme-is-required": "必須選擇主題", + "primary": "主要(Primary)", + "secondary": "次要(Secondary)", + "accent": "強調(Accent)", + "success": "成功(Success)", + "info": "信息(Info)", + "warning": "警告(Warning)", + "error": "錯誤(Error)", + "light": "淺色", + "dark": "深色", + "theme": "佈景主題", + "saved-color-theme": "已保存主題色調", + "delete-theme": "刪除主題", + "are-you-sure-you-want-to-delete-this-theme": "您確定要刪除此主題嗎?", + "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "選擇Mealie的外觀模式。設置佈景主題首選並依據您的主機系統設置,或者選擇使用淺色或深色主題。", + "default-to-system": "默認爲系統", + "theme-name": "主題名稱", + "theme-name-is-required": "主題名稱是必填項。" + }, + "webhooks": { + "meal-planner-webhooks": "用餐計劃器Webhooks", + "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "下方列出的網址將在預定日期接收到有關用餐計劃的食譜資料。Webhooks將在{ time }執行", + "test-webhooks": "測試Webhooks", + "webhook-url": "Webhook網址" + }, + "new-version-available": "檢測到Mealie最新版本出現,瀏覽倉庫", + "backup": { + "import-recipes": "導入食譜", + "import-themes": "導入主題", + "import-settings": "導入設置", + "create-heading": "創建備份", + "backup-tag": "標籤備份", + "backup-restore-report": "備份還原報告", + "failed-imports": "導入失敗", + "full-backup": "完整備份", + "partial-backup": "部分備份", + "successfully-imported": "成功導入" + }, + "available-backups": "可用備份", + "general-settings": "基本設置", + "language": "語言", + "local-api": "Local API" + }, + "migration": { + "recipe-migration": "食譜遷移", + "failed-imports": "導入失敗", + "chowdown": { + "description": "從Chowdown遷移數據", + "title": "Chowdown" + }, + "migration-report": "遷移報告", + "nextcloud": { + "description": "從Nextcloud Cookbook遷移數據", + "title": "Nextcloud Cookbook" + }, + "no-migration-data-available": "無遷移數據可用", + "successful-imports": "成功導入" + } +} diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue index db14c5ee5..9a9589e7f 100644 --- a/frontend/src/pages/HomePage.vue +++ b/frontend/src/pages/HomePage.vue @@ -1,15 +1,66 @@ diff --git a/frontend/src/store/modules/language.js b/frontend/src/store/modules/language.js index 6fff6d677..e273232e3 100644 --- a/frontend/src/store/modules/language.js +++ b/frontend/src/store/modules/language.js @@ -8,13 +8,25 @@ const state = { value: "en", }, { - name: "Dutch", + name: "Danish", value: "da", }, { name: "French", value: "fr", }, + { + name: "Swedish", + value: "sv", + }, + { + name: "简体中文", + value: "zh-CN", + }, + { + name: "繁體中文", + value: "zh-TW", + }, ], }; diff --git a/frontend/src/store/modules/recipes.js b/frontend/src/store/modules/recipes.js new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 2c0448fad..b0fd2d30f 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -18,6 +18,13 @@ const store = new Vuex.Store({ language, }, state: { + // Home Page Settings + homePageSettings: { + showRecent: true, + showLimit: 9, + categories: [], + homeCategories: [], + }, // Snackbar snackActive: false, snackText: "", @@ -29,6 +36,9 @@ const store = new Vuex.Store({ }, mutations: { + setHomePageSettings(state, payload) { + state.homePageSettings = payload; + }, setSnackBar(state, payload) { state.snackText = payload.text; state.snackType = payload.type; @@ -57,6 +67,16 @@ const store = new Vuex.Store({ this.commit("setRecentRecipes", payload); }, + + async requestHomePageSettings() { + // TODO: Query Backend for Categories + this.commit("setHomePageSettings", { + showRecent: true, + showLimit: 9, + categories: ["breakfast", "lunch", "dinner"], + homeCategories: [], + }); + }, }, getters: { @@ -65,7 +85,8 @@ const store = new Vuex.Store({ getSnackActive: state => state.snackActive, getSnackType: state => state.snackType, - getRecentRecipes: state => state.recentRecipes, + getRecentRecipes: (state) => state.recentRecipes, + getHomePageSettings: (state) => state.homePageSettings, }, }); diff --git a/mealie/app.py b/mealie/app.py index 5444c15cd..6cc299602 100644 --- a/mealie/app.py +++ b/mealie/app.py @@ -3,7 +3,7 @@ from fastapi import FastAPI from fastapi.staticfiles import StaticFiles # import utils.startup as startup -from app_config import PORT, PRODUCTION, SQLITE_FILE, WEB_PATH, docs_url, redoc_url +from app_config import PORT, PRODUCTION, WEB_PATH, docs_url, redoc_url from routes import ( backup_routes, meal_routes, @@ -14,7 +14,7 @@ from routes import ( user_routes, ) -# from utils.api_docs import generate_api_docs +from utils.api_docs import generate_api_docs from utils.logger import logger app = FastAPI( @@ -26,13 +26,13 @@ app = FastAPI( ) - def mount_static_files(): app.mount("/static", StaticFiles(directory=WEB_PATH, html=True)) def api_routers(): # First + print() app.include_router(recipe_routes.router) app.include_router(meal_routes.router) app.include_router(setting_routes.router) @@ -46,6 +46,11 @@ if PRODUCTION: api_routers() + +def start_scheduler(): + import services.scheduler.scheduled_jobs + + # API 404 Catch all CALL AFTER ROUTERS @app.get("/api/{full_path:path}", status_code=404, include_in_schema=False) def invalid_api(): @@ -56,8 +61,10 @@ app.include_router(static_routes.router) # Generate API Documentation -# if not PRODUCTION: -# generate_api_docs(app) +if not PRODUCTION: + generate_api_docs(app) + +start_scheduler() if __name__ == "__main__": logger.info("-----SYSTEM STARTUP-----") diff --git a/mealie/db/database.py b/mealie/db/database.py index c8ecef7c5..1085100e9 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -1,3 +1,5 @@ +from sqlalchemy.orm.session import Session + from db.db_base import BaseDocument from db.sql.meal_models import MealPlanModel from db.sql.recipe_models import RecipeModel @@ -16,8 +18,12 @@ class _Recipes(BaseDocument): self.primary_key = "slug" self.sql_model = RecipeModel - def update_image(self, slug: str, extension: str) -> None: - pass + def update_image(self, session: Session, slug: str, extension: str) -> str: + entry = self._query_one(session, match_value=slug) + entry.image = f"{slug}.{extension}" + session.commit() + + return f"{slug}.{extension}" class _Meals(BaseDocument): @@ -31,7 +37,7 @@ class _Settings(BaseDocument): self.primary_key = "name" self.sql_model = SiteSettingsModel - def save_new(self, session, main: dict, webhooks: dict) -> str: + def save_new(self, session: Session, main: dict, webhooks: dict) -> str: new_settings = self.sql_model(main.get("name"), webhooks) session.add(new_settings) @@ -45,14 +51,6 @@ class _Themes(BaseDocument): self.primary_key = "name" self.sql_model = SiteThemeModel - def update(self, session, data: dict) -> dict: - theme_model = self._query_one( - session=session, match_value=data["name"], match_key="name" - ) - - theme_model.update(**data) - session.commit() - class Database: def __init__(self) -> None: diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py index 7318de7ad..a7354bbbc 100644 --- a/mealie/db/db_base.py +++ b/mealie/db/db_base.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import List, Union from sqlalchemy.orm.session import Session @@ -11,7 +11,10 @@ class BaseDocument: self.store: str self.sql_model: SqlAlchemyBase - def get_all(self, session: Session, limit: int = None, order_by: str = None): + # TODO: Improve Get All Query Functionality + def get_all( + self, session: Session, limit: int = None, order_by: str = None + ) -> List[dict]: list = [x.dict() for x in session.query(self.sql_model).all()] if limit == 1: @@ -21,7 +24,7 @@ class BaseDocument: def _query_one( self, session: Session, match_value: str, match_key: str = None - ) -> Union[Session, SqlAlchemyBase]: + ) -> SqlAlchemyBase: """Query the sql database for one item an return the sql alchemy model object. If no match key is provided the primary_key attribute will be used. @@ -43,7 +46,7 @@ class BaseDocument: def get( self, session: Session, match_value: str, match_key: str = None, limit=1 - ) -> dict or list[dict]: + ) -> dict or List[dict]: """Retrieves an entry from the database by matching a key/value pair. If no key is provided the class objects primary key will be used to match against. @@ -67,6 +70,15 @@ class BaseDocument: return db_entry def save_new(self, session: Session, document: dict) -> dict: + """Creates a new database entry for the given SQL Alchemy Model. + + Args: + session (Session): A Database Session + document (dict): A python dictionary representing the data structure + + Returns: + dict: A dictionary representation of the database entry + """ new_document = self.sql_model(**document) session.add(new_document) return_data = new_document.dict() @@ -74,7 +86,18 @@ class BaseDocument: return return_data - def update(self, session: Session, match_value, new_data) -> dict: + def update(self, session: Session, match_value: str, new_data: str) -> dict: + """Update a database entry. + + Args: + session (Session): Database Session + match_value (str): Match "key" + new_data (str): Match "value" + + Returns: + dict: Returns a dictionary representation of the database entry + """ + entry = self._query_one(session=session, match_value=match_value) entry.update(session=session, **new_data) return_data = entry.dict() diff --git a/mealie/db/sql/theme_models.py b/mealie/db/sql/theme_models.py index 90c8f0ce6..7b12e34f0 100644 --- a/mealie/db/sql/theme_models.py +++ b/mealie/db/sql/theme_models.py @@ -12,7 +12,7 @@ class SiteThemeModel(SqlAlchemyBase): self.name = name self.colors = ThemeColorsModel(**colors) - def update(self, name, colors: dict) -> dict: + def update(self, session=None, name: str = None, colors: dict = None) -> dict: self.colors.update(**colors) return self.dict() diff --git a/mealie/models/backup_models.py b/mealie/models/backup_models.py index 21bf661b6..6e8893078 100644 --- a/mealie/models/backup_models.py +++ b/mealie/models/backup_models.py @@ -22,13 +22,13 @@ class BackupOptions(BaseModel): class BackupJob(BaseModel): tag: Optional[str] options: BackupOptions - templates: Optional[List[str]] = [] + templates: Optional[List[str]] class Config: schema_extra = { "example": { "tag": "July 23rd 2021", - "options": BackupOptions, + "options": BackupOptions(), "template": ["recipes.md"], } } diff --git a/mealie/routes/recipe_routes.py b/mealie/routes/recipe_routes.py index de6ed4434..9f0df9582 100644 --- a/mealie/routes/recipe_routes.py +++ b/mealie/routes/recipe_routes.py @@ -68,7 +68,6 @@ def get_recipe_img(recipe_slug: str): return FileResponse(recipe_image) -# Recipe Creations @router.post( "/api/recipe/create-url/", status_code=201, diff --git a/mealie/routes/setting_routes.py b/mealie/routes/setting_routes.py index 0763bc74a..0c3249787 100644 --- a/mealie/routes/setting_routes.py +++ b/mealie/routes/setting_routes.py @@ -1,9 +1,8 @@ from db.db_setup import generate_session from fastapi import APIRouter, Depends, HTTPException -from services.scheduler_services import post_webhooks from services.settings_services import SiteSettings, SiteTheme from sqlalchemy.orm.session import Session -from utils.global_scheduler import scheduler +from utils.post_webhooks import post_webhooks from utils.snackbar import SnackResponse router = APIRouter(tags=["Settings"]) @@ -34,7 +33,6 @@ def update_settings(data: SiteSettings, db: Session = Depends(generate_session)) # status_code=400, detail=SnackResponse.error("Unable to Save Settings") # ) - # scheduler.reschedule_webhooks() #! Need to fix Scheduler return SnackResponse.success("Settings Updated") diff --git a/mealie/services/recipe_services.py b/mealie/services/recipe_services.py index 939e2d3ca..0dbbaca0c 100644 --- a/mealie/services/recipe_services.py +++ b/mealie/services/recipe_services.py @@ -82,15 +82,6 @@ class Recipe(BaseModel): slug = calc_slug return slug - @classmethod - def _unpack_doc(cls, document): - document = json.loads(document.to_json()) - del document["_id"] - - document["dateAdded"] = document["dateAdded"]["$date"] - - return cls(**document) - @classmethod def get_by_slug(cls, session, slug: str): """ Returns a Recipe Object by Slug """ @@ -132,8 +123,15 @@ class Recipe(BaseModel): return updated_slug.get("slug") @staticmethod - def update_image(slug: str, extension: str): - db.recipes.update_image(slug, extension) + def update_image(slug: str, extension: str) -> str: + """A helper function to pass the new image name and extension + into the database. + + Args: + slug (str): The current recipe slug + extension (str): the file extension of the new image + """ + return db.recipes.update_image(slug, extension) @staticmethod def get_all(session: Session): diff --git a/mealie/services/scheduler/global_scheduler.py b/mealie/services/scheduler/global_scheduler.py new file mode 100644 index 000000000..cffbf8e7a --- /dev/null +++ b/mealie/services/scheduler/global_scheduler.py @@ -0,0 +1,3 @@ +from apscheduler.schedulers.background import BackgroundScheduler + +scheduler = BackgroundScheduler() \ No newline at end of file diff --git a/mealie/services/scheduler/scheduled_jobs.py b/mealie/services/scheduler/scheduled_jobs.py new file mode 100644 index 000000000..b273a8446 --- /dev/null +++ b/mealie/services/scheduler/scheduled_jobs.py @@ -0,0 +1,62 @@ +from apscheduler.schedulers.background import BackgroundScheduler +from db.db_setup import create_session +from services.backups.exports import auto_backup_job +from services.scheduler.global_scheduler import scheduler +from services.scheduler.scheduler_utils import Cron, cron_parser +from services.settings_services import SiteSettings +from utils.logger import logger +from utils.post_webhooks import post_webhooks + + +@scheduler.scheduled_job(trigger="interval", minutes=15) +def update_webhook_schedule(): + """ + A scheduled background job that runs every 15 minutes to + poll the database for changes and reschedule the webhook time + """ + session = create_session() + settings = SiteSettings.get_site_settings(session=session) + time = cron_parser(settings.webhooks.webhookTime) + job = JOB_STORE.get("webhooks") + + scheduler.reschedule_job( + job.scheduled_task.id, + trigger="cron", + hour=time.hours, + minute=time.minutes, + ) + + session.close() + logger.info(scheduler.print_jobs()) + + +class ScheduledFunction: + def __init__( + self, scheduler: BackgroundScheduler, function, cron: Cron, name: str + ) -> None: + self.scheduled_task = scheduler.add_job( + function, + trigger="cron", + name=name, + hour=cron.hours, + minute=cron.minutes, + max_instances=1, + replace_existing=True, + ) + + logger.info("New Function Scheduled") + logger.info(scheduler.print_jobs()) + + +logger.info("----INIT SCHEDULE OBJECT-----") + +JOB_STORE = { + "backup_job": ScheduledFunction( + scheduler, auto_backup_job, Cron(hours=00, minutes=00), "backups" + ), + "webhooks": ScheduledFunction( + scheduler, post_webhooks, Cron(hours=00, minutes=00), "webhooks" + ), +} + +scheduler.start() diff --git a/mealie/services/scheduler/scheduler_utils.py b/mealie/services/scheduler/scheduler_utils.py new file mode 100644 index 000000000..eb6240f4a --- /dev/null +++ b/mealie/services/scheduler/scheduler_utils.py @@ -0,0 +1,10 @@ +import collections + +Cron = collections.namedtuple("Cron", "hours minutes") + + +def cron_parser(time_str: str) -> Cron: + time = time_str.split(":") + cron = Cron(hours=int(time[0]), minutes=int(time[1])) + + return cron diff --git a/mealie/services/scheduler_services.py b/mealie/services/scheduler_services.py deleted file mode 100644 index 96aa49d42..000000000 --- a/mealie/services/scheduler_services.py +++ /dev/null @@ -1,73 +0,0 @@ -import collections -import json - -import requests -from apscheduler.schedulers.background import BackgroundScheduler -from db.db_setup import create_session -from utils.logger import logger - -from services.backups.exports import auto_backup_job -from services.meal_services import MealPlan -from services.recipe_services import Recipe -from services.settings_services import SiteSettings - -Cron = collections.namedtuple("Cron", "hours minutes") - - -def cron_parser(time_str: str) -> Cron: - time = time_str.split(":") - cron = Cron(hours=int(time[0]), minutes=int(time[1])) - - return cron - - -def post_webhooks(): - all_settings = SiteSettings.get_site_settings() - - if all_settings.webhooks.enabled: - todays_meal = Recipe.get_by_slug(MealPlan.today()).dict() - urls = all_settings.webhooks.webhookURLs - - for url in urls: - requests.post(url, json.dumps(todays_meal, default=str)) - - -class Scheduler: - def startup_scheduler(self): - self.scheduler = BackgroundScheduler() - logger.info("----INIT SCHEDULE OBJECT-----") - self.scheduler.start() - - self.scheduler.add_job( - auto_backup_job, trigger="cron", hour="3", max_instances=1 - ) - settings = SiteSettings.get_site_settings(create_session()) - time = cron_parser(settings.webhooks.webhookTime) - - self.webhook = self.scheduler.add_job( - post_webhooks, - trigger="cron", - name="webhooks", - hour=time.hours, - minute=time.minutes, - max_instances=1, - ) - - logger.info(self.scheduler.print_jobs()) - - def reschedule_webhooks(self): - """ - Reads the site settings database entry to reschedule the webhooks task - Called after each post to the webhooks endpoint. - """ - settings = SiteSettings.get_site_settings() - time = cron_parser(settings.webhooks.webhookTime) - - self.scheduler.reschedule_job( - self.webhook.id, - trigger="cron", - hour=time.hours, - minute=time.minutes, - ) - - logger.info(self.scheduler.print_jobs()) diff --git a/mealie/services/scrape_services.py b/mealie/services/scrape_services.py index 1980384e2..8e70155af 100644 --- a/mealie/services/scrape_services.py +++ b/mealie/services/scrape_services.py @@ -5,6 +5,7 @@ from typing import List, Tuple import extruct import requests import scrape_schema_recipe +import html from app_config import DEBUG_DIR from slugify import slugify from utils.logger import logger @@ -32,17 +33,17 @@ def normalize_instructions(instructions) -> List[dict]: # One long string split by (possibly multiple) new lines if type(instructions) == str: return [ - {"text": line.strip()} for line in filter(None, instructions.splitlines()) + {"text": normalize_instruction(line)} for line in instructions.splitlines() if line ] # Plain strings in a list elif type(instructions) == list and type(instructions[0]) == str: - return [{"text": step.strip()} for step in instructions] + return [{"text": normalize_instruction(step)} for step in instructions] # Dictionaries (let's assume it's a HowToStep) in a list elif type(instructions) == list and type(instructions[0]) == dict: return [ - {"text": step["text"].strip()} + {"text": normalize_instruction(step["text"])} for step in instructions if step["@type"] == "HowToStep" ] @@ -51,6 +52,14 @@ def normalize_instructions(instructions) -> List[dict]: raise Exception(f"Unrecognised instruction format: {instructions}") +def normalize_instruction(line) -> str: + l = line.strip() + # Some sites erroneously escape their strings on multiple levels + while not l == (l := html.unescape(l)): + pass + return l + + def normalize_yield(yld) -> str: if type(yld) == list: return yld[-1] diff --git a/mealie/services/settings_services.py b/mealie/services/settings_services.py index f562b63cc..3e3c8398e 100644 --- a/mealie/services/settings_services.py +++ b/mealie/services/settings_services.py @@ -1,7 +1,7 @@ from typing import List, Optional from db.database import db -from db.db_setup import create_session, generate_session, sql_exists +from db.db_setup import create_session, sql_exists from pydantic import BaseModel from sqlalchemy.orm.session import Session from utils.logger import logger @@ -103,7 +103,7 @@ class SiteTheme(BaseModel): db.themes.save_new(session, self.dict()) def update_document(self, session: Session): - db.themes.update(session, self.dict()) + db.themes.update(session, self.name, self.dict()) @staticmethod def delete_theme(session: Session, theme_name: str) -> str: diff --git a/mealie/tests/conftest.py b/mealie/tests/conftest.py index 550557742..2ec4a9e3b 100644 --- a/mealie/tests/conftest.py +++ b/mealie/tests/conftest.py @@ -1,9 +1,13 @@ +from pathlib import Path + from app import app from app_config import SQLITE_DIR from db.db_setup import generate_session, sql_global_init from fastapi.testclient import TestClient from pytest import fixture +from tests.test_config import TEST_DATA + SQLITE_FILE = SQLITE_DIR.joinpath("test.db") SQLITE_FILE.unlink(missing_ok=True) @@ -26,3 +30,8 @@ def api_client(): yield TestClient(app) SQLITE_FILE.unlink() + + +@fixture(scope="session") +def test_image(): + return TEST_DATA.joinpath("test_image.jpg") diff --git a/mealie/tests/data/migrations/chowdown/test_chowdown-gh-pages.zip b/mealie/tests/data/migrations/chowdown/test_chowdown-gh-pages.zip new file mode 100644 index 000000000..bd90e08d4 Binary files /dev/null and b/mealie/tests/data/migrations/chowdown/test_chowdown-gh-pages.zip differ diff --git a/mealie/tests/data/migrations/nextcloud/new_nextcloud.zip b/mealie/tests/data/migrations/nextcloud/new_nextcloud.zip deleted file mode 100644 index a420370ff..000000000 Binary files a/mealie/tests/data/migrations/nextcloud/new_nextcloud.zip and /dev/null differ diff --git a/mealie/tests/data/test_image.jpg b/mealie/tests/data/test_image.jpg new file mode 100644 index 000000000..11a2cd49e Binary files /dev/null and b/mealie/tests/data/test_image.jpg differ diff --git a/mealie/tests/test_routes/test_migration_routes.py b/mealie/tests/test_routes/test_migration_routes.py index e74950e82..8706a02c1 100644 --- a/mealie/tests/test_routes/test_migration_routes.py +++ b/mealie/tests/test_routes/test_migration_routes.py @@ -3,25 +3,57 @@ import shutil import pytest from app_config import MIGRATION_DIR -from tests.test_config import TEST_NEXTCLOUD_DIR - -#! Broken -# def test_import_chowdown_recipes(api_client): -# response = api_client.post( -# "/api/migration/chowdown/repo/", -# json={"url": "https://github.com/hay-kot/chowdown"}, -# ) - -# assert response.status_code == 200 - -# test_slug = "banana-bread" -# response = api_client.get(f"/api/recipe/{test_slug}/") -# assert response.status_code == 200 - -# recipe = json.loads(response.content) -# assert recipe["slug"] == test_slug +from tests.test_config import TEST_CHOWDOWN_DIR, TEST_NEXTCLOUD_DIR +### Chowdown +@pytest.fixture(scope="session") +def chowdown_zip(): + zip = TEST_CHOWDOWN_DIR.joinpath("test_chowdown-gh-pages.zip") + + zip_copy = TEST_CHOWDOWN_DIR.joinpath("chowdown-gh-pages.zip") + + shutil.copy(zip, zip_copy) + + yield zip_copy + + zip_copy.unlink() + + +def test_upload_chowdown_zip(api_client, chowdown_zip): + + response = api_client.post( + "/api/migrations/chowdown/upload/", files={"archive": chowdown_zip.open("rb")} + ) + + assert response.status_code == 200 + + assert MIGRATION_DIR.joinpath("chowdown", chowdown_zip.name).is_file() + + +def test_import_chowdown_directory(api_client, chowdown_zip): + selection = chowdown_zip.name + response = api_client.post(f"/api/migrations/chowdown/{selection}/import/") + + assert response.status_code == 200 + + report = json.loads(response.content) + assert report["failed"] == [] + + expected_slug = "roasted-okra" + response = api_client.get(f"/api/recipe/{expected_slug}/") + assert response.status_code == 200 + + +def test_delete_chowdown_migration_data(api_client, chowdown_zip): + selection = chowdown_zip.name + response = api_client.delete(f"/api/migrations/chowdown/{selection}/delete/") + + assert response.status_code == 200 + assert not MIGRATION_DIR.joinpath(chowdown_zip.name).is_file() + + +### Nextcloud @pytest.fixture(scope="session") def nextcloud_zip(): zip = TEST_NEXTCLOUD_DIR.joinpath("nextcloud.zip") @@ -30,7 +62,9 @@ def nextcloud_zip(): shutil.copy(zip, zip_copy) - return zip_copy + yield zip_copy + + zip_copy.unlink() def test_upload_nextcloud_zip(api_client, nextcloud_zip): @@ -58,7 +92,7 @@ def test_import_nextcloud_directory(api_client, nextcloud_zip): assert response.status_code == 200 -def test_delete_migration_data(api_client, nextcloud_zip): +def test_delete__nextcloud_migration_data(api_client, nextcloud_zip): selection = nextcloud_zip.name response = api_client.delete(f"/api/migrations/nextcloud/{selection}/delete/") diff --git a/mealie/tests/test_routes/test_recipe_routes.py b/mealie/tests/test_routes/test_recipe_routes.py index 0946c0424..1543df416 100644 --- a/mealie/tests/test_routes/test_recipe_routes.py +++ b/mealie/tests/test_routes/test_recipe_routes.py @@ -2,9 +2,12 @@ import json import pytest from slugify import slugify -from tests.test_routes.utils.routes_data import (RecipeTestData, - raw_recipe_dict, - recipe_test_data) +from tests.test_routes.utils.routes_data import ( + RecipeTestData, + raw_recipe, + raw_recipe_no_image, + recipe_test_data, +) @pytest.mark.parametrize("recipe_data", recipe_test_data) @@ -15,12 +18,31 @@ def test_create_by_url(api_client, recipe_data: RecipeTestData): def test_create_by_json(api_client): - response = api_client.post("/api/recipe/create/", json=raw_recipe_dict) + response = api_client.post("/api/recipe/create/", json=raw_recipe) assert response.status_code == 200 assert json.loads(response.text) == "banana-bread" +def test_create_no_image(api_client): + response = api_client.post("/api/recipe/create/", json=raw_recipe_no_image) + + assert response.status_code == 200 + assert json.loads(response.text) == "banana-bread-no-image" + + +# def test_upload_image(api_client, test_image): +# data = {"image": test_image.open("rb").read(), "extension": "jpg"} + +# response = api_client.post( +# "/api/recipe/banana-bread-no-image/update/image/", files=data +# ) + +# assert response.status_code == 200 + +# response = api_client.get("/api/recipe/banana-bread-no-image/update/image/") + + def test_read_all_post(api_client): response = api_client.post( "/api/all-recipes/", json={"properties": ["slug", "description", "rating"]} diff --git a/mealie/tests/test_routes/utils/routes_data.py b/mealie/tests/test_routes/utils/routes_data.py index f7ca2852d..ca580ffaa 100644 --- a/mealie/tests/test_routes/utils/routes_data.py +++ b/mealie/tests/test_routes/utils/routes_data.py @@ -16,7 +16,7 @@ recipe_test_data = [ ] -raw_recipe_dict = { +raw_recipe = { "name": "Banana Bread", "description": "From Angie's mom", "image": "banana-bread.jpg", @@ -62,3 +62,50 @@ raw_recipe_dict = { "orgURL": None, "extras": {}, } + +raw_recipe_no_image = { + "name": "Banana Bread No Image", + "description": "From Angie's mom", + "image": "", + "recipeYield": "", + "recipeIngredient": [ + "4 bananas", + "1/2 cup butter", + "1/2 cup sugar", + "2 eggs", + "2 cups flour", + "1/2 tsp baking soda", + "1 tsp baking powder", + "pinch salt", + "1/4 cup nuts (we like pecans)", + ], + "recipeInstructions": [ + { + "@type": "Beat the eggs, then cream with the butter and sugar", + "text": "Beat the eggs, then cream with the butter and sugar", + }, + { + "@type": "Mix in bananas, then flour, baking soda/powder, salt, and nuts", + "text": "Mix in bananas, then flour, baking soda/powder, salt, and nuts", + }, + { + "@type": "Add to greased and floured pan", + "text": "Add to greased and floured pan", + }, + { + "@type": "Bake until brown/cracked, toothpick comes out clean", + "text": "Bake until brown/cracked, toothpick comes out clean", + }, + ], + "totalTime": "None", + "prepTime": None, + "performTime": None, + "slug": "", + "categories": [], + "tags": ["breakfast", " baking"], + "dateAdded": "2021-01-12", + "notes": [], + "rating": 0, + "orgURL": None, + "extras": {}, +} diff --git a/mealie/tests/utils.py b/mealie/tests/utils.py index e69de29bb..6e02cf326 100644 --- a/mealie/tests/utils.py +++ b/mealie/tests/utils.py @@ -0,0 +1 @@ +test_ \ No newline at end of file diff --git a/mealie/utils/global_scheduler.py b/mealie/utils/global_scheduler.py deleted file mode 100644 index 9b30ebb2e..000000000 --- a/mealie/utils/global_scheduler.py +++ /dev/null @@ -1,11 +0,0 @@ -from services.scheduler_services import Scheduler - - -def start_scheduler(): - global scheduler - scheduler = Scheduler() - scheduler.startup_scheduler() - return scheduler - - -scheduler = start_scheduler diff --git a/mealie/utils/post_webhooks.py b/mealie/utils/post_webhooks.py new file mode 100644 index 000000000..56bbedc33 --- /dev/null +++ b/mealie/utils/post_webhooks.py @@ -0,0 +1,21 @@ +import json + +import requests +from db.db_setup import create_session +from services.meal_services import MealPlan +from services.recipe_services import Recipe +from services.settings_services import SiteSettings + + +def post_webhooks(): + session = create_session() + all_settings = SiteSettings.get_site_settings(session) + + if all_settings.webhooks.enabled: + todays_meal = Recipe.get_by_slug(MealPlan.today()).dict() + urls = all_settings.webhooks.webhookURLs + + for url in urls: + requests.post(url, json.dumps(todays_meal, default=str)) + + session.close() diff --git a/mealie/utils/startup.py b/mealie/utils/startup.py deleted file mode 100644 index 04be244e3..000000000 --- a/mealie/utils/startup.py +++ /dev/null @@ -1,17 +0,0 @@ -from pathlib import Path - -from app_config import REQUIRED_DIRS -from services.settings_services import default_theme_init - -CWD = Path(__file__).parent - - -def post_start(): - default_theme_init() - - - - - -if __name__ == "__main__": - pass diff --git a/mealie/utils/unzip.py b/mealie/utils/unzip.py index 24c626845..b8e53767b 100644 --- a/mealie/utils/unzip.py +++ b/mealie/utils/unzip.py @@ -6,6 +6,7 @@ from app_config import TEMP_DIR def unpack_zip(selection: Path) -> tempfile.TemporaryDirectory: + TEMP_DIR.mkdir(parents=True, exist_ok=True) temp_dir = tempfile.TemporaryDirectory(dir=TEMP_DIR) temp_dir_path = Path(temp_dir.name) if selection.suffix == ".zip":