Compare commits

..

344 Commits

Author SHA1 Message Date
Michael Genson
a7c8b33cca dev: Improve Docs Generation (#4402)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-10-21 15:01:40 +00:00
github-actions[bot]
674ad237f1 chore(auto): Update pre-commit hooks (#4406)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-10-21 14:37:39 +00:00
Hayden
8ecddb07ae chore(l10n): New Crowdin updates (#4405) 2024-10-21 09:06:48 +02:00
renovate[bot]
b86c01e405 chore(deps): update dependency coverage to v7.6.4 (#4403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-20 20:27:31 -05:00
renovate[bot]
124301823c chore(deps): update dependency mkdocs-material to v9.5.42 (#4398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-20 17:01:44 -05:00
Hayden
c784a64c44 chore(l10n): New Crowdin updates (#4397) 2024-10-20 17:23:15 +11:00
renovate[bot]
e79aeb9e8c fix(deps): update dependency pydantic-settings to v2.6.0 (#4383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-20 02:18:48 +00:00
renovate[bot]
85fe770be0 fix(deps): update dependency pillow-heif to ^0.20.0 (#4388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 21:04:34 -05:00
renovate[bot]
6f157b60a5 chore(deps): update dependency mypy to v1.12.1 (#4395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-20 01:49:18 +00:00
renovate[bot]
1e6bbc6699 fix(deps): update dependency orjson to v3.10.9 (#4394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-19 20:38:25 -05:00
Michael Genson
543a53cab4 fix: Bulk Update Owner Removes Some Recipe Data (#4393) 2024-10-19 15:36:34 -05:00
boc-the-git
a17529bd71 dev: Add highlights and new contributors to release template (#4391) 2024-10-19 13:33:33 +02:00
Michael Genson
1dc7b24146 feat: Change Recipe Owner (#4355)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-10-19 09:33:32 +00:00
Kuchenpirat
60ea83d737 dev: add internal dev section to release notes (#4390) 2024-10-19 09:20:47 +00:00
Hayden
8180aefc0b chore(l10n): New Crowdin updates (#4389) 2024-10-19 18:04:06 +11:00
Hayden
007c99c77a chore(l10n): New Crowdin updates (#4387) 2024-10-18 09:18:04 +02:00
renovate[bot]
292ff8313b chore(deps): update dependency ruff to ^0.7.0 (#4384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-17 22:15:03 +00:00
renovate[bot]
79b3308f15 chore(deps): update dependency psycopg2-binary to v2.9.10 (#4375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-17 17:03:53 -05:00
renovate[bot]
56a557fc82 fix(deps): update dependency openai to v1.52.0 (#4385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-17 16:49:51 -05:00
Michael Genson
b8e62ab8dd feat: Query Filter Builder for Cookbooks and Meal Plans (#4346) 2024-10-17 17:35:39 +02:00
renovate[bot]
2a9a6fa5e6 fix(deps): update dependency sqlalchemy to v2.0.36 (#4369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-17 11:09:09 +02:00
Michael Genson
79b36024a4 fix: Mealplans Disappearing/Can't be edited (#4379) 2024-10-17 08:45:50 +00:00
Hayden
c40d2d0486 chore(l10n): New Crowdin updates (#4382) 2024-10-17 08:01:45 +02:00
besque
8da08cdd60 docs: removed TRACE log level from backend-config.md (#4381) 2024-10-16 19:08:29 +00:00
Michael Genson
e47d171463 fix: Good data being deleted upon restore (#4376)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-10-16 14:56:07 +00:00
Carter
80caa5ffaf fix: Prevent login via credentials when Auth Method is Mealie (#4370) 2024-10-16 14:34:51 +00:00
renovate[bot]
03485ecc73 fix(deps): update dependency uvicorn to ^0.32.0 (#4367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-16 06:51:32 +00:00
Hayden
b3ad32ee31 chore(l10n): New Crowdin updates (#4371) 2024-10-16 06:38:12 +00:00
Michael Genson
6d89fe37ad fix: Prevent Bad Cookbook Names (#4364) 2024-10-15 17:54:58 +00:00
renovate[bot]
1af2473a72 fix(deps): update dependency pillow to v11 (#4366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-15 11:03:06 -05:00
renovate[bot]
77de9fee98 chore(deps): update dependency mkdocs-material to v9.5.41 (#4365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-15 17:46:58 +02:00
Michael Genson
cba381cb67 fix: Handle Data With Invalid User (#4325)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-10-14 15:10:55 +00:00
renovate[bot]
02791e294d chore(deps): update dependency mypy to v1.12.0 (#4362)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-14 14:20:03 +00:00
renovate[bot]
7f396ab483 fix(deps): update dependency fastapi to v0.115.2 (#4352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-14 09:09:56 -05:00
Hayden
0e299e98ee chore(l10n): New Crowdin updates (#4360) 2024-10-14 04:16:03 +00:00
renovate[bot]
4e839711eb chore(deps): update dependency coverage to v7.6.3 (#4359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-13 23:07:02 -05:00
Tom Brennan
02c0fe993b feat: adding the rest ofthe nutrition properties from schema.org (#4301) 2024-10-13 15:04:29 +02:00
Hayden
3aea229f2d chore(l10n): New Crowdin updates (#4356) 2024-10-12 22:58:28 -05:00
Hayden
d08b3d8943 chore(l10n): New Crowdin updates (#4351) 2024-10-12 09:35:35 +02:00
renovate[bot]
91353d6d7e chore(deps): update dependency coverage to v7.6.2 (#4338)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-12 01:14:55 +00:00
renovate[bot]
d12d3d12ef fix(deps): update dependency isodate to ^0.7.0 (#4330)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-11 20:04:22 -05:00
Michael Genson
e06572b7ca feat: User Tooltip (#4319) 2024-10-12 00:36:26 +00:00
Michael Genson
a2bdb02a7f fix: More Fault Tolerance with OpenAI Schemas (#4328)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-10-12 00:24:22 +00:00
Hayden
433336b8b4 chore(l10n): New Crowdin updates (#4344) 2024-10-11 19:10:20 -05:00
Aaron Echols
d50d4bee08 docs: update oidc-v2.md (#4347) 2024-10-10 14:33:57 -05:00
renovate[bot]
445f55812b fix(deps): update dependency uvicorn to v0.31.1 (#4343)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-10-10 14:00:44 +00:00
renovate[bot]
0e247f22f8 chore(deps): update dependency mkdocs-material to v9.5.40 (#4345)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-10 15:47:44 +02:00
Nicole Zeckner
4e4bc1b542 docs: Update rest call for home assistant (#4341) 2024-10-10 02:30:41 +00:00
Michael Genson
8379e3565f fix: Fix Locked User Reset (#4342) 2024-10-10 02:17:02 +00:00
Kuchenpirat
d36041fa75 chore: cancel running pull request jobs on new push (#4339)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-10-09 09:51:10 -05:00
Kuchenpirat
655122c390 fix: user creation without username or fullName (#4337) 2024-10-09 14:35:27 +00:00
github-actions[bot]
07e70b419d chore(auto): Update pre-commit hooks (#4321)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-10-09 16:21:01 +02:00
renovate[bot]
b4ebe4f9a6 chore(deps): update dependency pre-commit to v4.0.1 (#4333)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-09 13:55:36 +02:00
renovate[bot]
172160c862 fix(deps): update dependency openai to v1.51.2 (#4332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-09 13:33:11 +02:00
Brian Choromanski
01a43f28f2 feat: Added support for plural_name for foods (#4305) 2024-10-08 21:56:50 -05:00
Marc
00baa397dd feat: Add image source label to dockerfiles (#4331) 2024-10-08 09:38:06 -05:00
renovate[bot]
a2beab6cbb fix(deps): update dependency openai to v1.51.1 (#4326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-07 18:45:36 +02:00
Kuchenpirat
56bd68d824 fix: recipe scaling (#4324) 2024-10-07 16:31:49 +00:00
renovate[bot]
a559335bd7 chore(deps): update dependency pre-commit to v4 (#4318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-06 21:03:02 -05:00
Michael Genson
b0ed242ff2 fix: Strip Timezone from Timestamps in DB (#4310)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-10-06 06:30:30 +00:00
Hayden
b5c0104aba chore(l10n): New Crowdin updates (#4317) 2024-10-05 21:51:50 +00:00
Carter
5ed0ec029b feat: Add OIDC_CLIENT_SECRET and other changes for v2 (#4254)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-10-05 21:12:11 +00:00
renovate[bot]
4f1abcf4a3 chore(deps): update dependency ruff to v0.6.9 (#4315)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-04 18:45:59 +00:00
renovate[bot]
7140dcb188 fix(deps): update dependency recipe-scrapers to v15.2.1 (#4316)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-04 13:35:46 -05:00
renovate[bot]
8f229b0dde chore(deps): update dependency rich to v13.9.2 (#4314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-04 12:16:16 +00:00
Michael Genson
113347a6e5 docs: Fix Formatting (#4309)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-10-02 15:20:14 +00:00
Hayden
c904f2d818 chore(l10n): New Crowdin updates (#4308) 2024-10-02 15:07:54 +00:00
renovate[bot]
49fe6a6057 chore(deps): update dependency rich to v13.9.1 (#4299)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-02 19:51:51 +10:00
Brian Choromanski
fef3f1cee3 fix: GH Actions node deprecation (#4306) 2024-10-02 11:21:22 +10:00
renovate[bot]
fa32a6489c fix(deps): update dependency openai to v1.51.0 (#4303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-01 19:45:30 +00:00
Michael Genson
3d1b08779b fix: Shopping List Label Text Color (#4302) 2024-10-01 17:47:51 +02:00
Hayden
f1d56cad9c chore(l10n): New Crowdin updates (#4300) 2024-10-01 14:42:49 +00:00
Kuchenpirat
14dbd79c7f feat: refactor recipe scaling (#4298) 2024-10-01 16:31:04 +02:00
Kuchenpirat
1bd3d38dfc fix: scrape images as list (#4293) 2024-09-30 12:07:14 -05:00
Tom Brennan
49a392f4e2 fix: add setting to fix mypy pydantic warning on vs code dev container setup (#4292) 2024-09-30 16:34:22 +00:00
Michael Genson
4c1d855690 feat: Create Recipe From HTML or JSON (#4274)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-30 15:52:13 +00:00
renovate[bot]
edf420491f fix(deps): update dependency python-multipart to ^0.0.12 (#4284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 15:34:54 +00:00
Kuchenpirat
75bbe34ce5 docs: add note on recipe scaling to docs (#4287) 2024-09-30 15:25:37 +00:00
github-actions[bot]
743d52ef81 chore(auto): Update pre-commit hooks (#4285)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-09-30 15:16:28 +00:00
Hayden
e125d1a45a chore(l10n): New Crowdin updates (#4289) 2024-09-30 14:39:06 +00:00
renovate[bot]
df359a58a4 chore(deps): update dependency mkdocs-material to v9.5.39 (#4283)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-29 15:53:02 +02:00
renovate[bot]
73fafa9fb3 fix(deps): update dependency python-multipart to ^0.0.11 (#4281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-29 14:44:27 +10:00
Cody
28b0190648 feat: Shopping list UI overhaul - add wakelock (#4236)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-28 15:16:06 +00:00
boc-the-git
8fe1b0c123 fix: Hide rating on notes (#4278) 2024-09-28 15:05:14 +00:00
Hayden
30cf37effa chore(l10n): New Crowdin updates (#4280) 2024-09-28 15:57:01 +02:00
renovate[bot]
cd305cd47d fix(deps): update dependency uvicorn to ^0.31.0 (#4277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-28 10:24:52 +00:00
renovate[bot]
a9dcb1538a chore(deps): update dependency ruff to v0.6.8 (#4272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-28 20:13:04 +10:00
renovate[bot]
30699ac4cd fix(deps): update dependency openai to v1.50.2 (#4273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-28 19:52:17 +10:00
Michael Genson
4712994242 feat: Add Household Filter to Meal Plan Rules (#4231) 2024-09-27 14:06:45 +00:00
jlssmt
38502e82d4 feat: add regex to url before scraping (#4174)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-27 13:02:34 +02:00
Hayden
f8cd8b00a5 chore(l10n): New Crowdin updates (#4271) 2024-09-26 12:01:27 +00:00
renovate[bot]
8f2f4d45af chore(deps): update dependency mkdocs-material to v9.5.38 (#4270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-26 11:39:47 +02:00
renovate[bot]
291808b204 fix(deps): update dependency openai to v1.48.0 (#4269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-25 17:25:46 +00:00
Michael Genson
75166b5b0f docs: Update Docs for Households (#4266)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-25 17:30:49 +02:00
Hayden
f7608071d8 chore(l10n): New Crowdin updates (#4268) 2024-09-25 22:01:21 +10:00
renovate[bot]
64a1a652ca chore(deps): update dependency mkdocs-material to v9.5.37 (#4267) 2024-09-25 13:09:06 +02:00
Seamus Lowry
2053f29ff8 feat: sort labels by name,asc in shopping lists (#4253) 2024-09-24 21:33:30 +00:00
renovate[bot]
82737613b4 chore(deps): update dependency pylint to v3.3.1 (#4265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-24 17:44:42 +02:00
Cody
04dc593b16 feat: Shopping list UI overhaul - add label headings per category (#4235)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-24 16:29:06 +02:00
Hayden
d1e3b64a19 chore(l10n): New Crowdin updates (#4264) 2024-09-24 12:24:31 +02:00
renovate[bot]
7922e4d2c1 fix(deps): update dependency tzdata to v2024.2 (#4261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-23 21:42:44 +02:00
renovate[bot]
f393c05d6d fix(deps): update dependency openai to v1.47.1 (#4259)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-23 10:50:48 -05:00
renovate[bot]
eb640ac270 fix(deps): update dependency alembic to v1.13.3 (#4258)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-23 15:31:05 +00:00
renovate[bot]
02a36509b6 fix(deps): update dependency rapidfuzz to v3.10.0 (#4257)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-23 10:18:26 -05:00
github-actions[bot]
1d4ff66824 chore(auto): Update pre-commit hooks (#4255)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-09-23 11:49:46 +00:00
Hayden
13522a0402 chore(l10n): New Crowdin updates (#4256) 2024-09-23 12:31:17 +02:00
Michael Genson
ea1f727a8b feat: OpenAI Custom Headers/Params and Debug Page (#4227)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-23 11:04:36 +02:00
Michael Genson
7c274de778 feat: Filter Recipes By Household (and a ton of bug fixes) (#4207)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-22 14:59:20 +00:00
renovate[bot]
2a6922a85c chore(deps): update dependency ruff to v0.6.7 (#4249)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-21 22:28:49 -05:00
renovate[bot]
c4f753ee32 fix(deps): update dependency python-multipart to ^0.0.10 (#4248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-21 22:12:14 -05:00
bigcat2014
c774f626ee fix: Exclude additional sensitive information from mealie logs (#4218)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-09-21 22:07:47 +00:00
renovate[bot]
abc1174877 fix(deps): update dependency openai to v1.47.0 (#4245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-21 15:46:18 +02:00
renovate[bot]
f7a1ef597a chore(deps): update dependency mkdocs-material to v9.5.36 (#4247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-21 11:31:13 +02:00
renovate[bot]
12938f9cd5 chore(deps): update dependency pylint to v3.3.0 (#4243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-20 09:23:22 -05:00
Kuchenpirat
7f4e958198 fix: revert shopping list items - increased width (#4241) 2024-09-20 21:44:30 +10:00
renovate[bot]
67791e4d86 chore(deps): update dependency ruff to v0.6.6 (#4240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-20 04:23:00 +00:00
Michael Genson
ba363da251 chore: Optimize Loads on Queries (#4220)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-09-19 23:10:10 -05:00
Cody
e971efd809 feat: Shopping list UI overhaul - label sorted by default (#4238)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-19 22:24:46 +02:00
Cody
09b688cc22 feat: Shopping list UI overhaul - increase list item width (#4237)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-19 21:48:21 +02:00
renovate[bot]
22edec4d9a fix(deps): update dependency openai to v1.46.1 (#4234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-19 21:27:48 +02:00
Hayden
821766a6ae chore(l10n): New Crowdin updates (#4233) 2024-09-19 11:14:16 +02:00
renovate[bot]
96a2ccb5e4 chore(deps): update dependency mkdocs-material to v9.5.35 (#4229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-18 11:24:58 +02:00
Hayden
d41273592a chore(l10n): New Crowdin updates (#4230) 2024-09-18 10:24:26 +02:00
renovate[bot]
64273144d9 fix(deps): update dependency pydantic to v2.9.2 (#4225)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-18 03:34:28 +00:00
renovate[bot]
edb43a80a1 fix(deps): update dependency openai to v1.46.0 (#4226)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-18 03:21:38 +00:00
renovate[bot]
f736423fff fix(deps): update dependency fastapi to ^0.115.0 (#4228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-17 22:08:19 -05:00
Michael Genson
fd0257c1b8 feat: Additional Household Permissions (#4158)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-17 10:48:14 -05:00
renovate[bot]
b1820f9b23 fix(deps): update dependency openai to v1.45.1 (#4215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-17 08:13:13 +02:00
renovate[bot]
cff33cb15c fix(deps): update dependency sqlalchemy to v2.0.35 (#4221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-16 18:57:09 -05:00
Michael Genson
8778559a20 chore: Improve Alembic Migration Generation (#4192) 2024-09-16 13:52:12 +00:00
github-actions[bot]
77208384ed chore(auto): Update pre-commit hooks (#4213)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-09-16 10:20:25 +00:00
Michael Genson
dbbd662e7d feat: Allow Cookbooks To Share Names (#4186) 2024-09-15 11:42:58 +00:00
Michael Chisholm
abe4504640 fix(deps): Update dependencies for Python 3.12 (#4204) 2024-09-15 01:48:50 +00:00
Michael Genson
d8dbcac196 feat: Use Backend for Recipe Post Actions (#4163) 2024-09-14 14:59:36 +00:00
renovate[bot]
8bd26d2230 fix(deps): update dependency fastapi to v0.114.2 (#4206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-14 01:12:27 +00:00
renovate[bot]
400d251381 chore(deps): update dependency ruff to v0.6.5 (#4205)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-13 19:59:47 -05:00
renovate[bot]
7088bea90a fix(deps): update dependency openai to v1.45.0 (#4203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-13 19:38:52 -05:00
Hayden
0eb3e3f7ca chore(l10n): New Crowdin updates (#4201) 2024-09-12 09:49:08 -05:00
Kuchenpirat
6f1df3a95e feat: Reorder ShoppingListItemEditor (#4200) 2024-09-12 09:07:26 -05:00
renovate[bot]
c97053ef83 chore(deps): update dependency pydantic-to-typescript2 to v1.0.6 (#4199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-12 21:59:37 +10:00
Michael Genson
f796258529 fix: Broken Social Preview Links (#4183) 2024-09-12 09:43:23 +00:00
Michael Genson
f6cf58334f fix: Tweak recipe card height to be consistent (#4150)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-09-11 22:59:15 +00:00
Ryan Breen
dda6f297d8 docs: boolean value in docker-compose examples not deserializable in env variables (#4130)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-09-11 22:47:40 +00:00
boc-the-git
97c3135a43 feat: Change autolabeler regex to include : (#4196) 2024-09-11 14:03:17 +00:00
renovate[bot]
871160cb42 fix(deps): update dependency pydantic-settings to v2.5.2 (#4195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-11 10:12:35 +00:00
renovate[bot]
e77b9e972f fix(deps): update dependency fastapi to v0.114.1 (#4194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-11 20:01:23 +10:00
renovate[bot]
e1f382f8ba fix(deps): update dependency pydantic-settings to v2.5.0 (#4191)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-10 10:38:45 -05:00
renovate[bot]
baf78573f7 chore(deps): update dependency rich to v13.8.1 (#4190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-10 14:44:50 +00:00
renovate[bot]
e579017e6d chore(deps): update dependency pytest to v8.3.3 (#4189)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-10 14:32:50 +00:00
boc-the-git
589b226360 feat: Add autolabeler functionality for PRs (#4188) 2024-09-10 09:23:23 -05:00
renovate[bot]
98ac9cd290 fix(deps): update dependency openai to v1.44.1 (#4185)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-09 20:33:43 +00:00
Hayden
6253fdb3db chore(l10n): New Crowdin updates (#4181) 2024-09-09 14:23:07 +00:00
github-actions[bot]
7ff532429f chore(auto): Update pre-commit hooks (#4179)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-09-09 14:14:42 +00:00
renovate[bot]
a14a4bcf89 fix(deps): update dependency pydantic to v2.9.1 (#4180)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-09 20:57:20 +10:00
Hayden
12a4b16bde chore(l10n): New Crowdin updates (#4176) 2024-09-08 15:44:57 +02:00
Hayden
98f91351d5 chore(l10n): New Crowdin updates (#4171) 2024-09-07 15:37:51 +02:00
renovate[bot]
6a4a88371f fix(deps): update dependency openai to v1.44.0 (#4170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-07 03:09:50 +00:00
renovate[bot]
fc749f520a fix(deps): update dependency fastapi to ^0.114.0 (#4169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-06 21:57:57 -05:00
Vlad Shulcz
5b3be18fe2 feat: Added feature to limit mealplan data by date range (#4111)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-09-07 02:37:42 +00:00
Michael Genson
0aaa40432d docs: Fix OpenAI docs link (#4168) 2024-09-07 01:35:10 +00:00
paaff
6ecdb39a2d docs: add keep screen alive entry (#4116)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-09-06 19:03:05 +00:00
Michael Genson
29c5944d92 docs: Added OpenAI Page to mkdocs (#4167) 2024-09-06 08:38:55 -08:00
renovate[bot]
68ec3f7e42 fix(deps): update dependency sqlalchemy to v2.0.34 (#4156)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-06 09:32:28 -05:00
Michael Genson
59e5094669 docs: OpenAI Documentation (#4165) 2024-09-06 12:09:19 +00:00
renovate[bot]
ed5cd2a0c5 fix(deps): update dependency openai to v1.43.1 (#4164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-05 20:07:24 +00:00
renovate[bot]
bb2badc526 fix(deps): update dependency pydantic to v2.9.0 (#4162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-05 14:54:14 -05:00
renovate[bot]
41df1d67e0 fix(deps): update dependency fastapi to ^0.113.0 (#4161)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-05 16:44:13 +00:00
renovate[bot]
4d3edbfccb chore(deps): update dependency ruff to v0.6.4 (#4160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-05 11:31:45 -05:00
renovate[bot]
979a977d77 fix(deps): update dependency fastapi to v0.112.3 (#4159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-05 09:08:13 -05:00
boc-the-git
b2c8585ec5 chore: Set "documentation" label on PR (#4153) 2024-09-04 14:06:30 +00:00
boc-the-git
70d5f1a918 chore: Set 'chore' label on PR (#4154) 2024-09-04 08:58:07 -05:00
Hayden
82169fc316 chore(l10n): New Crowdin updates (#4145) 2024-09-04 11:15:35 +00:00
boc-the-git
c2fb4d3fa7 feat: Automate release drafting (#4147) 2024-09-04 08:08:39 +10:00
renovate[bot]
4734fae891 fix(deps): update dependency sqlalchemy to v2.0.33 (#4149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 13:56:25 -05:00
Brian Choromanski
787b826aa1 fix: PWA respects orientation lock (#4143) 2024-09-03 00:01:21 +00:00
renovate[bot]
22cdb7305b fix(deps): update dependency apprise to v1.9.0 (#4141)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 20:35:14 +00:00
renovate[bot]
107fc138fd fix(deps): update dependency rapidfuzz to v3.9.7 (#4142)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 15:23:43 -05:00
renovate[bot]
e9285881f0 chore(deps): update dependency mkdocs-material to v9.5.34 (#4133)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 15:12:06 -05:00
github-actions[bot]
e0b5d76278 fix(auto): Update pre-commit hooks (#4138)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-09-02 10:23:48 +00:00
Michael Genson
9acf9ec27c feat: Cross-Household Recipes (#4089) 2024-09-01 02:54:10 +00:00
Hayden
7ef2e91ecf chore(l10n): New Crowdin updates (#4132) 2024-08-31 15:24:12 -08:00
renovate[bot]
220c383ccb chore(deps): update dependency pylint to v3.2.7 (#4131)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-31 15:23:58 -08:00
Michael Genson
a3f474e088 feat: Change OpenAI Image Format to JPG (#4117) 2024-08-30 21:24:25 +00:00
Hayden
2ad6e1b198 chore(l10n): New Crowdin updates (#4127) 2024-08-30 13:02:06 -05:00
ipitio
8b6d8e60eb docs: Update ghcr pulls badge endpoint (#4124) 2024-08-30 12:14:36 +00:00
Michael Genson
9e392cbdad fix: Update Task Template for V2 (#4078) 2024-08-30 10:25:22 +00:00
renovate[bot]
29f21a0cd8 fix(deps): update dependency openai to v1.43.0 (#4122)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-30 10:14:42 +00:00
Michael Genson
22e9c8b462 fix: Docs Reference Old Build (#4096) 2024-08-30 10:03:44 +00:00
Hayden
eae474d9b9 chore(l10n): New Crowdin updates (#4121) 2024-08-29 17:45:15 +00:00
renovate[bot]
296a1a036c chore(deps): update dependency ruff to v0.6.3 (#4120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-29 11:22:08 -05:00
Hayden
0ca1b6e57c chore(l10n): New Crowdin updates (#4115) 2024-08-28 17:24:26 +00:00
Hayden
9636fc82f6 chore(l10n): New Crowdin updates (#4113) 2024-08-27 11:51:54 -05:00
renovate[bot]
e4aeb01acb fix(deps): update dependency httpx to v0.27.2 (#4112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-27 14:47:44 +00:00
renovate[bot]
4c6357e8dc chore(deps): update dependency rich to v13.8.0 (#4105)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-26 13:08:07 -05:00
renovate[bot]
91a7e09569 fix(deps): update dependency authlib to v1.3.2 (#4102)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-26 17:37:34 +00:00
renovate[bot]
2c2c07feb2 chore(deps): update dependency mypy to v1.11.2 (#4093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-26 17:25:04 +00:00
renovate[bot]
c49610ec74 fix(deps): update dependency fastapi to v0.112.2 (#4092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-26 12:10:32 -05:00
github-actions[bot]
587002c523 fix(auto): Update pre-commit hooks (#4101)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-08-26 16:57:27 +00:00
Hayden
7466e5d7f5 chore(l10n): New Crowdin updates (#4104) 2024-08-26 11:43:16 -05:00
renovate[bot]
795f5ea4f1 fix(deps): update dependency recipe-scrapers to v15.1.0 (#4103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-26 16:19:45 +00:00
Hayden
9b6f323b6f chore(l10n): New Crowdin updates (#4098) 2024-08-25 15:30:20 -05:00
boc-the-git
c688114e15 docs: Tidy unicorn workers description (#4095) 2024-08-25 03:05:50 +00:00
Hayden
042ac6bfa5 chore(l10n): New Crowdin updates (#4090) 2024-08-24 11:14:33 -05:00
Arsène Reymond
67dc0d7066 fix: Image height in home screen mobile format (#4088) 2024-08-23 13:26:12 -05:00
Hayden
22057cad19 chore(l10n): New Crowdin updates (#4087) 2024-08-23 16:09:56 +00:00
renovate[bot]
ce13242f61 chore(deps): update dependency mkdocs-material to v9.5.33 (#4085)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-23 14:40:47 +00:00
renovate[bot]
7dd66a52d5 chore(deps): update dependency ruff to v0.6.2 (#4075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-23 14:27:23 +00:00
renovate[bot]
6ed2b99188 chore(deps): update dependency pytest-asyncio to ^0.24.0 (#4074)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-23 14:15:09 +00:00
Sach
29f88eade0 fix: Recipe with a single 'space' as the title returns "404 page not found" (#4065)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-08-23 14:03:10 +00:00
Hayden
a63cdf8534 chore(l10n): New Crowdin updates (#4076) 2024-08-22 15:28:57 +00:00
Michael Genson
eb170cc7e5 feat: Add Households to Mealie (#3970) 2024-08-22 10:14:32 -05:00
Hayden
0c29cef17d chore(l10n): New Crowdin updates (#4068)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-08-22 10:01:22 +02:00
VolumeData21
f86d4d5d8d fix: removed version line from docker compose yml files (#4072) 2024-08-22 09:42:41 +02:00
github-actions[bot]
c721533557 docs(auto): Update image tag, for release v1.12.0 (#4064)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-08-21 11:27:25 +00:00
renovate[bot]
0d06494bbf fix(deps): update dependency openai to v1.42.0 (#4060)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 20:47:39 +10:00
Hayden
9c04950948 chore(l10n): New Crowdin updates (#4058) 2024-08-20 15:10:27 +00:00
vahtos
34c37a2bee docs: fix import bookmarklet trailing slashes (#4057) 2024-08-20 14:54:15 +00:00
Brian Choromanski
2cd1e0ad37 feat: Seed support for plural units (#3933)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-08-20 14:33:20 +00:00
renovate[bot]
828afe6674 fix(deps): update dependency openai to v1.41.1 (#4056)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-19 21:38:42 -05:00
renovate[bot]
6a705b7352 chore(deps): update dependency mkdocs-material to v9.5.32 (#4053)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-19 20:36:49 -05:00
github-actions[bot]
3fa931466e fix(auto): Update pre-commit hooks (#4052)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-08-20 00:04:42 +00:00
renovate[bot]
4ba8269ff1 fix(deps): update dependency openai to v1.41.0 (#4042)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-19 18:54:19 -05:00
Hayden
581310b57d chore(l10n): New Crowdin updates (#4054) 2024-08-19 21:16:45 +02:00
renovate[bot]
ace18ab4aa fix(deps): update dependency fastapi to v0.112.1 (#4041)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-18 21:18:11 +00:00
renovate[bot]
abae973454 chore(deps): update dependency ruff to ^0.6.0 (#4039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-18 16:05:45 -05:00
Ikko Eltociear Ashimine
08bc29ca8a docs: update home-assistant.md (#4049) 2024-08-18 18:59:35 +02:00
Hayden
9d7ef1837b chore(l10n): New Crowdin updates (#4047) 2024-08-18 17:45:57 +02:00
Michael Genson
8a15f400e1 feat: Import + Translate recipe images with OpenAI (#3974)
Co-authored-by: Johan Lindell <johan@lindell.me>
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-08-18 08:07:01 +10:00
Hayden
3d921cb677 chore(l10n): New Crowdin updates (#4043) 2024-08-16 15:14:28 +02:00
renovate[bot]
f0e065efa4 fix(deps): update dependency openai to v1.40.6 (#4023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-15 12:02:27 +00:00
renovate[bot]
d06589b31b fix(deps): update dependency uvicorn to v0.30.6 (#4030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-15 21:53:06 +10:00
Hayden
3405bc4eb6 chore(l10n): New Crowdin updates (#4034) 2024-08-13 22:35:10 +00:00
Hayden
a75eb07a47 chore(l10n): New Crowdin updates (#4025) 2024-08-12 22:11:52 +00:00
Christian Clauss
432914e310 fix: Lint Python code with ruff (#3799) 2024-08-12 15:09:30 +00:00
Andrew Morgan
65ece35966 fix: Don't load from secrets dir if nonexistent or inaccessible (#4002)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-08-12 14:55:32 +00:00
renovate[bot]
f11af52d30 fix(deps): update dependency lxml to v5.3.0 (#4015)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-12 10:56:25 +00:00
renovate[bot]
b4da5c3d5a fix(deps): update dependency openai to v1.40.3 (#4012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-12 10:43:59 +00:00
renovate[bot]
a4e416cabc fix(deps): update dependency orjson to v3.10.7 (#4009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-12 10:33:29 +00:00
github-actions[bot]
26173704aa fix(auto): Update pre-commit hooks (#4020)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-08-12 10:24:15 +00:00
Hayden
5876c1ecf7 chore(l10n): New Crowdin updates (#4019) 2024-08-11 17:09:17 -05:00
Hayden
c4a339ed36 chore(l10n): New Crowdin updates (#4016) 2024-08-11 00:49:18 +02:00
Hayden
2967eca819 chore(l10n): New Crowdin updates (#4011) 2024-08-10 09:28:46 +02:00
boc-the-git
66b19eecfb feat: If there's only one shopping list, navigate directly to it (#3958) 2024-08-09 14:11:54 +00:00
renovate[bot]
37d93d4e4b chore(deps): update dependency ruff to v0.5.7 (#4005)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-08-09 10:57:38 +00:00
renovate[bot]
656d46e9cb fix(deps): update dependency openai to v1.40.2 (#4008)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 20:47:16 +10:00
Hayden
169d659b72 chore(l10n): New Crowdin updates (#4007) 2024-08-08 23:36:04 +02:00
TheSuperBeaver
f92a9afbe5 feat: Added fr-BE (#4004) 2024-08-08 09:50:14 -05:00
renovate[bot]
d53f81cdfb fix(deps): update dependency openai to v1.40.1 (#4001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-07 13:17:57 -05:00
renovate[bot]
bd4f858ba7 fix(deps): update dependency pyyaml to v6.0.2 (#3996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-06 22:53:25 -05:00
renovate[bot]
ce3a95f38a fix(deps): update dependency rapidfuzz to v3.9.6 (#3997)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-06 22:11:10 -05:00
Hayden
e3ffa03ffd chore(l10n): New Crowdin updates (#3995) 2024-08-06 16:07:42 -05:00
renovate[bot]
79910deb8a fix(deps): update dependency openai to v1.40.0 (#3994)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-06 17:43:07 +00:00
Michael Genson
e0c532ab94 fix: Bump tzdata 2024.1 (#3993) 2024-08-06 09:29:34 -05:00
renovate[bot]
73c09ab138 fix(deps): update dependency openai to v1.39.0 (#3989)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 16:46:18 -05:00
renovate[bot]
7edf0ee3cc fix(deps): update dependency tzdata to v2024 (#3990)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 20:40:05 +00:00
renovate[bot]
48381fe897 fix(deps): update dependency sqlalchemy to v2.0.32 (#3988)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 15:27:10 -05:00
github-actions[bot]
e68300037c fix(auto): Update pre-commit hooks (#3987)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-08-05 15:04:43 +00:00
renovate[bot]
c643f24a72 chore(deps): update dependency coverage to v7.6.1 (#3985)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 09:54:14 -05:00
renovate[bot]
7820ddc8f7 fix(deps): update dependency uvicorn to v0.30.5 (#3979)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 17:46:21 +00:00
renovate[bot]
ca3cb2447c fix(deps): update dependency fastapi to ^0.112.0 (#3977)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 17:35:19 +00:00
renovate[bot]
a55fdb634d chore(deps): update dependency mkdocs-material to v9.5.31 (#3978)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 17:23:35 +00:00
renovate[bot]
8b0c607712 chore(deps): update dependency ruff to v0.5.6 (#3980)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 17:12:25 +00:00
renovate[bot]
8c990a5dd2 fix(deps): update dependency openai to v1.38.0 (#3981)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 16:59:42 +00:00
renovate[bot]
abf5cf0116 fix(deps): update dependency pyjwt to v2.9.0 (#3973)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 11:25:35 -05:00
renovate[bot]
84069bf9df fix(deps): update dependency openai to v1.37.2 (#3976)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 09:04:11 +02:00
Hayden
059e5b7ea2 chore(l10n): New Crowdin updates (#3975) 2024-08-02 08:48:16 +02:00
Michael Genson
3677d04b56 fix: Make recipe scraper cleaner more fault tolerant (#3967)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-08-01 08:33:46 +02:00
renovate[bot]
05c034fca2 fix(deps): update dependency uvicorn to v0.30.4 (#3971)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-31 21:02:44 -05:00
Kuchenpirat
17d1cd26dc fix: recipe clean_time function missing translator argument on recursion (#3969) 2024-07-31 17:22:30 +00:00
github-actions[bot]
c9bbae6f77 docs(auto): Update image tag, for release v1.11.0 (#3965)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-07-31 18:44:37 +02:00
renovate[bot]
a41ad8c6ed chore(deps): update dependency mypy to v1.11.1 (#3963)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 19:26:12 -05:00
João Antunes
9c38c89c44 Update swag.md (#3961) 2024-07-30 21:19:18 +00:00
renovate[bot]
998440d064 fix(deps): update dependency pydantic-settings to v2.4.0 (#3960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 17:14:12 +00:00
renovate[bot]
b01d12c377 chore(deps): update dependency pre-commit to v3.8.0 (#3954)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 12:02:42 -05:00
renovate[bot]
1dee574a08 fix(deps): update dependency pillow-heif to ^0.18.0 (#3949)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 11:40:42 -05:00
renovate[bot]
257c4461a3 fix(deps): update dependency rapidfuzz to v3.9.5 (#3959)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 11:12:17 -05:00
renovate[bot]
01f4257190 fix(deps): update dependency apprise to v1.8.1 (#3942)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 16:00:07 +00:00
renovate[bot]
d7b7dd6c83 fix(deps): update dependency recipe-scrapers to v15 (#3955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
2024-07-30 17:48:47 +02:00
github-actions[bot]
23c2eab682 fix(auto): Update pre-commit hooks (#3957)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-07-29 10:41:26 +00:00
Michael Genson
def346d16d fix: Reduce search tolerance on organizers page (#3950) 2024-07-28 04:23:26 +00:00
tyme-dev
cc324b29ae fix: Homepage icon and text update (#3922)
Co-authored-by: Timothy Pace <Timothy@timothy.dev>
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-07-28 04:08:34 +00:00
Michael Genson
9d58f9b266 fix: Offline Shopping List Fixes V2 - Electric Boogaloo (#3837)
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
2024-07-27 21:25:58 -05:00
renovate[bot]
30b2776f3c fix(deps): update dependency openai to v1.37.1 (#3943)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-26 12:27:34 +02:00
Hayden
60d23d0686 chore(l10n): New Crowdin updates (#3944) 2024-07-26 10:15:33 +00:00
Hayden
edf649dea6 fix: prevent postgres credentials leak (#3895)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-07-25 20:27:50 +00:00
renovate[bot]
29b4a3cd22 chore(deps): update dependency ruff to v0.5.5 (#3940)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-25 20:07:09 +00:00
renovate[bot]
f3a5148628 fix(deps): update dependency bcrypt to v4.2.0 (#3928)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-25 11:55:26 -08:00
renovate[bot]
33abd777e0 chore(deps): update dependency pytest to v8.3.2 (#3939)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-25 11:22:12 +00:00
Hayden
739055caf6 chore(l10n): New Crowdin updates (#3938) 2024-07-25 21:10:07 +10:00
Kuchenpirat
8c29bd3439 fix: task py:migrate description (#3932) 2024-07-23 15:43:21 +00:00
renovate[bot]
2c4d0b692b chore(deps): update dependency mkdocs-material to v9.5.30 (#3930)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-23 17:31:01 +02:00
Brian Choromanski
946b79b77a feat: PWA Additions (#3896)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-07-23 17:04:15 +02:00
Hayden
cd154d09b2 chore(l10n): New Crowdin updates (#3929) 2024-07-23 07:26:34 +10:00
github-actions[bot]
236c930b54 fix(auto): Update pre-commit hooks (#3925)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-07-22 18:08:24 +00:00
renovate[bot]
980c847e36 fix(deps): update dependency openai to v1.37.0 (#3927)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-22 12:58:29 -05:00
renovate[bot]
91700771e6 chore(deps): update dependency pylint to v3.2.6 (#3923)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-21 22:02:55 +00:00
Hayden
abb6ad5fd0 chore(l10n): New Crowdin updates (#3921) 2024-07-21 16:50:51 -05:00
renovate[bot]
ac7af02f77 chore(deps): update dependency pytest to v8.3.1 (#3916)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 22:39:57 -05:00
Hayden
525b398687 chore(l10n): New Crowdin updates (#3917) 2024-07-21 03:08:27 +00:00
renovate[bot]
fafc836ccc chore(deps): update dependency ruff to v0.5.4 (#3915)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 21:58:11 -05:00
renovate[bot]
c617b829e5 fix(deps): update dependency openai to v1.36.1 (#3919)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 21:42:13 -05:00
Michael Genson
5b1e827d45 fix: Convert Daily Schedule Time to UTC (#3914) 2024-07-20 21:57:02 +00:00
boc-the-git
e33b62be2a docs: Change allow signup to false (#3913) 2024-07-20 10:38:57 -05:00
Arsène Reymond
60c33b499c feat: Internationalize sent emails (#3818) 2024-07-20 10:32:24 +00:00
Hayden
c205dff523 chore(l10n): New Crowdin updates (#3911) 2024-07-20 10:19:38 +00:00
renovate[bot]
ce69899c4b fix(deps): update dependency uvicorn to v0.30.3 (#3912)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 20:09:31 +10:00
renovate[bot]
a4183e3453 chore(deps): update dependency mypy to v1.11.0 (#3910)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-19 17:27:56 -05:00
renovate[bot]
ab39408a24 fix(deps): update dependency openai to v1.36.0 (#3909)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-19 12:09:20 -05:00
boc-the-git
8c6c98483c docs: Remove duplicated "step 2" (#3908) 2024-07-19 14:32:40 +02:00
renovate[bot]
ae095ab572 chore(deps): update dependency ruff to v0.5.3 (#3905)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 16:58:55 -05:00
renovate[bot]
65356bc21a fix(deps): update dependency openai to v1.35.15 (#3906)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 22:42:34 +02:00
renovate[bot]
3aed5de3fc chore(deps): update dependency pytest-asyncio to v0.23.8 (#3901)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-17 18:29:24 -05:00
renovate[bot]
a4e9e54dae fix(deps): update dependency fastapi to v0.111.1 (#3891)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-17 09:51:28 -05:00
renovate[bot]
8f698e437e fix(deps): update dependency recipe-scrapers to v14.58.0 (#3894)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-17 11:34:48 +00:00
boc-the-git
ab0d36825a fix: Create directory used for Docker Secrets (#3888) 2024-07-17 06:24:53 -05:00
renovate[bot]
4e2f6c57f1 fix(deps): update dependency openai to v1.35.14 (#3897)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 09:10:37 +02:00
renovate[bot]
de4cb8ba83 chore(deps): update dependency mkdocs-material to v9.5.29 (#3889)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-15 10:35:31 +00:00
github-actions[bot]
375f43c596 fix(auto): Update pre-commit hooks (#3893)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-07-15 10:26:27 +00:00
renovate[bot]
3034945e7e chore(deps): update dependency ruff to v0.5.2 (#3890)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-15 20:16:07 +10:00
Hayden
8e5effa532 chore(l10n): New Crowdin updates (#3887) 2024-07-13 15:40:41 +02:00
Litchi Pi
3b81d3b18a fix: Use env variable to get alembic config file in exporter (#3882)
Signed-off-by: Litchi Pi <litchi.pi@proton.me>
2024-07-12 12:18:06 +00:00
Michael Genson
d0f8b5773d fix: Bump other version numbers in GH workflow (#3840) 2024-07-12 11:30:22 +00:00
renovate[bot]
14910162dc chore(deps): update dependency coverage to v7.6.0 (#3884)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-12 21:00:35 +10:00
Hayden
035f780d27 chore(l10n): New Crowdin updates (#3886) 2024-07-11 22:22:37 +02:00
renovate[bot]
f10161ee92 fix(deps): update dependency recipe-scrapers to v14.57.1 (#3883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 09:56:19 -05:00
Michael Genson
b1a100a8c5 feat: Push On Hand Items to Bottom Of Add To Shopping List Dialog (#3862)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-07-10 20:41:15 +00:00
Hayden
7db39d32d1 chore(l10n): New Crowdin updates (#3878) 2024-07-10 15:24:34 -05:00
renovate[bot]
10921f9a64 fix(deps): update dependency openai to v1.35.13 (#3877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 13:48:02 -05:00
renovate[bot]
ba1c44172e fix(deps): update dependency tzdata to v2024 (#3825)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 17:01:27 +00:00
zeskeertwee
fd2dc15a15 fix: Follow redirects during scraping (#3875) 2024-07-10 16:49:13 +00:00
renovate[bot]
47124488bb fix(deps): update dependency pydantic to v2.8.2 (#3844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 10:10:21 -05:00
Marcus Noble
6e680c972a feat: Show recipe tags on mobile view and meal plan (#3864)
Signed-off-by: Marcus Noble <github@marcusnoble.co.uk>
2024-07-10 09:43:33 +00:00
renovate[bot]
1fd2eb37ae fix(deps): update dependency openai to v1.35.12 (#3873)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 19:33:45 +10:00
Hayden
923a59791a chore(l10n): New Crowdin updates (#3869) 2024-07-09 10:12:16 +00:00
Carter
1fcc2c755a fix: Add a default value of list when a user's group is None (#3872) 2024-07-08 21:13:04 -05:00
Michael Genson
d5f7a883df fix: Make Mealie Timezone-Aware (#3847)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2024-07-08 21:12:20 +00:00
Michael Genson
17f9eef551 docs: Update API Docs (#3856) 2024-07-08 10:01:31 +00:00
Elijah Mock
ca1ab33291 docs: Fix FAQ typos (#3866) 2024-07-07 19:11:53 -05:00
Michael Genson
6e6ae80c46 fix: Restore Webhook Test Functionality (#3857)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-07-06 19:10:01 +00:00
Hayden
aa6e109162 chore(l10n): New Crowdin updates (#3860) 2024-07-06 21:01:58 +02:00
renovate[bot]
a6e4b778c1 fix(deps): update dependency openai to v1.35.10 (#3843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-05 19:16:40 +00:00
github-actions[bot]
31c7cb7906 docs(auto): Update image tag, for release v1.10.2 (#3851)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-07-05 19:07:22 +00:00
Michael Genson
d954b5cf48 fix: Minor Typo (#3855)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
2024-07-05 18:58:52 +00:00
renovate[bot]
e5c2f5570f chore(deps): update dependency ruff to v0.5.1 (#3854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-05 10:43:22 -05:00
769 changed files with 56315 additions and 19128 deletions

View File

@@ -38,3 +38,6 @@ RUN apt-get update \
libwebp-dev \
libsasl2-dev libldap2-dev libssl-dev \
gnupg gnupg2 gnupg1
# create directory used for Docker Secrets
RUN mkdir -p /run/secrets

View File

@@ -25,8 +25,10 @@
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"mypy.runUsingActiveInterpreter": true
},
"extensions": [
"charliermarsh.ruff",
"dbaeumer.vscode-eslint",
"matangover.mypy",
"ms-python.black-formatter",

View File

@@ -4,7 +4,7 @@ description: "CONTRIBUTORS ONLY: Submit a Task that needs to be completed"
title: "[Task] - TASK DESCRIPTION"
labels:
- task
- v1
- v2
body:
- type: markdown
attributes:

View File

@@ -6,6 +6,7 @@
- `fix: `
- `docs: `
- `chore: `
- `dev:`
If a section of the PR template does not apply to this PR, then delete that section.
@@ -27,10 +28,11 @@ _(REQUIRED)_
Delete any of the following that do not apply:
-->
- bug
- cleanup
- documentation
- feature
- bug
- documentation
- cleanup
- dev (Internal development)
## What this PR does / why we need it:

88
.github/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
---
name-template: "v$RESOLVED_VERSION"
tag-template: "v$RESOLVED_VERSION"
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
sort-direction: ascending
categories:
- title: "🚨 Breaking changes"
labels:
- "breaking-change"
- "major"
- title: "✨ New features"
labels:
- "feature"
- "minor"
- title: "🐛 Bug fixes"
labels:
- "bugfix"
- title: "🧰 Maintenance"
collapse-after: 3
labels:
- "ci"
- "chore"
- "l10n"
- title: "📚 Documentation"
labels:
- "documentation"
- title: "🔨 Internal development"
labels:
- "dev"
- title: "⬆️ Dependency updates"
collapse-after: 3
labels:
- "dependencies"
version-resolver:
major:
labels:
- "major"
- "breaking-change"
minor:
labels:
- "minor"
- "feature"
patch:
labels:
- "bugfix"
- "chore"
- "ci"
- "dependencies"
- "documentation"
- "l10n"
- "dev"
default: patch
template: |
# 🍴🍴🍴🍴🍴🍴
## 🎉 Highlights
- Highlight 1
- Highlight 2
$CHANGES
## 🙏 New Contributors
!!! Need to source this from GitHub's auto generated release notes !!!
# 🍴🍴🍴🍴🍴🍴
autolabeler:
- label: 'feature'
title:
- '/feat/i'
- label: 'bugfix'
title:
- '/fix:/i'
- label: 'documentation'
title:
- '/docs:/i'
- label: 'chore'
title:
- '/chore:/i'
- label: 'dev'
title:
- '/dev:/i'

View File

@@ -14,10 +14,9 @@ name: "CodeQL"
on:
push:
branches: [ "mealie-next" ]
pull_request:
branches: [ "mealie-next" ]
schedule:
- cron: '36 9 * * 3'
workflow_call:
jobs:
analyze:
@@ -45,7 +44,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -1,8 +1,7 @@
name: E2E Tests
on:
pull_request:
branches:
- mealie-next
workflow_call:
jobs:
test:
timeout-minutes: 60
@@ -11,8 +10,8 @@ jobs:
run:
working-directory: ./tests/e2e
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'yarn'

View File

@@ -36,7 +36,7 @@ jobs:
# Steps
steps:
- name: Install Task
uses: arduino/setup-task@v1
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -45,7 +45,7 @@ jobs:
uses: actions/checkout@v4
- name: Set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.10"
@@ -57,7 +57,7 @@ jobs:
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}

View File

@@ -22,7 +22,7 @@ jobs:
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache node_modules 📦
uses: actions/cache@v3.3.2
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -60,7 +60,7 @@ jobs:
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache node_modules 📦
uses: actions/cache@v3.3.2
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -26,6 +26,6 @@ jobs:
output: "trivy-results.sarif"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: "trivy-results.sarif"

View File

@@ -20,10 +20,11 @@ jobs:
# Configure which types are allowed (newline-delimited).
# Default: https://github.com/commitizen/conventional-commit-types
types: |
fix
feat
fix
docs
chore
dev
# Configure which scopes are allowed (newline-delimited).
# These are regex patterns auto-wrapped in `^ $`.
scopes: |

View File

@@ -5,6 +5,10 @@ on:
branches:
- mealie-next
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
pull-request-lint:
name: "Lint PR"
@@ -21,3 +25,15 @@ jobs:
container-scanning:
name: "Trivy Container Scanning"
uses: ./.github/workflows/partial-trivy-container-scanning.yml
end-to-end:
name: "End-to-End Tests"
uses: ./.github/workflows/e2e.yml
code-ql:
name: "CodeQL"
uses: ./.github/workflows/codeql.yml
permissions:
actions: read
contents: read
security-events: write

30
.github/workflows/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
---
name: Release Drafter
on:
push:
branches:
- mealie-next
# pull_request event is required for autolabeler
pull_request:
types: [opened, labeled, unlabeled, reopened, synchronize]
# pull_request_target event is required for autolabeler to support PRs from forks
pull_request_target:
types: [opened, labeled, unlabeled, reopened, synchronize]
workflow_dispatch:
jobs:
update_release_draft:
permissions:
# write permission is required to create a github release
contents: write
# write permission is required for autolabeler
# otherwise, read permission is required at least
pull-requests: write
name: ✏️ Draft release
runs-on: ubuntu-latest
steps:
- name: 🚀 Run Release Drafter
uses: release-drafter/release-drafter@v6.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -61,10 +61,16 @@ jobs:
- name: Checkout 🛎
uses: actions/checkout@v4
- name: Extract Version From Tag Name
run: echo "VERSION_NUM=$(echo ${{ github.event.release.tag_name }} | sed 's/^v//')" >> $GITHUB_ENV
- name: Modify version strings
run: |
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/sqlite.md
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:${{ github.event.release.tag_name }}/' docs/docs/documentation/getting-started/installation/postgres.md
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/installation-checklist.md
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/sqlite.md
sed -i 's/:v[0-9]*.[0-9]*.[0-9]*/:v${{ env.VERSION_NUM }}/' docs/docs/documentation/getting-started/installation/postgres.md
sed -i 's/^version = "[^"]*"/version = "${{ env.VERSION_NUM }}"/' pyproject.toml
sed -i 's/^\s*"version": "[^"]*"/"version": "${{ env.VERSION_NUM }}"/' frontend/package.json
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
@@ -74,6 +80,8 @@ jobs:
with:
commit-message: "Update image tag, for release ${{ github.event.release.tag_name }}"
branch: "docs/newrelease-update-version-${{ github.event.release.tag_name }}"
labels: |
documentation
delete-branch: true
base: mealie-next
title: "docs(auto): Update image tag, for release ${{ github.event.release.tag_name }}"

34
.github/workflows/scheduled-checks.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Scheduled Checks
on:
schedule:
# Every monday at 7 AM
- cron: 0 7 * * 1
jobs:
update:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout 🛎
uses: actions/checkout@v4
- name: Update pre-commit Hooks
uses: vrslev/pre-commit-autoupdate@v1.0.0
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
# This doesn't currently work for us because it creates the PR but the workflows don't run.
# TODO: Provide a personal access token as a parameter here, that solves that problem.
# https://github.com/peter-evans/create-pull-request
with:
commit-message: "Update pre-commit hooks"
branch: "fix/update-pre-commit-hooks"
labels: |
chore
delete-branch: true
base: mealie-next
title: "chore(auto): Update pre-commit hooks"
body: "Auto-generated by `.github/workflows/scheduled-checks.yml`"

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-yaml
exclude: "mkdocs.yml"
@@ -12,6 +12,7 @@ repos:
exclude: ^tests/data/
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.5.0
rev: v0.7.0
hooks:
- id: ruff
- id: ruff-format

View File

@@ -90,7 +90,7 @@ Thanks to Depot for providing build instances for our Docker image builds.
[contributors-shield]: https://img.shields.io/github/contributors/mealie-recipes/mealie.svg?style=flat-square
[docker-pull]: https://img.shields.io/docker/pulls/hkotel/mealie?style=flat-square
[docker-url]: https://hub.docker.com/r/hkotel/mealie
[ghcr-pulls]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fipitio%2Fghcr-pulls%2Fmaster%2Findex.json&query=%24%5B%3F(%40.owner%3D%3D%22mealie-recipes%22%20%26%26%20%40.repo%3D%3D%22mealie%22%20%26%26%20%40.image%3D%3D%22mealie%22)%5D.pulls&style=flat-square&label=ghcr%20pulls
[ghcr-pulls]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fipitio.github.io%2Fbackage%2Fmealie-recipes%2Fmealie%2Fmealie.json&query=%24.downloads&style=flat-square&label=ghcr%20pulls
[ghcr-url]: https://github.com/mealie-recipes/mealie/pkgs/container/mealie
[contributors-url]: https://github.com/mealie-recipes/mealie/graphs/contributors
[stars-shield]: https://img.shields.io/github/stars/mealie-recipes/mealie.svg?style=flat-square

View File

@@ -148,7 +148,7 @@ tasks:
- poetry run python mealie/app.py
py:migrate:
desc: generates a new database migration file e.g. task py:migrate "add new column"
desc: generates a new database migration file e.g. task py:migrate -- "add new column"
cmds:
- poetry run alembic revision --autogenerate -m "{{ .CLI_ARGS }}"
- task: py:format

View File

@@ -1,4 +1,6 @@
from sqlalchemy import engine_from_config, pool
from typing import Any
import sqlalchemy as sa
import mealie.db.models._all_models # noqa: F401
from alembic import context
@@ -29,6 +31,28 @@ if not settings.DB_URL:
config.set_main_option("sqlalchemy.url", settings.DB_URL.replace("%", "%%"))
def include_object(object: Any, name: str, type_: str, reflected: bool, compare_to: Any):
# skip dropping food/unit unique constraints; they are defined manually so alembic doesn't see them
# see: revision dded3119c1fe
if type_ == "unique_constraint" and name == "ingredient_foods_name_group_id_key" and compare_to is None:
return False
if type_ == "unique_constraint" and name == "ingredient_units_name_group_id_key" and compare_to is None:
return False
# skip changing the quantity column in recipes_ingredients; it's a float on postgres, but an integer on sqlite
# see: revision 263dd6707191
if (
type_ == "column"
and name == "quantity"
and object.table.name == "recipes_ingredients"
and hasattr(compare_to, "type")
and isinstance(compare_to.type, sa.Integer)
):
return False
return True
def run_migrations_offline():
"""Run migrations in 'offline' mode.
@@ -60,15 +84,19 @@ def run_migrations_online():
and associate a connection with the context.
"""
connectable = engine_from_config(
connectable = sa.engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
poolclass=sa.pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata, user_module_prefix="mealie.db.migration_types."
connection=connection,
target_metadata=target_metadata,
user_module_prefix="mealie.db.migration_types.",
render_as_batch=True,
include_object=include_object,
)
with context.begin_transaction():

View File

@@ -9,13 +9,15 @@ import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
${imports if imports else ""}
% if imports:
${imports}
% endif
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
down_revision: str | None = ${repr(down_revision)}
branch_labels: str | tuple[str, ...] | None = ${repr(branch_labels)}
depends_on: str | tuple[str, ...] | None = ${repr(depends_on)}
def upgrade():

View File

@@ -14,9 +14,9 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "6b0f5f32d602"
down_revision = None
branch_labels = None
depends_on = None
down_revision: str | None = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Adapted from https://improveandrepeat.com/2021/09/python-friday-87-handling-pre-existing-tables-with-alembic-and-sqlalchemy/

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "263dd6707191"
down_revision = "6b0f5f32d602"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "f1a2dbee5fe9"
down_revision = "263dd6707191"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "59eb59135381"
down_revision = "f1a2dbee5fe9"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "09dfc897ad62"
down_revision = "59eb59135381"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "ab0bae02578f"
down_revision = "09dfc897ad62"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "f30cf048c228"
down_revision = "ab0bae02578f"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "188374910655"
down_revision = "f30cf048c228"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "089bfa50d0ed"
down_revision = "188374910655"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "44e8d670719d"
down_revision = "089bfa50d0ed"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "2ea7a807915c"
down_revision = "44e8d670719d"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "1923519381ad"
down_revision = "2ea7a807915c"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "167eb69066ad"
down_revision = "1923519381ad"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "165d943c64ee"
down_revision = "167eb69066ad"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -11,8 +11,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "ff5f73b01a7a"
down_revision = "165d943c64ee"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -6,16 +6,13 @@ Create Date: 2023-02-10 21:18:32.405130
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "16160bf731a0"
down_revision = "ff5f73b01a7a"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -11,15 +11,14 @@ from sqlalchemy import orm, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from text_unidecode import unidecode
import mealie.db.migration_types
from alembic import op
from mealie.db.models._model_utils import GUID
from mealie.db.models._model_utils.guid import GUID
# revision identifiers, used by Alembic.
revision = "5ab195a474eb"
down_revision = "16160bf731a0"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
class SqlAlchemyBase(DeclarativeBase):

View File

@@ -13,14 +13,32 @@ from sqlalchemy import orm
import mealie.db.migration_types
from alembic import op
from mealie.db.models.group.shopping_list import ShoppingList
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models._model_utils.guid import GUID
# revision identifiers, used by Alembic.
revision = "b04a08da2108"
down_revision = "5ab195a474eb"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass
class ShoppingList(SqlAlchemyBase):
__tablename__ = "shopping_lists"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
class MultiPurposeLabel(SqlAlchemyBase):
__tablename__ = "multi_purpose_labels"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
def populate_shopping_lists_multi_purpose_labels(

View File

@@ -8,14 +8,13 @@ Create Date: 2023-02-22 21:45:52.900964
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "38514b39a824"
down_revision = "b04a08da2108"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@@ -6,18 +6,13 @@ Create Date: 2023-04-13 06:47:04.617131
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
import alembic.context as context
from mealie.core.config import get_app_settings
# revision identifiers, used by Alembic.
revision = "b3dbb554ba53"
down_revision = "38514b39a824"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def get_db_type():

View File

@@ -16,8 +16,8 @@ from mealie.db.models.group.group import Group
# revision identifiers, used by Alembic.
revision = "04ac51cbe9a4"
down_revision = "b3dbb554ba53"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_group_slugs(session: Session):

View File

@@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "1825b5225403"
down_revision = "04ac51cbe9a4"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -11,8 +11,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "bcfdad6b7355"
down_revision = "1825b5225403"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -15,8 +15,8 @@ from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUn
# revision identifiers, used by Alembic.
revision = "0341b154f79a"
down_revision = "bcfdad6b7355"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_normalized_fields():

View File

@@ -12,19 +12,40 @@ from typing import Any
import sqlalchemy as sa
from pydantic import UUID4
from sqlalchemy import orm
from sqlalchemy.orm import Session, load_only
from alembic import op
from mealie.db.models._model_base import SqlAlchemyBase
from mealie.db.models.group.shopping_list import ShoppingListItem
from mealie.db.models._model_utils.guid import GUID
from mealie.db.models.labels import MultiPurposeLabel
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel, RecipeIngredientModel
# revision identifiers, used by Alembic.
revision = "dded3119c1fe"
down_revision = "0341b154f79a"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass
class ShoppingList(SqlAlchemyBase):
__tablename__ = "shopping_lists"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
group_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("groups.id"), nullable=False, index=True)
class ShoppingListItem(SqlAlchemyBase):
__tablename__ = "shopping_list_items"
id: orm.Mapped[GUID] = orm.mapped_column(GUID, primary_key=True, default=GUID.generate)
food_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("ingredient_foods.id"))
unit_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("ingredient_units.id"))
label_id: orm.Mapped[GUID] = orm.mapped_column(GUID, sa.ForeignKey("multi_purpose_labels.id"))
@dataclass
@@ -42,7 +63,7 @@ def _is_postgres():
return op.get_context().dialect.name == "postgresql"
def _get_duplicates(session: Session, model: SqlAlchemyBase) -> defaultdict[str, list]:
def _get_duplicates(session: Session, model: orm.DeclarativeBase) -> defaultdict[str, list]:
duplicate_map: defaultdict[str, list] = defaultdict(list)
query = session.execute(sa.text(f"SELECT id, group_id, name FROM {model.__tablename__}"))

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "ba1e4a6cfe99"
down_revision = "dded3119c1fe"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -20,8 +20,8 @@ logger = get_logger()
# revision identifiers, used by Alembic.
revision = "2298bb460ffd"
down_revision = "ba1e4a6cfe99"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
@@ -66,7 +66,7 @@ def populate_shopping_list_users():
user_id = find_user_id_for_group(group_id)
if user_id:
session.execute(
sa.text(f"UPDATE shopping_lists SET user_id=:user_id WHERE id=:id").bindparams(
sa.text("UPDATE shopping_lists SET user_id=:user_id WHERE id=:id").bindparams(
user_id=user_id, id=list_id
)
)
@@ -74,7 +74,7 @@ def populate_shopping_list_users():
logger.warning(
f"No user found for shopping list {list_id} with group {group_id}; deleting shopping list"
)
session.execute(sa.text(f"DELETE FROM shopping_lists WHERE id=:id").bindparams(id=list_id))
session.execute(sa.text("DELETE FROM shopping_lists WHERE id=:id").bindparams(id=list_id))
def upgrade():

View File

@@ -6,16 +6,13 @@ Create Date: 2024-03-10 05:08:32.397027
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "09aba125b57a"
down_revision = "2298bb460ffd"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@@ -6,7 +6,7 @@ Create Date: 2024-03-18 02:28:15.896959
"""
from datetime import datetime
from datetime import datetime, timezone
from textwrap import dedent
from typing import Any
from uuid import uuid4
@@ -20,8 +20,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "d7c6efd2de42"
down_revision = "09aba125b57a"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
@@ -32,9 +32,9 @@ def new_user_rating(user_id: Any, recipe_id: Any, rating: float | None = None, i
if is_postgres():
id = str(uuid4())
else:
id = "%.32x" % uuid4().int
id = "%.32x" % uuid4().int # noqa: UP031
now = datetime.now().isoformat()
now = datetime.now(timezone.utc).isoformat()
return {
"id": id,
"user_id": user_id,
@@ -202,8 +202,6 @@ def downgrade():
)
op.drop_index(op.f("ix_recipes_rating"), table_name="recipes")
op.alter_column("recipes", "rating", existing_type=sa.Float(), type_=sa.INTEGER(), existing_nullable=True)
op.create_unique_constraint("ingredient_units_name_group_id_key", "ingredient_units", ["name", "group_id"])
op.create_unique_constraint("ingredient_foods_name_group_id_key", "ingredient_foods", ["name", "group_id"])
op.create_table(
"users_to_favorites",
sa.Column("user_id", sa.CHAR(length=32), nullable=True),

View File

@@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "7788478a0338"
down_revision = "d7c6efd2de42"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@@ -11,12 +11,11 @@ from sqlalchemy import orm
from alembic import op
# revision identifiers, used by Alembic.
revision = "32d69327997b"
down_revision = "7788478a0338"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@@ -0,0 +1,321 @@
"""add households
Revision ID: feecc8ffb956
Revises: 32d69327997b
Create Date: 2024-07-12 16:16:29.973929
"""
from datetime import datetime, timezone
from textwrap import dedent
from typing import Any
from uuid import uuid4
import sqlalchemy as sa
from slugify import slugify
from sqlalchemy import orm
import mealie.db.migration_types
from alembic import op
from mealie.core.config import get_app_settings
# revision identifiers, used by Alembic.
revision = "feecc8ffb956"
down_revision = "32d69327997b"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
settings = get_app_settings()
def is_postgres():
return op.get_context().dialect.name == "postgresql"
def generate_id() -> str:
"""See GUID.convert_value_to_guid"""
val = uuid4()
if is_postgres():
return str(val)
else:
return f"{val.int:032x}"
def dedupe_cookbook_slugs():
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
sql = sa.text(
dedent(
"""
SELECT slug, group_id, COUNT(*)
FROM cookbooks
GROUP BY slug, group_id
HAVING COUNT(*) > 1
"""
)
)
rows = session.execute(sql).fetchall()
for slug, group_id, _ in rows:
sql = sa.text(
dedent(
"""
SELECT id
FROM cookbooks
WHERE slug = :slug AND group_id = :group_id
ORDER BY id
"""
)
)
cookbook_ids = session.execute(sql, {"slug": slug, "group_id": group_id}).fetchall()
for i, (cookbook_id,) in enumerate(cookbook_ids):
if i == 0:
continue
sql = sa.text(
dedent(
"""
UPDATE cookbooks
SET slug = :slug || '-' || :i
WHERE id = :id
"""
)
)
session.execute(sql, {"slug": slug, "i": i, "id": cookbook_id})
def create_household(session: orm.Session, group_id: str) -> str:
# create/insert household
household_id = generate_id()
timestamp = datetime.now(timezone.utc).isoformat()
household_data = {
"id": household_id,
"name": settings.DEFAULT_HOUSEHOLD,
"slug": slugify(settings.DEFAULT_HOUSEHOLD),
"group_id": group_id,
"created_at": timestamp,
"update_at": timestamp,
}
columns = ", ".join(household_data.keys())
placeholders = ", ".join(f":{key}" for key in household_data.keys())
sql_statement = f"INSERT INTO households ({columns}) VALUES ({placeholders})"
session.execute(sa.text(sql_statement), household_data)
# fetch group preferences so we can copy them over to household preferences
migrated_field_defaults = {
"private_group": True, # this is renamed later
"first_day_of_week": 0,
"recipe_public": True,
"recipe_show_nutrition": False,
"recipe_show_assets": False,
"recipe_landscape_view": False,
"recipe_disable_comments": False,
"recipe_disable_amount": True,
}
sql_statement = (
f"SELECT {', '.join(migrated_field_defaults.keys())} FROM group_preferences WHERE group_id = :group_id"
)
group_preferences = session.execute(sa.text(sql_statement), {"group_id": group_id}).fetchone()
# build preferences data
if group_preferences:
preferences_data: dict[str, Any] = {}
for i, (field, default_value) in enumerate(migrated_field_defaults.items()):
value = group_preferences[i]
preferences_data[field] = value if value is not None else default_value
else:
preferences_data = migrated_field_defaults
preferences_data["id"] = generate_id()
preferences_data["household_id"] = household_id
preferences_data["created_at"] = timestamp
preferences_data["update_at"] = timestamp
preferences_data["private_household"] = preferences_data.pop("private_group")
# insert preferences data
columns = ", ".join(preferences_data.keys())
placeholders = ", ".join(f":{key}" for key in preferences_data.keys())
sql_statement = f"INSERT INTO household_preferences ({columns}) VALUES ({placeholders})"
session.execute(sa.text(sql_statement), preferences_data)
return household_id
def create_households_for_groups() -> dict[str, str]:
bind = op.get_bind()
session = orm.Session(bind=bind)
group_id_household_id_map: dict[str, str] = {}
with session:
rows = session.execute(sa.text("SELECT id FROM groups")).fetchall()
for row in rows:
group_id = row[0]
group_id_household_id_map[group_id] = create_household(session, group_id)
return group_id_household_id_map
def _do_assignment(session: orm.Session, table: str, group_id: str, household_id: str):
sql = sa.text(
dedent(
f"""
UPDATE {table}
SET household_id = :household_id
WHERE group_id = :group_id
""",
)
)
session.execute(sql, {"group_id": group_id, "household_id": household_id})
def assign_households(group_id_household_id_map: dict[str, str]):
tables = [
"cookbooks",
"group_events_notifiers",
"group_meal_plan_rules",
"invite_tokens",
"recipe_actions",
"users",
"webhook_urls",
]
bind = op.get_bind()
session = orm.Session(bind=bind)
with session:
for table in tables:
for group_id, household_id in group_id_household_id_map.items():
_do_assignment(session, table, group_id, household_id)
def populate_household_data():
group_id_household_id_map = create_households_for_groups()
assign_households(group_id_household_id_map)
def upgrade():
dedupe_cookbook_slugs()
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"households",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column("slug", sa.String(), nullable=True),
sa.Column("group_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["group_id"],
["groups.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("group_id", "name", name="household_name_group_id_key"),
sa.UniqueConstraint("group_id", "slug", name="household_slug_group_id_key"),
)
op.create_index(op.f("ix_households_created_at"), "households", ["created_at"], unique=False)
op.create_index(op.f("ix_households_group_id"), "households", ["group_id"], unique=False)
op.create_index(op.f("ix_households_name"), "households", ["name"], unique=False)
op.create_index(op.f("ix_households_slug"), "households", ["slug"], unique=False)
op.create_table(
"household_preferences",
sa.Column("id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=False),
sa.Column("private_household", sa.Boolean(), nullable=True),
sa.Column("first_day_of_week", sa.Integer(), nullable=True),
sa.Column("recipe_public", sa.Boolean(), nullable=True),
sa.Column("recipe_show_nutrition", sa.Boolean(), nullable=True),
sa.Column("recipe_show_assets", sa.Boolean(), nullable=True),
sa.Column("recipe_landscape_view", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_comments", sa.Boolean(), nullable=True),
sa.Column("recipe_disable_amount", sa.Boolean(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.Column("update_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["household_id"],
["households.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_household_preferences_created_at"), "household_preferences", ["created_at"], unique=False)
op.create_index(
op.f("ix_household_preferences_household_id"), "household_preferences", ["household_id"], unique=False
)
with op.batch_alter_table("cookbooks") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_cookbooks_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_cookbooks_household_id", "households", ["household_id"], ["id"])
# not directly related to households, but important for frontend routes
batch_op.create_unique_constraint("cookbook_slug_group_id_key", ["slug", "group_id"])
with op.batch_alter_table("group_events_notifiers") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_group_events_notifiers_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_group_events_notifiers_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("group_meal_plan_rules") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_group_meal_plan_rules_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_group_meal_plan_rules_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("invite_tokens") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_invite_tokens_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_invite_tokens_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("recipe_actions") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_recipe_actions_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_recipe_actions_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("users") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_users_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_users_household_id", "households", ["household_id"], ["id"])
with op.batch_alter_table("webhook_urls") as batch_op:
batch_op.add_column(sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_index(op.f("ix_webhook_urls_household_id"), ["household_id"], unique=False)
batch_op.create_foreign_key("fk_webhook_urls_household_id", "households", ["household_id"], ["id"])
# ### end Alembic commands ###
populate_household_data()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, "webhook_urls", type_="foreignkey")
op.drop_index(op.f("ix_webhook_urls_household_id"), table_name="webhook_urls")
op.drop_column("webhook_urls", "household_id")
op.drop_constraint(None, "users", type_="foreignkey")
op.drop_index(op.f("ix_users_household_id"), table_name="users")
op.drop_column("users", "household_id")
op.drop_constraint(None, "recipe_actions", type_="foreignkey")
op.drop_index(op.f("ix_recipe_actions_household_id"), table_name="recipe_actions")
op.drop_column("recipe_actions", "household_id")
op.drop_constraint(None, "invite_tokens", type_="foreignkey")
op.drop_index(op.f("ix_invite_tokens_household_id"), table_name="invite_tokens")
op.drop_column("invite_tokens", "household_id")
op.drop_constraint(None, "group_meal_plan_rules", type_="foreignkey")
op.drop_index(op.f("ix_group_meal_plan_rules_household_id"), table_name="group_meal_plan_rules")
op.drop_column("group_meal_plan_rules", "household_id")
op.drop_constraint(None, "group_events_notifiers", type_="foreignkey")
op.drop_index(op.f("ix_group_events_notifiers_household_id"), table_name="group_events_notifiers")
op.drop_column("group_events_notifiers", "household_id")
op.drop_constraint(None, "cookbooks", type_="foreignkey")
op.drop_index(op.f("ix_cookbooks_household_id"), table_name="cookbooks")
op.drop_column("cookbooks", "household_id")
op.drop_constraint("cookbook_slug_group_id_key", "cookbooks", type_="unique")
op.drop_index(op.f("ix_household_preferences_household_id"), table_name="household_preferences")
op.drop_index(op.f("ix_household_preferences_created_at"), table_name="household_preferences")
op.drop_table("household_preferences")
op.drop_index(op.f("ix_households_slug"), table_name="households")
op.drop_index(op.f("ix_households_name"), table_name="households")
op.drop_index(op.f("ix_households_group_id"), table_name="households")
op.drop_index(op.f("ix_households_created_at"), table_name="households")
op.drop_table("households")
# ### end Alembic commands ###

View File

@@ -0,0 +1,75 @@
"""added household recipe lock setting and household management user permission
Revision ID: be568e39ffdf
Revises: feecc8ffb956
Create Date: 2024-09-02 21:39:49.210355
"""
from textwrap import dedent
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "be568e39ffdf"
down_revision = "feecc8ffb956"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_defaults():
if op.get_context().dialect.name == "postgresql":
TRUE = "TRUE"
FALSE = "FALSE"
else:
TRUE = "1"
FALSE = "0"
op.execute(
dedent(
f"""
UPDATE household_preferences
SET lock_recipe_edits_from_other_households = {TRUE}
WHERE lock_recipe_edits_from_other_households IS NULL
"""
)
)
op.execute(
dedent(
f"""
UPDATE users
SET can_manage_household = {FALSE}
WHERE can_manage_household IS NULL AND admin = {FALSE}
"""
)
)
op.execute(
dedent(
f"""
UPDATE users
SET can_manage_household = {TRUE}
WHERE can_manage_household IS NULL AND admin = {TRUE}
"""
)
)
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"household_preferences",
sa.Column("lock_recipe_edits_from_other_households", sa.Boolean(), nullable=True),
)
op.add_column("users", sa.Column("can_manage_household", sa.Boolean(), nullable=True))
# ### end Alembic commands ###
populate_defaults()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("users", "can_manage_household")
op.drop_column("household_preferences", "lock_recipe_edits_from_other_households")
# ### end Alembic commands ###

View File

@@ -0,0 +1,53 @@
"""add households filter to meal plans
Revision ID: 1fe4bd37ccc8
Revises: be568e39ffdf
Create Date: 2024-09-18 14:52:55.831540
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "1fe4bd37ccc8"
down_revision: str | None = "be568e39ffdf"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"plan_rules_to_households",
sa.Column("group_plan_rule_id", mealie.db.migration_types.GUID(), nullable=True),
sa.Column("household_id", mealie.db.migration_types.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["group_plan_rule_id"],
["group_meal_plan_rules.id"],
),
sa.ForeignKeyConstraint(
["household_id"],
["households.id"],
),
sa.UniqueConstraint("group_plan_rule_id", "household_id", name="group_plan_rule_id_household_id_key"),
)
with op.batch_alter_table("plan_rules_to_households", schema=None) as batch_op:
batch_op.create_index(
batch_op.f("ix_plan_rules_to_households_group_plan_rule_id"), ["group_plan_rule_id"], unique=False
)
batch_op.create_index(batch_op.f("ix_plan_rules_to_households_household_id"), ["household_id"], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("plan_rules_to_households", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_plan_rules_to_households_household_id"))
batch_op.drop_index(batch_op.f("ix_plan_rules_to_households_group_plan_rule_id"))
op.drop_table("plan_rules_to_households")
# ### end Alembic commands ###

View File

@@ -0,0 +1,39 @@
"""'add the rest of the schema.org nutrition properties'
Revision ID: 602927e1013e
Revises: 1fe4bd37ccc8
Create Date: 2024-10-01 14:17:00.611398
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "602927e1013e"
down_revision: str | None = "1fe4bd37ccc8"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipe_nutrition", schema=None) as batch_op:
batch_op.add_column(sa.Column("cholesterol_content", sa.String(), nullable=True))
batch_op.add_column(sa.Column("saturated_fat_content", sa.String(), nullable=True))
batch_op.add_column(sa.Column("trans_fat_content", sa.String(), nullable=True))
batch_op.add_column(sa.Column("unsaturated_fat_content", sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("recipe_nutrition", schema=None) as batch_op:
batch_op.drop_column("unsaturated_fat_content")
batch_op.drop_column("trans_fat_content")
batch_op.drop_column("saturated_fat_content")
batch_op.drop_column("cholesterol_content")
# ### end Alembic commands ###

View File

@@ -0,0 +1,188 @@
"""added query_filter_string to cookbook and mealplan
Revision ID: 86054b40fd06
Revises: 602927e1013e
Create Date: 2024-10-08 21:17:31.601903
"""
import sqlalchemy as sa
from sqlalchemy import orm
from alembic import op
from mealie.db.models._model_utils import guid
# revision identifiers, used by Alembic.
revision = "86054b40fd06"
down_revision: str | None = "602927e1013e"
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions
class SqlAlchemyBase(orm.DeclarativeBase):
pass
class Category(SqlAlchemyBase):
__tablename__ = "categories"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
class Tag(SqlAlchemyBase):
__tablename__ = "tags"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
class Tool(SqlAlchemyBase):
__tablename__ = "tools"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
class Household(SqlAlchemyBase):
__tablename__ = "households"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
cookbooks_to_categories = sa.Table(
"cookbooks_to_categories",
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("category_id", guid.GUID, sa.ForeignKey("categories.id"), index=True),
)
cookbooks_to_tags = sa.Table(
"cookbooks_to_tags",
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id"), index=True),
)
cookbooks_to_tools = sa.Table(
"cookbooks_to_tools",
SqlAlchemyBase.metadata,
sa.Column("cookbook_id", guid.GUID, sa.ForeignKey("cookbooks.id"), index=True),
sa.Column("tool_id", guid.GUID, sa.ForeignKey("tools.id"), index=True),
)
plan_rules_to_categories = sa.Table(
"plan_rules_to_categories",
SqlAlchemyBase.metadata,
sa.Column("group_plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("category_id", guid.GUID, sa.ForeignKey("categories.id"), index=True),
)
plan_rules_to_tags = sa.Table(
"plan_rules_to_tags",
SqlAlchemyBase.metadata,
sa.Column("plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("tag_id", guid.GUID, sa.ForeignKey("tags.id"), index=True),
)
plan_rules_to_households = sa.Table(
"plan_rules_to_households",
SqlAlchemyBase.metadata,
sa.Column("group_plan_rule_id", guid.GUID, sa.ForeignKey("group_meal_plan_rules.id"), index=True),
sa.Column("household_id", guid.GUID, sa.ForeignKey("households.id"), index=True),
)
class CookBook(SqlAlchemyBase):
__tablename__ = "cookbooks"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
query_filter_string: orm.Mapped[str] = orm.mapped_column(sa.String, nullable=False, default="")
categories: orm.Mapped[list[Category]] = orm.relationship(
Category, secondary=cookbooks_to_categories, single_parent=True
)
require_all_categories: orm.Mapped[bool | None] = orm.mapped_column(sa.Boolean, default=True)
tags: orm.Mapped[list[Tag]] = orm.relationship(Tag, secondary=cookbooks_to_tags, single_parent=True)
require_all_tags: orm.Mapped[bool | None] = orm.mapped_column(sa.Boolean, default=True)
tools: orm.Mapped[list[Tool]] = orm.relationship(Tool, secondary=cookbooks_to_tools, single_parent=True)
require_all_tools: orm.Mapped[bool | None] = orm.mapped_column(sa.Boolean, default=True)
class GroupMealPlanRules(SqlAlchemyBase):
__tablename__ = "group_meal_plan_rules"
id: orm.Mapped[guid.GUID] = orm.mapped_column(guid.GUID, primary_key=True, default=guid.GUID.generate)
query_filter_string: orm.Mapped[str] = orm.mapped_column(sa.String, nullable=False, default="")
categories: orm.Mapped[list[Category]] = orm.relationship(Category, secondary=plan_rules_to_categories)
tags: orm.Mapped[list[Tag]] = orm.relationship(Tag, secondary=plan_rules_to_tags)
households: orm.Mapped[list["Household"]] = orm.relationship("Household", secondary=plan_rules_to_households)
def migrate_cookbooks():
bind = op.get_bind()
session = orm.Session(bind=bind)
cookbooks = session.query(CookBook).all()
for cookbook in cookbooks:
parts = []
if cookbook.categories:
relop = "CONTAINS ALL" if cookbook.require_all_categories else "IN"
vals = ",".join([f'"{cat.id}"' for cat in cookbook.categories])
parts.append(f"recipe_category.id {relop} [{vals}]")
if cookbook.tags:
relop = "CONTAINS ALL" if cookbook.require_all_tags else "IN"
vals = ",".join([f'"{tag.id}"' for tag in cookbook.tags])
parts.append(f"tags.id {relop} [{vals}]")
if cookbook.tools:
relop = "CONTAINS ALL" if cookbook.require_all_tools else "IN"
vals = ",".join([f'"{tool.id}"' for tool in cookbook.tools])
parts.append(f"tools.id {relop} [{vals}]")
cookbook.query_filter_string = " AND ".join(parts)
session.commit()
def migrate_mealplan_rules():
bind = op.get_bind()
session = orm.Session(bind=bind)
rules = session.query(GroupMealPlanRules).all()
for rule in rules:
parts = []
if rule.categories:
vals = ",".join([f'"{cat.id}"' for cat in rule.categories])
parts.append(f"recipe_category.id CONTAINS ALL [{vals}]")
if rule.tags:
vals = ",".join([f'"{tag.id}"' for tag in rule.tags])
parts.append(f"tags.id CONTAINS ALL [{vals}]")
if rule.households:
vals = ",".join([f'"{household.id}"' for household in rule.households])
parts.append(f"household_id IN [{vals}]")
rule.query_filter_string = " AND ".join(parts)
session.commit()
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("cookbooks", schema=None) as batch_op:
batch_op.add_column(sa.Column("query_filter_string", sa.String(), nullable=False, server_default=""))
with op.batch_alter_table("group_meal_plan_rules", schema=None) as batch_op:
batch_op.add_column(sa.Column("query_filter_string", sa.String(), nullable=False, server_default=""))
# ### end Alembic commands ###
migrate_cookbooks()
migrate_mealplan_rules()
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("group_meal_plan_rules", schema=None) as batch_op:
batch_op.drop_column("query_filter_string")
with op.batch_alter_table("cookbooks", schema=None) as batch_op:
batch_op.drop_column("query_filter_string")
# ### end Alembic commands ###

View File

@@ -1,5 +1,8 @@
import json
from fastapi import FastAPI
from freezegun import freeze_time
from mealie.app import app
from mealie.core.config import determine_data_dir
@@ -36,11 +39,12 @@ HTML_TEMPLATE = """<!-- Custom HTML site displayed as the Home chapter -->
HTML_PATH = DATA_DIR.parent.parent.joinpath("docs/docs/overrides/api.html")
def generate_api_docs(my_app):
def generate_api_docs(my_app: FastAPI):
with open(HTML_PATH, "w") as fd:
text = HTML_TEMPLATE.replace("MY_SPECIFIC_TEXT", json.dumps(my_app.openapi()))
fd.write(text)
if __name__ == "__main__":
generate_api_docs(app)
with freeze_time("2024-01-20T17:00:55Z"):
generate_api_docs(app)

View File

@@ -67,7 +67,7 @@ def rename_non_compliant_paths():
kabab case.
"""
ignore_files = ["DS_Store", ".gitkeep"]
ignore_files = ["DS_Store", ".gitkeep", "af-ZA.json", "en-US.json"]
ignore_extensions = [".pyc", ".pyo", ".py"]

View File

@@ -4,7 +4,7 @@ from pathlib import Path
from fastapi import FastAPI
from jinja2 import Template
from pydantic import BaseModel, ConfigDict
from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject, RequestType
from utils import PROJECT_DIR, CodeTemplates, HTTPRequest, RouteObject
CWD = Path(__file__).parent
@@ -17,9 +17,16 @@ class PathObject(BaseModel):
http_verbs: list[HTTPRequest]
def get_path_objects(app: FastAPI):
paths = []
def force_include_in_schema(app: FastAPI):
# clear schema cache
app.openapi_schema = None
for route in app.routes:
route.include_in_schema = True
def get_path_objects(app: FastAPI):
force_include_in_schema(app)
paths = []
for key, value in app.openapi().items():
if key == "paths":
for key, value2 in value.items():

View File

@@ -35,6 +35,7 @@ LOCALE_DATA: dict[str, LocaleData] = {
"es-ES": LocaleData(name="Español (Spanish)"),
"fi-FI": LocaleData(name="Suomi (Finnish)"),
"fr-FR": LocaleData(name="Français (French)"),
"fr-BE": LocaleData(name="Belge (Belgian)"),
"gl-ES": LocaleData(name="Galego (Galician)"),
"he-IL": LocaleData(name="עברית (Hebrew)", dir="rtl"),
"hr-HR": LocaleData(name="Hrvatski (Croatian)"),

View File

@@ -1,5 +1,6 @@
import logging
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
@@ -23,6 +24,11 @@ def render_python_template(template_file: Path | str, dest: Path, data: dict):
dest.write_text(text)
# lint/format file with Ruff
log.info(f"Formatting {dest}")
subprocess.run(["poetry", "run", "ruff", "check", str(dest), "--fix"])
subprocess.run(["poetry", "run", "ruff", "format", str(dest)])
@dataclass
class CodeSlicer:

View File

@@ -173,7 +173,7 @@ def recipe_data(name: str, slug: str, id: str, userId: str, groupId: str) -> dic
"dateAdded": "2022-09-03",
"dateUpdated": "2022-09-10T15:18:19.866085",
"createdAt": "2022-09-03T18:31:17.488118",
"updateAt": "2022-09-10T15:18:19.869630",
"updatedAt": "2022-09-10T15:18:19.869630",
"recipeInstructions": [
{
"id": "60ae53a3-b3ff-40ee-bae3-89fea0b1c637",

View File

@@ -78,6 +78,7 @@ RUN echo "crfpp-container"
# Production Image
###############################################
FROM python-base as production
LABEL org.opencontainers.image.source="https://github.com/mealie-recipes/mealie"
ENV PRODUCTION=true
ENV TESTING=false
@@ -92,6 +93,9 @@ RUN apt-get update \
libldap-2.5 \
&& rm -rf /var/lib/apt/lists/*
# create directory used for Docker Secrets
RUN mkdir -p /run/secrets
# copying poetry and venv into image
COPY --from=builder-base $POETRY_HOME $POETRY_HOME
COPY --from=builder-base $PYSETUP_PATH $PYSETUP_PATH

View File

@@ -1,4 +1,3 @@
version: "3.4"
services:
mailpit:
image: axllent/mailpit:latest

View File

@@ -1,4 +1,3 @@
version: "3.4"
services:
mealie:
container_name: mealie
@@ -24,7 +23,6 @@ services:
POSTGRES_SERVER: postgres
POSTGRES_PORT: 5432
POSTGRES_DB: mealie
# =====================================
# Email Configuration
# SMTP_HOST=

View File

@@ -9,77 +9,7 @@ How exactly you need to modify it is of course highly contextual to the change y
## Using Alembic to generate upgrade script
In your dev container you can run something like (change the message) `task py:migrate "Add creation tag to group preferences"` to have Alembic generate an upgrade script for you.
In your dev container you can run something like (change the message) `task py:migrate -- "Add creation tag to group preferences"` to have Alembic generate an upgrade script for you.
The script Alembic generates, will be limited! (Perhaps there's a way to resolve that? Haven't looked into it yet)
For example, Alembic generated a script _similar_ to this (it has been modified already to have accurate foreign key names, for instance):
```Python
"""Add creation tag to group preferences
Revision ID: 0ea6eb8eaa44
Revises: ba1e4a6cfe99
Create Date: 2024-01-04 12:40:03.062671
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "0ea6eb8eaa44"
down_revision = "ba1e4a6cfe99"
branch_labels = None
depends_on = None
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column(
"group_preferences", sa.Column("recipe_creation_tag", mealie.db.migration_types.GUID(), nullable=True)
)
op.create_foreign_key("fk_groupprefs_tags", "group_preferences", "tags", ["recipe_creation_tag"], ["id"])
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_constraint("fk_groupprefs_tags", "group_preferences", type_="foreignkey")
op.drop_column("group_preferences", "recipe_creation_tag")
### end Alembic commands ###
```
But when trying to actually use that upgrade script, it becomes clear that our SQLite database doesn't like them. The minor modification needed looks like:
```Python
"""Add creation tag to group preferences
Revision ID: 0ea6eb8eaa44
Revises: ba1e4a6cfe99
Create Date: 2024-01-04 12:40:03.062671
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "0ea6eb8eaa44"
down_revision = "ba1e4a6cfe99"
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table("group_preferences", schema=None) as batch_op:
batch_op.add_column(sa.Column("recipe_creation_tag", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_foreign_key("fk_groupprefs_tags", "tags", ["recipe_creation_tag"], ["id"])
def downgrade():
with op.batch_alter_table("group_preferences", schema=None) as batch_op:
batch_op.drop_constraint("fk_groupprefs_tags", type_="foreignkey")
batch_op.drop_column("recipe_creation_tag")
```
Alembic's script migration isn't perfect, so you will need to review which changes are generated. You will also need to make sure any custom operations work on both SQLite and Postgres.
There are some known limitations with our migrations and Alembic's auto-generation, which is accounted for in `/alembic/env.py`. If any of your migrations overlap with the columns in `include_object`, you may need to manually adjust the migration.

View File

@@ -0,0 +1,59 @@
# Migration Guide
This guide is a reference for developers maintaining custom integrations with Mealie. While we aim to keep breaking changes to a minimum, major versions are likely to contain at least *some* breaking changes. To clarify: *most users do not need to worry about this, this is **only** for those maintaining integrations and/or leveraging the API*.
While this guide aims to simplify the migration process for developers, it's not necessarily a comprehensive list of breaking changes. Starting with v2, a comprehensive list of breaking changes are highlighted in the release notes.
## V1 → V2
The biggest change between V1 and V2 is the introduction of Households. For more information on how households work in relation to groups/users, check out the [Groups and Households](./features.md#groups-and-households) section in the Features guide.
### `updateAt` is now `updatedAt`
We have renamed the `updateAt` field to `updatedAt`. While the API will still accept `updateAt` as an alias, the API will return it as `updatedAt`. The field's behavior has otherwise been unchanged.
### Backend Endpoint Changes
These endpoints have moved, but are otherwise unchanged:
- `/recipes/create-url` -> `/recipes/create/url`
- `/recipes/create-url/bulk` -> `/recipes/create/url/bulk`
- `/recipes/create-from-zip` -> `/recipes/create/zip`
- `/recipes/create-from-image` -> `/recipes/create/image`
- `/groups/webhooks` -> `/households/webhooks`
- `/groups/shopping/items` -> `/households/shopping/items`
- `/groups/shopping/lists` -> `/households/shopping/lists`
- `/groups/mealplans` -> `/households/mealplans`
- `/groups/mealplans/rules` -> `/households/mealplans/rules`
- `/groups/invitations` -> `/households/invitations`
- `/groups/recipe-actions` -> `/households/recipe-actions`
- `/groups/events/notifications` -> `/households/events/notifications`
- `/groups/cookbooks` -> `/households/cookbooks`
- `/explore/foods/{group_slug}` -> `/explore/groups/{group_slug}/foods`
- `/explore/organizers/{group_slug}/categories` -> `/explore/groups/{group_slug}/categories`
- `/explore/organizers/{group_slug}/tags` -> `/explore/groups/{group_slug}/tags`
- `/explore/organizers/{group_slug}/tools` -> `/explore/groups/{group_slug}/tools`
- `/explore/cookbooks/{group_slug}` -> `/explore/groups/{group_slug}/cookbooks`
- `/explore/recipes/{group_slug}` -> `/explore/groups/{group_slug}/recipes`
`/groups/members` previously returned a `UserOut` object, but now returns a `UserSummary`. Should you need the full user information (username, email, etc.), rather than just the summary, see `/households/members` instead for the household members.
`/groups/members` previously returned a list of users, but now returns paginated users (similar to all other list endpoints).
These endpoints have been completely removed:
- `/admin/analytics` (no longer used)
- `/groups/permissions` (see household permissions)
- `/groups/statistics` (see household statistics)
- `/groups/categories` (see organizer endpoints)
- `/recipes/summary/untagged` (no longer used)
- `/recipes/summary/uncategorized` (no longer used)
- `/users/group-users` (see `/groups/members` and `/households/members`)
### Frontend Links
These frontend pages have moved:
- `/group/mealplan/...` -> `/household/mealplan/...`
- `/group/members` -> `/household/members`
- `/group/notifiers` -> `/household/notifiers`
- `/group/webhooks` -> `/household/webhooks`

View File

@@ -23,7 +23,7 @@ function import_from_file () {
do
echo $line
curl -X 'POST' \
"$3/api/recipes/create-url" \
"$3/api/recipes/create/url" \
-H "Authorization: Bearer $2" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
@@ -81,7 +81,7 @@ def import_from_file(input_file, token, mealie_url):
data = {
'url': line
}
response = requests.post(mealie_url + "/api/recipes/create-url", headers=headers, json=data)
response = requests.post(mealie_url + "/api/recipes/create/url", headers=headers, json=data)
print(response.text)
input_file="list"

View File

@@ -18,32 +18,26 @@ Create an API token from Mealie's User Settings page (https://hay-kot.github.io/
#### 2. Create Home Assistant Sensors
Create REST sensors in home assistant to get the details of today's meal.
We will create sensors to get the name and ID of the first meal in today's meal plan (note that this may not be what is wanted if there is more than one meal planned for the day). We need the ID as well as the name to be able to retreive the image for the meal.
We will create sensors to get the name and ID of the first meal in today's meal plan (note that this may not be what is wanted if there is more than one meal planned for the day). We need the ID as well as the name to be able to retrieve the image for the meal.
Make sure the url and port (`http://mealie:9000` ) matches your installation's address and _API_ port.
```yaml
- platform: rest
resource: "http://mealie:9000/api/groups/mealplans/today"
method: GET
name: Mealie todays meal
headers:
Authorization: Bearer <<API_TOKEN>>
value_template: "{{ value_json[0].recipe.name }}"
force_update: true
scan_interval: 30
```
```yaml
- platform: rest
resource: "http://mealie:9000/api/groups/mealplans/today"
method: GET
name: Mealie todays meal ID
headers:
Authorization: Bearer <<API_TOKEN>>
value_template: "{{ value_json[0].recipe.id }}"
force_update: true
scan_interval: 30
rest:
- resource: "http://mealie:9000/api/groups/mealplans/today"
method: GET
headers:
Authorization: Bearer <<API_TOKEN>>
scan_interval: 3600
sensor:
- name: Mealie todays meal
value_template: "{{ value_json[0]['recipe']['name'] }}"
force_update: true
unique_id: mealie_todays_meal
- name: Mealie todays meal ID
value_template: "{{ value_json[0]['recipe']['id'] }}"
force_update: true
unique_id: mealie_todays_meal_id
```
#### 3. Create a Camera Entity

View File

@@ -7,7 +7,9 @@ You can use bookmarklets to generate a bookmark that will take your current loca
You can use a [bookmarklet generator site](https://caiorss.github.io/bookmarklet-maker/) and the code below to generate a bookmark for your site. Just change the `http://localhost:8080` to your sites web address and follow the instructions.
```js
var url = document.URL;
var url = document.URL.endsWith('/') ?
document.URL.slice(0, -1) :
document.URL;
var mealie = "http://localhost:8080";
var group_slug = "home" // Change this to your group slug. You can obtain this from your URL after logging-in to Mealie
var use_keywords= "&use_keywords=1" // Optional - use keywords from recipe - update to "" if you don't want that

View File

@@ -48,7 +48,7 @@ services:
restart: unless-stopped
```
Don't forget to change the <code>mydomain.duckns</code> into your personal domain and the <code>duckdnstoken</code> into your token and remove the brackets.
Don't forget to change the <code>mydomain.duckdns</code> into your personal domain and the <code>duckdnstoken</code> into your token and remove the brackets.
## Step 3: Change the config files

View File

@@ -0,0 +1,98 @@
# OpenID Connect (OIDC) Authentication
:octicons-tag-24: v2.0.0
!!! note
Breaking changes to OIDC Authentication were introduced with Mealie v2. Please see the below for [migration steps](#migration-from-mealie-v1x).
Looking instead for the docs for Mealie :octicons-tag-24: v1.x? [Click here](./oidc.md)
Mealie supports 3rd party authentication via [OpenID Connect (OIDC)](https://openid.net/connect/), an identity layer built on top of OAuth2. OIDC is supported by many Identity Providers (IdP), including:
- [Authentik](https://goauthentik.io/integrations/sources/oauth/#openid-connect)
- [Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/)
- [Keycloak](https://www.keycloak.org/docs/latest/securing_apps/#_oidc)
- [Okta](https://www.okta.com/openid-connect/)
## Account Linking
Signing in with OAuth will automatically find your account in Mealie and link to it. If a user does not exist in Mealie, then one will be created (if enabled), but will be unable to log in with any other authentication method. An admin can configure another authentication method for such a user.
If a user previously accessed Mealie via credentials and you want to no longer allow users to log in with `LDAP` or `Mealie` credentials, then you can set the user's *Authentication Method* to `OIDC`. Conversely, if a user's auth method is not `OIDC`, then they can still log in with whatever their auth method is as well as OIDC.
## Provider Setup
Before you can start using OIDC Authentication, you must first configure a new client application in your identity provider. Your identity provider must support the OAuth **Authorization Code flow with PKCE**. The steps will vary by provider, but generally, the steps are as follows.
1. Create a new client application
- The Provider type should be OIDC or OAuth2
- The Grant type should be `Authorization Code`
- The Client type should be `confidential` (you should have a **Client Secret**)
2. Configure redirect URI
The redirect URI(s) that are needed:
1. `http(s)://DOMAIN:PORT/login`
2. `https(s)://DOMAIN:PORT/login?direct=1`
1. This URI is only required if your IdP supports [RP-Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) such as Keycloak. You may also be able to combine this into the previous URI by using a wildcard: `http(s)://DOMAIN:PORT/login*`
The redirect URI(s) should include any URL that Mealie is accessible from. Some examples include
http://localhost:9091/login
https://mealie.example.com/login
3. Configure allowed scopes
The scopes required are `openid profile email`
If you plan to use the [groups](#groups) to configure access within Mealie, you will need to also add the scope defined by the `OIDC_GROUPS_CLAIM` environment variable. The default claim is `groups`
## Mealie Setup
Take the client id and your discovery URL and update your environment variables to include the required OIDC variables described in [Installation - Backend Configuration](../installation/backend-config.md#openid-connect-oidc).
### Groups
There are two (optional) [environment variables](../installation/backend-config.md#openid-connect-oidc) that can control which of the users in your IdP can log in to Mealie and what permissions they will have. Keep in mind that these groups **do not necessarily correspond to groups in Mealie**. The groups claim is configurable via the `OIDC_GROUPS_CLAIM` environment variable. The groups should be **defined in your IdP** and be returned in the configured claim value.
`OIDC_USER_GROUP`: Users must be a part of this group (within your IdP) to be able to log in.
`OIDC_ADMIN_GROUP`: Users that are in this group (within your IdP) will be made an **admin** in Mealie. Users in this group do not need to be in the `OIDC_USER_GROUP`
## Examples
Example configurations for several Identity Providers have been provided by the Community in the [GitHub Discussions](https://github.com/mealie-recipes/mealie/discussions/categories/oauth-provider-example).
If you don't see your provider and have successfully set it up, please consider [creating your own example](https://github.com/mealie-recipes/mealie/discussions/new?category=oauth-provider-example) so that others can have a smoother setup.
## Migration from Mealie v1.x
**High level changes**
- A Client Secret is now required
- CORS is no longer a requirement since all authentication happens server-side
- A user will be successfully authenticated if they are part of *either* `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. Admins no longer need to be part of both groups
- ID Token signing algorithm is now inferred using the `id_token_signing_alg_values_supported` metadata from the discovery URL
### Changes in your IdP
**Required**
- You must change the Mealie client in your IdP to be **confidential**. The option is different for every provider, but you need to obtain a **client secret**.
**Optional**
- You may now also remove the `OIDC_USER_GROUP` from your admin users if you so desire. Users within the `OIDC_ADMIN_GROUP` will now be able to successfully authenticate with only that group.
- You may remove any CORS configuration. i.e. configured origins
### Changes in Mealie
**Required**
- After obtaining the **client secret** from your IdP, you must add it to Mealie using the `OIDC_CLIENT_SECRET` environment variable or via [docker secrets](../installation/backend-config.md#docker-secrets). This secret will not be logged on startup.
**Optional**
- Remove `OIDC_SIGNING_ALGORITHM` from your environment. It will no longer have any effect.

View File

@@ -2,14 +2,14 @@
## How do I enable "smart" ingredient handling?
You might have noticed that scaling up a recipe or making a shopping list doesn't by default handle the ingredients in a way you might expect. Depending on your settings, scaling up might yield things like `2 1 cup broth` instead of `2 cup broth`. And making shopping lists from reciepes that have shared ingredients can yield multiple lines of the same ingredient. **But**, mealie has a mechanism to intelligently handle ingredients and make your day better. How?
You might have noticed that scaling up a recipe or making a shopping list doesn't by default handle the ingredients in a way you might expect. Depending on your settings, scaling up might yield things like `2 1 cup broth` instead of `2 cup broth`. And, making shopping lists from recipes that have shared ingredients can yield multiple lines of the same ingredient. **But**, Mealie has a mechanism to intelligently handle ingredients and make your day better. How?
### Set up your Foods and Units
Do the following just **once**. Doing this applies to your whole group, so be careful.
1. Click on your name in the upper left corner to get to your settings
2. In the bottom right, select `Manage Data`
3. In the Management page, make sure that a little orange button says `Foods`
4. If your Foods database is empty, click `Seed` and choose your language. You should end up with a list of foods. (Wait bit for seeding to happen, and try not to seed more than once or you will have duplicates)
4. If your Foods database is empty, click `Seed` and choose your language. You should end up with a list of foods. (Wait a bit for seeding to happen, and try not to seed more than once or you will have duplicates)
5. Click the little orange `Foods` button and now choose `Units`.
6. Click `Seed` and choose your language. You should end up with a list of units (e.g. `tablespoon`)
@@ -33,9 +33,9 @@ Do the following for each recipe you want to intelligently handle ingredients.
Scaling up this recipe or adding it to a Shopping List will now smartly take care of ingredient amounts and duplicate combinations.
## Is it Safe to Upgrade Mealie?
## Is it safe to upgrade Mealie?
Yes. If you are using the v1 branches (including beta), you can upgrade to the latest version of Mealie without performing a site Export/Restore. This process was required in previous versions of Mealie, however we've automated the database migration process to make it easier to upgrade. Not that if you were using the v0.5.x version, you CANNOT upgrade to the latest version automatically. You must follow the migration instructions in the documentation.
Yes. If you are using the v1 branches (including beta), you can upgrade to the latest version of Mealie without performing a site Export/Restore. This process was required in previous versions of Mealie, however we've automated the database migration process to make it easier to upgrade. Note that if you were using the v0.5.x version, you CANNOT upgrade to the latest version automatically. You must follow the migration instructions in the documentation.
- [Migration From v0.5.x](./migrating-to-mealie-v1.md)
@@ -45,7 +45,7 @@ You can change the theme by settings the environment variables.
- [Backend Config - Themeing](./installation/backend-config.md#themeing)
## How can I change the Login Session Timeout?
## How can I change the login session timeout?
Login session can be configured by setting the `TOKEN_TIME` variable on the backend container.
@@ -53,7 +53,7 @@ Login session can be configured by setting the `TOKEN_TIME` variable on the back
## Can I serve Mealie on a subpath?
No. Due to limitations from the Javascript Framework, mealie doesn't support serving Mealie on a subpath.
No. Due to limitations from the JavaScript Framework, Mealie doesn't support serving Mealie on a subpath.
## Can I install Mealie without docker?
@@ -98,13 +98,14 @@ python /app/mealie/scripts/change_password.py
Follow the [steps above](#how-can-i-change-my-password) for changing your password. You will be prompted if you would like to switch your authentication method back to local auth so you can log in again.
## How do private groups and recipes work?
## How do private groups, households, and recipes work?
Managing private groups and recipes can be confusing. The following diagram and notes should help explain how they work to determine if a recipe can be shared publicly.
- Private links that are generated from the recipe page using the `Share` button bypass all group and recipe permissions
- Private groups block all access to recipes, including those that are public, except as noted above.
- Groups with "Allow users outside of your group to see your recipes" disabled block all access to recipes, except as noted above.
- Private households, similar to private groups, block all access to recipes, except as noted above.
- Households with "Allow users outside of your group to see your recipes" disabled block all access to recipes, except as noted above.
- Private recipes block all access to the recipe from public links. This does not affect Private Links.
```mermaid
@@ -112,7 +113,8 @@ stateDiagram-v2
r1: Request Access
p1: Using Private Link?
p2: Is Group Private?
p3: Is Recipe Private?
p3: Is Household Private?
p4: Is Recipe Private?
s1: Deny Access
n1: Allow Access
@@ -125,13 +127,16 @@ stateDiagram-v2
p2 --> p3: No
p3 --> s1: Yes
p3 --> n1: No
p3 --> p4: No
p4 --> s1: Yes
p4 --> n1: No
```
For more information, check out the [Permissions and Public Access guide](./usage/permissions-and-public-access.md).
For more information on public access, check out the [Permissions and Public Access guide](./usage/permissions-and-public-access.md). For more information on groups vs. households, check out the [Groups and Households](./features.md#groups-and-households) section in the Features guide.
## Can I use fail2ban with mealie?
Yes, mealie is configured to properly forward external IP addresses into the `mealie.log` logfile. Note that due to restrictions in docker, IP address forwarding only works on Linux.
## Can I use fail2ban with Mealie?
Yes, Mealie is configured to properly forward external IP addresses into the `mealie.log` logfile. Note that due to restrictions in docker, IP address forwarding only works on Linux.
Your fail2ban usage should look like the following:
```
@@ -139,13 +144,21 @@ Use datepattern : %d-%b-%y %H:%M:%S : Day-MON-Year2 24hour:Minute:Second
Use failregex line : ^ERROR:\s+Incorrect username or password from <HOST>
```
## Why An API?
An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based on Meal Plan data to remind you to defrost the chicken, marinade the steak, or start the CrockPot. Additionally, you can access nearly any backend service via the API giving you total control to extend the application. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation.
## Why an API?
An API allows integration into applications like [Home Assistant](https://www.home-assistant.io/) that can act as notification engines to provide custom notifications based on Meal Plan data to remind you to defrost the chicken, marinate the steak, or start the CrockPot. Additionally, you can access nearly any backend service via the API giving you total control to extend the application. To explore the API spin up your server and navigate to http://yourserver.com/docs for interactive API documentation.
## Why a Database?
Some users of static-site generator applications like ChowDown have expressed concerns about their data being stuck in a database. Considering this is a new project, it is a valid concern to be worried about your data. Mealie specifically addresses this concern by provided automatic daily backups that export your data in json, plain-text markdown files, and/or custom Jinja2 templates. **This puts you in control of how your data is represented** when exported from Mealie, which means you can easily migrate to any other service provided Mealie doesn't work for you.
## Why a database?
Some users of static-site generator applications like ChowDown have expressed concerns about their data being stuck in a database. Considering this is a new project, it is a valid concern to be worried about your data. Mealie specifically addresses this concern by providing automatic daily backups that export your data in json, plain-text markdown files, and/or custom Jinja2 templates. **This puts you in control of how your data is represented** when exported from Mealie, which means you can easily migrate to any other service provided Mealie doesn't work for you.
As to why we need a database?
- **Developer Experience:** Without a database, a lot of the work to maintain your data is taken on by the developer instead of a battle-tested platform for storing data.
- **Multi User Support:** With a solid database as backend storage for your data, Mealie can better support multi-user sites and avoid read/write access errors when multiple actions are taken at the same time.
## Why is there no "Keep Screen Alive" button when I access a recipe?
You've perhaps visited the Mealie Demo and noticed that it had a "Keep Screen Alive" button, but it doesn't show up in your own Mealie instance.
There are typically two possible reasons for this:
1. You're accessing your Mealie instance without using HTTPS. The Wake Lock API is only available if HTTPS is used. Read more here: https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
2. You're accessing your Mealie instance on a browser which doesn't support the API. You can test this here: https://vueuse.org/core/useWakeLock/#demo
Solving the above points will most likely resolve your issues. However, if you're still having problems, you are welcome to create an issue. Just remember to add that you've tried the above two options first in your description.

View File

@@ -35,7 +35,6 @@ Mealie has a robust and flexible recipe organization system with a few different
#### Categories
Categories are the overarching organizer for recipes. You can assign as many categories as you'd like to a recipe, but we recommend that you try to limit the categories you assign to a recipe to one or two. This helps keep categories as focused as possible while still allowing you to find recipes that are related to each other. For example, you might assign a recipe to the category **Breakfast**, **Lunch**, **Dinner**, or **Side**.
[Categories Demo](https://demo.mealie.io/g/home/recipes/categories){ .md-button .md-button--primary }
@@ -73,13 +72,13 @@ Mealie uses a calendar like view to help you plan your meals. It shows you the p
!!! tip
You can also add a "Note" type entry to your meal-plan when you want to include something that might not have a specific recipes. This is great for leftovers, or for ordering out.
[Mealplanner Demo](https://demo.mealie.io/group/mealplan/planner/view){ .md-button .md-button--primary }
[Mealplanner Demo](https://demo.mealie.io/household/mealplan/planner/view){ .md-button .md-button--primary }
### Planner Rules
The meal planner has the concept of plan rules. These offer a flexible way to use your organizers to customize how a random recipe is inserted into your meal plan. You can set rules to restrict the pool of recipes based on the Tags and/or Categories of a recipe. Additionally, since meal plans have a Breakfast, Lunch, Dinner, and Snack labels, you can specifically set a rule to be active for a **specific meal type** or even a **specific day of the week.**
[Planner Settings Demo](https://demo.mealie.io/group/mealplan/settings){ .md-button .md-button--primary }
[Planner Settings Demo](https://demo.mealie.io/household/mealplan/settings){ .md-button .md-button--primary }
## Shopping Lists
@@ -105,22 +104,22 @@ Notifiers use the [Apprise library](https://github.com/caronc/apprise/wiki), whi
- `json` and `jsons`
- `xml` and `xmls`
[Notifiers Demo](https://demo.mealie.io/group/notifiers){ .md-button .md-button--primary }
[Notifiers Demo](https://demo.mealie.io/household/notifiers){ .md-button .md-button--primary }
### Webhooks
Unlike notifiers, which are event-driven notifications, Webhooks allow you to send scheduled notifications to your desired endpoint. Webhooks are sent on the day of a scheduled mealplan, at the specified time, and contain the mealplan data in the request.
[Webhooks Demo](https://demo.mealie.io/group/webhooks){ .md-button .md-button--primary }
[Webhooks Demo](https://demo.mealie.io/household/webhooks){ .md-button .md-button--primary }
### Recipe Actions
Recipe Actions are custom actions you can add to all recipes in Mealie. This is a great way to add custom integrations that are fired manually. There are two types of recipe actions:
1. link - these actions will take you directly to an external page
1. link - these actions will take you directly to an external page. Merge fields can be used to customize the URL for each recipe
2. post - these actions will send a `POST` request to the specified URL, with the recipe JSON in the request body. These can be used, for instance, to manually trigger a webhook in Home Assistant
Recipe Action URLs can include merge fields to inject the current recipe's data. For instance, you can use the following URL to include a Google search with the recipe's slug:
When using the "link" action type, Recipe Action URLs can include merge fields to inject the current recipe's data. For instance, you can use the following URL to include a Google search with the recipe's slug:
```
https://www.google.com/search?q=${slug}
```
@@ -164,6 +163,46 @@ Managing a robust collection of recipes inevitable requires a lot of data. Meali
[Data Management Demo](https://demo.mealie.io/group/data/foods){ .md-button .md-button--primary }
## Groups and Households
Mealie lets you fully customize how you organize your users. You can use Groups to host multiple instances (or tenants) of Mealie which are completely isolated from each other. Within each Group you can organize users into Households which allow users to share recipes, but keep other items separate (e.g. meal plans and shopping lists).
### Groups
Groups are fully isolated instances of Mealie. Think of a goup as a completely separate, fully self-contained site. There is no data shared between groups. Each group has its own users, recipes, tags, categories, etc. A user logged-in to one group cannot make any changes to another.
Common use cases for groups include:
- Hosting multiple instances of Mealie for others who want to keep their data private and secure
- Creating completely isolated recipe pools
### Households
Households are subdivisions within a single Group. Households maintain their own users and settings, while sharing their recipes with other households. Households also share organizers (tags, categories, etc.) with the entire group. Meal Plans, Shopping Lists, and Integrations are only accessible within a household.
Common use cases for households include:
- Sharing a common recipe pool amongst families
- Maintaining separate meal plans and shopping lists from other households
- Maintaining separate integrations and customizations from other households
```mermaid
flowchart TB
mealie[(Mealie)] ==> groups
%% Groups
groups((Groups)) --> ingredients & organizers
groups((Groups)) ====> households
ingredients("Ingredients<br/>(Foods, Units, Labels)")
organizers("Organizers<br/>(Categories, Tags, Tools)")
%% Households
households((Households)) --> recipes & mealplans & shoppinglists & integrations
recipes(Recipes & Cookbooks)
mealplans(Meal Plans)
shoppinglists(Shopping Lists)
integrations("Integrations<br/>(Notifiers, Webhooks)")
```
## Server Administration
### Site Settings
@@ -172,11 +211,13 @@ The site settings page contains general information about your installation like
[Settings Demo](https://demo.mealie.io/admin/site-settings){ .md-button .md-button--primary }
### Users and Group
### Users, Households, and Groups
There is a small management area for users and groups that allows you to create, edit, and delete users and groups.
There is a small management area for users, households, and groups.
[Users Demo](https://demo.mealie.io/admin/manage/users){ .md-button .md-button--primary }
[Households Demo](https://demo.mealie.io/admin/manage/households){ .md-button .md-button--primary }
[Groups Demo](https://demo.mealie.io/admin/manage/groups){ .md-button .md-button--primary }
### Backups

View File

@@ -4,22 +4,22 @@
### General
| Variables | Default | Description |
| ----------------------------- | :-------------------: | ----------------------------------------------------------------------------------- |
| PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users |
| BASE_URL | http://localhost:8080 | Used for Notifications |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally. |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug, trace) |
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run the daily tasks. |
| Variables | Default | Description |
| ----------------------------- | :-------------------: | --------------------------------------------------------------------------------------------------------- |
| PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users |
| BASE_URL | http://localhost:8080 | Used for Notifications |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug) |
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run daily server tasks, in HH:MM format. Use the server's local time, *not* UTC |
<super>\*</super> Starting in v1.4.0 this was changed to default to `false` as apart of a security review of the application.
<super>\*</super> Starting in v1.4.0 this was changed to default to `false` as part of a security review of the application.
### Security
@@ -58,7 +58,7 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
| Variables | Default | Description |
| --------------- | :-----: | ----------------------------------------------------------------------------- |
| UVICORN_WORKERS | 1 | Sets the number of works for the web server [more info here][unicorn_workers] |
| UVICORN_WORKERS | 1 | Sets the number of workers for the web server. [More info here][unicorn_workers] |
### LDAP
@@ -82,7 +82,7 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
:octicons-tag-24: v1.4.0
For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
For usage, see [Usage - OpenID Connect](../authentication/oidc-v2.md)
| Variables | Default | Description |
| ---------------------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -90,12 +90,12 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
| OIDC_SIGNUP_ENABLED | True | Enables new users to be created when signing in for the first time with OIDC |
| OIDC_CONFIGURATION_URL | None | The URL to the OIDC configuration of your provider. This is usually something like https://auth.example.com/.well-known/openid-configuration |
| OIDC_CLIENT_ID | None | The client id of your configured client in your provider |
| OIDC_USER_GROUP | None | If specified, only users belonging to this group will be able to successfully authenticate, regardless of the `OIDC_ADMIN_GROUP`. For more information see [this page](../authentication/oidc.md#groups) |
| OIDC_ADMIN_GROUP | None | If specified, users belonging to this group will be made an admin. For more information see [this page](../authentication/oidc.md#groups) |
| OIDC_CLIENT_SECRET <br/> :octicons-tag-24: v2.0.0 | None | The client secret of your configured client in your provider|
| OIDC_USER_GROUP | None | If specified, only users belonging to this group will be able to successfully authenticate. For more information see [this page](../authentication/oidc-v2.md#groups) |
| OIDC_ADMIN_GROUP | None | If specified, users belonging to this group will be able to successfully authenticate *and* be made an admin. For more information see [this page](../authentication/oidc-v2.md#groups) |
| OIDC_AUTO_REDIRECT | False | If `True`, then the login page will be bypassed an you will be sent directly to your Identity Provider. You can still get to the login page by adding `?direct=1` to the login URL |
| OIDC_PROVIDER_NAME | OAuth | The provider name is shown in SSO login button. "Login with <OIDC_PROVIDER_NAME\>" |
| OIDC_REMEMBER_ME | False | Because redirects bypass the login screen, you cant extend your session by clicking the "Remember Me" checkbox. By setting this value to true, a session will be extended as if "Remember Me" was checked |
| OIDC_SIGNING_ALGORITHM | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) |
| OIDC_USER_CLAIM | email | This is the claim which Mealie will use to look up an existing user by (e.g. "email", "preferred_username") |
| OIDC_GROUPS_CLAIM | groups | Optional if not using `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP`. This is the claim Mealie will request from your IdP and will use to compare to `OIDC_USER_GROUP` or `OIDC_ADMIN_GROUP` to allow the user to log in to Mealie or is set as an admin. **Your IdP must be configured to grant this claim** |
| OIDC_TLS_CACERTFILE | None | File path to Certificate Authority used to verify server certificate (e.g. `/path/to/ca.crt`) |
@@ -104,18 +104,22 @@ For usage, see [Usage - OpenID Connect](../authentication/oidc.md)
:octicons-tag-24: v1.7.0
Mealie supports various integrations using OpenAI. To enable OpenAI, [you must provide your OpenAI API key](https://platform.openai.com/api-keys). You can tweak how OpenAI is used using these backend settings. Please note that while OpenAI usage is optimized to reduce API costs, you're unlikely to be able to use OpenAI features with the free tier limits.
Mealie supports various integrations using OpenAI. For more information, check out our [OpenAI documentation](./open-ai.md).
For custom mapping variables (e.g. OPENAI_CUSTOM_HEADERS) you should pass values as JSON encoded strings (e.g. `OPENAI_CUSTOM_PARAMS='{"k1": "v1", "k2": "v2"}'`)
| Variables | Default | Description |
| ------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| OPENAI_BASE_URL | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
| OPENAI_API_KEY | None | Your OpenAI API Key. Enables OpenAI-related features |
| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
| OPENAI_REQUEST_TIMEOUT | 10 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
| Variables | Default | Description |
| ---------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| OPENAI_BASE_URL | None | The base URL for the OpenAI API. If you're not sure, leave this empty to use the standard OpenAI platform |
| OPENAI_API_KEY | None | Your OpenAI API Key. Enables OpenAI-related features |
| OPENAI_MODEL | gpt-4o | Which OpenAI model to use. If you're not sure, leave this empty |
| OPENAI_CUSTOM_HEADERS | None | Custom HTTP headers to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
| OPENAI_CUSTOM_PARAMS | None | Custom HTTP query params to add to all OpenAI requests. This should generally be left empty unless your custom service requires them |
| OPENAI_ENABLE_IMAGE_SERVICES | True | Whether to enable OpenAI image services, such as creating recipes via image. Leave this enabled unless your custom model doesn't support it, or you want to reduce costs |
| OPENAI_WORKERS | 2 | Number of OpenAI workers per request. Higher values may increase processing speed, but will incur additional API costs |
| OPENAI_SEND_DATABASE_DATA | True | Whether to send Mealie data to OpenAI to improve request accuracy. This will incur additional API costs |
| OPENAI_REQUEST_TIMEOUT | 60 | The number of seconds to wait for an OpenAI request to complete before cancelling the request. Leave this empty unless you're running into timeout issues on slower hardware |
### Themeing
### Theming
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.

View File

@@ -31,7 +31,7 @@ To deploy mealie on your local network, it is highly recommended to use Docker t
We've gone through a few versions of Mealie v1 deployment targets. We have settled on a single container deployment, and we've begun publishing the nightly container on github containers. If you're looking to move from the old nightly (split containers _or_ the omni image) to the new nightly, there are a few things you need to do:
1. Take a backup just in case!
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v1.0.0-RC1.1`
2. Replace the image for the API container with `ghcr.io/mealie-recipes/mealie:v1.12.0`
3. Take the external port from the frontend container and set that as the port mapped to port `9000` on the new container. The frontend is now served on port 9000 from the new container, so it will need to be mapped for you to have access.
4. Restart the container
@@ -58,7 +58,7 @@ The following steps were tested on a Ubuntu 20.04 server, but should work for mo
4. Create a docker-compose.yaml file in the mealie directory: `touch docker-compose.yaml`
5. Use the text editor of your choice to edit the file and copy the contents of the docker-compose template for the deployment type you want to use: `nano docker-compose.yaml` or `vi docker-compose.yaml`
## Step 2: Customizing The `docker-compose.yaml` files.
## Step 3: Customizing The `docker-compose.yaml` files.
After you've decided setup the files it's important to set a few ENV variables to ensure that you can use all the features of Mealie. I recommend that you verify and check that:
@@ -67,7 +67,7 @@ After you've decided setup the files it's important to set a few ENV variables t
- [x] You've set the [`BASE_URL`](./backend-config.md#general) variable.
- [x] You've set the `DEFAULT_EMAIL` and `DEFAULT_GROUP` variable.
## Step 3: Startup
## Step 4: Startup
After you've configured your database and updated the `docker-compose.yaml` files, you can start Mealie by running the following command in the directory where you've added your `docker-compose.yaml`.
@@ -87,11 +87,11 @@ You should see the containers start up without error. You should now be able to
**Password:** MyPassword
## Step 4: Validate Installation
## Step 5: Validate Installation
After the startup is complete, you should see a login screen. Use the default credentials above to log in and navigate to `/admin/site-settings`. Here, you'll find a summary of your configuration details and their respective status. Before proceeding, you should validate that the configuration is correct. For any warnings or errors the page will display an error and notify you of what you need to verify.
## Step 5: Backup
## Step 6: Backup
While v1.0.0 is a great step to data-stability and security, it's not a backup. Mealie provides a full site data backup mechanism through the UI.
@@ -101,7 +101,7 @@ These backups are just plain .zip files that you can download from the UI or acc
### Docker Tags
See all available tags on [GitHub](https://github.com/mealie-recipes/mealie/pkgs/container/mealie). We do not currently publish new images to Dockerhub.
See all available tags on [GitHub](https://github.com/mealie-recipes/mealie/pkgs/container/mealie).
`ghcr.io/mealie-recipes/mealie:nightly`

View File

@@ -0,0 +1,18 @@
# OpenAI Integration
:octicons-tag-24: v1.7.0
Mealie's OpenAI integration enables several features and enhancements throughout the application. To enable OpenAI features, you must have an account with OpenAI and configure Mealie to use the OpenAI API key (for more information, check out the [backend configuration](./backend-config.md#openai)).
## Configuration
For most users, supplying the OpenAI API key is all you need to do; you will use the regular OpenAI service with the default language model. Note that while OpenAI has a free tier, it's not sufficiently capable for Mealie (or most other production use cases). For more information, check out [OpenAI's rate limits](https://platform.openai.com/docs/guides/rate-limits). If you deposit $5 into your OpenAI account, you will be permanently bumped up to Tier 1, which is sufficient for Mealie. Cost per-request is dependant on many factors, but Mealie tries to keep token counts conservative.
Alternatively, if you have another service you'd like to use in-place of OpenAI, you can configure Mealie to use that instead, as long as it has an OpenAI-compatible API. For instance, a common self-hosted alternative to OpenAI is [Ollama](https://ollama.com/). To use Ollama with Mealie, change your `OPENAI_BASE_URL` to `http://localhost:11434/v1` (where `http://localhost:11434` is wherever you're hosting Ollama, and `/v1` enables the OpenAI-compatible endpoints). Note that you *must* provide an API key, even though it is ultimately ignored by Ollama.
If you wish to disable image recognition features (to save costs, or because your custom model doesn't support them) you can set `OPENAI_ENABLE_IMAGE_SERVICES` to `False`. For more information on what configuration options are available, check out the [backend configuration](./backend-config.md#openai).
## OpenAI Features
- The OpenAI Ingredient Parser can be used as an alternative to the NLP and Brute Force parsers. Simply choose the OpenAI parser while parsing ingredients (:octicons-tag-24: v1.7.0)
- When importing a recipe via URL, if the default recipe scraper is unable to read the recipe data from a webpage, the webpage contents will be parsed by OpenAI (:octicons-tag-24: v1.9.0)
- You can import an image of a written recipe, which is sent to OpenAI and imported into Mealie. The recipe can be hand-written or typed, as long as the text is in the picture. You can also optionally have OpenAI translate the recipe into your own language (:octicons-tag-24: v1.12.0)

View File

@@ -7,7 +7,7 @@ PostgreSQL might be considered if you need to support many concurrent users. In
```yaml
services:
mealie:
image: ghcr.io/mealie-recipes/mealie:v1.10.1 # (3)
image: ghcr.io/mealie-recipes/mealie:v1.12.0 # (3)
container_name: mealie
restart: always
ports:
@@ -20,7 +20,7 @@ services:
- mealie-data:/app/data/
environment:
# Set Backend ENV Variables Here
ALLOW_SIGNUP: true
ALLOW_SIGNUP: "false"
PUID: 1000
PGID: 1000
TZ: America/Anchorage

View File

@@ -18,7 +18,7 @@ Use your best judgement when deciding what to do.
By default, the API is **not** rate limited. This leaves Mealie open to a potential **Denial of Service Attack**. While it's possible to perform a **Denial of Service Attack** on any endpoint, there are a few key endpoints that are more vulnerable than others.
- `/api/recipes/create-url`
- `/api/recipes/create/url`
- `/api/recipes/{id}/image`
These endpoints are used to scrape data based off a user provided URL. It is possible for a malicious user to issue multiple requests to download an arbitrarily large external file (e.g a Debian ISO) and sufficiently saturate a CPU assigned to the container. While we do implement some protections against this by chunking the response, and using a timeout strategy, it's still possible to overload the CPU if an attacker issues multiple requests concurrently.
@@ -33,7 +33,7 @@ If you'd like to mitigate this risk, we suggest that you rate limit the API in g
## Server Side Request Forgery
- `/api/recipes/create-url`
- `/api/recipes/create/url`
- `/api/recipes/{id}/image`
Given the nature of these APIs it's possible to perform a **Server Side Request Forgery** attack. This is where a malicious user can issue a request to an internal network resource, and potentially exfiltrate data. We _do_ perform some checks to mitigate access to resources within your network but at the end of the day, users of Mealie are allowed to trigger HTTP requests on **your server**.

View File

@@ -11,7 +11,7 @@ SQLite is a popular, open source, self-contained, zero-configuration database th
```yaml
services:
mealie:
image: ghcr.io/mealie-recipes/mealie:v1.10.1 # (3)
image: ghcr.io/mealie-recipes/mealie:v1.12.0 # (3)
container_name: mealie
restart: always
ports:
@@ -24,7 +24,7 @@ services:
- mealie-data:/app/data/
environment:
# Set Backend ENV Variables Here
ALLOW_SIGNUP: true
ALLOW_SIGNUP: "false"
PUID: 1000
PGID: 1000
TZ: America/Anchorage

View File

@@ -11,7 +11,8 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
- 🕸 Import recipes from around the web by URL
- 📱 Progressive Web App
- 📆 Create Meal Plans
- 🛒 Generate shopping lists
- 🛒 Generate Shopping Lists
- 🏠 Separate Users into Households and share Recipes
- 🐳 Easy setup with Docker
- 🎨 Customize your interface with color themed layouts
- 🌍 localized in many languages
@@ -27,7 +28,7 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
- Copy Me That
- Paprika
- Tandoor Recipes
- Random meal plan generation
- Random Meal Plan generation
- Advanced rule configuration to fine tune random recipes
## FAQ

View File

@@ -9,15 +9,15 @@
- Create a Backup and Download from the UI
- Upgrade
## Upgrading to Mealie v1
If you are upgrading from pre-v1.0.0 to v1.0.0, make sure you read [Migrating to Mealie v1](./migrating-to-mealie-v1.md)!
## Upgrading to Mealie v1 or later
If you are upgrading from pre-v1.0.0 to v1.0.0 or later (v2.0.0, etc.), make sure you read [Migrating to Mealie v1](./migrating-to-mealie-v1.md)!
## Backing Up Your Data
[See Backups and Restore Section](../getting-started/usage/backups-and-restoring.md) for details on backing up your data
## Docker
For all setups using Docker the updating process looks something like this
For all setups using Docker, the updating process looks something like this:
- Stop the container using `docker compose down`
- If you are not using the latest tag, change the version (image tag) in your docker-compose file

View File

@@ -19,9 +19,10 @@ Administrators can navigate to the Settings page and access the User Management
## Public Recipe Access
By default, groups are set to private, meaning only logged-in users may access the group. In order for a recipe to be viewable by public (not logged-in) users, two criteria must be met:
By default, groups and households are set to private, meaning only logged-in users may access the group/household. In order for a recipe to be viewable by public (not logged-in) users, three criteria must be met:
1. The group must not be private, *and* the group setting for allowing users outside of your group to see your recipes must be enabled. These can be toggled on the Group Settings page
1. The group must not be private
2. The household must not be private, *and* the household setting for allowing users outside of your group to see your recipes must be enabled. These can be toggled on the Household Settings page
2. The recipe must be set to public. This can be toggled for each recipe individually, or in bulk using the Recipe Data Management page
Additionally, if the group is not private, public users can view all public group data (public recipes, public cookbooks, etc.) from the home page ([e.g. the demo home page](https://demo.mealie.io/g/home)).
@@ -32,7 +33,8 @@ More broadly, here are the rules for how recipe access is determined:
- Private links that are generated from the recipe page using the `Share` button bypass all group and recipe permissions
- Private groups block all access to recipes, including those that are public, except as noted above.
- Groups with "Allow users outside of your group to see your recipes" disabled block all access to recipes, except as noted above.
- Private households, similar to private groups, block all access to recipes, except as noted above.
- Households with "Allow users outside of your group to see your recipes" disabled block all access to recipes, except as noted above.
- Private recipes block all access to the recipe from public links. This does not affect Private Links.
```mermaid
@@ -40,7 +42,8 @@ stateDiagram-v2
r1: Request Access
p1: Using Private Link?
p2: Is Group Private?
p3: Is Recipe Private?
p3: Is Household Private?
p4: Is Recipe Private?
s1: Deny Access
n1: Allow Access
@@ -53,5 +56,8 @@ stateDiagram-v2
p2 --> p3: No
p3 --> s1: Yes
p3 --> n1: No
p3 --> p4: No
p4 --> s1: Yes
p4 --> n1: No
```

File diff suppressed because one or more lines are too long

View File

@@ -74,13 +74,14 @@ nav:
- Backend Configuration: "documentation/getting-started/installation/backend-config.md"
- Security: "documentation/getting-started/installation/security.md"
- Logs: "documentation/getting-started/installation/logs.md"
- OpenAI: "documentation/getting-started/installation/open-ai.md"
- Usage:
- Backup and Restoring: "documentation/getting-started/usage/backups-and-restoring.md"
- Permissions and Public Access: "documentation/getting-started/usage/permissions-and-public-access.md"
- Authentication:
- LDAP: "documentation/getting-started/authentication/ldap.md"
- OpenID Connect: "documentation/getting-started/authentication/oidc.md"
- OpenID Connect: "documentation/getting-started/authentication/oidc-v2.md"
- Community Guides:
- iOS Shortcuts: "documentation/community-guide/ios.md"
@@ -100,5 +101,6 @@ nav:
- Dev Getting Started: "contributors/developers-guide/starting-dev-server.md"
- Database Changes: "contributors/developers-guide/database-changes.md"
- Maintainers Guide: "contributors/developers-guide/maintainers.md"
- Migration Guide: "contributors/developers-guide/migration-guide.md"
- Guides:
- Improving Ingredient Parser: "contributors/guides/ingredient-parser.md"

View File

@@ -1,11 +1,13 @@
<template>
<div>
<v-card-text v-if="cookbook">
<v-card-text v-if="cookbook" class="px-1">
<v-text-field v-model="cookbook.name" :label="$t('cookbook.cookbook-name')"></v-text-field>
<v-textarea v-model="cookbook.description" auto-grow :rows="2" :label="$t('recipe.description')"></v-textarea>
<RecipeOrganizerSelector v-model="cookbook.categories" selector-type="categories" />
<RecipeOrganizerSelector v-model="cookbook.tags" selector-type="tags" />
<RecipeOrganizerSelector v-model="cookbook.tools" selector-type="tools" />
<QueryFilterBuilder
:field-defs="fieldDefs"
:initial-query-filter="cookbook.queryFilter"
@input="handleInput"
/>
<v-switch v-model="cookbook.public" hide-details single-line>
<template #label>
{{ $t('cookbook.public-cookbook') }}
@@ -14,33 +16,19 @@
</HelpIcon>
</template>
</v-switch>
<div class="mt-4">
<h3 class="text-subtitle-1 d-flex align-center mb-0 pb-0">
{{ $t('cookbook.filter-options') }}
<HelpIcon right small class="ml-2">
{{ $t('cookbook.filter-options-description') }}
</HelpIcon>
</h3>
<v-switch v-model="cookbook.requireAllCategories" class="mt-0" hide-details single-line>
<template #label> {{ $t('cookbook.require-all-categories') }} </template>
</v-switch>
<v-switch v-model="cookbook.requireAllTags" hide-details single-line>
<template #label> {{ $t('cookbook.require-all-tags') }} </template>
</v-switch>
<v-switch v-model="cookbook.requireAllTools" hide-details single-line>
<template #label> {{ $t('cookbook.require-all-tools') }} </template>
</v-switch>
</div>
</v-card-text>
</div>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { defineComponent, useContext } from "@nuxtjs/composition-api";
import { ReadCookBook } from "~/lib/api/types/cookbook";
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
import { Organizer } from "~/lib/api/types/non-generated";
import QueryFilterBuilder from "~/components/Domain/QueryFilterBuilder.vue";
import { FieldDefinition } from "~/composables/use-query-filter-builder";
export default defineComponent({
components: { RecipeOrganizerSelector },
components: { QueryFilterBuilder },
props: {
cookbook: {
type: Object as () => ReadCookBook,
@@ -51,5 +39,50 @@ export default defineComponent({
required: true,
},
},
setup(props) {
const { i18n } = useContext();
function handleInput(value: string | undefined) {
props.cookbook.queryFilterString = value || "";
}
const fieldDefs: FieldDefinition[] = [
{
name: "recipe_category.id",
label: i18n.tc("category.categories"),
type: Organizer.Category,
},
{
name: "tags.id",
label: i18n.tc("tag.tags"),
type: Organizer.Tag,
},
{
name: "tools.id",
label: i18n.tc("tool.tools"),
type: Organizer.Tool,
},
{
name: "household_id",
label: i18n.tc("household.households"),
type: Organizer.Household,
},
{
name: "created_at",
label: i18n.tc("general.date-created"),
type: "date",
},
{
name: "updated_at",
label: i18n.tc("general.date-updated"),
type: "date",
},
];
return {
handleInput,
fieldDefs,
};
},
});
</script>

View File

@@ -4,11 +4,13 @@
<BaseDialog
v-if="editTarget"
v-model="dialogStates.edit"
:width="650"
width="100%"
max-width="1100px"
:icon="$globals.icons.pages"
:title="$t('general.edit')"
:submit-icon="$globals.icons.save"
:submit-text="$tc('general.save')"
:submit-disabled="!editTarget.queryFilterString"
@submit="editCookbook"
>
<v-card-text>

View File

@@ -2,30 +2,11 @@
<div v-if="preferences">
<BaseCardSectionTitle :title="$tc('group.general-preferences')"></BaseCardSectionTitle>
<v-checkbox v-model="preferences.privateGroup" class="mt-n4" :label="$t('group.private-group')"></v-checkbox>
<v-select
v-model="preferences.firstDayOfWeek"
:prepend-icon="$globals.icons.calendarWeekBegin"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
/>
<BaseCardSectionTitle class="mt-5" :title="$tc('group.group-recipe-preferences')"></BaseCardSectionTitle>
<template v-for="(_, key) in preferences">
<v-checkbox
v-if="labels[key]"
:key="key"
v-model="preferences[key]"
class="mt-n4"
:label="labels[key]"
></v-checkbox>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
import { defineComponent, computed } from "@nuxtjs/composition-api";
export default defineComponent({
props: {
@@ -35,48 +16,6 @@ export default defineComponent({
},
},
setup(props, context) {
const { i18n } = useContext();
const labels = {
recipePublic: i18n.tc("group.allow-users-outside-of-your-group-to-see-your-recipes"),
recipeShowNutrition: i18n.tc("group.show-nutrition-information"),
recipeShowAssets: i18n.tc("group.show-recipe-assets"),
recipeLandscapeView: i18n.tc("group.default-to-landscape-view"),
recipeDisableComments: i18n.tc("group.disable-users-from-commenting-on-recipes"),
recipeDisableAmount: i18n.tc("group.disable-organizing-recipe-ingredients-by-units-and-food"),
};
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
const preferences = computed({
get() {
return props.value;
@@ -87,8 +26,6 @@ export default defineComponent({
});
return {
allDays,
labels,
preferences,
};
},

View File

@@ -0,0 +1,91 @@
<template>
<v-select
v-model="selected"
:items="households"
:label="label"
:hint="description"
:persistent-hint="!!description"
item-text="name"
:multiple="multiselect"
:prepend-inner-icon="$globals.icons.household"
return-object
>
<template #selection="data">
<v-chip
:key="data.index"
class="ma-1"
:input-value="data.selected"
small
close
label
color="accent"
dark
@click:close="removeByIndex(data.index)"
>
{{ data.item.name || data.item }}
</v-chip>
</template>
</v-select>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, useContext } from "@nuxtjs/composition-api";
import { useHouseholdStore } from "~/composables/store/use-household-store";
interface HouseholdLike {
id: string;
name: string;
}
export default defineComponent({
props: {
value: {
type: Array as () => HouseholdLike[],
required: true,
},
multiselect: {
type: Boolean,
default: false,
},
description: {
type: String,
default: "",
},
},
setup(props, context) {
const selected = computed({
get: () => props.value,
set: (val) => {
context.emit("input", val);
},
});
onMounted(() => {
if (selected.value === undefined) {
selected.value = [];
}
});
const { i18n } = useContext();
const label = computed(
() => props.multiselect ? i18n.tc("household.households") : i18n.tc("household.household")
);
const { store: households } = useHouseholdStore();
function removeByIndex(index: number) {
if (selected.value === undefined) {
return;
}
const newSelected = selected.value.filter((_, i) => i !== index);
selected.value = [...newSelected];
}
return {
selected,
label,
households,
removeByIndex,
};
},
});
</script>

View File

@@ -39,7 +39,7 @@
import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
import { Recipe } from "~/lib/api/types/recipe";
import RecipeDialogAddToShoppingList from "~/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue";
import { ShoppingListSummary } from "~/lib/api/types/group";
import { ShoppingListSummary } from "~/lib/api/types/household";
import { useUserApi } from "~/composables/api";
export interface ContextMenuItem {

View File

@@ -5,8 +5,13 @@
<v-select v-model="inputEntryType" :items="MEAL_TYPE_OPTIONS" :label="$t('meal-plan.meal-type')"></v-select>
</div>
<RecipeOrganizerSelector v-model="inputCategories" selector-type="categories" />
<RecipeOrganizerSelector v-model="inputTags" selector-type="tags" />
<div class="mb-5">
<QueryFilterBuilder
:field-defs="fieldDefs"
:initial-query-filter="queryFilter"
@input="handleQueryFilterInput"
/>
</div>
<!-- TODO: proper pluralization of inputDay -->
{{ $t('meal-plan.this-rule-will-apply', {
@@ -18,12 +23,14 @@
<script lang="ts">
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
import { RecipeTag, RecipeCategory } from "~/lib/api/types/recipe";
import QueryFilterBuilder from "~/components/Domain/QueryFilterBuilder.vue";
import { FieldDefinition } from "~/composables/use-query-filter-builder";
import { Organizer } from "~/lib/api/types/non-generated";
import { QueryFilterJSON } from "~/lib/api/types/response";
export default defineComponent({
components: {
RecipeOrganizerSelector,
QueryFilterBuilder,
},
props: {
day: {
@@ -34,13 +41,13 @@ export default defineComponent({
type: String,
default: "unset",
},
categories: {
type: Array as () => RecipeCategory[],
default: () => [],
queryFilterString: {
type: String,
default: "",
},
tags: {
type: Array as () => RecipeTag[],
default: () => [],
queryFilter: {
type: Object as () => QueryFilterJSON,
default: null,
},
showHelp: {
type: Boolean,
@@ -87,31 +94,65 @@ export default defineComponent({
},
});
const inputCategories = computed({
const inputQueryFilterString = computed({
get: () => {
return props.categories;
return props.queryFilterString;
},
set: (val) => {
context.emit("update:categories", val);
context.emit("update:query-filter-string", val);
},
});
const inputTags = computed({
get: () => {
return props.tags;
function handleQueryFilterInput(value: string | undefined) {
inputQueryFilterString.value = value || "";
};
const fieldDefs: FieldDefinition[] = [
{
name: "recipe_category.id",
label: i18n.tc("category.categories"),
type: Organizer.Category,
},
set: (val) => {
context.emit("update:tags", val);
{
name: "tags.id",
label: i18n.tc("tag.tags"),
type: Organizer.Tag,
},
});
{
name: "tools.id",
label: i18n.tc("tool.tools"),
type: Organizer.Tool,
},
{
name: "household_id",
label: i18n.tc("household.households"),
type: Organizer.Household,
},
{
name: "last_made",
label: i18n.tc("general.last-made"),
type: "date",
},
{
name: "created_at",
label: i18n.tc("general.date-created"),
type: "date",
},
{
name: "updated_at",
label: i18n.tc("general.date-updated"),
type: "date",
},
];
return {
MEAL_TYPE_OPTIONS,
MEAL_DAY_OPTIONS,
inputDay,
inputEntryType,
inputCategories,
inputTags,
inputQueryFilterString,
handleQueryFilterInput,
fieldDefs,
};
},
});

View File

@@ -18,8 +18,6 @@
icon: $globals.icons.testTube,
text: $tc('general.test'),
event: 'test',
// TODO: There is no functionality hooked up to this. Enable it when there is
disabled: true,
},
{
icon: $globals.icons.save,
@@ -37,7 +35,7 @@
<script lang="ts">
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
import { ReadWebhook } from "~/lib/api/types/group";
import { ReadWebhook } from "~/lib/api/types/household";
import { timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
export default defineComponent({

View File

@@ -0,0 +1,166 @@
<template>
<div v-if="preferences">
<BaseCardSectionTitle class="mt-10" :title="$tc('household.household-preferences')"></BaseCardSectionTitle>
<div class="mb-6">
<v-checkbox
v-model="preferences.privateHousehold"
hide-details
dense
:label="$t('household.private-household')"
/>
<div class="ml-8">
<p class="text-subtitle-2 my-0 py-0">
{{ $t("household.private-household-description") }}
</p>
<DocLink class="mt-2" link="/documentation/getting-started/faq/#how-do-private-groups-and-recipes-work" />
</div>
</div>
<div class="mb-6">
<v-checkbox
v-model="preferences.lockRecipeEditsFromOtherHouseholds"
hide-details
dense
:label="$t('household.lock-recipe-edits-from-other-households')"
/>
<div class="ml-8">
<p class="text-subtitle-2 my-0 py-0">
{{ $t("household.lock-recipe-edits-from-other-households-description") }}
</p>
</div>
</div>
<v-select
v-model="preferences.firstDayOfWeek"
:prepend-icon="$globals.icons.calendarWeekBegin"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
/>
<BaseCardSectionTitle class="mt-5" :title="$tc('household.household-recipe-preferences')"></BaseCardSectionTitle>
<div class="preference-container">
<div v-for="p in recipePreferences" :key="p.key">
<v-checkbox
v-model="preferences[p.key]"
hide-details
dense
:label="p.label"
/>
<p class="ml-8 text-subtitle-2 my-0 py-0">
{{ p.description }}
</p>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, useContext } from "@nuxtjs/composition-api";
import { ReadHouseholdPreferences } from "~/lib/api/types/household";
export default defineComponent({
props: {
value: {
type: Object,
required: true,
},
},
setup(props, context) {
const { i18n } = useContext();
type Preference = {
key: keyof ReadHouseholdPreferences;
label: string;
description: string;
}
const recipePreferences: Preference[] = [
{
key: "recipePublic",
label: i18n.tc("group.allow-users-outside-of-your-group-to-see-your-recipes"),
description: i18n.tc("group.allow-users-outside-of-your-group-to-see-your-recipes-description"),
},
{
key: "recipeShowNutrition",
label: i18n.tc("group.show-nutrition-information"),
description: i18n.tc("group.show-nutrition-information-description"),
},
{
key: "recipeShowAssets",
label: i18n.tc("group.show-recipe-assets"),
description: i18n.tc("group.show-recipe-assets-description"),
},
{
key: "recipeLandscapeView",
label: i18n.tc("group.default-to-landscape-view"),
description: i18n.tc("group.default-to-landscape-view-description"),
},
{
key: "recipeDisableComments",
label: i18n.tc("group.disable-users-from-commenting-on-recipes"),
description: i18n.tc("group.disable-users-from-commenting-on-recipes-description"),
},
{
key: "recipeDisableAmount",
label: i18n.tc("group.disable-organizing-recipe-ingredients-by-units-and-food"),
description: i18n.tc("group.disable-organizing-recipe-ingredients-by-units-and-food-description"),
},
];
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
const preferences = computed({
get() {
return props.value;
},
set(val) {
context.emit("input", val);
},
});
return {
allDays,
preferences,
recipePreferences,
};
},
});
</script>
<style lang="css">
.preference-container {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 600px;
}
</style>

View File

@@ -0,0 +1,622 @@
<template>
<v-card class="ma-0" style="overflow-x: auto;">
<v-card-text class="ma-0 pa-0">
<v-container fluid class="ma-0 pa-0">
<draggable
:value="fields"
handle=".handle"
v-bind="{
animation: 200,
group: 'recipe-instructions',
ghostClass: 'ghost',
}"
@start="drag = true"
@end="onDragEnd"
>
<v-row
v-for="(field, index) in fields"
:key="index"
class="d-flex flex-nowrap"
style="max-width: 100%;"
>
<v-col
:cols="attrs.fields.icon.cols"
:class="attrs.col.class"
:style="attrs.fields.icon.style"
>
<v-icon
class="handle"
style="width: 100%; height: 100%;"
>
{{ $globals.icons.arrowUpDown }}
</v-icon>
</v-col>
<v-col
:cols="attrs.fields.logicalOperator.cols"
:class="attrs.col.class"
:style="attrs.fields.logicalOperator.style"
>
<v-select
v-if="index"
v-model="field.logicalOperator"
:items="[logOps.AND, logOps.OR]"
item-text="label"
item-value="value"
@input="setLogicalOperatorValue(field, index, $event)"
>
<template #selection="{ item }">
<span :class="attrs.select.textClass" style="width: 100%;">
{{ item.label }}
</span>
</template>
</v-select>
</v-col>
<v-col
v-if="showAdvanced"
:cols="attrs.fields.leftParens.cols"
:class="attrs.col.class"
:style="attrs.fields.leftParens.style"
>
<v-select
v-model="field.leftParenthesis"
:items="['', '(', '((', '(((']"
@input="setLeftParenthesisValue(field, index, $event)"
>
<template #selection="{ item }">
<span :class="attrs.select.textClass" style="width: 100%;">
{{ item }}
</span>
</template>
</v-select>
</v-col>
<v-col
:cols="attrs.fields.fieldName.cols"
:class="attrs.col.class"
:style="attrs.fields.fieldName.style"
>
<v-select
v-model="field.label"
:items="fieldDefs"
item-text="label"
@change="setField(index, $event)"
>
<template #selection="{ item }">
<span :class="attrs.select.textClass" style="width: 100%;">
{{ item.label }}
</span>
</template>
</v-select>
</v-col>
<v-col
:cols="attrs.fields.relationalOperator.cols"
:class="attrs.col.class"
:style="attrs.fields.relationalOperator.style"
>
<v-select
v-if="field.type !== 'boolean'"
v-model="field.relationalOperatorValue"
:items="field.relationalOperatorOptions"
item-text="label"
item-value="value"
@input="setRelationalOperatorValue(field, index, $event)"
>
<template #selection="{ item }">
<span :class="attrs.select.textClass" style="width: 100%;">
{{ item.label }}
</span>
</template>
</v-select>
</v-col>
<v-col
:cols="attrs.fields.fieldValue.cols"
:class="attrs.col.class"
:style="attrs.fields.fieldValue.style"
>
<v-select
v-if="field.fieldOptions"
v-model="field.values"
:items="field.fieldOptions"
item-text="label"
item-value="value"
multiple
@input="setFieldValues(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'string'"
v-model="field.value"
@input="setFieldValue(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'number'"
v-model="field.value"
type="number"
@input="setFieldValue(field, index, $event)"
/>
<v-checkbox
v-else-if="field.type === 'boolean'"
v-model="field.value"
@change="setFieldValue(field, index, $event)"
/>
<v-menu
v-else-if="field.type === 'date'"
v-model="datePickers[index]"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290px"
min-width="auto"
>
<template #activator="{ on, attrs: menuAttrs }">
<v-text-field
v-model="field.value"
persistent-hint
:prepend-icon="$globals.icons.calendar"
v-bind="menuAttrs"
readonly
v-on="on"
/>
</template>
<v-date-picker
v-model="field.value"
no-title
:first-day-of-week="firstDayOfWeek"
:local="$i18n.locale"
@input="setFieldValue(field, index, $event)"
/>
</v-menu>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Category"
v-model="field.organizers"
:selector-type="Organizer.Category"
:show-add="false"
:show-label="false"
:show-icon="false"
@input="setOrganizerValues(field, index, $event)"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tag"
v-model="field.organizers"
:selector-type="Organizer.Tag"
:show-add="false"
:show-label="false"
:show-icon="false"
@input="setOrganizerValues(field, index, $event)"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tool"
v-model="field.organizers"
:selector-type="Organizer.Tool"
:show-add="false"
:show-label="false"
:show-icon="false"
@input="setOrganizerValues(field, index, $event)"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Food"
v-model="field.organizers"
:selector-type="Organizer.Food"
:show-add="false"
:show-label="false"
:show-icon="false"
@input="setOrganizerValues(field, index, $event)"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.Household"
v-model="field.organizers"
:selector-type="Organizer.Household"
:show-add="false"
:show-label="false"
:show-icon="false"
@input="setOrganizerValues(field, index, $event)"
/>
</v-col>
<v-col
v-if="showAdvanced"
:cols="attrs.fields.rightParens.cols"
:class="attrs.col.class"
:style="attrs.fields.rightParens.style"
>
<v-select
v-model="field.rightParenthesis"
:items="['', ')', '))', ')))']"
@input="setRightParenthesisValue(field, index, $event)"
>
<template #selection="{ item }">
<span :class="attrs.select.textClass" style="width: 100%;">
{{ item }}
</span>
</template>
</v-select>
</v-col>
<v-col
:cols="attrs.fields.fieldActions.cols"
:class="attrs.col.class"
:style="attrs.fields.fieldActions.style"
>
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.delete,
text: $tc('general.delete'),
event: 'delete',
disabled: fields.length === 1,
}
]"
class="my-auto"
@delete="removeField(index)"
/>
</v-col>
</v-row>
</draggable>
</v-container>
</v-card-text>
<v-card-actions>
<v-container fluid class="d-flex justify-end pa-0">
<v-checkbox
v-model="showAdvanced"
hide-details
:label="$tc('general.show-advanced')"
class="my-auto mr-4"
/>
<BaseButton create :text="$tc('general.add-field')" @click="addField(fieldDefs[0])" />
</v-container>
</v-card-actions>
</v-card>
</template>
<script lang="ts">
import draggable from "vuedraggable";
import { computed, defineComponent, reactive, ref, toRefs, watch } from "@nuxtjs/composition-api";
import { useHouseholdSelf } from "~/composables/use-households";
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
import { Organizer } from "~/lib/api/types/non-generated";
import { LogicalOperator, QueryFilterJSON, QueryFilterJSONPart, RelationalKeyword, RelationalOperator } from "~/lib/api/types/response";
import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store";
import { Field, FieldDefinition, FieldValue, OrganizerBase, useQueryFilterBuilder } from "~/composables/use-query-filter-builder";
export default defineComponent({
components: {
draggable,
RecipeOrganizerSelector,
},
props: {
fieldDefs: {
type: Array as () => FieldDefinition[],
required: true,
},
initialQueryFilter: {
type: Object as () => QueryFilterJSON | null,
default: null,
}
},
setup(props, context) {
const { household } = useHouseholdSelf();
const { logOps, relOps, buildQueryFilterString, getFieldFromFieldDef, isOrganizerType } = useQueryFilterBuilder();
const firstDayOfWeek = computed(() => {
return household.value?.preferences?.firstDayOfWeek || 0;
});
const state = reactive({
showAdvanced: false,
qfValid: false,
datePickers: [] as boolean[],
drag: false,
});
const storeMap = {
[Organizer.Category]: useCategoryStore(),
[Organizer.Tag]: useTagStore(),
[Organizer.Tool]: useToolStore(),
[Organizer.Food]: useFoodStore(),
[Organizer.Household]: useHouseholdStore(),
};
function onDragEnd(event: any) {
state.drag = false;
const oldIndex: number = event.oldIndex;
const newIndex: number = event.newIndex;
state.datePickers[oldIndex] = false;
state.datePickers[newIndex] = false;
const field = fields.value.splice(oldIndex, 1)[0];
fields.value.splice(newIndex, 0, field);
}
const fields = ref<Field[]>([]);
function addField(field: FieldDefinition) {
fields.value.push(getFieldFromFieldDef(field));
state.datePickers.push(false);
};
function setField(index: number, fieldLabel: string) {
state.datePickers[index] = false;
const fieldDef = props.fieldDefs.find((fieldDef) => fieldDef.label === fieldLabel);
if (!fieldDef) {
return;
}
const resetValue = (fieldDef.type !== fields.value[index].type) || (fieldDef.fieldOptions !== fields.value[index].fieldOptions);
const updatedField = {...fields.value[index], ...fieldDef};
// we have to set this explicitly since it might be undefined
updatedField.fieldOptions = fieldDef.fieldOptions;
fields.value.splice(index, 1, getFieldFromFieldDef(updatedField, resetValue));
}
function setLeftParenthesisValue(field: Field, index: number, value: string) {
fields.value.splice(index, 1, {
...field,
leftParenthesis: value,
});
}
function setRightParenthesisValue(field: Field, index: number, value: string) {
fields.value.splice(index, 1, {
...field,
rightParenthesis: value,
});
}
function setLogicalOperatorValue(field: Field, index: number, value: LogicalOperator | undefined) {
if (!value) {
value = logOps.value.AND.value;
}
fields.value.splice(index, 1, {
...field,
logicalOperator: value ? logOps.value[value] : undefined,
});
}
function setRelationalOperatorValue(field: Field, index: number, value: RelationalKeyword | RelationalOperator) {
fields.value.splice(index, 1, {
...field,
relationalOperatorValue: relOps.value[value],
});
}
function setFieldValue(field: Field, index: number, value: FieldValue) {
state.datePickers[index] = false;
fields.value.splice(index, 1, {
...field,
value,
});
}
function setFieldValues(field: Field, index: number, values: FieldValue[]) {
fields.value.splice(index, 1, {
...field,
values,
});
}
function setOrganizerValues(field: Field, index: number, values: OrganizerBase[]) {
setFieldValues(field, index, values.map((value) => value.id.toString()));
}
function removeField(index: number) {
fields.value.splice(index, 1);
state.datePickers.splice(index, 1);
};
watch(
// Toggling showAdvanced changes the builder logic without changing the field values,
// so we need to manually trigger reactivity to re-run the builder.
() => state.showAdvanced,
() => {
if (fields.value?.length) {
fields.value = [...fields.value];
}
},
)
watch(
() => fields.value,
(newFields) => {
newFields.forEach((field, index) => {
const updatedField = getFieldFromFieldDef(field);
fields.value[index] = updatedField;
});
const qf = buildQueryFilterString(fields.value, state.showAdvanced);
if (qf) {
console.debug(`Set query filter: ${qf}`);
}
state.qfValid = !!qf;
context.emit("input", qf || undefined);
},
{
deep: true
},
);
async function hydrateOrganizers(field: Field, index: number) {
if (!field.values?.length || !isOrganizerType(field.type)) {
return;
}
field.organizers = [];
const { store, actions } = storeMap[field.type];
if (!store.value.length) {
await actions.refresh();
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const organizers = field.values.map((value) => store.value.find((organizer) => organizer.id === value));
field.organizers = organizers.filter((organizer) => organizer !== undefined) as OrganizerBase[];
setOrganizerValues(field, index, field.organizers);
}
function initFieldsError(error = "") {
if (error) {
console.error(error);
}
fields.value = [];
if (props.fieldDefs.length) {
addField(props.fieldDefs[0]);
}
}
function initializeFields() {
if (!props.initialQueryFilter?.parts?.length) {
return initFieldsError();
};
const initFields: Field[] = [];
let error = false;
props.initialQueryFilter.parts.forEach((part: QueryFilterJSONPart, index: number) => {
const fieldDef = props.fieldDefs.find((fieldDef) => fieldDef.name === part.attributeName);
if (!fieldDef) {
error = true;
return initFieldsError(`Invalid query filter; unknown attribute name "${part.attributeName || ""}"`);
}
const field = getFieldFromFieldDef(fieldDef);
field.leftParenthesis = part.leftParenthesis || field.leftParenthesis;
field.rightParenthesis = part.rightParenthesis || field.rightParenthesis;
field.logicalOperator = part.logicalOperator ?
logOps.value[part.logicalOperator] : field.logicalOperator;
field.relationalOperatorValue = part.relationalOperator ?
relOps.value[part.relationalOperator] : field.relationalOperatorValue;
if (field.leftParenthesis || field.rightParenthesis) {
state.showAdvanced = true;
}
if (field.fieldOptions?.length || isOrganizerType(field.type)) {
if (typeof part.value === "string") {
field.values = part.value ? [part.value] : [];
} else {
field.values = part.value || [];
}
if (isOrganizerType(field.type)) {
hydrateOrganizers(field, index);
}
} else if (field.type === "boolean") {
const boolString = part.value || "false";
field.value = (
boolString[0].toLowerCase() === "t" ||
boolString[0].toLowerCase() === "y" ||
boolString[0] === "1"
);
} else if (field.type === "number") {
field.value = Number(part.value as string || "0");
if (isNaN(field.value)) {
error = true;
return initFieldsError(`Invalid query filter; invalid number value "${(part.value || "").toString()}"`);
}
} else if (field.type === "date") {
field.value = part.value as string || "";
const date = new Date(field.value);
if (isNaN(date.getTime())) {
error = true;
return initFieldsError(`Invalid query filter; invalid date value "${(part.value || "").toString()}"`);
}
} else {
field.value = part.value as string || "";
}
initFields.push(field);
});
if (initFields.length && !error) {
fields.value = initFields;
} else {
initFieldsError();
}
};
try {
initializeFields();
} catch (error) {
initFieldsError(`Error initializing fields: ${(error || "").toString()}`);
}
const attrs = computed(() => {
const baseColMaxWidth = 55;
const attrs = {
col: {
class: "d-flex justify-center align-end field-col pa-1",
},
select: {
textClass: "d-flex justify-center text-center",
},
fields: {
icon: {
cols: 1,
style: "width: fit-content;",
},
leftParens: {
cols: state.showAdvanced ? 1 : 0,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`,
},
logicalOperator: {
cols: 1,
style: `min-width: ${baseColMaxWidth}px;`,
},
fieldName: {
cols: state.showAdvanced ? 2 : 3,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`,
},
relationalOperator: {
cols: 2,
style: `min-width: ${baseColMaxWidth * 2}px;`,
},
fieldValue: {
cols: state.showAdvanced ? 3 : 4,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`,
},
rightParens: {
cols: state.showAdvanced ? 1 : 0,
style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`,
},
fieldActions: {
cols: 1,
style: `min-width: ${baseColMaxWidth}px;`,
},
},
}
return attrs;
})
return {
Organizer,
...toRefs(state),
logOps,
relOps,
attrs,
firstDayOfWeek,
onDragEnd,
// Fields
fields,
addField,
setField,
setLeftParenthesisValue,
setRightParenthesisValue,
setLogicalOperatorValue,
setRelationalOperatorValue,
setFieldValue,
setFieldValues,
setOrganizerValues,
removeField,
};
},
});
</script>
<style scoped>
* {
font-size: 1em;
}
</style>

View File

@@ -21,31 +21,23 @@
<v-spacer></v-spacer>
<div v-if="!open" class="custom-btn-group ma-1">
<RecipeFavoriteBadge v-if="loggedIn" class="mx-1" color="info" button-style :recipe-id="recipe.id" show-always />
<RecipeTimelineBadge v-if="loggedIn" button-style :slug="recipe.slug" :recipe-name="recipe.name" />
<RecipeFavoriteBadge v-if="loggedIn" class="ml-1" color="info" button-style :recipe-id="recipe.id" show-always />
<RecipeTimelineBadge v-if="loggedIn" button-style class="ml-1" :slug="recipe.slug" :recipe-name="recipe.name" />
<div v-if="loggedIn">
<v-tooltip v-if="!locked" bottom color="info">
<v-tooltip v-if="canEdit" bottom color="info">
<template #activator="{ on, attrs }">
<v-btn fab small class="mx-1" color="info" v-bind="attrs" v-on="on" @click="$emit('edit', true)">
<v-btn fab small class="ml-1" color="info" v-bind="attrs" v-on="on" @click="$emit('edit', true)">
<v-icon> {{ $globals.icons.edit }} </v-icon>
</v-btn>
</template>
<span>{{ $t("general.edit") }}</span>
</v-tooltip>
<v-tooltip v-else bottom color="info">
<template #activator="{ on, attrs }">
<v-btn fab small class="mx-1" color="info" v-bind="attrs" v-on="on">
<v-icon> {{ $globals.icons.lock }} </v-icon>
</v-btn>
</template>
<span> {{ $t("recipe.locked-by-owner") }} </span>
</v-tooltip>
</div>
<RecipeTimerMenu
fab
color="info"
class="mr-1"
class="ml-1"
/>
<RecipeContextMenu
@@ -72,6 +64,7 @@
share: loggedIn,
recipeActions: true,
}"
class="ml-1"
@print="$emit('print')"
/>
</div>
@@ -135,7 +128,7 @@ export default defineComponent({
required: true,
type: String,
},
locked: {
canEdit: {
type: Boolean,
default: false,
},

View File

@@ -50,7 +50,7 @@
:recipe-id="recipeId"
:use-items="{
delete: false,
edit: true,
edit: false,
download: true,
mealplanner: true,
shoppingList: true,

View File

@@ -2,6 +2,8 @@
<v-img
v-if="!fallBackImage"
:height="height"
min-height="125"
max-height="fill-height"
:src="getImage(recipeId)"
@click="$emit('click')"
@load="fallBackImage = false"
@@ -52,8 +54,8 @@ export default defineComponent({
default: null,
},
height: {
type: Number,
default: 200,
type: [Number, String],
default: "fill-height",
},
},
setup(props) {

View File

@@ -1,80 +1,86 @@
<template>
<v-expand-transition>
<v-card
:ripple="false"
:class="isFlat ? 'mx-auto flat' : 'mx-auto'"
:style="{ cursor }"
hover
:to="$listeners.selected ? undefined : recipeRoute"
@click="$emit('selected')"
>
<v-img v-if="vertical" class="rounded-sm">
<RecipeCardImage
:icon-size="100"
:height="150"
:slug="slug"
:recipe-id="recipeId"
small
:image-version="image"
/>
</v-img>
<v-list-item three-line :class="vertical ? 'px-2' : 'px-0'">
<slot v-if="!vertical" name="avatar">
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0">
<RecipeCardImage
:icon-size="100"
:height="125"
:slug="slug"
:recipe-id="recipeId"
small
:image-version="image"
/>
</v-list-item-avatar>
</slot>
<v-list-item-content class="py-0">
<v-list-item-title class="mt-3 mb-1">{{ name }} </v-list-item-title>
<v-list-item-subtitle>
<SafeMarkdown :source="description" />
</v-list-item-subtitle>
<div class="d-flex flex-wrap justify-end align-center">
<slot name="actions">
<RecipeFavoriteBadge v-if="isOwnGroup && showRecipeContent" :recipe-id="recipeId" show-always />
<RecipeRating
:class="isOwnGroup ? 'ml-auto' : 'ml-auto pb-2'"
:value="rating"
:recipe-id="recipeId"
<div :style="`height: ${height}`">
<v-expand-transition>
<v-card
:ripple="false"
:class="isFlat ? 'mx-auto flat' : 'mx-auto'"
:style="{ cursor }"
hover
:to="$listeners.selected ? undefined : recipeRoute"
@click="$emit('selected')"
>
<v-img v-if="vertical" class="rounded-sm">
<RecipeCardImage
:icon-size="100"
:height="height"
:slug="slug"
:recipe-id="recipeId"
small
:image-version="image"
/>
</v-img>
<v-list-item three-line :class="vertical ? 'px-2' : 'px-0'">
<slot v-if="!vertical" name="avatar">
<v-list-item-avatar tile :height="height" width="125" class="v-mobile-img rounded-sm my-0">
<RecipeCardImage
:icon-size="100"
:height="height"
:slug="slug"
:small="true"
:recipe-id="recipeId"
:image-version="image"
small
/>
<v-spacer></v-spacer>
</v-list-item-avatar>
</slot>
<v-list-item-content class="py-0">
<v-list-item-title class="mt-1 mb-1 text-top">{{ name }}</v-list-item-title>
<v-list-item-subtitle class="ma-0 text-top">
<SafeMarkdown :source="description" />
</v-list-item-subtitle>
<div class="d-flex flex-wrap justify-start ma-0">
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" url-prefix="tags" />
</div>
<div class="d-flex flex-wrap justify-end align-center">
<slot name="actions">
<RecipeFavoriteBadge v-if="isOwnGroup && showRecipeContent" :recipe-id="recipeId" show-always />
<RecipeRating
v-if="showRecipeContent"
:class="isOwnGroup ? 'ml-auto' : 'ml-auto pb-2'"
:value="rating"
:recipe-id="recipeId"
:slug="slug"
:small="true"
/>
<v-spacer></v-spacer>
<!-- If we're not logged-in, no items display, so we hide this menu -->
<!-- We also add padding to the v-rating above to compensate -->
<RecipeContextMenu
v-if="isOwnGroup && showRecipeContent"
:slug="slug"
:menu-icon="$globals.icons.dotsHorizontal"
:name="name"
:recipe-id="recipeId"
:use-items="{
delete: false,
edit: true,
download: true,
mealplanner: true,
shoppingList: true,
print: false,
printPreferences: false,
share: true,
}"
@deleted="$emit('delete', slug)"
/>
</slot>
</div>
</v-list-item-content>
</v-list-item>
<slot />
</v-card>
</v-expand-transition>
<!-- If we're not logged-in, no items display, so we hide this menu -->
<!-- We also add padding to the v-rating above to compensate -->
<RecipeContextMenu
v-if="isOwnGroup && showRecipeContent"
:slug="slug"
:menu-icon="$globals.icons.dotsHorizontal"
:name="name"
:recipe-id="recipeId"
:use-items="{
delete: false,
edit: false,
download: true,
mealplanner: true,
shoppingList: true,
print: false,
printPreferences: false,
share: true,
}"
@deleted="$emit('delete', slug)"
/>
</slot>
</div>
</v-list-item-content>
</v-list-item>
<slot />
</v-card>
</v-expand-transition>
</div>
</template>
<script lang="ts">
@@ -83,6 +89,7 @@ import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
import RecipeContextMenu from "./RecipeContextMenu.vue";
import RecipeCardImage from "./RecipeCardImage.vue";
import RecipeRating from "./RecipeRating.vue";
import RecipeChips from "./RecipeChips.vue";
import { useLoggedInState } from "~/composables/use-logged-in-state";
export default defineComponent({
@@ -91,6 +98,7 @@ export default defineComponent({
RecipeContextMenu,
RecipeRating,
RecipeCardImage,
RecipeChips,
},
props: {
name: {
@@ -114,6 +122,10 @@ export default defineComponent({
required: false,
default: "abc123",
},
tags: {
type: Array,
default: () => [],
},
recipeId: {
type: String,
required: true,
@@ -126,6 +138,14 @@ export default defineComponent({
type: Boolean,
default: false,
},
height: {
type: [Number, String],
default: 150,
},
imageHeight: {
type: [Number, String],
default: "fill-height",
},
},
setup(props) {
const { $auth } = useContext();

View File

@@ -58,7 +58,7 @@
</v-list>
</v-menu>
<ContextMenu
v-if="!$vuetify.breakpoint.xsOnly"
v-if="!$vuetify.breakpoint.smAndDown"
:items="[
{
title: $tc('general.toggle-view'),
@@ -69,50 +69,52 @@
@toggle-dense-view="toggleMobileCards()"
/>
</v-app-bar>
<div v-if="recipes" class="mt-2">
<v-row v-if="!useMobileCards">
<v-col v-for="(recipe, index) in recipes" :key="recipe.slug + index" :sm="6" :md="6" :lg="4" :xl="3">
<v-lazy>
<RecipeCard
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
:tags="recipe.tags"
:recipe-id="recipe.id"
/>
</v-lazy>
</v-col>
</v-row>
<v-row v-else dense>
<v-col
v-for="recipe in recipes"
:key="recipe.name"
cols="12"
:sm="singleColumn ? '12' : '12'"
:md="singleColumn ? '12' : '6'"
:lg="singleColumn ? '12' : '4'"
:xl="singleColumn ? '12' : '3'"
>
<v-lazy>
<RecipeCardMobile
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
:tags="recipe.tags"
:recipe-id="recipe.id"
/>
</v-lazy>
</v-col>
</v-row>
<div v-if="recipes && ready">
<div class="mt-2">
<v-row v-if="!useMobileCards">
<v-col v-for="(recipe, index) in recipes" :key="recipe.slug + index" :sm="6" :md="6" :lg="4" :xl="3">
<v-lazy>
<RecipeCard
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
:tags="recipe.tags"
:recipe-id="recipe.id"
/>
</v-lazy>
</v-col>
</v-row>
<v-row v-else dense>
<v-col
v-for="recipe in recipes"
:key="recipe.name"
cols="12"
:sm="singleColumn ? '12' : '12'"
:md="singleColumn ? '12' : '6'"
:lg="singleColumn ? '12' : '4'"
:xl="singleColumn ? '12' : '3'"
>
<v-lazy>
<RecipeCardMobile
:name="recipe.name"
:description="recipe.description"
:slug="recipe.slug"
:rating="recipe.rating"
:image="recipe.image"
:tags="recipe.tags"
:recipe-id="recipe.id"
/>
</v-lazy>
</v-col>
</v-row>
</div>
<v-card v-intersect="infiniteScroll"></v-card>
<v-fade-transition>
<AppLoader v-if="loading" :loading="loading" />
</v-fade-transition>
</div>
<v-card v-intersect="infiniteScroll"></v-card>
<v-fade-transition>
<AppLoader v-if="loading" :loading="loading" />
</v-fade-transition>
</div>
</template>
@@ -223,36 +225,42 @@ export default defineComponent({
const queryFilter = computed(() => {
const orderBy = props.query?.orderBy || preferences.value.orderBy;
return preferences.value.filterNull && orderBy ? `${orderBy} IS NOT NULL` : null;
const orderByFilter = preferences.value.filterNull && orderBy ? `${orderBy} IS NOT NULL` : null;
if (props.query.queryFilter && orderByFilter) {
return `(${props.query.queryFilter}) AND ${orderByFilter}`;
} else if (props.query.queryFilter) {
return props.query.queryFilter;
} else {
return orderByFilter;
}
});
async function fetchRecipes(pageCount = 1) {
return await fetchMore(
page.value,
// we double-up the first call to avoid a bug with large screens that render the entire first page without scrolling, preventing additional loading
perPage * pageCount,
props.query?.orderBy || preferences.value.orderBy,
props.query?.orderDirection || preferences.value.orderDirection,
props.query,
// filter out recipes that have a null value for the property we're sorting by
// we use a computed queryFilter to filter out recipes that have a null value for the property we're sorting by
queryFilter.value
);
}
onMounted(async () => {
if (props.query) {
await initRecipes();
ready.value = true;
}
await initRecipes();
ready.value = true;
});
let lastQuery: string | undefined;
let lastQuery: string | undefined = JSON.stringify(props.query);
watch(
() => props.query,
async (newValue: RecipeSearchQuery | undefined) => {
const newValueString = JSON.stringify(newValue)
if (newValue && (!ready.value || lastQuery !== newValueString)) {
if (lastQuery !== newValueString) {
lastQuery = newValueString;
ready.value = false;
await initRecipes();
ready.value = true;
}
@@ -261,8 +269,12 @@ export default defineComponent({
async function initRecipes() {
page.value = 1;
const newRecipes = await fetchRecipes(2);
if (!newRecipes.length) {
hasMore.value = true;
// we double-up the first call to avoid a bug with large screens that render
// the entire first page without scrolling, preventing additional loading
const newRecipes = await fetchRecipes(page.value + 1);
if (newRecipes.length < perPage) {
hasMore.value = false;
}
@@ -274,7 +286,7 @@ export default defineComponent({
const infiniteScroll = useThrottleFn(() => {
useAsync(async () => {
if (!ready.value || !hasMore.value || loading.value) {
if (!hasMore.value || loading.value) {
return;
}
@@ -282,9 +294,10 @@ export default defineComponent({
page.value = page.value + 1;
const newRecipes = await fetchRecipes();
if (!newRecipes.length) {
if (newRecipes.length < perPage) {
hasMore.value = false;
} else {
}
if (newRecipes.length) {
context.emit(APPEND_RECIPES_EVENT, newRecipes);
}
@@ -337,7 +350,7 @@ export default defineComponent({
);
break;
case EVENTS.updated:
setter("update_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending, "desc", false);
setter("updated_at", $globals.icons.sortClockAscending, $globals.icons.sortClockDescending, "desc", false);
break;
case EVENTS.lastMade:
setter(
@@ -379,6 +392,7 @@ export default defineComponent({
displayTitleIcon,
EVENTS,
infiniteScroll,
ready,
loading,
navigateRandom,
preferences,

View File

@@ -138,11 +138,11 @@ import RecipeDialogShare from "./RecipeDialogShare.vue";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useUserApi } from "~/composables/api";
import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions";
import { useGroupSelf } from "~/composables/use-groups";
import { useHouseholdSelf } from "~/composables/use-households";
import { alert } from "~/composables/use-toast";
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
import { Recipe } from "~/lib/api/types/recipe";
import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/group";
import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/household";
import { PlanEntryType } from "~/lib/api/types/meal-plan";
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
@@ -254,14 +254,14 @@ export default defineComponent({
});
const { i18n, $auth, $globals } = useContext();
const { group } = useGroupSelf();
const { household } = useHouseholdSelf();
const { isOwnGroup } = useLoggedInState();
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const firstDayOfWeek = computed(() => {
return group.value?.preferences?.firstDayOfWeek || 0;
return household.value?.preferences?.firstDayOfWeek || 0;
});
// ===========================================================================
@@ -376,7 +376,7 @@ export default defineComponent({
const response = await groupRecipeActionsStore.execute(action, props.recipe);
if (action.actionType === "post") {
if (!response || (response.status >= 200 && response.status < 300)) {
if (!response?.error) {
alert.success(i18n.tc("events.message-sent"));
} else {
alert.error(i18n.tc("events.something-went-wrong"));

Some files were not shown because too many files have changed in this diff Show More