diff --git a/Caddyfile b/Caddyfile
index ba5d2e454488..0faf9fe7d519 100644
--- a/Caddyfile
+++ b/Caddyfile
@@ -10,8 +10,8 @@
encode gzip
uri strip_suffix /
- handle_path /api/recipes/image/* {
- root * /app/data/img/
+ handle_path /api/recipes/media/* {
+ root * /app/data/recipes/
file_server
}
diff --git a/README.md b/README.md
index 72a9a3176ff6..b8b0833a0847 100644
--- a/README.md
+++ b/README.md
@@ -57,14 +57,16 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
## Key Features
- 🔍 Fuzzy search
-- 🏷️ Tag recipes with categories or tags to flexible sorting
+- 🏷️ Tag recipes with categories or tags for flexible sorting
- 🕸 Import recipes from around the web by URL
+- 💪 Powerful bulk Category/Tag assignment
- 📱 Beautiful Mobile Views
- 📆 Create Meal Plans
- 🛒 Generate shopping lists
- 🐳 Easy setup with Docker
-- 🎨 Customize your interface with color themes layouts
-- 💾 Export all your data in any format with Jinja2 Templates, with easy data restoration from the user interface.
+- 🎨 Customize your interface with color themes
+- 💾 Export all your data in any format with Jinja2 Templates
+- 🔒 Keep your data safe with automated backup and easy restore options
- 🌍 localized in many languages
- ➕ Plus tons more!
- Flexible API
diff --git a/docs/docs/changelog/v0.5.0.md b/docs/docs/changelog/v0.5.0.md
index 72b8dec2648c..1f2aff96d006 100644
--- a/docs/docs/changelog/v0.5.0.md
+++ b/docs/docs/changelog/v0.5.0.md
@@ -11,6 +11,9 @@
#### Database
Database version has been bumped from v0.4.x -> v0.5.0. You will need to export and import your data.
+ #### Image Directory
+ the /data/img directory has been depreciated. All images are now stored in the /recipes/{slug}/image directory. Images should be migrated automatically, but you may experience issues related to this change.
+
## Bug Fixes
- Fixed #332 - Language settings are saved for one browser
- Fixes #281 - Slow Handling of Large Sets of Recipes
diff --git a/frontend/src/api/about.js b/frontend/src/api/about.js
new file mode 100644
index 000000000000..f04322939937
--- /dev/null
+++ b/frontend/src/api/about.js
@@ -0,0 +1,59 @@
+import { baseURL } from "./api-utils";
+import { apiReq } from "./api-utils";
+
+const prefix = baseURL + "about";
+
+const aboutURLs = {
+ version: `${prefix}/version`,
+ debug: `${prefix}`,
+ lastRecipe: `${prefix}/last-recipe-json`,
+ demo: `${prefix}/is-demo`,
+ log: num => `${prefix}/log/${num}`,
+ statistics: `${prefix}/statistics`,
+ events: `${prefix}/events`,
+ event: id => `${prefix}/events/${id}`,
+};
+
+export const aboutAPI = {
+ async getEvents() {
+ const resposne = await apiReq.get(aboutURLs.events);
+ return resposne.data;
+ },
+ async deleteEvent(id) {
+ const resposne = await apiReq.delete(aboutURLs.event(id));
+ return resposne.data;
+ },
+ async deleteAllEvents() {
+ const resposne = await apiReq.delete(aboutURLs.events);
+ return resposne.data;
+ },
+ // async getAppInfo() {
+ // const response = await apiReq.get(aboutURLs.version);
+ // return response.data;
+ // },
+
+ // async getDebugInfo() {
+ // const response = await apiReq.get(aboutURLs.debug);
+ // return response.data;
+ // },
+
+ // async getLogText(num) {
+ // const response = await apiReq.get(aboutURLs.log(num));
+ // return response.data;
+ // },
+
+ // async getLastJson() {
+ // const response = await apiReq.get(aboutURLs.lastRecipe);
+ // return response.data;
+ // },
+
+ // async getIsDemo() {
+ // const response = await apiReq.get(aboutURLs.demo);
+ // return response.data;
+ // },
+
+ // async getStatistics() {
+ // const response = await apiReq.get(aboutURLs.statistics);
+ // return response.data;
+ // },
+};
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index aa1c612fc56d..53d22e8acc82 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -11,6 +11,7 @@ import { userAPI } from "./users";
import { signupAPI } from "./signUps";
import { groupAPI } from "./groups";
import { siteSettingsAPI } from "./siteSettings";
+import { aboutAPI } from "./about";
/**
* The main object namespace for interacting with the backend database
@@ -30,4 +31,5 @@ export const api = {
users: userAPI,
signUps: signupAPI,
groups: groupAPI,
+ about: aboutAPI,
};
diff --git a/frontend/src/api/meta.js b/frontend/src/api/meta.js
index 59183c0c5ea1..16f7477b77ea 100644
--- a/frontend/src/api/meta.js
+++ b/frontend/src/api/meta.js
@@ -8,11 +8,13 @@ const debugURLs = {
debug: `${prefix}`,
lastRecipe: `${prefix}/last-recipe-json`,
demo: `${prefix}/is-demo`,
+ log: num => `${prefix}/log/${num}`,
+ statistics: `${prefix}/statistics`,
};
export const metaAPI = {
async getAppInfo() {
- let response = await apiReq.get(debugURLs.version);
+ const response = await apiReq.get(debugURLs.version);
return response.data;
},
@@ -21,13 +23,23 @@ export const metaAPI = {
return response.data;
},
+ async getLogText(num) {
+ const response = await apiReq.get(debugURLs.log(num));
+ return response.data;
+ },
+
async getLastJson() {
- let response = await apiReq.get(debugURLs.lastRecipe);
+ const response = await apiReq.get(debugURLs.lastRecipe);
return response.data;
},
async getIsDemo() {
- let response = await apiReq.get(debugURLs.demo);
+ const response = await apiReq.get(debugURLs.demo);
+ return response.data;
+ },
+
+ async getStatistics() {
+ const response = await apiReq.get(debugURLs.statistics);
return response.data;
},
};
diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js
index fe755f6f771c..d35c4d1bc9b8 100644
--- a/frontend/src/api/recipe.js
+++ b/frontend/src/api/recipe.js
@@ -14,9 +14,9 @@ const recipeURLs = {
recipe: slug => prefix + slug,
update: slug => prefix + slug,
delete: slug => prefix + slug,
+ createAsset: slug => `${prefix}media/${slug}/assets`,
recipeImage: slug => `${prefix}${slug}/image`,
updateImage: slug => `${prefix}${slug}/image`,
- createAsset: slug => `${prefix}${slug}/asset`,
};
export const recipeAPI = {
@@ -84,7 +84,7 @@ export const recipeAPI = {
fd.append("extension", fileObject.name.split(".").pop());
fd.append("name", name);
fd.append("icon", icon);
- let response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd);
+ const response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd);
return response;
},
@@ -135,14 +135,14 @@ export const recipeAPI = {
},
recipeImage(recipeSlug) {
- return `/api/recipes/image/${recipeSlug}/original.webp`;
+ return `/api/recipes/media/${recipeSlug}/image/original.webp`;
},
recipeSmallImage(recipeSlug) {
- return `/api/recipes/image/${recipeSlug}/min-original.webp`;
+ return `/api/recipes/media/${recipeSlug}/image/min-original.webp`;
},
recipeTinyImage(recipeSlug) {
- return `/api/recipes/image/${recipeSlug}/tiny-original.webp`;
+ return `/api/recipes/media/${recipeSlug}/image/tiny-original.webp`;
},
};
diff --git a/frontend/src/components/Recipe/Parts/Assets.vue b/frontend/src/components/Recipe/Parts/Assets.vue
index bb84178050e8..62119ba21ad0 100644
--- a/frontend/src/components/Recipe/Parts/Assets.vue
+++ b/frontend/src/components/Recipe/Parts/Assets.vue
@@ -18,15 +18,20 @@
v-if="!edit"
color="primary"
icon
- :href="`/api/recipes/${slug}/asset?file_name=${item.fileName}`"
+ :href="`/api/recipes/media/${slug}/assets/${item.fileName}`"
target="_blank"
top
>
mdi-download
-
- mdi-delete
-
+
+
+ mdi-delete
+
+
+ mdi-content-copy
+
+
@@ -107,6 +112,11 @@ export default {
],
};
},
+ computed: {
+ baseURL() {
+ return window.location.origin;
+ },
+ },
methods: {
setFileObject(obj) {
this.fileObject = obj;
@@ -124,6 +134,13 @@ export default {
deleteAsset(index) {
this.value.splice(index, 1);
},
+ copyLink(name, fileName) {
+ const copyText = ``;
+ navigator.clipboard.writeText(copyText).then(
+ () => console.log("Copied", copyText),
+ () => console.log("Copied Failed", copyText)
+ );
+ },
},
};
diff --git a/frontend/src/components/Recipe/RecipeEditor/index.vue b/frontend/src/components/Recipe/RecipeEditor/index.vue
index ef735d13de31..bdc153ff8772 100644
--- a/frontend/src/components/Recipe/RecipeEditor/index.vue
+++ b/frontend/src/components/Recipe/RecipeEditor/index.vue
@@ -27,23 +27,36 @@
+
+
+ {{ $t("recipe.categories") }}
+
+
+
+
+
+
- {{ $t("recipe.categories") }}
-
-
- {{ $t("tag.tags") }}
-
+
+
+ {{ $t("tag.tags") }}
+
+
+
+
+
+
diff --git a/frontend/src/components/Recipe/RecipeViewer/index.vue b/frontend/src/components/Recipe/RecipeViewer/index.vue
index aef80568ce38..d4a4fac9a68a 100644
--- a/frontend/src/components/Recipe/RecipeViewer/index.vue
+++ b/frontend/src/components/Recipe/RecipeViewer/index.vue
@@ -1,14 +1,14 @@
- {{ name }}
+ {{ recipe.name }}
-
+
-
+
-
+
-
+
{{ $t("recipe.categories") }}
-
+
-
+
{{ $t("tag.tags") }}
-
+
-
-
+
+
-
-
+
+
-
- {{ showButtonText }}
-
+
+
+
+ {{ showButtonText }}
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/UI/TheSidebar.vue b/frontend/src/components/UI/TheSidebar.vue
index 834c60d977bd..69cfad55250e 100644
--- a/frontend/src/components/UI/TheSidebar.vue
+++ b/frontend/src/components/UI/TheSidebar.vue
@@ -147,6 +147,11 @@ export default {
},
adminLinks() {
return [
+ {
+ icon: "mdi-view-dashboard",
+ to: "/admin/dashboard",
+ title: this.$t("general.dashboard"),
+ },
{
icon: "mdi-cog",
to: "/admin/settings",
diff --git a/frontend/src/locales/dateTimeFormats/af-ZA.json b/frontend/src/locales/dateTimeFormats/af-ZA.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/af-ZA.json
+++ b/frontend/src/locales/dateTimeFormats/af-ZA.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/ar-SA.json b/frontend/src/locales/dateTimeFormats/ar-SA.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/ar-SA.json
+++ b/frontend/src/locales/dateTimeFormats/ar-SA.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/ca-ES.json b/frontend/src/locales/dateTimeFormats/ca-ES.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/ca-ES.json
+++ b/frontend/src/locales/dateTimeFormats/ca-ES.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/cs-CZ.json b/frontend/src/locales/dateTimeFormats/cs-CZ.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/cs-CZ.json
+++ b/frontend/src/locales/dateTimeFormats/cs-CZ.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/da-DK.json b/frontend/src/locales/dateTimeFormats/da-DK.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/da-DK.json
+++ b/frontend/src/locales/dateTimeFormats/da-DK.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/de-DE.json b/frontend/src/locales/dateTimeFormats/de-DE.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/de-DE.json
+++ b/frontend/src/locales/dateTimeFormats/de-DE.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/el-GR.json b/frontend/src/locales/dateTimeFormats/el-GR.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/el-GR.json
+++ b/frontend/src/locales/dateTimeFormats/el-GR.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/en-US.json b/frontend/src/locales/dateTimeFormats/en-US.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/en-US.json
+++ b/frontend/src/locales/dateTimeFormats/en-US.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/es-ES.json b/frontend/src/locales/dateTimeFormats/es-ES.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/es-ES.json
+++ b/frontend/src/locales/dateTimeFormats/es-ES.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/fi-FI.json b/frontend/src/locales/dateTimeFormats/fi-FI.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/fi-FI.json
+++ b/frontend/src/locales/dateTimeFormats/fi-FI.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/fr-FR.json b/frontend/src/locales/dateTimeFormats/fr-FR.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/fr-FR.json
+++ b/frontend/src/locales/dateTimeFormats/fr-FR.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/he-IL.json b/frontend/src/locales/dateTimeFormats/he-IL.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/he-IL.json
+++ b/frontend/src/locales/dateTimeFormats/he-IL.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/hu-HU.json b/frontend/src/locales/dateTimeFormats/hu-HU.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/hu-HU.json
+++ b/frontend/src/locales/dateTimeFormats/hu-HU.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/it-IT.json b/frontend/src/locales/dateTimeFormats/it-IT.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/it-IT.json
+++ b/frontend/src/locales/dateTimeFormats/it-IT.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/ja-JP.json b/frontend/src/locales/dateTimeFormats/ja-JP.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/ja-JP.json
+++ b/frontend/src/locales/dateTimeFormats/ja-JP.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/ko-KR.json b/frontend/src/locales/dateTimeFormats/ko-KR.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/ko-KR.json
+++ b/frontend/src/locales/dateTimeFormats/ko-KR.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/nl-NL.json b/frontend/src/locales/dateTimeFormats/nl-NL.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/nl-NL.json
+++ b/frontend/src/locales/dateTimeFormats/nl-NL.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/no-NO.json b/frontend/src/locales/dateTimeFormats/no-NO.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/no-NO.json
+++ b/frontend/src/locales/dateTimeFormats/no-NO.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/pl-PL.json b/frontend/src/locales/dateTimeFormats/pl-PL.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/pl-PL.json
+++ b/frontend/src/locales/dateTimeFormats/pl-PL.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/pt-BR.json b/frontend/src/locales/dateTimeFormats/pt-BR.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/pt-BR.json
+++ b/frontend/src/locales/dateTimeFormats/pt-BR.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/pt-PT.json b/frontend/src/locales/dateTimeFormats/pt-PT.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/pt-PT.json
+++ b/frontend/src/locales/dateTimeFormats/pt-PT.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/ro-RO.json b/frontend/src/locales/dateTimeFormats/ro-RO.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/ro-RO.json
+++ b/frontend/src/locales/dateTimeFormats/ro-RO.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/ru-RU.json b/frontend/src/locales/dateTimeFormats/ru-RU.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/ru-RU.json
+++ b/frontend/src/locales/dateTimeFormats/ru-RU.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/sr-SP.json b/frontend/src/locales/dateTimeFormats/sr-SP.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/sr-SP.json
+++ b/frontend/src/locales/dateTimeFormats/sr-SP.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/sv-SE.json b/frontend/src/locales/dateTimeFormats/sv-SE.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/sv-SE.json
+++ b/frontend/src/locales/dateTimeFormats/sv-SE.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/tr-TR.json b/frontend/src/locales/dateTimeFormats/tr-TR.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/tr-TR.json
+++ b/frontend/src/locales/dateTimeFormats/tr-TR.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/uk-UA.json b/frontend/src/locales/dateTimeFormats/uk-UA.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/uk-UA.json
+++ b/frontend/src/locales/dateTimeFormats/uk-UA.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/vi-VN.json b/frontend/src/locales/dateTimeFormats/vi-VN.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/vi-VN.json
+++ b/frontend/src/locales/dateTimeFormats/vi-VN.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/zh-CN.json b/frontend/src/locales/dateTimeFormats/zh-CN.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/zh-CN.json
+++ b/frontend/src/locales/dateTimeFormats/zh-CN.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/dateTimeFormats/zh-TW.json b/frontend/src/locales/dateTimeFormats/zh-TW.json
index 485c132d5979..9e0240f38cf8 100644
--- a/frontend/src/locales/dateTimeFormats/zh-TW.json
+++ b/frontend/src/locales/dateTimeFormats/zh-TW.json
@@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
+ },
+ "long": {
+ "year": "numeric",
+ "month": "long",
+ "day": "numeric",
+ "weekday": "long",
+ "hour": "numeric",
+ "minute": "numeric",
+ "hour12": true
}
}
\ No newline at end of file
diff --git a/frontend/src/locales/messages/af-ZA.json b/frontend/src/locales/messages/af-ZA.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/af-ZA.json
+++ b/frontend/src/locales/messages/af-ZA.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/ar-SA.json b/frontend/src/locales/messages/ar-SA.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/ar-SA.json
+++ b/frontend/src/locales/messages/ar-SA.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/ca-ES.json b/frontend/src/locales/messages/ca-ES.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/ca-ES.json
+++ b/frontend/src/locales/messages/ca-ES.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/cs-CZ.json b/frontend/src/locales/messages/cs-CZ.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/cs-CZ.json
+++ b/frontend/src/locales/messages/cs-CZ.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/da-DK.json b/frontend/src/locales/messages/da-DK.json
index 1d10a2fe5170..399693982560 100644
--- a/frontend/src/locales/messages/da-DK.json
+++ b/frontend/src/locales/messages/da-DK.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Anvend",
@@ -36,6 +37,7 @@
"confirm": "Bekræft",
"create": "Opret",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Slet",
"disabled": "Disabled",
"download": "Hent",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importere",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Mandag",
"name": "Navn",
"no": "Nej",
diff --git a/frontend/src/locales/messages/de-DE.json b/frontend/src/locales/messages/de-DE.json
index 208d95aca133..0712e2bf52a2 100644
--- a/frontend/src/locales/messages/de-DE.json
+++ b/frontend/src/locales/messages/de-DE.json
@@ -14,10 +14,10 @@
"demo-status": "Demostatus",
"development": "Entwicklung",
"download-log": "Protokoll herunterladen",
- "download-recipe-json": "Rezept JSON herunterladen",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Keine Demo",
"production": "Production",
- "sqlite-file": "SQLite Datei",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Entfernen der Kategorie fehlgeschlagen",
"category-filter": "Kategoriefilter",
"category-update-failed": "Aktualisieren der Kategorie fehlgeschlagen",
- "category-updated": "Kategorie aktualisiert"
+ "category-updated": "Kategorie aktualisiert",
+ "category": "Category"
},
"general": {
"apply": "Anwenden",
@@ -36,6 +37,7 @@
"confirm": "Bestätigen",
"create": "Erstellen",
"current-parenthesis": "(Neueste)",
+ "dashboard": "Dashboard",
"delete": "Löschen",
"disabled": "Deaktiviert",
"download": "Herunterladen",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importieren",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Montag",
"name": "Name",
"no": "Nein",
diff --git a/frontend/src/locales/messages/el-GR.json b/frontend/src/locales/messages/el-GR.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/el-GR.json
+++ b/frontend/src/locales/messages/el-GR.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/en-US.json b/frontend/src/locales/messages/en-US.json
index 0ca827e7fb88..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/en-US.json
+++ b/frontend/src/locales/messages/en-US.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
diff --git a/frontend/src/locales/messages/es-ES.json b/frontend/src/locales/messages/es-ES.json
index 446fe4b86dc7..2d19e72adafc 100644
--- a/frontend/src/locales/messages/es-ES.json
+++ b/frontend/src/locales/messages/es-ES.json
@@ -1,60 +1,63 @@
{
"404": {
- "page-not-found": "404 Page Not Found",
- "take-me-home": "Take me Home"
+ "page-not-found": "404 Página No Encontrada",
+ "take-me-home": "Ir a Inicio"
},
"about": {
- "about-mealie": "About Mealie",
- "api-docs": "API Docs",
- "api-port": "API Port",
- "application-mode": "Application Mode",
- "database-type": "Database Type",
- "default-group": "Default Group",
- "demo": "Demo",
- "demo-status": "Demo Status",
- "development": "Development",
- "download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
- "not-demo": "Not Demo",
- "production": "Production",
- "sqlite-file": "SQLite File",
- "version": "Version"
+ "about-mealie": "Acerca de Mealie",
+ "api-docs": "Documentación API",
+ "api-port": "Puerto API",
+ "application-mode": "Modo de Aplicación",
+ "database-type": "Tipo de base de datos",
+ "default-group": "Grupo Predeterminado",
+ "demo": "Versión Demo",
+ "demo-status": "Estado Demo",
+ "development": "Desarrollo",
+ "download-log": "Descargar Log",
+ "download-recipe-json": "Last Scraped JSON",
+ "not-demo": "No Demo",
+ "production": "Producción",
+ "database-url": "Database URL",
+ "version": "Versión"
},
"category": {
- "category-created": "Category created",
- "category-creation-failed": "Category creation failed",
- "category-deleted": "Category Deleted",
- "category-deletion-failed": "Category deletion failed",
- "category-filter": "Category Filter",
- "category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-created": "Categoría creada",
+ "category-creation-failed": "Error al crear categoría",
+ "category-deleted": "Categoría Eliminada",
+ "category-deletion-failed": "Error al eliminar categoría",
+ "category-filter": "Filtros de Categorías",
+ "category-update-failed": "Error al actualizar categoría",
+ "category-updated": "Categoría actualizada",
+ "category": "Category"
},
"general": {
- "apply": "Apply",
- "cancel": "Cancel",
- "close": "Close",
- "confirm": "Confirm",
- "create": "Create",
- "current-parenthesis": "(Current)",
- "delete": "Delete",
- "disabled": "Disabled",
- "download": "Download",
- "edit": "Edit",
- "enabled": "Enabled",
- "exception": "Exception",
- "failed-count": "Failed: {count}",
- "failure-uploading-file": "Failure uploading file",
- "field-required": "Field Required",
- "file-folder-not-found": "File/folder not found",
- "file-uploaded": "File uploaded",
- "filter": "Filter",
- "friday": "Friday",
- "get": "Get",
- "groups": "Groups",
- "image": "Image",
- "image-upload-failed": "Image upload failed",
- "import": "Import",
- "keyword": "Keyword",
+ "apply": "Aplicar",
+ "cancel": "Cancelar",
+ "close": "Cerrar",
+ "confirm": "Confirmar",
+ "create": "Crear",
+ "current-parenthesis": "(Actual)",
+ "dashboard": "Dashboard",
+ "delete": "Eliminar",
+ "disabled": "Deshabilitado",
+ "download": "Descargar",
+ "edit": "Editar",
+ "enabled": "Habilitado",
+ "exception": "Excepción",
+ "failed-count": "Error: {count}",
+ "failure-uploading-file": "Error al cargar el archivo",
+ "field-required": "Campo Requerido",
+ "file-folder-not-found": "No se ha encontrado el archivo o la carpeta",
+ "file-uploaded": "Archivo cargado",
+ "filter": "Filtro",
+ "friday": "Viernes",
+ "get": "Obtener",
+ "groups": "Grupos",
+ "image": "Imagen",
+ "image-upload-failed": "Error al subir la imagen",
+ "import": "Importar",
+ "keyword": "Etiqueta",
+ "link": "Enlace",
"monday": "Monday",
"name": "Name",
"no": "No",
@@ -84,23 +87,23 @@
"url": "URL",
"users": "Users",
"wednesday": "Wednesday",
- "yes": "Yes"
+ "yes": "Si"
},
"group": {
- "are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete {groupName}?",
- "cannot-delete-default-group": "Cannot delete default group",
- "cannot-delete-group-with-users": "Cannot delete group with users",
- "confirm-group-deletion": "Confirm Group Deletion",
- "create-group": "Create Group",
- "error-updating-group": "Error updating group",
- "group": "Group",
- "group-deleted": "Group deleted",
- "group-deletion-failed": "Group deletion failed",
- "group-id-with-value": "Group ID: {groupID}",
- "group-name": "Group Name",
- "group-not-found": "Group not found",
- "groups": "Groups",
- "groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
+ "are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar {groupName}",
+ "cannot-delete-default-group": "No se puede eliminar el grupo predeterminado",
+ "cannot-delete-group-with-users": "No se puede eliminar el grupo con usuarios",
+ "confirm-group-deletion": "Confirmar Eliminación de Grupo",
+ "create-group": "Crear Grupo",
+ "error-updating-group": "Error al actualizar grupo",
+ "group": "Grupo",
+ "group-deleted": "Grupo eliminado",
+ "group-deletion-failed": "Error al eliminar grupo",
+ "group-id-with-value": "ID del Grupo: {groupID}",
+ "group-name": "Nombre del Grupo",
+ "group-not-found": "Grupo no encontrado",
+ "groups": "Grupos",
+ "groups-can-only-be-set-by-administrators": "Los grupos sólo pueden ser establecidos por administradores",
"user-group": "User Group",
"user-group-created": "User Group Created",
"user-group-creation-failed": "User Group Creation Failed"
@@ -109,13 +112,13 @@
"create-a-new-meal-plan": "Create a New Meal Plan",
"dinner-this-week": "Dinner This Week",
"dinner-today": "Dinner Today",
- "edit-meal-plan": "Edit Meal Plan",
- "end-date": "End Date",
- "group": "Group (Beta)",
- "meal-planner": "Meal Planner",
- "meal-plans": "Meal Plans",
- "mealplan-created": "Mealplan created",
- "mealplan-creation-failed": "Mealplan creation failed",
+ "edit-meal-plan": "Editar Plan de Comida",
+ "end-date": "Fecha de Finalización",
+ "group": "Grupo (Beta)",
+ "meal-planner": "Plan de Comidas",
+ "meal-plans": "Planes de Comidas",
+ "mealplan-created": "Plan de Comida creado",
+ "mealplan-creation-failed": "Error al crear Plan de Comida",
"mealplan-deleted": "Mealplan Deleted",
"mealplan-deletion-failed": "Mealplan deletion failed",
"mealplan-update-failed": "Mealplan update failed",
diff --git a/frontend/src/locales/messages/fi-FI.json b/frontend/src/locales/messages/fi-FI.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/fi-FI.json
+++ b/frontend/src/locales/messages/fi-FI.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/fr-FR.json b/frontend/src/locales/messages/fr-FR.json
index e0a988b5ce24..0a617f1963eb 100644
--- a/frontend/src/locales/messages/fr-FR.json
+++ b/frontend/src/locales/messages/fr-FR.json
@@ -14,10 +14,10 @@
"demo-status": "Mode démo",
"development": "Développement",
"download-log": "Télécharger les logs",
- "download-recipe-json": "Télécharger le JSON de la recette",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Non",
"production": "Production",
- "sqlite-file": "Fichier SQLite",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "La suppression de la catégorie a échoué",
"category-filter": "Filtre par catégories",
"category-update-failed": "La mise à jour de la catégorie a échoué",
- "category-updated": "Catégorie mise à jour"
+ "category-updated": "Catégorie mise à jour",
+ "category": "Category"
},
"general": {
"apply": "Appliquer",
@@ -36,6 +37,7 @@
"confirm": "Confirmer",
"create": "Créer",
"current-parenthesis": "(Actuel)",
+ "dashboard": "Dashboard",
"delete": "Supprimer",
"disabled": "Désactivé",
"download": "Télécharger",
@@ -55,6 +57,7 @@
"image-upload-failed": "Le téléchargement de l'image a échoué",
"import": "Importer",
"keyword": "Mot-clé",
+ "link": "Lien",
"monday": "Lundi",
"name": "Nom",
"no": "Non",
diff --git a/frontend/src/locales/messages/he-IL.json b/frontend/src/locales/messages/he-IL.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/he-IL.json
+++ b/frontend/src/locales/messages/he-IL.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/hu-HU.json b/frontend/src/locales/messages/hu-HU.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/hu-HU.json
+++ b/frontend/src/locales/messages/hu-HU.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/it-IT.json b/frontend/src/locales/messages/it-IT.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/it-IT.json
+++ b/frontend/src/locales/messages/it-IT.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/ja-JP.json b/frontend/src/locales/messages/ja-JP.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/ja-JP.json
+++ b/frontend/src/locales/messages/ja-JP.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/ko-KR.json b/frontend/src/locales/messages/ko-KR.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/ko-KR.json
+++ b/frontend/src/locales/messages/ko-KR.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/nl-NL.json b/frontend/src/locales/messages/nl-NL.json
index c2a2735303da..f19426757437 100644
--- a/frontend/src/locales/messages/nl-NL.json
+++ b/frontend/src/locales/messages/nl-NL.json
@@ -14,10 +14,10 @@
"demo-status": "Demo status",
"development": "Versies in ontwikkeling",
"download-log": "Logbestand downloaden",
- "download-recipe-json": "Download Recept JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Niet Demo",
"production": "Productie",
- "sqlite-file": "SQLite bestand",
+ "database-url": "Database URL",
"version": "Versie"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Categorie verwijderen mislukt",
"category-filter": "Categorie Filter",
"category-update-failed": "Categorie bijwerken mislukt",
- "category-updated": "Categorie bijgewerkt"
+ "category-updated": "Categorie bijgewerkt",
+ "category": "Category"
},
"general": {
"apply": "Toepassen",
@@ -36,6 +37,7 @@
"confirm": "Bevestigen",
"create": "Maak",
"current-parenthesis": "(Huidig)",
+ "dashboard": "Dashboard",
"delete": "Verwijderen",
"disabled": "Uitgeschakeld",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Afbeelding uploaden mislukt",
"import": "Importeren",
"keyword": "Trefwoord",
+ "link": "Link",
"monday": "Maandag",
"name": "Naam",
"no": "Nee",
diff --git a/frontend/src/locales/messages/no-NO.json b/frontend/src/locales/messages/no-NO.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/no-NO.json
+++ b/frontend/src/locales/messages/no-NO.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/pl-PL.json b/frontend/src/locales/messages/pl-PL.json
index cfaeb48ddc12..73479c31f083 100644
--- a/frontend/src/locales/messages/pl-PL.json
+++ b/frontend/src/locales/messages/pl-PL.json
@@ -14,10 +14,10 @@
"demo-status": "Status demo",
"development": "Wersja testowa",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Nie demo",
"production": "Produkcyjna",
- "sqlite-file": "Plik SQLite'a",
+ "database-url": "Database URL",
"version": "Wersja"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Zastosuj",
@@ -36,6 +37,7 @@
"confirm": "Potwierdź",
"create": "Utwórz",
"current-parenthesis": "(Bieżący)",
+ "dashboard": "Dashboard",
"delete": "Usuń",
"disabled": "Wyłączone",
"download": "Pobierz",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importuj",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Poniedziałek",
"name": "Nazwa",
"no": "Nie",
diff --git a/frontend/src/locales/messages/pt-BR.json b/frontend/src/locales/messages/pt-BR.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/pt-BR.json
+++ b/frontend/src/locales/messages/pt-BR.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/pt-PT.json b/frontend/src/locales/messages/pt-PT.json
index b54a8d11561b..68f5be09d07b 100644
--- a/frontend/src/locales/messages/pt-PT.json
+++ b/frontend/src/locales/messages/pt-PT.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirmar",
"create": "Criar",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Eliminar",
"disabled": "Disabled",
"download": "Transferir",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importar",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Nome",
"no": "No",
diff --git a/frontend/src/locales/messages/ro-RO.json b/frontend/src/locales/messages/ro-RO.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/ro-RO.json
+++ b/frontend/src/locales/messages/ro-RO.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/ru-RU.json b/frontend/src/locales/messages/ru-RU.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/ru-RU.json
+++ b/frontend/src/locales/messages/ru-RU.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/sr-SP.json b/frontend/src/locales/messages/sr-SP.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/sr-SP.json
+++ b/frontend/src/locales/messages/sr-SP.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/sv-SE.json b/frontend/src/locales/messages/sv-SE.json
index 94515b94db24..7c5bd7aa45da 100644
--- a/frontend/src/locales/messages/sv-SE.json
+++ b/frontend/src/locales/messages/sv-SE.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Skapa",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Ta bort",
"disabled": "Disabled",
"download": "Ladda ner",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importera",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Namn",
"no": "No",
diff --git a/frontend/src/locales/messages/tr-TR.json b/frontend/src/locales/messages/tr-TR.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/tr-TR.json
+++ b/frontend/src/locales/messages/tr-TR.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/uk-UA.json b/frontend/src/locales/messages/uk-UA.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/uk-UA.json
+++ b/frontend/src/locales/messages/uk-UA.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/vi-VN.json b/frontend/src/locales/messages/vi-VN.json
index 446fe4b86dc7..5aab6900f2f5 100644
--- a/frontend/src/locales/messages/vi-VN.json
+++ b/frontend/src/locales/messages/vi-VN.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",
diff --git a/frontend/src/locales/messages/zh-CN.json b/frontend/src/locales/messages/zh-CN.json
index a7f93f761793..863e86463faf 100644
--- a/frontend/src/locales/messages/zh-CN.json
+++ b/frontend/src/locales/messages/zh-CN.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "应用",
@@ -36,6 +37,7 @@
"confirm": "确定",
"create": "创建",
"current-parenthesis": "(当前)",
+ "dashboard": "Dashboard",
"delete": "删除",
"disabled": "Disabled",
"download": "下载",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "导入",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "名称",
"no": "否",
diff --git a/frontend/src/locales/messages/zh-TW.json b/frontend/src/locales/messages/zh-TW.json
index 37b0ced6ef20..6553dc6b2c4e 100644
--- a/frontend/src/locales/messages/zh-TW.json
+++ b/frontend/src/locales/messages/zh-TW.json
@@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
- "download-recipe-json": "Download Recipe JSON",
+ "download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
- "sqlite-file": "SQLite File",
+ "database-url": "Database URL",
"version": "Version"
},
"category": {
@@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
- "category-updated": "Category updated"
+ "category-updated": "Category updated",
+ "category": "Category"
},
"general": {
"apply": "Apply",
@@ -36,6 +37,7 @@
"confirm": "確定",
"create": "創建",
"current-parenthesis": "(Current)",
+ "dashboard": "Dashboard",
"delete": "删除",
"disabled": "Disabled",
"download": "下载",
@@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "導入",
"keyword": "Keyword",
+ "link": "Link",
"monday": "Monday",
"name": "名稱",
"no": "No",
diff --git a/frontend/src/pages/Admin/About/index.vue b/frontend/src/pages/Admin/About/index.vue
index efbb5b843ccd..b410b3bfec8e 100644
--- a/frontend/src/pages/Admin/About/index.vue
+++ b/frontend/src/pages/Admin/About/index.vue
@@ -22,19 +22,26 @@
-
-
+
+
+
+ mdi-code-braces {{ $t("about.download-recipe-json") }}
+
+
+
+
-
-
diff --git a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue
index 591b31b826c8..b4e333c9c7d7 100644
--- a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue
+++ b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue
@@ -19,7 +19,7 @@
{{ backup.name }}
- {{ $d(new Date(backup.date), "medium") }}
+ {{ $d(Date.parse(backup.date), "medium") }}
diff --git a/frontend/src/pages/Admin/Backup/ImportDialog.vue b/frontend/src/pages/Admin/Backup/ImportDialog.vue
index 9d4aceef395a..0aed4c0e2f46 100644
--- a/frontend/src/pages/Admin/Backup/ImportDialog.vue
+++ b/frontend/src/pages/Admin/Backup/ImportDialog.vue
@@ -15,7 +15,7 @@
{{ name }}
- {{ $d(new Date(date), "medium") }}
+ {{ $d(new Date(date), "medium") }}
diff --git a/frontend/src/pages/Admin/Dashboard/BackupViewer.vue b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue
new file mode 100644
index 000000000000..5ef1259685c6
--- /dev/null
+++ b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ mdi-cloud-upload Upload
+
+
+
+
+ mdi-plus Create
+
+
+
+
+
+
+
+
+ mdi-backup-restore
+
+
+
+
+
+
+
+ {{ $d(Date.parse(item.date), "medium") }}
+
+
+
+
+
+ mdi-delete
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/pages/Admin/Dashboard/EventViewer.vue b/frontend/src/pages/Admin/Dashboard/EventViewer.vue
new file mode 100644
index 000000000000..5070c677b6eb
--- /dev/null
+++ b/frontend/src/pages/Admin/Dashboard/EventViewer.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+ mdi-notification-clear-all Clear
+
+
+
+
+
+
+
+
+ {{ icons[item.category].icon }}
+
+
+
+
+
+
+
+
+ {{ $d(Date.parse(item.timeStamp), "long") }}
+
+
+
+
+
+ mdi-delete
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/pages/Admin/Dashboard/StatCard.vue b/frontend/src/pages/Admin/Dashboard/StatCard.vue
new file mode 100644
index 000000000000..f79cc8289d3e
--- /dev/null
+++ b/frontend/src/pages/Admin/Dashboard/StatCard.vue
@@ -0,0 +1,100 @@
+w
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/Admin/Dashboard/index.vue b/frontend/src/pages/Admin/Dashboard/index.vue
new file mode 100644
index 000000000000..771cc4da0635
--- /dev/null
+++ b/frontend/src/pages/Admin/Dashboard/index.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+ {{ statistics.totalRecipes }}
+
+
+
+
+
+
+ mdi-tag Untagged {{ statistics.untaggedRecipes }}
+
+
+ mdi-tag Uncategorized {{ statistics.uncategorizedRecipes }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ statistics.totalUsers }}
+
+
+
+
+
+
+ mdi-account
+ Manage Users
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ statistics.totalGroups }}
+
+
+
+
+
+
+ mdi-account-group
+ Manage Groups
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue
index 6deac3ce1b60..21138a520b1e 100644
--- a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue
+++ b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue
@@ -160,10 +160,10 @@ export default {
methods: {
updateClipboard(newClip) {
navigator.clipboard.writeText(newClip).then(
- function() {
+ () => {
console.log("Copied", newClip);
},
- function() {
+ () => {
console.log("Copy Failed", newClip);
}
);
diff --git a/frontend/src/pages/Admin/ManageUsers/index.vue b/frontend/src/pages/Admin/ManageUsers/index.vue
index fbbb1aef5f37..74aa8bd27726 100644
--- a/frontend/src/pages/Admin/ManageUsers/index.vue
+++ b/frontend/src/pages/Admin/ManageUsers/index.vue
@@ -4,30 +4,30 @@
-
+
{{ $t("user.users") }}
mdi-account
-
+
{{ $t("signup.sign-up-links") }}
mdi-account-plus-outline
-
+
{{ $t("group.groups") }}
mdi-account-group
-
+
-
+
-
+
@@ -42,9 +42,17 @@ import TheSignUpTable from "./TheSignUpTable";
export default {
components: { TheUserTable, GroupDashboard, TheSignUpTable },
data() {
- return {
- tab: 0,
- };
+ return {};
+ },
+ computed: {
+ tab: {
+ set(tab) {
+ this.$router.replace({ query: { ...this.$route.query, tab } });
+ },
+ get() {
+ return this.$route.query.tab;
+ },
+ },
},
mounted() {
this.$store.dispatch("requestAllGroups");
diff --git a/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue b/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue
new file mode 100644
index 000000000000..b83cdfcde624
--- /dev/null
+++ b/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+ mdi-tag-multiple
+ {{ $t("category.category") }}
+
+
+
+ mdi-tag-multiple
+ {{ $t("tag.tags") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/pages/Admin/ToolBox/index.vue b/frontend/src/pages/Admin/ToolBox/index.vue
index 50333cd6ddca..d5d029bf2e49 100644
--- a/frontend/src/pages/Admin/ToolBox/index.vue
+++ b/frontend/src/pages/Admin/ToolBox/index.vue
@@ -4,20 +4,25 @@
-
+
{{ $t("recipe.categories") }}
mdi-tag-multiple-outline
-
+
{{ $t("tag.tags") }}
mdi-tag-multiple-outline
+
+ Organize
+ mdi-broom
+
-
-
+
+
+
@@ -25,14 +30,24 @@
diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue
index 3b2bc453602d..abc1f7d52d71 100644
--- a/frontend/src/pages/HomePage.vue
+++ b/frontend/src/pages/HomePage.vue
@@ -37,7 +37,6 @@ export default {
return this.$store.getters.getSiteSettings;
},
recentRecipes() {
- console.log("Recent Recipes");
return this.$store.getters.getRecentRecipes;
},
},
diff --git a/frontend/src/pages/Recipe/ViewRecipe.vue b/frontend/src/pages/Recipe/ViewRecipe.vue
index ea97cb941880..8ee5c02930e4 100644
--- a/frontend/src/pages/Recipe/ViewRecipe.vue
+++ b/frontend/src/pages/Recipe/ViewRecipe.vue
@@ -25,22 +25,7 @@
class="sticky"
/>
-
+
logging.Logger:
return logger
+root_logger = logger_init()
+
+
def get_logger(module=None) -> logging.Logger:
""" Returns a child logger for mealie """
global root_logger
@@ -38,6 +41,3 @@ def get_logger(module=None) -> logging.Logger:
return root_logger
return root_logger.getChild(module)
-
-
-root_logger = logger_init()
diff --git a/mealie/db/database.py b/mealie/db/database.py
index 31aad381ab8b..0881d29bd21d 100644
--- a/mealie/db/database.py
+++ b/mealie/db/database.py
@@ -1,6 +1,7 @@
from logging import getLogger
from mealie.db.db_base import BaseDocument
+from mealie.db.models.event import Event
from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlanModel
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
@@ -9,6 +10,7 @@ from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import User
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
+from mealie.schema.events import Event as EventSchema
from mealie.schema.meal import MealPlanInDB
from mealie.schema.recipe import Recipe
from mealie.schema.settings import CustomPageOut
@@ -18,7 +20,6 @@ from mealie.schema.theme import SiteTheme
from mealie.schema.user import GroupInDB, UserInDB
from sqlalchemy.orm.session import Session
-
logger = getLogger()
@@ -35,6 +36,26 @@ class _Recipes(BaseDocument):
return f"{slug}.{extension}"
+ def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int:
+ eff_schema = override_schema or self.schema
+ if count:
+ return session.query(self.sql_model).filter(RecipeModel.recipe_category == None).count() # noqa: 711
+ else:
+ return [
+ eff_schema.from_orm(x)
+ for x in session.query(self.sql_model).filter(RecipeModel.tags == None).all() # noqa: 711
+ ]
+
+ def count_untagged(self, session: Session, count=True, override_schema=None) -> int:
+ eff_schema = override_schema or self.schema
+ if count:
+ return session.query(self.sql_model).filter(RecipeModel.tags == None).count() # noqa: 711
+ else:
+ return [
+ eff_schema.from_orm(x)
+ for x in session.query(self.sql_model).filter(RecipeModel.tags == None).all() # noqa: 711
+ ]
+
class _Categories(BaseDocument):
def __init__(self) -> None:
@@ -110,8 +131,6 @@ class _Groups(BaseDocument):
"""
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
- # Potentially not needed? column is sorted by SqlAlchemy based on startDate
- # return sorted(group.mealplans, key=lambda mealplan: mealplan.startDate)
return group.mealplans
@@ -129,6 +148,13 @@ class _CustomPages(BaseDocument):
self.schema = CustomPageOut
+class _Events(BaseDocument):
+ def __init__(self) -> None:
+ self.primary_key = "id"
+ self.sql_model = Event
+ self.schema = EventSchema
+
+
class Database:
def __init__(self) -> None:
self.recipes = _Recipes()
@@ -141,6 +167,7 @@ class Database:
self.sign_ups = _SignUps()
self.groups = _Groups()
self.custom_pages = _CustomPages()
+ self.events = _Events()
db = Database()
diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py
index b2c239ef8585..9d178adf45b3 100644
--- a/mealie/db/db_base.py
+++ b/mealie/db/db_base.py
@@ -23,6 +23,14 @@ class BaseDocument:
) -> List[dict]:
eff_schema = override_schema or self.schema
+ if order_by:
+ order_attr = getattr(self.sql_model, str(order_by))
+
+ return [
+ eff_schema.from_orm(x)
+ for x in session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all()
+ ]
+
return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()]
def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]:
@@ -154,3 +162,14 @@ class BaseDocument:
session.commit()
return results_as_model
+
+ def delete_all(self, session: Session) -> None:
+ session.query(self.sql_model).delete()
+ session.commit()
+
+ def count_all(self, session: Session, match_key=None, match_value=None) -> int:
+
+ if None in [match_key, match_value]:
+ return session.query(self.sql_model).count()
+ else:
+ return session.query(self.sql_model).filter_by(**{match_key: match_value}).count()
diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py
index f5cfa746f7b1..a9a243a9fb27 100644
--- a/mealie/db/init_db.py
+++ b/mealie/db/init_db.py
@@ -5,6 +5,7 @@ from mealie.db.database import db
from mealie.db.db_setup import create_session
from mealie.schema.settings import SiteSettings
from mealie.schema.theme import SiteTheme
+from mealie.services.events import create_general_event
from sqlalchemy.orm import Session
logger = root_logger.get_logger("init_db")
@@ -58,6 +59,7 @@ def main():
else:
print("Database Doesn't Exists, Initializing...")
init_db()
+ create_general_event("Initialize Database", "Initialize database with default values", session)
if __name__ == "__main__":
diff --git a/mealie/db/models/_all_models.py b/mealie/db/models/_all_models.py
index 8034475ca490..0e5daca17d56 100644
--- a/mealie/db/models/_all_models.py
+++ b/mealie/db/models/_all_models.py
@@ -1,7 +1,8 @@
+from mealie.db.models.event import *
+from mealie.db.models.group import *
from mealie.db.models.mealplan import *
from mealie.db.models.recipe.recipe import *
from mealie.db.models.settings import *
+from mealie.db.models.sign_up import *
from mealie.db.models.theme import *
from mealie.db.models.users import *
-from mealie.db.models.sign_up import *
-from mealie.db.models.group import *
diff --git a/mealie/db/models/event.py b/mealie/db/models/event.py
new file mode 100644
index 000000000000..1ee63b12065b
--- /dev/null
+++ b/mealie/db/models/event.py
@@ -0,0 +1,17 @@
+import sqlalchemy as sa
+from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
+
+
+class Event(SqlAlchemyBase, BaseMixins):
+ __tablename__ = "events"
+ id = sa.Column(sa.Integer, primary_key=True)
+ title = sa.Column(sa.String)
+ text = sa.Column(sa.String)
+ time_stamp = sa.Column(sa.DateTime)
+ category = sa.Column(sa.String)
+
+ def __init__(self, title, text, time_stamp, category, *args, **kwargs) -> None:
+ self.title = title
+ self.text = text
+ self.time_stamp = time_stamp
+ self.category = category
diff --git a/mealie/db/models/model_base.py b/mealie/db/models/model_base.py
index 57662ab5309f..2eb385f8f1fb 100644
--- a/mealie/db/models/model_base.py
+++ b/mealie/db/models/model_base.py
@@ -4,5 +4,5 @@ SqlAlchemyBase = dec.declarative_base()
class BaseMixins:
- def _pass_on_me():
- pass
+ def update(self, *args, **kwarg):
+ self.__init__(*args, **kwarg)
diff --git a/mealie/routes/about/__init__.py b/mealie/routes/about/__init__.py
new file mode 100644
index 000000000000..e36affaa90d1
--- /dev/null
+++ b/mealie/routes/about/__init__.py
@@ -0,0 +1,7 @@
+from fastapi import APIRouter
+
+from .events import router as events_router
+
+about_router = APIRouter(prefix="/api/about")
+
+about_router.include_router(events_router)
diff --git a/mealie/routes/about/events.py b/mealie/routes/about/events.py
new file mode 100644
index 000000000000..3eed7690f962
--- /dev/null
+++ b/mealie/routes/about/events.py
@@ -0,0 +1,28 @@
+from fastapi import APIRouter, Depends
+from mealie.db.database import db
+from mealie.db.db_setup import generate_session
+from mealie.routes.deps import get_current_user
+from mealie.schema.events import EventsOut
+from sqlalchemy.orm.session import Session
+
+router = APIRouter(prefix="/events", tags=["App Events"])
+
+
+@router.get("", response_model=EventsOut)
+async def get_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
+ """ Get event from the Database """
+ # Get Item
+ return EventsOut(total=db.events.count_all(session), events=db.events.get_all(session, order_by="time_stamp"))
+
+
+@router.delete("")
+async def delete_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
+ """ Get event from the Database """
+ # Get Item
+ return db.events.delete_all(session)
+
+
+@router.delete("/{id}")
+async def delete_event(id: int, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
+ """ Delete event from the Database """
+ return db.events.delete(session, id)
diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py
index 864a1380ac6f..d4fef6c357bd 100644
--- a/mealie/routes/backup_routes.py
+++ b/mealie/routes/backup_routes.py
@@ -1,17 +1,21 @@
import operator
import shutil
+from pathlib import Path
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from mealie.core.config import app_dirs
+from mealie.core.root_logger import get_logger
from mealie.core.security import create_file_token
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
from mealie.services.backups import imports
from mealie.services.backups.exports import backup_all
+from mealie.services.events import create_backup_event
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depends(get_current_user)])
+logger = get_logger()
@router.get("/available", response_model=Imports)
@@ -43,8 +47,10 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
export_users=data.options.users,
export_groups=data.options.groups,
)
+ create_backup_event("Database Backup", f"Manual Backup Created '{Path(export_path).name}'", session)
return {"export_path": export_path}
- except Exception:
+ except Exception as e:
+ logger.error(e)
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
@@ -72,7 +78,7 @@ async def download_backup_file(file_name: str):
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
""" Import a database backup file generated from Mealie. """
- return imports.import_database(
+ db_import = imports.import_database(
session=session,
archive=import_data.name,
import_recipes=import_data.recipes,
@@ -84,6 +90,8 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D
force_import=import_data.force,
rebase=import_data.rebase,
)
+ create_backup_event("Database Restore", f"Restored Database File {file_name}", session)
+ return db_import
@router.delete("/{file_name}/delete", status_code=status.HTTP_200_OK)
diff --git a/mealie/routes/debug_routes.py b/mealie/routes/debug_routes.py
index 3e5d6c4b82c3..04f1516bbc96 100644
--- a/mealie/routes/debug_routes.py
+++ b/mealie/routes/debug_routes.py
@@ -2,8 +2,11 @@ from fastapi import APIRouter, Depends
from mealie.core.config import APP_VERSION, app_dirs, settings
from mealie.core.root_logger import LOGGER_FILE
from mealie.core.security import create_file_token
+from mealie.db.database import db
+from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
-from mealie.schema.debug import AppInfo, DebugInfo
+from mealie.schema.about import AppInfo, AppStatistics, DebugInfo
+from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/debug", tags=["Debug"])
@@ -18,11 +21,23 @@ async def get_debug_info(current_user=Depends(get_current_user)):
demo_status=settings.IS_DEMO,
api_port=settings.API_PORT,
api_docs=settings.API_DOCS,
+ db_type=settings.DB_ENGINE,
db_url=settings.DB_URL,
default_group=settings.DEFAULT_GROUP,
)
+@router.get("/statistics")
+async def get_app_statistics(session: Session = Depends(generate_session)):
+ return AppStatistics(
+ total_recipes=db.recipes.count_all(session),
+ uncategorized_recipes=db.recipes.count_uncategorized(session),
+ untagged_recipes=db.recipes.count_untagged(session),
+ total_users=db.users.count_all(session),
+ total_groups=db.groups.count_all(session),
+ )
+
+
@router.get("/version")
async def get_mealie_version():
""" Returns the current version of mealie"""
diff --git a/mealie/routes/mealplans/crud.py b/mealie/routes/mealplans/crud.py
index 7b58853d7533..bb2f17672ec1 100644
--- a/mealie/routes/mealplans/crud.py
+++ b/mealie/routes/mealplans/crud.py
@@ -88,7 +88,7 @@ def get_todays_image(session: Session = Depends(generate_session), group_name: s
recipe = get_todays_meal(session, group_in_db)
if recipe:
- recipe_image = image.read_image(recipe.slug, image_type=image.IMG_OPTIONS.ORIGINAL_IMAGE)
+ recipe_image = recipe.image_dir.joinpath(image.ImageOptions.ORIGINAL_IMAGE)
else:
raise HTTPException(status.HTTP_404_NOT_FOUND)
if recipe_image:
diff --git a/mealie/routes/recipe/__init__.py b/mealie/routes/recipe/__init__.py
index 8af92586aae6..1d54034f0bf7 100644
--- a/mealie/routes/recipe/__init__.py
+++ b/mealie/routes/recipe/__init__.py
@@ -1,10 +1,10 @@
from fastapi import APIRouter
-from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_assets, recipe_crud_routes, tag_routes
+from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, recipe_media, tag_routes
router = APIRouter()
router.include_router(all_recipe_routes.router)
router.include_router(recipe_crud_routes.router)
-router.include_router(recipe_assets.router)
+router.include_router(recipe_media.router)
router.include_router(category_routes.router)
router.include_router(tag_routes.router)
diff --git a/mealie/routes/recipe/all_recipe_routes.py b/mealie/routes/recipe/all_recipe_routes.py
index 014815774ada..9a61751c9383 100644
--- a/mealie/routes/recipe/all_recipe_routes.py
+++ b/mealie/routes/recipe/all_recipe_routes.py
@@ -8,7 +8,7 @@ from sqlalchemy.orm.session import Session
router = APIRouter(tags=["Query All Recipes"])
-@router.get("/api/recipes/summary")
+@router.get("/api/recipes/summary", response_model=list[RecipeSummary])
async def get_recipe_summary(
start=0,
limit=9999,
@@ -29,6 +29,16 @@ async def get_recipe_summary(
return db.recipes.get_all(session, limit=limit, start=start, override_schema=RecipeSummary)
+@router.get("/api/recipes/summary/untagged", response_model=list[RecipeSummary])
+async def get_untagged_recipes(session: Session = Depends(generate_session)):
+ return db.recipes.count_untagged(session, False, override_schema=RecipeSummary)
+
+
+@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary])
+async def get_uncategorized_recipes(session: Session = Depends(generate_session)):
+ return db.recipes.count_uncategorized(session, False, override_schema=RecipeSummary)
+
+
@router.post("/api/recipes/category")
def filter_by_category(categories: list, session: Session = Depends(generate_session)):
""" pass a list of categories and get a list of recipes associated with those categories """
diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py
index 8547aa09fe5d..6604b84e664f 100644
--- a/mealie/routes/recipe/recipe_crud_routes.py
+++ b/mealie/routes/recipe/recipe_crud_routes.py
@@ -4,7 +4,9 @@ from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.recipe import Recipe, RecipeURLIn
-from mealie.services.image.image import delete_image, rename_image, scrape_image, write_image
+from mealie.services.events import create_recipe_event
+from mealie.services.image.image import scrape_image, write_image
+from mealie.services.recipe.media import check_assets, delete_assets
from mealie.services.scraper.scraper import create_from_url
from sqlalchemy.orm.session import Session
@@ -21,6 +23,8 @@ def create_from_json(
""" Takes in a JSON string and loads data into the database as a new entry"""
recipe: Recipe = db.recipes.create(session, data.dict())
+ create_recipe_event("Recipe Created", f"Recipe '{recipe.name}' created", session=session)
+
return recipe.slug
@@ -34,6 +38,7 @@ def parse_recipe_url(
recipe = create_from_url(url.url)
recipe: Recipe = db.recipes.create(session, recipe.dict())
+ create_recipe_event("Recipe Created (URL)", f"'{recipe.name}' by {current_user.full_name}", session=session)
return recipe.slug
@@ -57,8 +62,7 @@ def update_recipe(
recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict())
print(recipe.assets)
- if recipe_slug != recipe.slug:
- rename_image(original_slug=recipe_slug, new_slug=recipe.slug)
+ check_assets(original_slug=recipe_slug, recipe=recipe)
return recipe
@@ -75,8 +79,8 @@ def patch_recipe(
recipe: Recipe = db.recipes.patch(
session, recipe_slug, new_data=data.dict(exclude_unset=True, exclude_defaults=True)
)
- if recipe_slug != recipe.slug:
- rename_image(original_slug=recipe_slug, new_slug=recipe.slug)
+
+ check_assets(original_slug=recipe_slug, recipe=recipe)
return recipe
@@ -90,10 +94,10 @@ def delete_recipe(
""" Deletes a recipe by slug """
try:
- delete_data = db.recipes.delete(session, recipe_slug)
- delete_image(recipe_slug)
-
- return delete_data
+ recipe: Recipe = db.recipes.delete(session, recipe_slug)
+ delete_assets(recipe_slug=recipe_slug)
+ create_recipe_event("Recipe Deleted", f"'{recipe.name}' deleted by {current_user.full_name}", session=session)
+ return recipe
except Exception:
raise HTTPException(status.HTTP_400_BAD_REQUEST)
diff --git a/mealie/routes/recipe/recipe_assets.py b/mealie/routes/recipe/recipe_media.py
similarity index 73%
rename from mealie/routes/recipe/recipe_assets.py
rename to mealie/routes/recipe/recipe_media.py
index ebab8d246c27..b89605c72c82 100644
--- a/mealie/routes/recipe/recipe_assets.py
+++ b/mealie/routes/recipe/recipe_media.py
@@ -3,7 +3,6 @@ from enum import Enum
from fastapi import APIRouter, Depends, File, Form, HTTPException, status
from fastapi.datastructures import UploadFile
-from mealie.core.config import app_dirs
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
@@ -12,7 +11,7 @@ from slugify import slugify
from sqlalchemy.orm.session import Session
from starlette.responses import FileResponse
-router = APIRouter(prefix="/api/recipes", tags=["Recipe Media"])
+router = APIRouter(prefix="/api/recipes/media", tags=["Recipe Media"])
class ImageType(str, Enum):
@@ -21,25 +20,30 @@ class ImageType(str, Enum):
tiny = "tiny-original.webp"
-@router.get("/image/{recipe_slug}/{file_name}")
+@router.get("/{recipe_slug}/image/{file_name}")
async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original):
"""Takes in a recipe slug, returns the static image. This route is proxied in the docker image
and should not hit the API in production"""
- recipe_image = app_dirs.IMG_DIR.joinpath(recipe_slug, file_name.value)
+ recipe_image = Recipe(slug=recipe_slug).image_dir.joinpath(file_name.value)
+
if recipe_image:
return FileResponse(recipe_image)
else:
raise HTTPException(status.HTTP_404_NOT_FOUND)
-@router.get("/{recipe_slug}/asset")
-async def get_recipe_asset(recipe_slug, file_name: str):
+@router.get("/{recipe_slug}/assets/{file_name}")
+async def get_recipe_asset(recipe_slug: str, file_name: str):
""" Returns a recipe asset """
- file = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name)
- return FileResponse(file)
+ file = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name)
+
+ try:
+ return FileResponse(file)
+ except Exception:
+ raise HTTPException(status.HTTP_404_NOT_FOUND)
-@router.post("/{recipe_slug}/asset", response_model=RecipeAsset)
+@router.post("/{recipe_slug}/assets", response_model=RecipeAsset)
def upload_recipe_asset(
recipe_slug: str,
name: str = Form(...),
@@ -52,8 +56,7 @@ def upload_recipe_asset(
""" Upload a file to store as a recipe asset """
file_name = slugify(name) + "." + extension
asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name)
- dest = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name)
- dest.parent.mkdir(exist_ok=True, parents=True)
+ dest = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name)
with dest.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)
diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py
index 3398545d81b2..63d11dbdb9a7 100644
--- a/mealie/routes/users/crud.py
+++ b/mealie/routes/users/crud.py
@@ -1,6 +1,6 @@
import shutil
-from fastapi import APIRouter, Depends, File, UploadFile, status, HTTPException
+from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import FileResponse
from mealie.core import security
from mealie.core.config import app_dirs, settings
@@ -9,6 +9,7 @@ from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut
+from mealie.services.events import create_sign_up_event
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/users", tags=["Users"])
@@ -22,7 +23,7 @@ async def create_user(
):
new_user.password = get_password_hash(new_user.password)
-
+ create_sign_up_event("User Created", f"Created by {current_user.full_name}", session=session)
return db.users.create(session, new_user.dict())
diff --git a/mealie/routes/users/sign_up.py b/mealie/routes/users/sign_up.py
index c62d4f20414e..9d6bdaf798b1 100644
--- a/mealie/routes/users/sign_up.py
+++ b/mealie/routes/users/sign_up.py
@@ -1,14 +1,14 @@
import uuid
+from fastapi import APIRouter, Depends, HTTPException, status
from mealie.core.security import get_password_hash
from mealie.db.database import db
from mealie.db.db_setup import generate_session
-from fastapi import APIRouter, Depends
from mealie.routes.deps import get_current_user
from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken
from mealie.schema.user import UserIn, UserInDB
+from mealie.services.events import create_sign_up_event
from sqlalchemy.orm.session import Session
-from fastapi import HTTPException, status
router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"])
@@ -20,9 +20,7 @@ async def get_all_open_sign_ups(
):
""" Returns a list of open sign up links """
- all_sign_ups = db.sign_ups.get_all(session)
-
- return all_sign_ups
+ return db.sign_ups.get_all(session)
@router.post("", response_model=SignUpToken)
@@ -41,6 +39,7 @@ async def create_user_sign_up_key(
"name": key_data.name,
"admin": key_data.admin,
}
+ create_sign_up_event("Sign-up Token Created", f"Created by {current_user.full_name}", session=session)
return db.sign_ups.create(session, sign_up)
@@ -63,6 +62,7 @@ async def create_user_with_token(
db.users.create(session, new_user.dict())
# DeleteToken
+ create_sign_up_event("Sign-up Token Used", f"New User {new_user.full_name}", session=session)
db.sign_ups.delete(session, token)
diff --git a/mealie/schema/debug.py b/mealie/schema/about.py
similarity index 59%
rename from mealie/schema/debug.py
rename to mealie/schema/about.py
index d00c17719325..202264a97125 100644
--- a/mealie/schema/debug.py
+++ b/mealie/schema/about.py
@@ -1,7 +1,16 @@
from pathlib import Path
+
from fastapi_camelcase import CamelModel
+class AppStatistics(CamelModel):
+ total_recipes: int
+ total_users: int
+ total_groups: int
+ uncategorized_recipes: int
+ untagged_recipes: int
+
+
class AppInfo(CamelModel):
production: bool
version: str
@@ -11,5 +20,6 @@ class AppInfo(CamelModel):
class DebugInfo(AppInfo):
api_port: int
api_docs: bool
+ db_type: str
db_url: Path
default_group: str
diff --git a/mealie/schema/events.py b/mealie/schema/events.py
new file mode 100644
index 000000000000..5ee4e8a77510
--- /dev/null
+++ b/mealie/schema/events.py
@@ -0,0 +1,31 @@
+from datetime import datetime
+from enum import Enum
+from typing import Optional
+
+from fastapi_camelcase import CamelModel
+from pydantic import Field
+
+
+class EventCategory(str, Enum):
+ general = "general"
+ recipe = "recipe"
+ backup = "backup"
+ scheduled = "scheduled"
+ migration = "migration"
+ sign_up = "signup"
+
+
+class Event(CamelModel):
+ id: Optional[int]
+ title: str
+ text: str
+ time_stamp: datetime = Field(default_factory=datetime.now)
+ category: EventCategory = EventCategory.general
+
+ class Config:
+ orm_mode = True
+
+
+class EventsOut(CamelModel):
+ total: int
+ events: list[Event]
diff --git a/mealie/schema/recipe.py b/mealie/schema/recipe.py
index 1ff2aabf7116..debdec1d5cd4 100644
--- a/mealie/schema/recipe.py
+++ b/mealie/schema/recipe.py
@@ -1,7 +1,9 @@
import datetime
+from pathlib import Path
from typing import Any, Optional
from fastapi_camelcase import CamelModel
+from mealie.core.config import app_dirs
from mealie.db.models.recipe.recipe import RecipeModel
from pydantic import BaseModel, Field, validator
from pydantic.utils import GetterDict
@@ -58,8 +60,8 @@ class Nutrition(CamelModel):
class RecipeSummary(CamelModel):
id: Optional[int]
- name: str
- slug: Optional[str] = ""
+ name: Optional[str]
+ slug: str = ""
image: Optional[Any]
description: Optional[str]
@@ -98,6 +100,28 @@ class Recipe(RecipeSummary):
org_url: Optional[str] = Field(None, alias="orgURL")
extras: Optional[dict] = {}
+ @staticmethod
+ def directory_from_slug(slug) -> Path:
+ return app_dirs.RECIPE_DATA_DIR.joinpath(slug)
+
+ @property
+ def directory(self) -> Path:
+ dir = app_dirs.RECIPE_DATA_DIR.joinpath(self.slug)
+ dir.mkdir(exist_ok=True, parents=True)
+ return dir
+
+ @property
+ def asset_dir(self) -> Path:
+ dir = self.directory.joinpath("assets")
+ dir.mkdir(exist_ok=True, parents=True)
+ return dir
+
+ @property
+ def image_dir(self) -> Path:
+ dir = self.directory.joinpath("images")
+ dir.mkdir(exist_ok=True, parents=True)
+ return dir
+
class Config:
orm_mode = True
@@ -140,6 +164,8 @@ class Recipe(RecipeSummary):
@validator("slug", always=True, pre=True)
def validate_slug(slug: str, values):
+ if not values["name"]:
+ return slug
name: str = values["name"]
calc_slug: str = slugify(name)
diff --git a/mealie/services/backups/exports.py b/mealie/services/backups/exports.py
index afd9a735eb84..321dd9e04e3a 100644
--- a/mealie/services/backups/exports.py
+++ b/mealie/services/backups/exports.py
@@ -9,6 +9,7 @@ from mealie.core import root_logger
from mealie.core.config import app_dirs
from mealie.db.database import db
from mealie.db.db_setup import create_session
+from mealie.services.events import create_backup_event
from pathvalidate import sanitize_filename
from pydantic.main import BaseModel
@@ -32,7 +33,7 @@ class ExportDatabase:
export_tag = datetime.now().strftime("%Y-%b-%d")
self.main_dir = app_dirs.TEMP_DIR.joinpath(export_tag)
- self.img_dir = self.main_dir.joinpath("images")
+ self.recipes = self.main_dir.joinpath("recipes")
self.templates_dir = self.main_dir.joinpath("templates")
try:
@@ -43,7 +44,7 @@ class ExportDatabase:
required_dirs = [
self.main_dir,
- self.img_dir,
+ self.recipes,
self.templates_dir,
]
@@ -67,10 +68,10 @@ class ExportDatabase:
with open(out_file, "w") as f:
f.write(content)
- def export_images(self):
- shutil.copytree(app_dirs.IMG_DIR, self.img_dir, dirs_exist_ok=True)
+ def export_recipe_dirs(self):
+ shutil.copytree(app_dirs.RECIPE_DATA_DIR, self.recipes, dirs_exist_ok=True)
- def export_items(self, items: list[BaseModel], folder_name: str, export_list=True):
+ def export_items(self, items: list[BaseModel], folder_name: str, export_list=True, slug_folder=False):
items = [x.dict() for x in items]
out_dir = self.main_dir.joinpath(folder_name)
out_dir.mkdir(parents=True, exist_ok=True)
@@ -79,8 +80,10 @@ class ExportDatabase:
ExportDatabase._write_json_file(items, out_dir.joinpath(f"{folder_name}.json"))
else:
for item in items:
- filename = sanitize_filename(f"{item.get('name')}.json")
- ExportDatabase._write_json_file(item, out_dir.joinpath(filename))
+ final_dest = out_dir if not slug_folder else out_dir.joinpath(item.get("slug"))
+ final_dest.mkdir(exist_ok=True)
+ filename = sanitize_filename(f"{item.get('slug')}.json")
+ ExportDatabase._write_json_file(item, final_dest.joinpath(filename))
@staticmethod
def _write_json_file(data: Union[dict, list], out_file: Path):
@@ -121,9 +124,9 @@ def backup_all(
if export_recipes:
all_recipes = db.recipes.get_all(session)
- db_export.export_items(all_recipes, "recipes", export_list=False)
+ db_export.export_recipe_dirs()
+ db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True)
db_export.export_templates(all_recipes)
- db_export.export_images()
if export_settings:
all_settings = db.settings.get_all(session)
@@ -148,3 +151,5 @@ def auto_backup_job():
session = create_session()
backup_all(session=session, tag="Auto", templates=templates)
logger.info("Auto Backup Called")
+ create_backup_event("Automated Backup", "Automated backup created", session)
+ session.close()
diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py
index b561752bd6d8..864a21fb4aa9 100644
--- a/mealie/services/backups/imports.py
+++ b/mealie/services/backups/imports.py
@@ -2,7 +2,7 @@ import json
import shutil
import zipfile
from pathlib import Path
-from typing import Callable, List
+from typing import Callable
from mealie.core.config import app_dirs
from mealie.db.database import db
@@ -49,7 +49,7 @@ class ImportDatabase:
def import_recipes(self):
recipe_dir: Path = self.import_dir.joinpath("recipes")
imports = []
- successful_imports = []
+ successful_imports = {}
recipes = ImportDatabase.read_models_file(
file_path=recipe_dir, model=Recipe, single_file=False, migrate=ImportDatabase._recipe_migration
@@ -68,7 +68,7 @@ class ImportDatabase:
)
if import_status.status:
- successful_imports.append(recipe.slug)
+ successful_imports.update({recipe.slug: recipe})
imports.append(import_status)
@@ -105,15 +105,25 @@ class ImportDatabase:
return recipe_dict
- def _import_images(self, successful_imports: List[str]):
+ def _import_images(self, successful_imports: list[Recipe]):
image_dir = self.import_dir.joinpath("images")
- for image in image_dir.iterdir():
- if image.stem in successful_imports:
- if image.is_dir():
- dest = app_dirs.IMG_DIR.joinpath(image.stem)
- shutil.copytree(image, dest, dirs_exist_ok=True)
- if image.is_file():
- shutil.copy(image, app_dirs.IMG_DIR)
+
+ if image_dir.exists(): # Migrate from before v0.5.0
+ for image in image_dir.iterdir():
+ item: Recipe = successful_imports.get(image.stem)
+
+ if item:
+ dest_dir = item.image_dir
+
+ if image.is_dir():
+ shutil.copytree(image, dest_dir, dirs_exist_ok=True)
+
+ if image.is_file():
+ shutil.copy(image, dest_dir)
+
+ else:
+ recipe_dir = self.import_dir.joinpath("recipes")
+ shutil.copytree(recipe_dir, app_dirs.RECIPE_DATA_DIR, dirs_exist_ok=True)
minify.migrate_images()
@@ -227,7 +237,7 @@ class ImportDatabase:
return [model(**g) for g in file_data]
all_models = []
- for file in file_path.glob("*.json"):
+ for file in file_path.glob("**/*.json"):
with open(file, "r") as f:
file_data = json.loads(f.read())
diff --git a/mealie/services/events.py b/mealie/services/events.py
new file mode 100644
index 000000000000..b47edf5dc79a
--- /dev/null
+++ b/mealie/services/events.py
@@ -0,0 +1,40 @@
+from mealie.db.database import db
+from mealie.db.db_setup import create_session
+from mealie.schema.events import Event, EventCategory
+from sqlalchemy.orm.session import Session
+
+
+def save_event(title, text, category, session: Session):
+ event = Event(title=title, text=text, category=category)
+ session = session or create_session()
+ db.events.create(session, event.dict())
+
+
+def create_general_event(title, text, session=None):
+ category = EventCategory.general
+ save_event(title=title, text=text, category=category, session=session)
+
+
+def create_recipe_event(title, text, session=None):
+ category = EventCategory.recipe
+ save_event(title=title, text=text, category=category, session=session)
+
+
+def create_backup_event(title, text, session=None):
+ category = EventCategory.backup
+ save_event(title=title, text=text, category=category, session=session)
+
+
+def create_scheduled_event(title, text, session=None):
+ category = EventCategory.scheduled
+ save_event(title=title, text=text, category=category, session=session)
+
+
+def create_migration_event(title, text, session=None):
+ category = EventCategory.migration
+ save_event(title=title, text=text, category=category, session=session)
+
+
+def create_sign_up_event(title, text, session=None):
+ category = EventCategory.sign_up
+ save_event(title=title, text=text, category=category, session=session)
diff --git a/mealie/services/image/image.py b/mealie/services/image/image.py
index ed5f90ab0cb0..229b2d08dea2 100644
--- a/mealie/services/image/image.py
+++ b/mealie/services/image/image.py
@@ -4,7 +4,7 @@ from pathlib import Path
import requests
from mealie.core import root_logger
-from mealie.core.config import app_dirs
+from mealie.schema.recipe import Recipe
from mealie.services.image import minify
logger = root_logger.get_logger()
@@ -20,47 +20,11 @@ class ImageOptions:
IMG_OPTIONS = ImageOptions()
-def read_image(recipe_slug: str, image_type: str = "original") -> Path:
- """returns the path to the image file for the recipe base of image_type
-
- Args:
- recipe_slug (str): Recipe Slug
- image_type (str, optional): Glob Style Matcher "original*" | "min-original* | "tiny-original*"
-
- Returns:
- Path: [description]
- """
- recipe_slug = recipe_slug.split(".")[0] # Incase of File Name
- recipe_image_dir = app_dirs.IMG_DIR.joinpath(recipe_slug)
-
- for file in recipe_image_dir.glob(image_type):
- return file
-
- return None
-
-
-def rename_image(original_slug, new_slug) -> Path:
- current_path = app_dirs.IMG_DIR.joinpath(original_slug)
- new_path = app_dirs.IMG_DIR.joinpath(new_slug)
-
- try:
- new_path = current_path.rename(new_path)
- except FileNotFoundError:
- logger.error(f"Image Directory {original_slug} Doesn't Exist")
-
- return new_path
-
-
def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path:
- try:
- delete_image(recipe_slug)
- except Exception:
- pass
-
- image_dir = Path(app_dirs.IMG_DIR.joinpath(f"{recipe_slug}"))
- image_dir.mkdir(exist_ok=True, parents=True)
+ image_dir = Recipe(slug=recipe_slug).image_dir
extension = extension.replace(".", "")
image_path = image_dir.joinpath(f"original.{extension}")
+ image_path.unlink(missing_ok=True)
if isinstance(file_data, Path):
shutil.copy2(file_data, image_path)
@@ -77,12 +41,6 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path:
return image_path
-def delete_image(recipe_slug: str) -> str:
- recipe_slug = recipe_slug.split(".")[0]
- for file in app_dirs.IMG_DIR.glob(f"{recipe_slug}*"):
- return shutil.rmtree(file)
-
-
def scrape_image(image_url: str, slug: str) -> Path:
if isinstance(image_url, str): # Handles String Types
image_url = image_url
@@ -96,7 +54,7 @@ def scrape_image(image_url: str, slug: str) -> Path:
image_url = image_url.get("url")
filename = slug + "." + image_url.split(".")[-1]
- filename = app_dirs.IMG_DIR.joinpath(filename)
+ filename = Recipe(slug=slug).image_dir.joinpath(filename)
try:
r = requests.get(image_url, stream=True)
diff --git a/mealie/services/image/minify.py b/mealie/services/image/minify.py
index e33bbb38e106..9dddcb63bc86 100644
--- a/mealie/services/image/minify.py
+++ b/mealie/services/image/minify.py
@@ -4,10 +4,8 @@ from pathlib import Path
from mealie.core import root_logger
from mealie.core.config import app_dirs
-from mealie.db.database import db
-from mealie.db.db_setup import create_session
+from mealie.schema.recipe import Recipe
from PIL import Image
-from sqlalchemy.orm.session import Session
logger = root_logger.get_logger()
@@ -20,11 +18,7 @@ class ImageSizes:
def get_image_sizes(org_img: Path, min_img: Path, tiny_img: Path) -> ImageSizes:
- return ImageSizes(
- org=sizeof_fmt(org_img),
- min=sizeof_fmt(min_img),
- tiny=sizeof_fmt(tiny_img),
- )
+ return ImageSizes(org=sizeof_fmt(org_img), min=sizeof_fmt(min_img), tiny=sizeof_fmt(tiny_img))
def minify_image(image_file: Path) -> ImageSizes:
@@ -110,28 +104,9 @@ def move_all_images():
if new_file.is_file():
new_file.unlink()
image_file.rename(new_file)
-
-
-def validate_slugs_in_database(session: Session = None):
- def check_image_path(image_name: str, slug_path: str) -> bool:
- existing_path: Path = app_dirs.IMG_DIR.joinpath(image_name)
- slug_path: Path = app_dirs.IMG_DIR.joinpath(slug_path)
-
- if existing_path.is_dir():
- slug_path.rename(existing_path)
- else:
- logger.info("No Image Found")
-
- session = session or create_session()
- all_recipes = db.recipes.get_all(session)
-
- slugs_and_images = [(x.slug, x.image) for x in all_recipes]
-
- for slug, image in slugs_and_images:
- image_slug = image.split(".")[0] # Remove Extension
- if slug != image_slug:
- logger.info(f"{slug}, Doesn't Match '{image_slug}'")
- check_image_path(image, slug)
+ if image_file.is_dir():
+ slug = image_file.name
+ image_file.rename(Recipe(slug=slug).image_dir)
def migrate_images():
@@ -139,7 +114,7 @@ def migrate_images():
move_all_images()
- for image in app_dirs.IMG_DIR.glob("*/original.*"):
+ for image in app_dirs.RECIPE_DATA_DIR.glob("**/original.*"):
minify_image(image)
@@ -148,4 +123,3 @@ def migrate_images():
if __name__ == "__main__":
migrate_images()
- validate_slugs_in_database()
diff --git a/mealie/services/recipe/__init__.py b/mealie/services/recipe/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/mealie/services/recipe/media.py b/mealie/services/recipe/media.py
new file mode 100644
index 000000000000..9be89214329b
--- /dev/null
+++ b/mealie/services/recipe/media.py
@@ -0,0 +1,34 @@
+from pathlib import Path
+from shutil import copytree, rmtree
+
+from mealie.core.config import app_dirs
+from mealie.core.root_logger import get_logger
+from mealie.schema.recipe import Recipe
+
+logger = get_logger()
+
+
+def check_assets(original_slug, recipe: Recipe) -> None:
+ if original_slug != recipe.slug:
+ current_dir = app_dirs.RECIPE_DATA_DIR.joinpath(original_slug)
+
+ try:
+ copytree(current_dir, recipe.directory, dirs_exist_ok=True)
+
+ except FileNotFoundError:
+ logger.error(f"Recipe Directory not Found: {original_slug}")
+ logger.info(f"Renaming Recipe Directory: {original_slug} -> {recipe.slug}")
+
+ all_asset_files = [x.file_name for x in recipe.assets]
+ for file in recipe.asset_dir.iterdir():
+ file: Path
+ if file.is_dir():
+ continue
+ if file.name not in all_asset_files:
+ file.unlink()
+
+
+def delete_assets(recipe_slug):
+ recipe_dir = Recipe(slug=recipe_slug).directory
+ rmtree(recipe_dir, ignore_errors=True)
+ logger.info(f"Recipe Directory Removed: {recipe_slug}")
diff --git a/mealie/utils/post_webhooks.py b/mealie/utils/post_webhooks.py
index 2ccaa28c0b99..a9884c024e73 100644
--- a/mealie/utils/post_webhooks.py
+++ b/mealie/utils/post_webhooks.py
@@ -2,6 +2,7 @@ import requests
from mealie.db.database import db
from mealie.db.db_setup import create_session
from mealie.schema.user import GroupInDB
+from mealie.services.events import create_scheduled_event
from mealie.services.meal_services import get_todays_meal
from sqlalchemy.orm.session import Session
@@ -21,4 +22,6 @@ def post_webhooks(group: int, session: Session = None):
for url in group_settings.webhook_urls:
requests.post(url, json=todays_recipe.json())
+ create_scheduled_event("Meal Plan Webhook", f"Meal plan webhook executed for group '{group}'")
+
session.close()